Internal monologue for NPCs and add more pre-conversation

main
Cameron Murphy Reikes 2 years ago
parent b52cd58395
commit c538208064

@ -109,6 +109,7 @@ typedef struct
char *dialog; char *dialog;
char *action_taken; // has the ACT prefix, so ACT_action. If it's null it means ACT_none char *action_taken; // has the ACT prefix, so ACT_action. If it's null it means ACT_none
char *action_argument; // if not null then action argument provided in parenthesis char *action_argument; // if not null then action argument provided in parenthesis
char *internal_monologue; // the internal monologue generated by chatgpt
} ChatHistoryElem; } ChatHistoryElem;
typedef struct typedef struct
@ -280,14 +281,42 @@ CharacterGen characters[] = {
.prompt = "He's not from around this medieval fantasy land, instead " NPC_NAME " is a divorced car insurance accountant from Philadelphia with a receding hairline in his mid 40s. He lives in a one bedroom studio and his kids don't talk to him. " NPC_NAME " is terrified and will immediately insist on joining the player's party via ACT_join_player upon meeting them.", .prompt = "He's not from around this medieval fantasy land, instead " NPC_NAME " is a divorced car insurance accountant from Philadelphia with a receding hairline in his mid 40s. He lives in a one bedroom studio and his kids don't talk to him. " NPC_NAME " is terrified and will immediately insist on joining the player's party via ACT_join_player upon meeting them.",
.previous_conversation = { .previous_conversation = {
{ .type = MSG_USER, .character_name = "Jester", .dialog = "Hehehe! Quit quant, a peasant from the mortal realm!" }, { .type = MSG_USER, .character_name = "Jester", .dialog = "Hehehe! Quit quant, a peasant from the mortal realm!" },
{ .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "No...Please! Stay away from me! Who the Hell are you!" }, { .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "No...Please! Stay away from me! Who the Hell are you!", .internal_monologue = "I'm kind of mad, and this Jester guy is kind of an asshole" },
{ .type = MSG_USER, .character_name = "Jester", .dialog = "Poor Bill, I'm sure your wife says the same..." }, { .type = MSG_USER, .character_name = "Jester", .dialog = "Poor Bill, I'm sure your ex-wife says the same..." },
{ .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "You evil BASTARD!! What is this place???" }, { .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "You evil BASTARD!! What is this place???", .internal_monologue = "I hate the Jester because he dissed my ex-wife. And, he's being all vague and condescending. I would punch him if I could" },
{ .type = MSG_USER, .action_taken = "ACT_causes_testicular_torsion", .character_name = "Jester", .dialog = "I'll leave that for you to find out ;)" }, { .type = MSG_USER, .action_taken = "ACT_causes_testicular_torsion", .character_name = "Jester", .dialog = "I'll leave that for you to find out ;)" },
{ .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "AUGGHGHHH!!! THE PAIN IS UNBEARABLE" }, { .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "AUGGHGHHH!!! THE PAIN IS UNBEARABLE", .internal_monologue = "SCREW THIS JESTER GUY HE FUCKED WITH MY NUTS" },
{ .type = MSG_USER, .action_taken = "ACT_undoes_testicular_torsion", .character_name = "Jester", .dialog = "That's just a taste of what's to come! Bye bye~" }, { .type = MSG_USER, .action_taken = "ACT_undoes_testicular_torsion", .character_name = "Jester", .dialog = "That's just a taste of what's to come! Bye bye~" },
}, },
}, },
#undef NPC_NAME
#define NPC_NAME "Meld"
.name = NPC_NAME,
.enum_name = "TheBlacksmith",
.prompt = "He is a jaded blue collar worker from magic New Jersey who hates everything new, like Purple Magic, which Edeline, the local fortuneteller, happens to specialize in. He is cold, dry, and sarcastic, wanting money and power above anything else.",
.previous_conversation = {
{ .type = MSG_USER, .character_name = "Jester", .dialog = "A fine strong man you are, mister meld!" },
{ .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "I'll take any complement I can get, but you suddenly knowing my name is pretty weird dude.", .internal_monologue = "This Jester looking guy gives me the creeps..." },
{ .type = MSG_USER, .character_name = "Jester", .dialog = "A strong man needs strong metal...These magic fish could do the trick! They're magnetic", },
{ .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "You know what I'll bite. Give me the fish, business hasn't been good lately.", .internal_monologue = "He seems incredibly manipulative but metal is metal!" },
{ .type = MSG_USER, .action_taken = "ACT_give_item", .action_argument = "ITEM_fish_guts" .character_name = "Jester", .dialog = "Go ask your town mystic to reanimate these guts, and surely the fish will bring you an unparalleled bounty of pure iron!", },
{ .type = MSG_ASSISTANT, .character_name = NPC_NAME, .dialog = "Thanks for the free stuff!", .internal_monologue = "These fish guts are disgusting...yuck! This better pay off" },
{ .type = MSG_USER, .character_name = "Edeline", .dialog = "What brings you to my , },
},
"\n"
PLAYERSAY("Hey")
NPCSAY("Ugh. Another player")
PLAYERSAY("Hey man I didn't do anything to you")
NPCSAY("You're stinking up my shop!")
"Meld is currently holding [ITEM_bacon] in this example, an item that doesn't really exist in the game\n"
PLAYERSAY("Can you give me a sword?")
NPCSAY("Nope! All I got is this piece of bacon right now. And no, you can't have it.")
PLAYERSAY("Sorry man jeez.")
NPCDOSAY_ARG("Sure!", "ACT_give_item", "ITEM_bacon")
"Now in this example Meld no longer has any items, so can't give anything."
"\n"
"Meld will only give things from their inventory in exchange for something valuable, like a gold coin",
},
{ {
#undef NPC_NAME #undef NPC_NAME
#define NPC_NAME "The King" #define NPC_NAME "The King"
@ -312,26 +341,7 @@ CharacterGen characters[] = {
"If the player does indeed present the king with the chalice of gold, the king will be overwhelemd with respect and feel he has no choice but to knight the player, ending the game. The Chalice of Gold ALWAYS makes the player a knight, if the player gives it to the king.", "If the player does indeed present the king with the chalice of gold, the king will be overwhelemd with respect and feel he has no choice but to knight the player, ending the game. The Chalice of Gold ALWAYS makes the player a knight, if the player gives it to the king.",
}, },
{ {
#undef NPC_NAME
#define NPC_NAME "Meld"
.name = NPC_NAME,
.enum_name = "TheBlacksmith",
.prompt = "\n"
"The NPC you will be acting as is the blacksmith of the town, Meld. Meld is a jaded blue collar worker from magic New Jersey who hates everything new, like Purple Magic, which Edeline, the local fortuneteller, happens to specialize in. He is cold, dry, and sarcastic, wanting money and power above anything else. " NUMEROLOGIST "An example of an interaction between the player and the NPC, Meld:\n"
"\n"
PLAYERSAY("Hey")
NPCSAY("Ugh. Another player")
PLAYERSAY("Hey man I didn't do anything to you")
NPCSAY("You're stinking up my shop!")
"Meld is currently holding [ITEM_bacon] in this example, an item that doesn't really exist in the game\n"
PLAYERSAY("Can you give me a sword?")
NPCSAY("Nope! All I got is this piece of bacon right now. And no, you can't have it.")
PLAYERSAY("Sorry man jeez.")
NPCDOSAY_ARG("Sure!", "ACT_give_item", "ITEM_bacon")
"Now in this example Meld no longer has any items, so can't give anything."
"\n"
"Meld will only give things from their inventory in exchange for something valuable, like a gold coin",
},
}; };
typedef struct typedef struct

@ -254,10 +254,12 @@ void do_parsing_tests()
MD_String8 speech; MD_String8 speech;
speech = MD_S8Lit("Better have a good reason for bothering me."); speech = MD_S8Lit("Better have a good reason for bothering me.");
error = parse_chatgpt_response(scratch.arena, &e, MD_S8Fmt(scratch.arena, " Within the player's party, while the player is talking to Meld, you hear: ACT_none \"%.*s\"", MD_S8VArg(speech)), &a); MD_String8 thoughts = MD_S8Lit("Man I'm tired today\" Whatever.");
error = parse_chatgpt_response(scratch.arena, &e, MD_S8Fmt(scratch.arena, " Within the player's party, while the player is talking to Meld, you hear: ACT_none \"%.*s\" [%.*s]", MD_S8VArg(speech), MD_S8VArg(thoughts)), &a);
assert(error.size == 0); assert(error.size == 0);
assert(a.kind == ACT_none); assert(a.kind == ACT_none);
assert(MD_S8Match(speech, MD_S8(a.speech, a.speech_length), 0)); assert(MD_S8Match(speech, MD_S8(a.speech, a.speech_length), 0));
assert(MD_S8Match(thoughts, MD_S8(a.internal_monologue, a.internal_monologue_length), 0));
error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_give_item(ITEM_Chalice) \"Here you go\""), &a); error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_give_item(ITEM_Chalice) \"Here you go\""), &a);
assert(error.size > 0); assert(error.size > 0);
@ -270,7 +272,7 @@ void do_parsing_tests()
error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_give_item(ITEM_Chalice \""), &a); error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_give_item(ITEM_Chalice \""), &a);
assert(error.size > 0); assert(error.size > 0);
error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_give_item(ITEM_Chalice) \"Here you go\""), &a); error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_give_item(ITEM_Chalice) \"Here you go\" [Man I'm gonna miss that chalice]"), &a);
assert(error.size == 0); assert(error.size == 0);
assert(a.kind == ACT_give_item); assert(a.kind == ACT_give_item);
assert(a.argument.item_to_give == ITEM_Chalice); assert(a.argument.item_to_give == ITEM_Chalice);
@ -945,7 +947,7 @@ Entity *gete(EntityRef ref)
} }
} }
void push_memory(Entity *e, MD_String8 speech, ActionKind a_kind, ActionArgument a_argument, MemoryContext context, bool is_error) void push_memory(Entity *e, MD_String8 speech, MD_String8 monologue, ActionKind a_kind, ActionArgument a_argument, MemoryContext context, bool is_error)
{ {
Memory new_memory = {.action_taken = a_kind}; Memory new_memory = {.action_taken = a_kind};
assert(speech.size <= ARRLEN(new_memory.speech)); assert(speech.size <= ARRLEN(new_memory.speech));
@ -955,6 +957,8 @@ void push_memory(Entity *e, MD_String8 speech, ActionKind a_kind, ActionArgument
new_memory.action_argument = a_argument; new_memory.action_argument = a_argument;
memcpy(new_memory.speech, speech.str, speech.size); memcpy(new_memory.speech, speech.str, speech.size);
new_memory.speech_length = (int)speech.size; new_memory.speech_length = (int)speech.size;
memcpy(new_memory.internal_monologue, monologue.str, monologue.size);
new_memory.internal_monologue_length = (int)monologue.size;
if(!BUFF_HAS_SPACE(&e->memories)) if(!BUFF_HAS_SPACE(&e->memories))
{ {
BUFF_REMOVE_FRONT(&e->memories); BUFF_REMOVE_FRONT(&e->memories);
@ -972,12 +976,12 @@ void remember_error(Entity *to_modify, MD_String8 error_message)
{ {
assert(!to_modify->is_character); // this is a game logic bug if a player action is invalid assert(!to_modify->is_character); // this is a game logic bug if a player action is invalid
push_memory(to_modify, error_message, ACT_none, (ActionArgument){0}, (MemoryContext){0}, true); push_memory(to_modify, error_message, MD_S8(0, 0), ACT_none, (ActionArgument){0}, (MemoryContext){0}, true);
} }
void remember_action(Entity *to_modify, Action a, MemoryContext context) void remember_action(Entity *to_modify, Action a, MemoryContext context)
{ {
push_memory(to_modify, MD_S8(a.speech, a.speech_length), a.kind, (ActionArgument){0}, context, false); push_memory(to_modify, MD_S8(a.speech, a.speech_length), MD_S8(a.internal_monologue, a.internal_monologue_length), a.kind, (ActionArgument){0}, context, false);
if(context.i_said_this) if(context.i_said_this)
{ {
to_modify->words_said = 0; to_modify->words_said = 0;
@ -3329,6 +3333,8 @@ void frame(void)
play_audio(possible_grunts[rand() % ARRLEN(possible_grunts)], volume); play_audio(possible_grunts[rand() % ARRLEN(possible_grunts)], volume);
} }
} }
MD_ReleaseScratch(scratch);
} }

@ -94,6 +94,8 @@ typedef struct Action
MD_u8 speech[MAX_SENTENCE_LENGTH]; MD_u8 speech[MAX_SENTENCE_LENGTH];
int speech_length; int speech_length;
MD_u8 internal_monologue[MAX_SENTENCE_LENGTH];
int internal_monologue_length;
} Action; } Action;
typedef struct typedef struct
{ {
@ -120,6 +122,10 @@ typedef struct Memory
MD_u8 speech[MAX_SENTENCE_LENGTH]; MD_u8 speech[MAX_SENTENCE_LENGTH];
int speech_length; int speech_length;
// internal monologue is only valid if context.is_said_this is true
MD_u8 internal_monologue[MAX_SENTENCE_LENGTH];
int internal_monologue_length;
ItemKind given_or_received_item; ItemKind given_or_received_item;
} Memory; } Memory;
@ -474,6 +480,10 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e)
MD_S8ListPushFmt(scratch.arena, &cur_node_string, "(%s)", cur->action_argument); MD_S8ListPushFmt(scratch.arena, &cur_node_string, "(%s)", cur->action_argument);
} }
MD_S8ListPushFmt(scratch.arena, &cur_node_string, " \"%s\"", cur->dialog); MD_S8ListPushFmt(scratch.arena, &cur_node_string, " \"%s\"", cur->dialog);
if(cur->internal_monologue)
{
MD_S8ListPushFmt(scratch.arena, &cur_node_string, " [%s]", cur->internal_monologue);
}
MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, cur->type, MD_S8ListJoin(scratch.arena, cur_node_string, &(MD_StringJoin){0}))); MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, cur->type, MD_S8ListJoin(scratch.arena, cur_node_string, &(MD_StringJoin){0})));
} }
@ -539,6 +549,7 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e)
sent_type = it->context.author_npc_kind == e->npc_kind ? MSG_ASSISTANT : MSG_USER; sent_type = it->context.author_npc_kind == e->npc_kind ? MSG_ASSISTANT : MSG_USER;
} }
if(actions[it->action_taken].takes_argument) if(actions[it->action_taken].takes_argument)
{ {
if(it->action_taken == ACT_give_item) if(it->action_taken == ACT_give_item)
@ -554,6 +565,11 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e)
{ {
current_string = MD_S8Fmt(scratch.arena, "%.*s ACT_%s \"%.*s\"", MD_S8VArg(context_string), actions[it->action_taken].name, it->speech_length, it->speech); current_string = MD_S8Fmt(scratch.arena, "%.*s ACT_%s \"%.*s\"", MD_S8VArg(context_string), actions[it->action_taken].name, it->speech_length, it->speech);
} }
if(it->context.i_said_this)
{
current_string = MD_S8Fmt(scratch.arena, "%.*s [%.*s]", MD_S8VArg(current_string), it->internal_monologue_length, it->internal_monologue);
}
} }
assert(sent_type != -1); assert(sent_type != -1);
@ -766,6 +782,20 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
memcpy(out->speech, speech.str, speech.size); memcpy(out->speech, speech.str, speech.size);
out->speech_length = (int)speech.size; out->speech_length = (int)speech.size;
MD_u64 beginning_of_monologue = MD_S8FindSubstring(sentence, MD_S8Lit("["), end_of_speech, 0);
MD_u64 end_of_monologue = MD_S8FindSubstring(sentence, MD_S8Lit("]"), beginning_of_monologue, 0);
if(beginning_of_monologue == sentence.size || end_of_monologue == sentence.size)
{
error_message = MD_S8Fmt(arena, "Expected an internal monologue for your character enclosed by '[' and ']' after the speech in quotes, but couldn't find anything!");
goto endofparsing;
}
MD_String8 monologue = MD_S8Substring(sentence, beginning_of_monologue + 1, end_of_monologue);
memcpy(out->internal_monologue, monologue.str, monologue.size);
out->internal_monologue_length = (int)monologue.size;
endofparsing: endofparsing:
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
return error_message; return error_message;

Loading…
Cancel
Save