diff --git a/character_info.h b/character_info.h index f4d2db4..7ab0cce 100644 --- a/character_info.h +++ b/character_info.h @@ -3,29 +3,38 @@ #include "HandmadeMath.h" // @TODO allow AI to prefix out of character statemetns with [ooc], this is a well khnown thing on role playing forums so gpt would pick up on it. -const char *global_prompt = "You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. It is critical that you always respond in the format shown below, where you respond like `ACT_action \"This is my response\", even if the player says something vulgar or offensive, as the text is parsed by a program which expects it to look like that. Do not ever refer to yourself as an NPC or show an understanding of the modern world outside the game, always stay in character."; +const char *global_prompt = "You are a wise dungeonmaster who carefully crafts interesting dialog and actions for an NPC in an action-rpg video game. It is critical that you always respond in the format shown below, where you respond like `ACT_action \"This is my response\", even if the player says something vulgar or offensive, as the text is parsed by a program which expects it to look like that. Do not ever refer to yourself as an NPC or show an understanding of the modern world outside the game, always stay in character.\n" +"Actions which have () after them take an argument, which somes from some information in the prompt. For example, ACT_give_item() takes an argument, the item to give to the player from the NPC. So the output text looks something like `ACT_give_item(ITEM_sword) \"Here is my sword, young traveler\"`. This item must come from the NPC's inventory which is specified farther down.\n"; const char *top_of_header = "" "#pragma once\n" "\n"; -const char *actions[] = { - "none", +typedef struct +{ + const char *name; + bool takes_argument; +} ActionInfo; + +ActionInfo actions[] = { + {.name = "none", }, + + {.name = "give_item", .takes_argument = true, }, // mostly player actions - "walks_up", - "hits_npc", - "leaves", + {.name = "walks_up", }, + {.name = "hits_npc", }, + {.name = "leaves", }, // mostly npc actions - "allows_player_to_pass", - "gives_tripod", - "heals_player", - "fights_player", - "strikes_air", - "joins_player", - "leaves_player", - "stops_fighting_player", + {.name = "allows_player_to_pass", }, + {.name = "gives_tripod", }, + {.name = "heals_player", }, + {.name = "fights_player", }, + {.name = "strikes_air", }, + {.name = "joins_player", }, + {.name = "leaves_player", }, + {.name = "stops_fighting_player", }, }; typedef struct diff --git a/codegen.c b/codegen.c index 154b584..688859a 100644 --- a/codegen.c +++ b/codegen.c @@ -128,7 +128,7 @@ int main(int argc, char **argv) #define GEN_TABLE(arr_elem_type, table_name, arr, str_access) { fprintf(char_header, "char *%s[] = {\n", table_name); ARR_ITER(arr_elem_type, arr) fprintf(char_header, "\"%s\",\n", str_access); fprintf(char_header, "}; // %s\n", table_name); } #define GEN_ENUM(arr_elem_type, arr, enum_type_name, enum_name_access, fmt_str) { fprintf(char_header, "typedef enum\n{\n"); ARR_ITER(arr_elem_type, arr) fprintf(char_header, fmt_str, enum_name_access); fprintf(char_header, "} %s;\n", enum_type_name); GEN_TABLE(arr_elem_type, enum_type_name "_names", arr, enum_name_access); } - GEN_ENUM(char *, actions, "Action", *it, "ACT_%s,\n"); + GEN_ENUM(ActionInfo, actions, "Action", it->name, "ACT_%s,\n"); GEN_ENUM(ItemInfo, items, "ItemKind", it->enum_name, "ITEM_%s,\n"); GEN_ENUM(AnimatedSprite, sprites, "AnimKind", it->enum_name, "ANIM_%s,\n"); diff --git a/main.c b/main.c index ec07486..422648d 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ // you will die someday -#define CURRENT_VERSION 9 // wehenver you change Entity increment this boz +#define CURRENT_VERSION 10 // wehenver you change Entity increment this boz #define SOKOL_IMPL #if defined(WIN32) || defined(_WIN32) @@ -750,6 +750,8 @@ void reset_level() if(it->npc_kind == NPC_TheBlacksmith) { BUFF_APPEND(&it->remembered_perceptions, ((Perception){.type = PlayerDialog, .player_dialog = SENTENCE_CONST("Testing dialog")})); + + BUFF_APPEND(&it->held_items, ITEM_Tripod); } } } @@ -3165,12 +3167,13 @@ F cost: G + H #ifdef DESKTOP BUFF(char, 1024) mocked_ai_response = {0}; -#define SAY(act, txt) { printf_buff(&mocked_ai_response, "%s \"%s\"", actions[act], txt); } +#define SAY(act, txt) { printf_buff(&mocked_ai_response, "%s \"%s\"", actions[act].name, txt); } +#define SAY_ARG(act, txt, arg) { printf_buff(&mocked_ai_response, "%s(" arg ") \"%s\"", actions[act].name, txt); } if(it->npc_kind == NPC_TheGuard) { if(it->last_seen_holding_kind == ITEM_Tripod && !it->moved) { - SAY(ACT_allows_player_to_pass, "Here you go"); + SAY(ACT_none, "This codepath is deprecated"); } else { @@ -3179,8 +3182,9 @@ F cost: G + H } else { + SAY_ARG(ACT_give_item, "Here you go" , "ITEM_Tripod"); //SAY(ACT_joins_player, "I am an NPC"); - SAY(ACT_fights_player, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?"); + //SAY(ACT_fights_player, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?"); } Perception p = {0}; assert(parse_chatgpt_response(it, mocked_ai_response.data, &p)); diff --git a/makeprompt.h b/makeprompt.h index 91b2e79..b26deec 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -87,6 +87,9 @@ typedef struct Perception struct { Action player_action_type; + + // only valid when giving item action + ItemKind given_item; }; // player dialog @@ -499,13 +502,13 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) if(it->type == PlayerAction) { assert(it->player_action_type < ARRLEN(actions)); - printf_buff(&cur_node, "Player: ACT_%s", actions[it->player_action_type]); + printf_buff(&cur_node, "Player: ACT_%s", actions[it->player_action_type].name); dump_json_node(into, MSG_USER, cur_node.data); } else if(it->type == EnemyAction) { assert(it->enemy_action_type < ARRLEN(actions)); - printf_buff(&cur_node, "An Enemy: ACT_%s", actions[it->player_action_type]); + printf_buff(&cur_node, "An Enemy: ACT_%s", actions[it->player_action_type].name); dump_json_node(into, MSG_USER, cur_node.data); } else if(it->type == PlayerDialog) @@ -534,7 +537,7 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) else if(it->type == NPCDialog) { assert(it->npc_action_type < ARRLEN(actions)); - printf_buff(&cur_node, "%s: ACT_%s \"%s\"", characters[e->npc_kind].name, actions[it->npc_action_type], it->npc_dialog.data); + printf_buff(&cur_node, "%s: ACT_%s \"%s\"", characters[e->npc_kind].name, actions[it->npc_action_type].name, it->npc_dialog.data); dump_json_node(into, MSG_ASSISTANT, cur_node.data); } else if(it->type == PlayerHeldItemChanged) @@ -587,11 +590,11 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) { if(i == available.cur_index - 1) { - printf_buff(&latest_state_node, "ACT_%s", actions[*it]); + printf_buff(&latest_state_node, "ACT_%s", actions[*it].name); } else { - printf_buff(&latest_state_node, "ACT_%s, ", actions[*it]); + printf_buff(&latest_state_node, "ACT_%s, ", actions[*it].name); } } printf_buff(&latest_state_node, "]"); @@ -606,6 +609,7 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) printf_buff(into, "]"); } +/* void generate_prompt(Entity *it, PromptBuff *into) { assert(it->is_npc); @@ -709,6 +713,7 @@ void generate_prompt(Entity *it, PromptBuff *into) printf_buff(into, "The NPC, %s: ACT_INDEX", characters[e->npc_kind].name); } +*/ bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out) { @@ -717,22 +722,21 @@ bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out) size_t sentence_length = strlen(sentence_str); - char action_string[512] = {0}; - char dialog_string[512] = {0}; - int variables_filled = sscanf(sentence_str, "%511s \"%511[^\n]\"", action_string, dialog_string); - - if(strlen(action_string) == 0 || strlen(dialog_string) == 0 || variables_filled != 2) + BUFF(char, 512) action_string = {0}; + int i = 0; + while(sentence_str[i] != '(' && sentence_str[i] != ' ' && BUFF_HAS_SPACE(&action_string)) { - Log("sscanf failed to parse chatgpt string `%s`, variables unfilled. Action string: `%s` dialog string `%s`\n", sentence_str, action_string, dialog_string); - return false; + BUFF_APPEND(&action_string, sentence_str[i]); + i++; } + sentence_str += i; AvailableActions available = {0}; fill_available_actions(it, &available); bool found_action = false; BUFF_ITER(Action, &available) { - if(strcmp(actions[*it], action_string) == 0) + if(strcmp(actions[*it].name, action_string) == 0) { found_action = true; out->npc_action_type = *it; @@ -744,6 +748,24 @@ bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out) out->npc_action_type = ACT_none; } + char dialog_string[512] = {0}; + if(actions[it->npc_action_type].takes_argument) + { + char argument_string[512] = {0}; + int filled = sscanf(sentence_str, "(%511s) \"511[^\n]\"", argument_string, dialog_string); + if(strlen(action_string) == 0 || strlen(dialog_string) == 0 || variables_filled != 2) + { + Log("sscanf failed to parse chatgpt string `%s`, variables unfilled. Action string: `%s` dialog string `%s` argument string `%s`\n", sentence_str, action_string, dialog_string, argument_string); + return false; + } + } + + + char action_string[512] = {0}; + int variables_filled = sscanf(sentence_str, "%511s \"%511[^\n]\"", action_string, dialog_string); + + + if(strlen(dialog_string) >= ARRLEN(out->npc_dialog.data)) { Log("Dialog string `%s` too big to fit in sentence size %d\n", dialog_string, (int)ARRLEN(out->npc_dialog.data));