@ -9,30 +9,14 @@
# include "character_info.h"
# include "characters.gen.h"
# include "tuning.h"
// TODO do strings: https://pastebin.com/Kwcw2sye
# define DO_CHATGPT_PARSING
# define Log(...) { printf("%s Log %d | ", __FILE__, __LINE__); printf(__VA_ARGS__); }
// REFACTORING:: also have to update in javascript!!!!!!!!
# define MAX_SENTENCE_LENGTH 400 // LOOOK AT AGBOVE COMMENT GBEFORE CHANGING
typedef BUFF ( char , MAX_SENTENCE_LENGTH ) Sentence ;
# define SENTENCE_CONST(txt) { .data = txt, .cur_index = sizeof(txt) }
# define SENTENCE_CONST_CAST(txt) (Sentence)SENTENCE_CONST(txt)
# define REMEMBERED_PERCEPTIONS 24
# define MAX_AFTERIMAGES 6
# define TIME_TO_GEN_AFTERIMAGE (0.09f)
# define AFTERIMAGE_LIFETIME (0.5f)
# define DAMAGE_SWORD 0.05f
# define DAMAGE_BULLET 0.2f
// A* tuning
# define MAX_ASTAR_NODES 512
# define TIME_BETWEEN_PATH_GENS (0.5f)
// Never expected such a stupid stuff from such a great director. If there is 0 stari can give that or -200 to this movie. Its worst to see and unnecessary loss of money
@ -98,47 +82,40 @@ MD_String8 escape_for_json(MD_Arena *arena, MD_String8 from)
return output ;
}
typedef enum PerceptionType
typedef struct Action
{
Invalid , // so that zero value in training structs means end of perception
ErrorMessage , // when chatgpt gives a response that we can't parse, explain to it why and retry. Can also log parse errors for training
PlayerAction ,
PlayerDialog ,
NPCDialog , // includes an npc action in every npc dialog. So it's often ACT_none
} PerceptionType ;
typedef struct Perception
ActionKind kind ;
MD_u8 speech [ MAX_SENTENCE_LENGTH ] ;
int speech_length ;
ItemKind item_to_give ; // only when giving items (duh)
} Action ;
typedef struct
{
PerceptionType type ;
bool eavesdropped_from_party ;
bool i_said_this ; // don't trigger npc action on own self memory modification
NpcKind author_npc_kind ; // only valid if author is AuthorNpc
bool was_directed_at_somebody ;
NpcKind directed_at_kind ;
} MemoryContext ;
bool was_eavesdropped ; // when the npc is in a party they perceive player conversations, but in the third party. Formatted differently
NpcKind talked_to_while_eavesdropped ; // better chatpgpt messages when the NPCs know who the player is talking to when they eavesdrop a perception
// memories are subjective to an individual NPC
typedef struct Memory
{
uint64_t tick_happened ; // can sort memories by time for some modes of display
// if action_taken is none, there might still be speech. If speech_length == 0 and action_taken == none, it's an invalid memory and something has gone wrong
ActionKind action_taken ;
float damage_done ; // Valid in player action and enemy action
ItemKind given_item ; // valid in player action and enemy action when the kind is such that there is an item to be given
union
{
// ErrorMessage
Sentence error ;
bool is_error ; // if is an error message then no context is relevant
// player action
struct
{
Action player_action_type ;
} ;
// the context that the action happened in
MemoryContext context ;
// player dialog
Sentence player_dialog ;
MD_u8 speech [ MAX_SENTENCE_LENGTH ] ;
int speech_length ;
// npc dialog
struct
{
NpcKind who_said_it ;
Action npc_action_type ;
Sentence npc_dialog ;
} ;
} ;
} Perception ;
ItemKind given_or_received_item ;
} Memory ;
typedef enum PropKind
{
@ -246,7 +223,7 @@ typedef struct Entity
int times_talked_to ; // for better mocked response string
# endif
BUFF ( Perception, REMEMBERED_PERCEPTIONS ) remembered_perception s;
BUFF ( Memory, REMEMBERED_MEMORIES ) memorie s;
bool direction_of_spiral_pattern ;
float dialog_panel_opacity ;
double characters_said ;
@ -319,8 +296,7 @@ bool npc_attacks_with_shotgun(Entity *it)
}
typedef BUFF ( char , MAX_SENTENCE_LENGTH * ( REMEMBERED_PERCEPTIONS + 4 ) ) PromptBuff ;
typedef BUFF ( Action , 8 ) AvailableActions ;
typedef BUFF ( ActionKind , 8 ) AvailableActions ;
void fill_available_actions ( Entity * it , AvailableActions * a )
{
@ -371,222 +347,18 @@ void fill_available_actions(Entity *it, AvailableActions *a)
}
}
// returns if action index was valid
bool action_from_index ( Entity * it , Action * out , int action_index )
{
AvailableActions available = { 0 } ;
fill_available_actions ( it , & available ) ;
if ( action_index < 0 | | action_index > = available . cur_index )
{
return false ;
}
else
{
* out = available . data [ action_index ] ;
return true ;
}
}
// don't call on untrusted action, doesn't return error
int action_to_index ( Entity * it , Action a )
{
AvailableActions available = { 0 } ;
fill_available_actions ( it , & available ) ;
Action target_action = a ;
int index = - 1 ;
for ( int i = 0 ; i < available . cur_index ; i + + )
{
if ( available . data [ i ] = = target_action )
{
index = i ;
break ;
}
}
assert ( index ! = - 1 ) ;
return index ;
}
# define MAX_ENTITIES 128
typedef struct GameState {
int version ; // this field must be first to detect versions of old saves. Must bee consistent
uint64_t tick ;
bool won ;
Entity entities [ MAX_ENTITIES ] ;
} GameState ;
# define ENTITIES_ITER(ents) for (Entity *it = ents; it < ents + ARRLEN(ents); it++) if (it->exists)
// gamestate to propagate eavesdropped perceptions in player party
// don't save perception outside of this function, it's modified
void process_perception ( Entity * happened_to_npc , Perception p , Entity * player , GameState * gs )
{
assert ( happened_to_npc - > is_npc ) ;
if ( ! p . was_eavesdropped & & p . type = = NPCDialog )
p . who_said_it = happened_to_npc - > npc_kind ;
bool should_respond_to_this = ! ( ! p . was_eavesdropped & & p . type = = NPCDialog ) ; // NPCs shouldn't respond to what they said, what they said is self-perception. Would trigger endless NPC thought loop if possible
if ( should_respond_to_this ) happened_to_npc - > perceptions_dirty = true ; // NPCs perceive their own actions. Self is a perception
if ( ! BUFF_HAS_SPACE ( & happened_to_npc - > remembered_perceptions ) )
BUFF_REMOVE_FRONT ( & happened_to_npc - > remembered_perceptions ) ;
BUFF_APPEND ( & happened_to_npc - > remembered_perceptions , p ) ;
if ( ! p . was_eavesdropped & & ( p . type = = NPCDialog | | p . type = = PlayerAction | | p . type = = PlayerDialog ) )
{
Perception eavesdropped = p ;
eavesdropped . was_eavesdropped = true ;
eavesdropped . talked_to_while_eavesdropped = happened_to_npc - > npc_kind ;
ENTITIES_ITER ( gs - > entities )
{
if ( it - > is_npc & & it - > standing = = STANDING_JOINED & & it ! = happened_to_npc )
{
process_perception ( it , eavesdropped , player , gs ) ;
}
}
}
if ( p . type = = PlayerAction )
{
if ( p . player_action_type = = ACT_hits_npc )
{
happened_to_npc - > damage + = p . damage_done ;
}
else if ( p . player_action_type = = ACT_give_item )
{
BUFF_APPEND ( & happened_to_npc - > held_items , p . given_item ) ;
}
else
{
assert ( ! actions [ p . player_action_type ] . takes_argument ) ;
}
}
else if ( p . type = = PlayerDialog )
{
}
else if ( p . type = = NPCDialog )
{
// everything in this branch has an effect
if ( ! p . was_eavesdropped )
{
if ( p . npc_action_type = = ACT_allows_player_to_pass )
{
happened_to_npc - > target_goto = AddV2 ( happened_to_npc - > pos , V2 ( - 50.0 , 0.0 ) ) ;
happened_to_npc - > moved = true ;
}
else if ( p . npc_action_type = = ACT_fights_player )
{
happened_to_npc - > standing = STANDING_FIGHTING ;
}
else if ( p . npc_action_type = = ACT_knights_player )
{
player - > knighted = true ;
}
else if ( p . npc_action_type = = ACT_stops_fighting_player )
{
happened_to_npc - > standing = STANDING_INDIFFERENT ;
}
else if ( p . npc_action_type = = ACT_leaves_player )
{
happened_to_npc - > standing = STANDING_INDIFFERENT ;
}
else if ( p . npc_action_type = = ACT_joins_player )
{
happened_to_npc - > standing = STANDING_JOINED ;
}
else if ( p . npc_action_type = = ACT_give_item )
{
int item_to_remove = - 1 ;
Entity * e = happened_to_npc ;
BUFF_ITER_I ( ItemKind , & e - > held_items , i )
{
if ( * it = = p . given_item )
{
item_to_remove = i ;
break ;
}
}
if ( item_to_remove < 0 )
{
Log ( " Can't find item %s to give from NPC %s to the player \n " , items [ p . given_item ] . name ,
characters [ happened_to_npc - > npc_kind ] . name ) ;
assert ( false ) ;
}
else
{
BUFF_REMOVE_AT_INDEX ( & happened_to_npc - > held_items , item_to_remove ) ;
BUFF_APPEND ( & player - > held_items , p . given_item ) ;
}
}
else
{
// actions that take an argument have to have some kind of side effect based on that argument...
assert ( ! actions [ p . npc_action_type ] . takes_argument ) ;
}
}
}
else if ( p . type = = ErrorMessage )
{
Log ( " Failed to parse chatgippity sentence because: '%s' \n " , p . error . data ) ;
}
else
{
assert ( false ) ;
}
}
// returns if printed into the buff without any errors
bool printf_buff_impl ( BuffRef into , const char * format , . . . )
{
assert ( * into . cur_index < into . max_data_elems ) ;
assert ( into . data_elem_size = = 1 ) ; // characters
va_list args ;
va_start ( args , format ) ;
size_t n = into . max_data_elems - * into . cur_index ;
int written = vsnprintf ( ( char * ) into . data + * into . cur_index , n , format , args ) ;
if ( written < 0 )
{
}
else
{
* into . cur_index + = written ;
}
// https://cplusplus.com/reference/cstdio/vsnprintf/
bool succeeded = true ;
if ( written < 0 ) succeeded = false ; // encoding error
if ( written > = n ) succeeded = false ; // didn't fit in buffer
va_end ( args ) ;
return succeeded ;
}
# define printf_buff(buff_ptr, ...) { printf_buff_impl(BUFF_MAKEREF(buff_ptr), __VA_ARGS__); if(false) printf(__VA_ARGS__); }
typedef BUFF ( char , 512 ) SmallTextChunk ;
SmallTextChunk percept_action_str ( Perception p , Action act )
{
SmallTextChunk to_return = { 0 } ;
printf_buff ( & to_return , " ACT_%s " , actions [ act ] . name ) ;
if ( actions [ act ] . takes_argument )
{
if ( act = = ACT_give_item )
{
printf_buff ( & to_return , " (ITEM_%s) " , items [ p . given_item ] . enum_name ) ;
}
else
{
assert ( false ) ;
}
}
return to_return ;
}
bool npc_does_dialog ( Entity * it )
{
return it - > npc_kind < ARRLEN ( characters ) ;
@ -599,23 +371,6 @@ typedef enum
MSG_ASSISTANT ,
} MessageType ;
// stops if the sentence is gonna run out of room
void append_str ( Sentence * to_append , const char * str )
{
size_t len = strlen ( str ) ;
for ( int i = 0 ; i < len ; i + + )
{
if ( ! BUFF_HAS_SPACE ( to_append ) )
{
break ;
}
else
{
BUFF_APPEND ( to_append , str [ i ] ) ;
}
}
}
// for no trailing comma just trim the last character
MD_String8 make_json_node ( MD_Arena * arena , MessageType type , MD_String8 content )
{
@ -637,30 +392,62 @@ MD_String8 make_json_node(MD_Arena *arena, MessageType type, MD_String8 content)
return to_return ;
}
// returns a string like `ITEM_one, ITEM_two`
Sentence item_string ( Entity * e )
MD_String8List held_item_strings ( MD_Arena * arena , Entity * e )
{
MD_String8List to_return = { 0 } ;
BUFF_ITER ( ItemKind , & e - > held_items )
{
MD_S8ListPushFmt ( arena , & to_return , " ITEM_%s " , items [ * it ] . enum_name ) ;
}
return to_return ;
}
// returns reason why allocated on arena if invalid
// to might be null here, from can't be null
MD_String8 is_action_valid ( MD_Arena * arena , Entity * from , Entity * to_might_be_null , Action a )
{
Sentence to_return = { 0 } ;
BUFF_ITER_I ( ItemKind , & e - > held_items , i )
assert ( a . speech_length < = MAX_SENTENCE_LENGTH & & a . speech_length > = 0 ) ;
assert ( a . kind > = 0 & & a . kind < ARRLEN ( actions ) ) ;
assert ( from ) ;
if ( a . kind = = ACT_give_item )
{
printf_buff ( & to_return , " ITEM_%s " , items [ * it ] . enum_name ) ;
if ( i = = e - > held_items . cur_index - 1 )
assert ( a . item_to_give > = 0 & & a . item_to_give < ARRLEN ( items ) ) ;
bool has_it = false ;
BUFF_ITER ( ItemKind , & from - > held_items )
{
printf_buff ( & to_return , " " ) ;
if ( * it = = a . item_to_give )
{
has_it = true ;
break ;
}
else
}
if ( ! has_it )
{
MD_StringJoin join = { . mid = MD_S8Lit ( " , " ) } ;
return MD_S8Fmt ( arena , " Can't give item `ITEM_%s`, you only have [%.*s] in your inventory " , items [ a . item_to_give ] . enum_name , MD_S8VArg ( MD_S8ListJoin ( arena , held_item_strings ( arena , from ) , & join ) ) ) ;
}
if ( ! to_might_be_null )
{
printf_buff ( & to_return , " , " ) ;
return MD_S8Lit ( " You can't give an item to nobody, you're currently not in conversation or targeting somebody. " ) ;
}
}
return to_return ;
if ( a . kind = = ACT_leaves_player & & from - > standing ! = STANDING_JOINED )
{
return MD_S8Lit ( " You can't leave the player unless you joined them. " ) ;
}
return ( MD_String8 ) { 0 } ;
}
// outputs json
MD_String8 generate_chatgpt_prompt ( MD_Arena * arena , Entity * it )
MD_String8 generate_chatgpt_prompt ( MD_Arena * arena , Entity * e )
{
assert ( it - > is_npc ) ;
assert ( it - > npc_kind < ARRLEN ( characters ) ) ;
assert ( e - > is_npc ) ;
assert ( e - > npc_kind < ARRLEN ( characters ) ) ;
MD_ArenaTemp scratch = MD_GetScratch ( & arena , 1 ) ;
@ -668,32 +455,42 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *it)
MD_S8ListPushFmt ( scratch . arena , & list , " [ " ) ;
MD_S8ListPush ( scratch . arena , & list , make_json_node ( scratch . arena , MSG_SYSTEM , MD_S8Fmt ( scratch . arena , " %s \n %s \n " , global_prompt , characters [ e - > npc_kind ] . prompt ) ) ) ;
MD_S8ListPush ( scratch . arena , & list , make_json_node ( scratch . arena , MSG_SYSTEM , MD_S8Fmt ( scratch . arena , " %s \n %s \n " , global_prompt , characters [ it - > npc_kind ] . prompt ) ) ) ;
Entity * e = it ;
ItemKind last_holding = ITEM_none ;
BUFF_ITER _I( Perception , & e - > remembered_perceptions , i )
BUFF_ITER ( Memory , & e - > memories )
{
MessageType sent_type = 0 ;
MD_String8 current_string = { 0 } ;
if ( it - > type = = ErrorMessage )
MessageType sent_type = - 1 ;
MD_String8 current_string = ( MD_String8 ) { 0 } ;
if ( it - > is_error )
{
assert ( it - > error . cur_index > 0 ) ;
current_string = MD_S8Fmt ( scratch . arena , " ERROR, YOU SAID SOMETHING WRONG: The program can't parse what you said because: %s " , it - > error . data ) ;
sent_type = MSG_SYSTEM ;
current_string = MD_S8Fmt ( scratch . arena , " ERROR, what you said is incorrect because: %.*s " , it - > speech_length , it - > speech ) ;
}
else if ( it - > type = = PlayerAction )
else
{
assert ( it - > player_action_type < ARRLEN ( actions ) ) ;
current_string = MD_S8Fmt ( scratch . arena , " Player: %s " , percept_action_str ( * it , it - > player_action_type ) . data ) ;
sent_type = MSG_USER ;
MD_String8 context_string = { 0 } ;
if ( it - > context . was_directed_at_somebody )
{
context_string = MD_S8Fmt ( scratch . arena , " %s, talking to %s: " , characters [ it - > context . author_npc_kind ] . name , characters [ it - > context . directed_at_kind ] . name ) ;
}
else
{
context_string = MD_S8Fmt ( scratch . arena , " %s: " , characters [ it - > context . author_npc_kind ] . name ) ;
}
else if ( it - > type = = PlayerDialog )
assert ( context_string . size > 0 ) ;
if ( it - > context . eavesdropped_from_party )
{
context_string = MD_S8Fmt ( scratch . arena , " While in the player's party, you hear: %.*s " , MD_S8VArg ( context_string ) ) ;
}
MD_String8 speech = MD_S8 ( it - > speech , it - > speech_length ) ;
if ( it - > context . author_npc_kind = = NPC_Player )
{
MD_String8 splits [ ] = { MD_S8Lit ( " * " ) } ;
MD_String8List split_up_speech = MD_S8Split ( scratch . arena , MD_S8CString ( it - > player_dialog . data ) , ARRLEN ( splits ) , splits ) ;
MD_String8List split_up_speech = MD_S8Split ( scratch . arena , speech , ARRLEN ( splits ) , splits ) ;
MD_String8List to_join = { 0 } ;
@ -713,63 +510,33 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *it)
}
MD_StringJoin join = { MD_S8Lit ( " " ) , MD_S8Lit ( " " ) , MD_S8Lit ( " " ) } ;
MD_String8 filtered_speech = MD_S8ListJoin ( scratch . arena , to_join , & join ) ;
current_string = MD_S8Fmt ( scratch . arena , " Player: %.*s " , MD_S8VArg ( filtered_speech ) ) ;
speech = MD_S8ListJoin ( scratch . arena , to_join , & join ) ;
sent_type = MSG_USER ;
}
else if ( it - > type = = NPCDialog )
{
assert ( it - > npc_action_type < ARRLEN ( actions ) ) ;
NpcKind who_said_it = e - > npc_kind ;
if ( it - > was_eavesdropped ) who_said_it = it - > talked_to_while_eavesdropped ;
current_string = MD_S8Fmt ( scratch . arena , " %s: %s \" %s \" " , characters [ who_said_it ] . name , percept_action_str ( * it , it - > npc_action_type ) . data , it - > npc_dialog . data ) ;
sent_type = MSG_ASSISTANT ;
}
else
{
assert ( false ) ;
sent_type = it - > context . author_npc_kind = = e - > npc_kind ? MSG_ASSISTANT : MSG_USER ;
}
if ( it - > was_eavesdropped )
{
MD_String8 new_string = MD_S8Fmt ( scratch . arena , " Within the player's party, while the player is talking to '%s', you hear: '%.*s' " , characters [ it - > talked_to_while_eavesdropped ] . name , MD_S8VArg ( current_string ) ) ;
current_string = new_string ;
current_string = MD_S8Fmt ( scratch . arena , " %.*s ACT_%s %.*s " , MD_S8VArg ( context_string ) , actions [ it - > action_taken ] . name , it - > speech_length , it - > speech ) ;
}
assert ( sent_type ! = - 1 ) ;
assert ( current_string . size > 0 ) ;
MD_S8ListPush ( scratch . arena , & list , make_json_node ( scratch . arena , sent_type , current_string ) ) ;
}
const char * health_string = 0 ;
{
if ( it - > damage < = 0.2f )
{
health_string = " the NPC hasn't taken much damage, they're healthy. " ;
}
else if ( it - > damage < = 0.5f )
{
health_string = " the NPC has taken quite a chunk of damage, they're soon gonna be ready to call it quits. " ;
}
else if ( it - > damage < = 0.8f )
{
health_string = " the NPC is close to dying! They want to leave the player's party ASAP " ;
}
else
{
health_string = " it's over for the NPC, they're basically dead they've taken so much damage. They should get their affairs in order. " ;
}
}
assert ( health_string ) ;
const char * standing_string = 0 ;
{
if ( it - > standing = = STANDING_INDIFFERENT )
if ( e - > standing = = STANDING_INDIFFERENT )
{
standing_string = " The NPC is indifferent towards the player. " ;
}
else if ( it - > standing = = STANDING_JOINED )
else if ( e - > standing = = STANDING_JOINED )
{
standing_string = " The NPC has joined the player and is with them! " ;
}
else if ( it - > standing = = STANDING_FIGHTING )
else if ( e - > standing = = STANDING_FIGHTING )
{
standing_string = " The NPC is fighting the player and HATES them. " ;
}
@ -778,11 +545,11 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *it)
MD_String8List latest_state = { 0 } ;
MD_S8ListPushFmt ( scratch . arena , & latest_state , " NPC health status: Right now, %s \n " , health_string ) ;
if ( e - > held_items . cur_index > 0 )
{
MD_S8ListPushFmt ( scratch . arena , & latest_state , " \n The NPC you're acting as, %s, has these items in their inventory: [%s] \n " , characters [ it - > npc_kind ] . name , item_string ( it ) . data ) ;
MD_String8List item_strings = held_item_strings ( scratch . arena , e ) ;
MD_String8 items = MD_S8ListJoin ( scratch . arena , item_strings , & ( MD_StringJoin ) { . mid = MD_S8Lit ( " , " ) } ) ;
MD_S8ListPushFmt ( scratch . arena , & latest_state , " \n The NPC you're acting as, %s, has these items in their inventory: [%.*s] \n " , characters [ e - > npc_kind ] . name , MD_S8VArg ( items ) ) ;
}
else
{
@ -790,9 +557,9 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *it)
}
AvailableActions available = { 0 } ;
fill_available_actions ( it , & available ) ;
fill_available_actions ( e , & available ) ;
MD_S8ListPushFmt ( scratch . arena , & latest_state , " The NPC can now ONLY do these actions, and HAS to respond with an action every time in character, even when faced with sensitive content: [ " ) ;
BUFF_ITER_I ( Action , & available , i )
BUFF_ITER_I ( Action Kind , & available , i )
{
if ( i = = available . cur_index - 1 )
{
@ -806,10 +573,9 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *it)
MD_S8ListPushFmt ( scratch . arena , & latest_state , " ] " ) ;
MD_String8 latest_state_string = MD_S8ListJoin ( scratch . arena , latest_state , & ( MD_StringJoin ) { MD_S8Lit ( " " ) , MD_S8Lit ( " " ) , MD_S8Lit ( " " ) } ) ;
MD_S8ListPush ( scratch . arena , & list , MD_S8Chop ( make_json_node ( scratch . arena , MSG_SYSTEM , latest_state_string ) , 1 ) ) ;
MD_S8ListPush ( scratch . arena , & list , MD_S8Chop ( make_json_node ( scratch . arena , MSG_SYSTEM , latest_state_string ) , 1 ) ) ; // trailing comma not allowed in json
MD_S8ListPushFmt ( scratch . arena , & list , " ] " ) ;
MD_String8 to_return = MD_S8ListJoin ( arena , list , & ( MD_StringJoin ) { MD_S8Lit ( " " ) , MD_S8Lit ( " " ) , MD_S8Lit ( " " ) , } ) ;
MD_ReleaseScratch ( scratch ) ;
@ -817,156 +583,15 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *it)
return to_return ;
}
/*
void generate_prompt ( Entity * it , PromptBuff * into )
{
assert ( it - > is_npc ) ;
* into = ( PromptBuff ) { 0 } ;
// global prompt
printf_buff ( into , " %s " , global_prompt ) ;
printf_buff ( into , " %s " , " \n " ) ;
// npc description prompt
assert ( it - > npc_kind < ARRLEN ( characters ) ) ;
printf_buff ( into , " %s " , characters [ it - > npc_kind ] . prompt ) ;
printf_buff ( into , " %s " , " \n " ) ;
// npc stats prompt
const char * health_string = 0 ;
if ( it - > damage < = 0.2f )
{
health_string = " The NPC hasn't taken much damage, they're healthy. " ;
}
else if ( it - > damage < = 0.5f )
{
health_string = " The NPC has taken quite a chunk of damage, they're soon gonna be ready to call it quits. " ;
}
else if ( it - > damage < = 0.8f )
{
health_string = " The NPC is close to dying! They want to leave the player's party ASAP " ;
}
else
{
health_string = " It's over for the NPC, they're basically dead they've taken so much damage. They should get their affairs in order. " ;
}
assert ( health_string ) ;
printf_buff ( into , " NPC Health Status: %s \n " , health_string ) ;
// item prompt
if ( it - > last_seen_holding_kind ! = ITEM_none )
{
assert ( it - > last_seen_holding_kind < ARRLEN ( items ) ) ;
printf_buff ( into , " %s " , items [ it - > last_seen_holding_kind ] . global_prompt ) ;
printf_buff ( into , " %s " , " \n " ) ;
}
// available actions prompt
AvailableActions available = { 0 } ;
fill_available_actions ( it , & available ) ;
printf_buff ( into , " %s " , " The NPC possible actions array, indexed by ACT_INDEX: [ " ) ;
BUFF_ITER ( Action , & available )
{
printf_buff ( into , " %s " , actions [ * it ] ) ;
printf_buff ( into , " %s " , " , " ) ;
}
printf_buff ( into , " %s " , " ] \n " ) ;
Entity * e = it ;
ItemKind last_holding = ITEM_none ;
BUFF_ITER ( Perception , & e - > remembered_perceptions )
{
if ( it - > type = = PlayerAction )
{
assert ( it - > player_action_type < ARRLEN ( actions ) ) ;
printf_buff ( into , " Player: ACT %s \n " , actions [ it - > player_action_type ] ) ;
}
else if ( it - > type = = EnemyAction )
{
assert ( it - > enemy_action_type < ARRLEN ( actions ) ) ;
printf_buff ( into , " An Enemy: ACT %s \n " , actions [ it - > player_action_type ] ) ;
}
else if ( it - > type = = PlayerDialog )
{
printf_buff ( into , " %s " , " Player: \" " ) ;
printf_buff ( into , " %s " , it - > player_dialog . data ) ;
printf_buff ( into , " %s " , " \" \n " ) ;
}
else if ( it - > type = = NPCDialog )
{
printf_buff ( into , " The NPC, %s: ACT %s \" %s \" \n " , characters [ e - > npc_kind ] . name , actions [ it - > npc_action_type ] , it - > npc_dialog . data ) ;
}
else if ( it - > type = = PlayerHeldItemChanged )
{
if ( last_holding ! = it - > holding )
{
if ( last_holding ! = ITEM_none )
{
printf_buff ( into , " %s " , items [ last_holding ] . discard ) ;
printf_buff ( into , " %s " , " \n " ) ;
}
if ( it - > holding ! = ITEM_none )
{
printf_buff ( into , " %s " , items [ it - > holding ] . possess ) ;
printf_buff ( into , " %s " , " \n " ) ;
}
last_holding = it - > holding ;
}
}
else
{
assert ( false ) ;
}
}
printf_buff ( into , " The NPC, %s: ACT_INDEX " , characters [ e - > npc_kind ] . name ) ;
}
*/
// puts characters from `str` into `into` until any character in `until` is encountered
// returns the number of characters read into into
int get_until ( SmallTextChunk * into , const char * str , const char * until )
{
int i = 0 ;
size_t until_size = strlen ( until ) ;
bool encountered_char = false ;
int before_cur_index = into - > cur_index ;
while ( BUFF_HAS_SPACE ( into ) & & str [ i ] ! = ' \0 ' & & ! encountered_char )
{
for ( int ii = 0 ; ii < until_size ; ii + + )
{
if ( until [ ii ] = = str [ i ] ) encountered_char = true ;
}
if ( ! encountered_char )
BUFF_APPEND ( into , str [ i ] ) ;
i + = 1 ;
}
return into - > cur_index - before_cur_index ;
}
bool char_in_str ( char c , const char * str )
{
size_t len = strlen ( str ) ;
for ( int i = 0 ; i < len ; i + + )
{
if ( str [ i ] = = c ) return true ;
}
return false ;
}
// if returned string has size greater than 0, it's the error message. Allocated
// on arena passed into it
MD_String8 parse_chatgpt_response ( MD_Arena * arena , Entity * e , MD_String8 sentence , Percep tion * out )
MD_String8 parse_chatgpt_response ( MD_Arena * arena , Entity * e , MD_String8 sentence , Action * out )
{
MD_ArenaTemp scratch = MD_GetScratch ( & arena , 1 ) ;
MD_String8 error_message = { 0 } ;
* out = ( Perception ) { 0 } ;
out - > type = NPCDialog ;
* out = ( Action ) { 0 } ;
MD_String8 action_prefix = MD_S8Lit ( " ACT_ " ) ;
MD_u64 act_pos = MD_S8FindSubstring ( sentence , action_prefix , 0 , 0 ) ;
@ -993,14 +618,14 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
fill_available_actions ( e , & available ) ;
bool found_action = false ;
MD_String8List given_action_strings = { 0 } ;
BUFF_ITER ( Action , & available )
BUFF_ITER ( Action Kind , & available )
{
MD_String8 action_str = MD_S8CString ( actions [ * it ] . name ) ;
MD_S8ListPush ( scratch . arena , & given_action_strings , action_str ) ;
if ( MD_S8Match ( action_str , given_action_string , 0 ) )
{
found_action = true ;
out - > npc_action_type = * it ;
out - > kind = * it ;
}
}
@ -1008,23 +633,23 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
{
MD_StringJoin join = { . pre = MD_S8Lit ( " " ) , . mid = MD_S8Lit ( " , " ) , . post = MD_S8Lit ( " " ) } ;
MD_String8 possible_actions_str = MD_S8ListJoin ( scratch . arena , given_action_strings , & join ) ;
error_message = MD_S8Fmt ( arena , " Action string given is '%.*s', but available actions are: [%.*s]" , MD_S8VArg ( given_action_string ) , MD_S8VArg ( possible_actions_str ) ) ;
error_message = MD_S8Fmt ( arena , " Action Kind string given is '%.*s', but available actions are: [%.*s]" , MD_S8VArg ( given_action_string ) , MD_S8VArg ( possible_actions_str ) ) ;
goto endofparsing ;
}
MD_u64 start_looking_for_quote = end_of_action ;
if ( actions [ out - > npc_action_type ] . takes_argument )
if ( actions [ out - > kind ] . takes_argument )
{
if ( end_of_action > = sentence . size )
{
error_message = MD_S8Fmt ( arena , " Expected '(' after the given action '%.*s%.*s' which takes an argument, but sentence ended prematurely " , MD_S8VArg ( action_prefix ) , MD_S8VArg ( MD_S8CString ( actions [ out - > npc_action_type ] . name ) ) ) ;
error_message = MD_S8Fmt ( arena , " Expected '(' after the given action '%.*s%.*s' which takes an argument, but sentence ended prematurely " , MD_S8VArg ( action_prefix ) , MD_S8VArg ( MD_S8CString ( actions [ out - > kind ] . name ) ) ) ;
goto endofparsing ;
}
char should_be_paren = sentence . str [ end_of_action ] ;
if ( should_be_paren ! = ' ( ' )
{
error_message = MD_S8Fmt ( arena , " Expected '(' after the given action '%.*s%.*s' which takes an argument, but found character '%c' " , MD_S8VArg ( action_prefix ) , MD_S8VArg ( MD_S8CString ( actions [ out - > npc_action_type ] . name ) ) , should_be_paren ) ;
error_message = MD_S8Fmt ( arena , " Expected '(' after the given action '%.*s%.*s' which takes an argument, but found character '%c' " , MD_S8VArg ( action_prefix ) , MD_S8VArg ( MD_S8CString ( actions [ out - > kind ] . name ) ) , should_be_paren ) ;
goto endofparsing ;
}
MD_u64 beginning_of_arg = end_of_action ;
@ -1038,7 +663,7 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
MD_String8 argument = MD_S8Substring ( sentence , beginning_of_arg , end_of_arg ) ;
start_looking_for_quote = end_of_arg + 1 ;
if ( out - > npc_action_type = = ACT_give_item )
if ( out - > kind = = ACT_give_item )
{
MD_String8 item_prefix = MD_S8Lit ( " ITEM_ " ) ;
MD_u64 item_prefix_begin = MD_S8FindSubstring ( argument , item_prefix , 0 , 0 ) ;
@ -1061,7 +686,7 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
if ( MD_S8Match ( item_str , item_name , 0 ) )
{
item_found = true ;
out - > given_item = * it ;
out - > item_to_give = * it ;
}
}
@ -1097,14 +722,14 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
MD_String8 speech = MD_S8Substring ( sentence , beginning_of_speech + 1 , end_of_speech ) ;
if ( speech . size > = ARRLEN ( out - > npc_dialog. data ) )
if ( speech . size > = ARRLEN ( out - > speech ) )
{
error_message = MD_S8Fmt ( arena , " The speech given is %llu bytes big, but the maximum allowed is %llu bytes. " , speech . size , ARRLEN ( out - > npc_dialog. data ) ) ;
error_message = MD_S8Fmt ( arena , " The speech given is %llu bytes big, but the maximum allowed is %llu bytes. " , speech . size , ARRLEN ( out - > speech ) ) ;
goto endofparsing ;
}
memcpy ( out - > npc_dialog. data , speech . str , speech . size ) ;
out - > npc_dialog. cur_index = ( int ) speech . size ;
memcpy ( out - > speech , speech . str , speech . size ) ;
out - > speech_length = ( int ) speech . size ;
endofparsing :
MD_ReleaseScratch ( scratch ) ;