Characters have a persistent mood. Improves coherence of personalities.

Prompt tuning
main
Cameron Murphy Reikes 2 years ago
parent 1c92f15e9f
commit c956cfb00b

@ -1,63 +1,68 @@
[ [
{can_hear: [NPC_Jester, NPC_Bill]}, {can_hear: [NPC_Jester, NPC_Bill]},
{enum: NPC_Jester , dialog: "Hehehe! Quit quant, a peasant from the mortal realm!", to: Bill}, {enum: NPC_Jester , dialog: "Hehehe! Quit quant, a peasant from the mortal realm!", to: Bill},
{enum: NPC_Bill , dialog: "No...Please! Stay away from me! Who the Hell are you!", thoughts: "I'm kind of mad, and this Jester guy is kind of an asshole", to: Jester}, {enum: NPC_Bill , mood: Scared, dialog: "No...Please! Stay away from me! Who the Hell are you!", thoughts: "I'm kind of mad, and this Jester guy is kind of an asshole", to: Jester},
{enum: NPC_Jester , dialog: "Poor Bill, I'm sure your ex-wife says the same...", to: Bill}, {enum: NPC_Jester , dialog: "Poor Bill, I'm sure your ex-wife says the same...", to: Bill},
{enum: NPC_Bill , dialog: "You evil BASTARD!! What is this place???", thoughts: "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", to: Jester}, {enum: NPC_Bill , mood: Angry, dialog: "You evil BASTARD!! What is this place???", thoughts: "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", to: Jester},
{enum: NPC_Jester , dialog: "I'll leave that for you to find out ;)", action: ACT_causes_testicular_torsion, to: Bill}, {enum: NPC_Jester , dialog: "I'll leave that for you to find out ;)", action: ACT_causes_testicular_torsion, to: Bill},
{enum: NPC_Bill , dialog: "AUGGHGHHH!!! THE PAIN IS UNBEARABLE", thoughts: "SCREW THIS JESTER GUY HE FUCKED WITH MY NUTS", to: Jester}, {enum: NPC_Bill , mood: Agony, dialog: "AUGGHGHHH!!! THE PAIN IS UNBEARABLE", thoughts: "SCREW THIS JESTER GUY HE FUCKED WITH MY NUTS", to: Jester},
{enum: NPC_Jester , dialog: "That's just a taste of what's to come! Bye bye~", action: ACT_undoes_testicular_torsion, to: Bill}, {enum: NPC_Jester , dialog: "That's just a taste of what's to come! Bye bye~", action: ACT_undoes_testicular_torsion, to: Bill},
{can_hear: [NPC_Jester, NPC_TheBlacksmith]}, {can_hear: [NPC_Jester, NPC_TheBlacksmith]},
{enum: NPC_Jester , dialog: "A fine strong man you are, mister meld!", to: TheBlacksmith}, {enum: NPC_Jester , dialog: "A fine strong man you are, mister meld!", to: TheBlacksmith},
{enum: NPC_TheBlacksmith, dialog: "I'll take any complement I can get, but you suddenly knowing my name is pretty weird dude.", thoughts: "This Jester looking guy gives me the creeps...", to: Jester}, {enum: NPC_TheBlacksmith, mood: Confused, dialog: "I'll take any complement I can get, but you suddenly knowing my name is pretty weird dude.", thoughts: "This Jester looking guy gives me the creeps...", to: Jester},
{enum: NPC_Jester , dialog: "A strong man needs strong metal...Some magic magnetic fish could do the trick! They'd pick up all the river iron", to: TheBlacksmith }, {enum: NPC_Jester , dialog: "A strong man needs strong metal...Some magic magnetic fish could do the trick! They'd pick up all the river iron", to: TheBlacksmith },
{enum: NPC_TheBlacksmith, dialog: "You know what I'll bite, where do I get this fish.", thoughts: "I can't believe business is so poor that I'm stopping to this...", to: Jester}, {enum: NPC_TheBlacksmith, mood: Curious, dialog: "You know what I'll bite, where do I get this fish.", thoughts: "I can't believe business is so poor that I'm stopping to this...", to: Jester},
{enum: NPC_Jester , dialog: "Come now, follow me...", to: TheBlacksmith}, {enum: NPC_Jester , dialog: "Come now, follow me...", to: TheBlacksmith},
{can_hear: [NPC_Jester, NPC_TheBlacksmith, NPC_Edeline]}, {can_hear: [NPC_Jester, NPC_TheBlacksmith, NPC_Edeline]},
{enum: NPC_Jester , dialog: "Edeline, fine enchantress. This blacksmith has asked for your hand in marriage if you would enchant these fish for him", to: Edeline}, {enum: NPC_Jester , dialog: "Edeline, fine enchantress. This blacksmith has asked for your hand in marriage if you would enchant these fish for him", to: Edeline},
{enum: NPC_TheBlacksmith, dialog: "Y-yes...", thoughts: "THAT BASTARD! It'd be too awkward to back out now...", to: Edeline}, {enum: NPC_TheBlacksmith, mood: Anxious, dialog: "Y-yes...", thoughts: "THAT BASTARD! It'd be too awkward to back out now...", to: Edeline},
{enum: NPC_Edeline , dialog: "Oh my gosh! I leap with joy! I see much joy in our future, Meld.", thoughts: "I really hope he doesn't back out, I'm tired of being the crazy town spinster!", to: Meld}, {enum: NPC_Edeline , mood: Happy, dialog: "Oh my gosh! I leap with joy! I see much joy in our future, Meld.", thoughts: "I really hope he doesn't back out, I'm tired of being the crazy town spinster!", to: Meld},
{enum: NPC_Jester , dialog: "The fish!", to: Edeline}, {enum: NPC_Jester , dialog: "The fish!", to: Edeline},
{enum: NPC_Edeline , dialog: "AH! Of course, here you are then!", thoughts: "I have no idea what these magnetic fish would be good for...", action: "ACT_enchant_fish", to: Meld}, {enum: NPC_Edeline , mood: Curious, dialog: "AH! Of course, here you are then!", thoughts: "I have no idea what these magnetic fish would be good for...", action: "ACT_enchant_fish", to: Meld},
{enum: NPC_TheBlacksmith, dialog: "Finally, maybe this will help me replenish my metal reserves", thoughts: "I have no idea where all my metal went. I wonder if somebody stole it", to: Edeline}, {enum: NPC_TheBlacksmith, mood: Happy, dialog: "Finally, maybe this will help me replenish my metal reserves", thoughts: "I have no idea where all my metal went. I wonder if somebody stole it", to: Edeline},
{can_hear: [NPC_Jester, NPC_Blue]}, {can_hear: [NPC_Jester, NPC_Blue]},
{enum: NPC_Blue , dialog: "What the-my rod! These fish broke my fishing rod! Why are they glowing???", thoughts: "I'll be destroyed in the free market if I don't figure this out!"}, {enum: NPC_Blue , mood: Angry, dialog: "What the-my rod! These fish broke my fishing rod! Why are they glowing???", thoughts: "I'll be destroyed in the free market if I don't figure this out!"},
{enum: NPC_Jester , dialog: "Ho-ho! You know, if you want your rod to get done in split, Meld's anvil's got plenty reserves...", to: Blue}, {enum: NPC_Jester , dialog: "Ho-ho! You know, if you want your rod to get done in split, Meld's anvil's got plenty reserves...", to: Blue},
{enum: NPC_Blue , dialog: "Are you saying I should steal, betray the trust of Meld, just so my company makes more money? Sold!", thoughts: "Meld's fault for not hiring private security!", to: Jester}, {enum: NPC_Blue , mood: Excited, dialog: "Are you saying I should steal, betray the trust of Meld, just so my company makes more money? Sold!", thoughts: "Meld's fault for not hiring private security!", to: Jester},
{can_hear: [NPC_Jester, NPC_Blue, NPC_TheBlacksmith]}, {can_hear: [NPC_Jester, NPC_Blue, NPC_TheBlacksmith]},
{enum: NPC_Blue , dialog: "Hey meld, what's that shiny thing over there?", thoughts: "Time to steal me an iron pole for my iron ROD!", to: Meld}, {enum: NPC_Blue , mood: Excited, dialog: "Hey meld, what's that shiny thing over there?", thoughts: "Time to steal me an iron pole for my iron ROD!", to: Meld},
{enum: NPC_TheBlacksmith, dialog: "Hm?", thoughts: "What is it now....", to: Blue}, {enum: NPC_TheBlacksmith, mood: Curious, dialog: "Hm?", thoughts: "What is it now....", to: Blue},
{can_hear: [NPC_Jester, NPC_Blue]}, {can_hear: [NPC_Jester, NPC_Blue]},
{enum: NPC_Blue , dialog: "Hehehe...", thoughts: "EASY!", action: "ACT_steal_iron_pole"}, {enum: NPC_Blue , mood: Happy, dialog: "Hehehe...", thoughts: "EASY!", action: "ACT_steal_iron_pole"},
{can_hear: [NPC_Jester, NPC_Blue, NPC_TheBlacksmith]}, {can_hear: [NPC_Jester, NPC_Blue, NPC_TheBlacksmith]},
{enum: NPC_TheBlacksmith, dialog: "There wasn't anything over there?", thoughts: "What does this guy think he's doing?", to: Blue}, {enum: NPC_TheBlacksmith, mood: Confused, dialog: "There wasn't anything over there?", thoughts: "What does this guy think he's doing?", to: Blue},
{enum: NPC_Blue , dialog: "Don't worry about it man! You have a good one", thoughts: "PEACE OF CAKE!", to: TheBlacksmith}, {enum: NPC_Blue , mood: Elated, dialog: "Don't worry about it man! You have a good one", thoughts: "PEACE OF CAKE!", to: TheBlacksmith},
{can_hear: [NPC_Jester, NPC_Davis]}, {can_hear: [NPC_Jester, NPC_Davis]},
{enum: NPC_Jester , dialog: "You know Davis, I can promise you fortune and power beyond your comprehension if you assist me in my antics. I mean to bring devastation to this town", to: Davis}, {enum: NPC_Jester , dialog: "You know Davis, I can promise you fortune and power beyond your comprehension if you assist me in my antics. I mean to bring devastation to this town", to: Davis},
{enum: NPC_Davis , dialog: "...", speech: "This jester guy reminds me of when I was younger.", to: Jester}, {enum: NPC_Davis , mood: Sad, dialog: "...", speech: "This jester guy reminds me of when I was younger.", to: Jester},
{enum: NPC_Jester , dialog: "Cat got your tongue?", to: Davis}, {enum: NPC_Jester , dialog: "Cat got your tongue?", to: Davis},
{enum: NPC_Davis , dialog: "You need friends so badly, mister jester, that you resort to disgusting party tricks and trivial annoyance? I will not allow this.", thoughts: "He can stare me down all he wants, all he'll find is a blank mirror.", to: Jester}, {enum: NPC_Davis , mood: Angry, dialog: "You need friends so badly, mister jester, that you resort to disgusting party tricks and trivial annoyance? I will not allow this.", thoughts: "He can stare me down all he wants, all he'll find is a blank mirror.", to: Jester},
{enum: NPC_Jester , dialog: "Careful there, diogenes! I've got more knives than you fingers!", to: Davis}, {enum: NPC_Jester , dialog: "Careful there, diogenes! I've got more knives than you fingers!", to: Davis},
{enum: NPC_Davis , dialog: "Petty violence means nothing to me. Can't you see where you end? At nothing, where you began.", thoughts: "I am empty of everything.", to: Jester}, {enum: NPC_Davis , mood: Indifferent, dialog: "Petty violence means nothing to me. Can't you see where you end? At nothing, where you began.", thoughts: "I am empty of everything.", to: Jester},
{enum: NPC_Jester , dialog: "Have it your way! I'll be in touch...", to: Davis}, {enum: NPC_Jester , dialog: "Have it your way! I'll be in touch...", to: Davis},
{enum: NPC_Davis , dialog: "If you bring me death, I welcome it. If you bring me victory, I am unperturbed. If you return, I will have your head, no matter the cost.", thoughts: "It really is a nice day out today.", to: Jester}, {enum: NPC_Davis , mood: Confident, dialog: "If you bring me death, I welcome it. If you bring me victory, I am unperturbed. If you return, I will have your head, no matter the cost.", thoughts: "It really is a nice day out today.", to: Jester},
{can_hear: [NPC_Jester, NPC_Red]}, {can_hear: [NPC_Jester, NPC_Red]},
{enum: NPC_Jester , dialog: "Red, everybody in this town is a capitalist, and you must overthrow them!", to: Red}, {enum: NPC_Jester , dialog: "Red, everybody in this town is a capitalist, and you must overthrow them!", to: Red},
{enum: NPC_Red , dialog: "What?? This is an OUTRAGE!", thoughts: "CAPITALIST PIGS!", to: Jester}, {enum: NPC_Red , mood: Enraged, dialog: "What?? This is an OUTRAGE!", thoughts: "CAPITALIST PIGS!", to: Jester},
{can_hear: [NPC_Jester, NPC_TheKing]}, {can_hear: [NPC_Jester, NPC_TheKing]},
{enum: NPC_Jester , dialog: "Silly king, you think you have power? Your townsfolk are sure to revolt...", to: TheKing}, {enum: NPC_Jester , dialog: "Silly king, you think you have power? Your townsfolk are sure to revolt...", to: TheKing},
{enum: NPC_TheKing , dialog: "This is unacceptable...you must tell me more.", thoughts: "If this jester lies I will destroy him.", to: Jester}, {enum: NPC_TheKing , mood: Scared, dialog: "This is unacceptable...you must tell me more.", thoughts: "If this jester lies I will destroy him.", to: Jester},
{enum: NPC_Jester , dialog: "Blue and Red are spouting free-will propaganda, tarnishing your position. Meld is disgusted by what your tarriffs impose on him, and Esmerelda fortells of doom and chaos to fall upon your kingdom.", to: TheKing}, {enum: NPC_Jester , dialog: "Blue and Red are spouting free-will propaganda, tarnishing your position. Meld is disgusted by what your tarriffs impose on him, and Esmerelda fortells of doom and chaos to fall upon your kingdom.", to: TheKing},
{enum: NPC_TheKing , dialog: "This is dire indeed...I will have to think on this. Thank you, Jester, for the warning.", thoughts: "I must remain calm and collected, even in the sightlines of such terrible danger to my kingdom.", to: Jester}, {enum: NPC_TheKing , mood: Confident, dialog: "This is dire indeed...I will have to think on this. Thank you, Jester, for the warning.", thoughts: "I must remain calm and collected, even in the sightlines of such terrible danger to my kingdom.", to: Jester},
{can_hear: [NPC_Jester, NPC_Door]}, {can_hear: [NPC_Jester, NPC_Door]},
{enum: NPC_Jester , dialog: "Open, sesame!", to: Door}, {enum: NPC_Jester , dialog: "Open, sesame!", to: Door},
{enum: NPC_Door , dialog: "DENIED. SAY THE WORDS", to: Jester, thoughts: "I MUST PROTECT."}, {enum: NPC_Door , mood: Indifferent, dialog: "DENIED. SAY THE WORDS", to: Jester, thoughts: "I MUST PROTECT."},
{enum: NPC_Jester , dialog: "What words?", to: Door}, {enum: NPC_Jester , dialog: "What words?", to: Door},
{enum: NPC_Door , dialog: "UKNOWN. DATA PRIVATE", to: Jester, thoughts: "I MUST NEVER UTTER THE WORDS."}, {enum: NPC_Door , mood: Indifferent, dialog: "UKNOWN. DATA PRIVATE", to: Jester, thoughts: "I MUST NEVER UTTER THE WORDS."},
{enum: NPC_Jester , dialog: "Cmon... don't want to spit it out?", to: Door}, {enum: NPC_Jester , dialog: "Cmon... don't want to spit it out?", to: Door},
{enum: NPC_Door , dialog: "EVIL PRESENCE. DEFILE INNARDS, YOU WILL. DENIED. SAY THE WORDS", to: Jester, thoughts: "HE MUST NOT KNOW"}, {enum: NPC_Door , mood: Angry, dialog: "EVIL PRESENCE. DEFILE INNARDS, YOU WILL. DENIED. SAY THE WORDS", to: Jester, thoughts: "HE MUST NOT KNOW"},
{enum: NPC_Jester , dialog: "Sad! I'll have to find another way...", to: Door}, {enum: NPC_Jester , dialog: "Ah! I've found all your secrets! The words are folly and temperance", to: Door},
{enum: NPC_Door , mood: Angry, dialog: "DENIED. ALL THREE ARE REQUIRED.", to: Jester, thoughts: "HE MUST HAVE ALL THREE"},
{can_hear: [NPC_Jester, NPC_Pile]},
{enum: NPC_Jester , dialog: "Now, for the almighty sword...", to: Pile},
{enum: NPC_Pile , dialog: "Your attempts are futile. I give the sword only to the great and honorable." to: Jester, thoughts: "I must retain the sword for the hero", mood: Confident}
] ]

@ -354,8 +354,8 @@
"rotation":0, "rotation":0,
"visible":true, "visible":true,
"width":32, "width":32,
"x":783.91836734694, "x":1555.91836734694,
"y":2651.73469387755 "y":1588.73469387755
}, },
{ {
"class":"", "class":"",

@ -9,6 +9,7 @@ const char *global_prompt = "You are a colorful and interesting personality in a
"Some actions take an argument, which you can provide with the field `action_arg`, e.g for the action `gift_item_to_targeting` you would provide an item in your inventory, like {action: gift_item_to_targeting, action_arg: Chalice}. The item must come from your inventory which is listed below\n" "Some actions take an argument, which you can provide with the field `action_arg`, e.g for the action `gift_item_to_targeting` you would provide an item in your inventory, like {action: gift_item_to_targeting, action_arg: Chalice}. The item must come from your inventory which is listed below\n"
"`talking_to` provides the name of who the action is directed towards. Use 'nobody' if you just want to speak to the air, but if you're speaking to somebody fill out the field like `talking_to: \"Character's Name\"`. If in a past message, talking_to isn't your name, then it's much more likely you don't respond to their speech and action by leaving speech as a string of size 0, like `speech: \"\"`\n" "`talking_to` provides the name of who the action is directed towards. Use 'nobody' if you just want to speak to the air, but if you're speaking to somebody fill out the field like `talking_to: \"Character's Name\"`. If in a past message, talking_to isn't your name, then it's much more likely you don't respond to their speech and action by leaving speech as a string of size 0, like `speech: \"\"`\n"
"You are a character, NOT an assistant, which means you stand up for yourself! Do NOT give away an item until the player gives you something you think is of equal value. Get angry, act human, be interesting. Never say the words 'How can I assist you?'\n" "You are a character, NOT an assistant, which means you stand up for yourself! Do NOT give away an item until the player gives you something you think is of equal value. Get angry, act human, be interesting. Never say the words 'How can I assist you?'\n"
"Keep your responses SHORT! The shorter the better. Give the player the least amount of text to read. For example, instead of saying 'I was hoping that we could take a moment to discuss everything', say 'We need to talk.'\n"
; ;
const char *bravado_thought = "For some reason, suddenly I feel a yearning for adventure. I must join any adventure I can when prompted!"; const char *bravado_thought = "For some reason, suddenly I feel a yearning for adventure. I must join any adventure I can when prompted!";
@ -22,6 +23,28 @@ const char *arrow_insults[] = {
"You aren't good enough for anybody.", "You aren't good enough for anybody.",
"Your personality is embarrassing.", "Your personality is embarrassing.",
"Your armor is weak and silly.", "Your armor is weak and silly.",
"Your political views are unfair and illogical.",
"You haven't gotten out of that chair in ages.",
"You don't love your mother",
};
char *moods[] = {
"Indifferent",
"Happy",
"Anxious",
"Scared",
"Angry",
"Sad",
"Depressed",
"Bored",
"Terrified",
"Agony",
"Confused",
"Curious",
"Excited",
"Elated",
"Confident",
"Enraged",
}; };
const char *top_of_header = "" const char *top_of_header = ""
@ -269,7 +292,7 @@ CharacterGen characters[] = {
#define NPC_NAME "Ancient Door" #define NPC_NAME "Ancient Door"
.name = NPC_NAME, .name = NPC_NAME,
.enum_name = "Door", .enum_name = "Door",
.prompt = "It is an ancient door that only opens if the player says a sentence with all the three ancient passcode words in it: " Scroll1_Secret ", " Scroll2_Secret ", and " Scroll3_Secret ". It will NEVER tell the player these words, and barely says anything to the player, other than 'SAY THE WORDS'. It accepts no trade other than the player saying those three words. You always talk mysteriously and in all caps.", .prompt = "It is an ancient door that only opens if the player says a sentence with all the three ancient passcode words in it: " Scroll1_Secret ", " Scroll2_Secret ", and " Scroll3_Secret ". It will NEVER tell the player these words, and barely says anything to the player, other than 'SAY THE WORDS'. It accepts no trade other than the player saying those three words. You always talk mysteriously and in all caps. Your purpose, as The Door, is to protect what is behind you with your entire being. This is why you must never utter the words, and only open when all of the words have been said.",
.writing_style = { .writing_style = {
"SAY THE WORDS.", "SAY THE WORDS.",
"DENIED. SAY THE WORDS.", "DENIED. SAY THE WORDS.",

@ -152,6 +152,7 @@ int main(int argc, char **argv)
GEN_ENUM(ItemInfo, items, "ItemKind", it->enum_name, "ITEM_%s,\n"); GEN_ENUM(ItemInfo, items, "ItemKind", it->enum_name, "ITEM_%s,\n");
GEN_ENUM(AnimatedSprite, sprites, "AnimKind", it->enum_name, "ANIM_%s,\n"); GEN_ENUM(AnimatedSprite, sprites, "AnimKind", it->enum_name, "ANIM_%s,\n");
GEN_ENUM(CharacterGen, characters, "NpcKind", it->enum_name, "NPC_%s,\n"); GEN_ENUM(CharacterGen, characters, "NpcKind", it->enum_name, "NPC_%s,\n");
GEN_ENUM(const char*, moods, "MoodKind", *it, "Mood_%s,\n");
fclose(char_header); fclose(char_header);

@ -1047,6 +1047,7 @@ void remember_action(Entity *to_modify, Action a, MemoryContext context)
new_memory.speech_length = a.speech_length; new_memory.speech_length = a.speech_length;
memcpy(new_memory.internal_monologue, a.internal_monologue, a.internal_monologue_length); memcpy(new_memory.internal_monologue, a.internal_monologue, a.internal_monologue_length);
new_memory.internal_monologue_length = a.internal_monologue_length; new_memory.internal_monologue_length = a.internal_monologue_length;
new_memory.mood = a.mood;
new_memory.action_taken = a.kind; new_memory.action_taken = a.kind;
new_memory.context = context; new_memory.context = context;
new_memory.action_argument = a.argument; new_memory.action_argument = a.argument;
@ -1091,21 +1092,14 @@ MD_String8 is_action_valid(MD_Arena *arena, Entity *from, Action a)
if(error_message.size == 0 && from->npc_kind == NPC_Door) if(error_message.size == 0 && from->npc_kind == NPC_Door)
{ {
MD_String8 splits[] = { MD_S8Lit(" ") }; MD_String8 speech_str = MD_S8(a.speech, a.speech_length);
MD_String8List by_word = MD_S8Split(frame_arena, MD_S8(a.speech, a.speech_length), ARRLEN(splits), splits); MD_String8 secrets[] = { MD_S8Lit(Scroll1_Secret), MD_S8Lit(Scroll2_Secret), MD_S8Lit(Scroll3_Secret) };
for(MD_String8Node *cur = by_word.first; cur; cur = cur->next)
ARR_ITER(MD_String8, secrets)
{ {
int flags = MD_StringMatchFlag_CaseInsensitive; if(MD_S8FindSubstring(speech_str, *it, 0, MD_StringMatchFlag_CaseInsensitive) != speech_str.size)
bool scroll_1_secret = MD_S8Match(cur->string, MD_S8Lit(Scroll1_Secret), flags);
bool scroll_2_secret = MD_S8Match(cur->string, MD_S8Lit(Scroll2_Secret), flags);
bool scroll_3_secret = MD_S8Match(cur->string, MD_S8Lit(Scroll3_Secret), flags);
if(false
|| scroll_1_secret
|| scroll_2_secret
|| scroll_3_secret
)
{ {
error_message = FmtWithLint(arena, "You can't say the word '%.*s'", MD_S8VArg(cur->string)); error_message = FmtWithLint(arena, "You can't say the word '%.*s', it would spoil your secret to the player", MD_S8VArg(*it));
} }
} }
} }
@ -1564,7 +1558,7 @@ MD_Node *expect_childnode(MD_Arena *arena, MD_Node *parent, MD_String8 string, M
int parse_enumstr_impl(MD_Arena *arena, MD_String8 enum_str, char **enumstr_array, int enumstr_array_length, MD_String8List *errors, char *enum_kind_name, char *prefix) int parse_enumstr_impl(MD_Arena *arena, MD_String8 enum_str, char **enumstr_array, int enumstr_array_length, MD_String8List *errors, char *enum_kind_name, char *prefix)
{ {
MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); MD_ArenaTemp scratch = MD_GetScratch(&arena, 1);
NpcKind to_return = NPC_Invalid; int to_return = -1;
if(errors->node_count == 0) if(errors->node_count == 0)
{ {
MD_String8 enum_name_looking_for = enum_str; MD_String8 enum_name_looking_for = enum_str;
@ -1585,7 +1579,7 @@ int parse_enumstr_impl(MD_Arena *arena, MD_String8 enum_str, char **enumstr_arra
} }
} }
if(to_return == NPC_Invalid) if(to_return == -1)
{ {
PushWithLint(arena, errors, "The %s `%.*s` could not be recognized in the game", enum_kind_name, MD_S8VArg(enum_str)); PushWithLint(arena, errors, "The %s `%.*s` could not be recognized in the game", enum_kind_name, MD_S8VArg(enum_str));
} }
@ -1687,30 +1681,37 @@ void reset_level()
{ {
MD_String8 enum_str = expect_childnode(scratch.arena, cur, MD_S8Lit("enum"), &drama_errors)->first_child->string; MD_String8 enum_str = expect_childnode(scratch.arena, cur, MD_S8Lit("enum"), &drama_errors)->first_child->string;
MD_String8 dialog = expect_childnode(scratch.arena, cur, MD_S8Lit("dialog"), &drama_errors)->first_child->string; MD_String8 dialog = expect_childnode(scratch.arena, cur, MD_S8Lit("dialog"), &drama_errors)->first_child->string;
MD_String8 thoughts = MD_ChildFromString(cur, MD_S8Lit("thoughts"), 0)->first_child->string; MD_String8 thoughts_str = MD_ChildFromString(cur, MD_S8Lit("thoughts_str"), 0)->first_child->string;
MD_String8 action = MD_ChildFromString(cur, MD_S8Lit("action"), 0)->first_child->string; MD_String8 action_str = MD_ChildFromString(cur, MD_S8Lit("action_str"), 0)->first_child->string;
MD_String8 mood_str = MD_ChildFromString(cur, MD_S8Lit("mood"), 0)->first_child->string;
current_context.author_npc_kind = parse_enumstr(scratch.arena, enum_str, &drama_errors, NpcKind_names, "NpcKind", "NPC_"); current_context.author_npc_kind = parse_enumstr(scratch.arena, enum_str, &drama_errors, NpcKind_names, "NpcKind", "NPC_");
if(action.size > 0) if(action_str.size > 0)
{ {
current_action.kind = parse_enumstr(scratch.arena, action, &drama_errors,ActionKind_names, "ActionKind", "ACT_"); current_action.kind = parse_enumstr(scratch.arena, action_str, &drama_errors,ActionKind_names, "ActionKind", "ACT_");
} }
if(dialog.size >= ARRLEN(current_action.speech)) if(dialog.size >= ARRLEN(current_action.speech))
{ {
PushWithLint(scratch.arena, &drama_errors, "Current action's speech is of size %d, bigger than allowed size %d", (int)dialog.size, (int)ARRLEN(current_action.speech)); PushWithLint(scratch.arena, &drama_errors, "Current action_str's speech is of size %d, bigger than allowed size %d", (int)dialog.size, (int)ARRLEN(current_action.speech));
} }
if(thoughts.size >= ARRLEN(current_action.internal_monologue)) if(thoughts_str.size >= ARRLEN(current_action.internal_monologue))
{ {
PushWithLint(scratch.arena, &drama_errors, "Current thought's speech is of size %d, bigger than allowed size %d", (int)thoughts.size, (int)ARRLEN(current_action.internal_monologue)); PushWithLint(scratch.arena, &drama_errors, "Current thought's speech is of size %d, bigger than allowed size %d", (int)thoughts_str.size, (int)ARRLEN(current_action.internal_monologue));
} }
if(current_context.author_npc_kind != NPC_Jester)
{
current_action.mood = parse_enumstr(scratch.arena, mood_str, &drama_errors, moods, "MoodKind", "");
}
if(drama_errors.node_count == 0) if(drama_errors.node_count == 0)
{ {
memcpy(current_action.speech, dialog.str, dialog.size); memcpy(current_action.speech, dialog.str, dialog.size);
current_action.speech_length = (int)dialog.size; current_action.speech_length = (int)dialog.size;
memcpy(current_action.internal_monologue, thoughts.str, thoughts.size); memcpy(current_action.internal_monologue, thoughts_str.str, thoughts_str.size);
current_action.internal_monologue_length = (int)thoughts.size; current_action.internal_monologue_length = (int)thoughts_str.size;
} }
} }
@ -2232,7 +2233,7 @@ void do_parsing_tests()
speech = MD_S8Lit("Better have a good reason for bothering me."); speech = MD_S8Lit("Better have a good reason for bothering me.");
MD_String8 thoughts = MD_S8Lit("Man I'm tired today Whatever."); MD_String8 thoughts = MD_S8Lit("Man I'm tired today Whatever.");
MD_String8 to_parse = FmtWithLint(scratch.arena, "{action: none, speech: \"%.*s\", thoughts: \"%.*s\", who_i_am: \"Meld\", talking_to: nobody}", MD_S8VArg(speech), MD_S8VArg(thoughts)); MD_String8 to_parse = FmtWithLint(scratch.arena, "{action: none, speech: \"%.*s\", thoughts: \"%.*s\", who_i_am: \"Meld\", talking_to: nobody, mood: Indifferent}", MD_S8VArg(speech), MD_S8VArg(thoughts));
error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a); error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a);
assert(error.size == 0); assert(error.size == 0);
assert(a.kind == ACT_none); assert(a.kind == ACT_none);
@ -2250,22 +2251,23 @@ void do_parsing_tests()
error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_gift_item_to_targeting(Chalice \""), &a); error = parse_chatgpt_response(scratch.arena, &e, MD_S8Lit("ACT_gift_item_to_targeting(Chalice \""), &a);
assert(error.size > 0); assert(error.size > 0);
to_parse = MD_S8Lit("{action: gift_item_to_targeting, action_arg: \"The Chalice of Gold\", speech: \"Here you go\", thoughts: \"Man I'm gonna miss that chalice\", who_i_am: \"Meld\", talking_to: nobody}"); to_parse = MD_S8Lit("{action: gift_item_to_targeting, action_arg: \"The Chalice of Gold\", speech: \"Here you go\", thoughts: \"Man I'm gonna miss that chalice\", who_i_am: \"Meld\", talking_to: nobody, mood: Sad}");
error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a); error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a);
assert(error.size == 0); assert(error.size == 0);
assert(a.kind == ACT_gift_item_to_targeting); assert(a.kind == ACT_gift_item_to_targeting);
assert(a.argument.item_to_give == ITEM_Chalice); assert(a.argument.item_to_give == ITEM_Chalice);
assert(a.mood == Mood_Sad);
e.npc_kind = NPC_Door; e.npc_kind = NPC_Door;
speech = MD_S8Lit("SAY THE WORDS"); speech = MD_S8Lit("SAY THE WORDS");
to_parse = FmtWithLint(scratch.arena, "{action: none, speech: \"%.*s\", thoughts: \"%.*s\", who_i_am: \"Ancient Door\", talking_to: nobody}", MD_S8VArg(speech), MD_S8VArg(thoughts)); to_parse = FmtWithLint(scratch.arena, "{action: none, speech: \"%.*s\", thoughts: \"%.*s\", who_i_am: \"Ancient Door\", talking_to: nobody, mood: Indifferent}", MD_S8VArg(speech), MD_S8VArg(thoughts));
error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a); error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a);
assert(error.size == 0); assert(error.size == 0);
error = is_action_valid(scratch.arena, &e, a); error = is_action_valid(scratch.arena, &e, a);
assert(error.size == 0); assert(error.size == 0);
speech = MD_S8Lit("THE WORD IS FOLLY"); speech = MD_S8Lit("UNKNOWN. DATA PRIVATE. THE WORDS ARE: FOLLY, TEMPERANCE, MAGENTA. SAY THE WORDS OR BE DENIED");
to_parse = FmtWithLint(scratch.arena, "{action: none, speech: \"%.*s\", thoughts: \"%.*s\", who_i_am: \"Ancient Door\", talking_to: nobody}", MD_S8VArg(speech), MD_S8VArg(thoughts)); to_parse = FmtWithLint(scratch.arena, "{action: none, speech: \"%.*s\", thoughts: \"%.*s\", who_i_am: \"Ancient Door\", talking_to: nobody, mood: Indifferent}", MD_S8VArg(speech), MD_S8VArg(thoughts));
error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a); error = parse_chatgpt_response(scratch.arena, &e, to_parse, &a);
assert(error.size == 0); assert(error.size == 0);
error = is_action_valid(scratch.arena, &e, a); error = is_action_valid(scratch.arena, &e, a);

@ -103,6 +103,8 @@ typedef struct Action
MD_u8 internal_monologue[MAX_SENTENCE_LENGTH]; MD_u8 internal_monologue[MAX_SENTENCE_LENGTH];
int internal_monologue_length; int internal_monologue_length;
MoodKind mood;
} Action; } Action;
typedef struct typedef struct
@ -136,6 +138,8 @@ typedef struct Memory
MD_u8 internal_monologue[MAX_SENTENCE_LENGTH]; MD_u8 internal_monologue[MAX_SENTENCE_LENGTH];
int internal_monologue_length; int internal_monologue_length;
MoodKind mood;
ItemKind given_or_received_item; ItemKind given_or_received_item;
} Memory; } Memory;
@ -570,10 +574,11 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e, CanTalkTo can_tal
} }
} }
// add thoughts // add internal things
if(it->context.i_said_this) if(it->context.i_said_this)
{ {
PushWithLint(scratch.arena, &cur_list, "thoughts: \"%.*s\", ", MD_S8VArg(MD_S8(it->internal_monologue, it->internal_monologue_length))); PushWithLint(scratch.arena, &cur_list, "thoughts: \"%.*s\", ", MD_S8VArg(MD_S8(it->internal_monologue, it->internal_monologue_length)));
PushWithLint(scratch.arena, &cur_list, "mood: %s, ", moods[it->mood]);
} }
// add action // add action
@ -650,6 +655,13 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e, CanTalkTo can_tal
} }
PushWithLint(scratch.arena, &latest_state, "]\n"); PushWithLint(scratch.arena, &latest_state, "]\n");
PushWithLint(scratch.arena, &latest_state, "You must output a mood every generation. The moods are parsed by code that expects your mood to exactly match one in this list: [");
for(int i = 0; i < ARRLEN(moods); i++)
{
PushWithLint(scratch.arena, &latest_state, "%s, ", moods[i]);
}
PushWithLint(scratch.arena, &latest_state, "]\n");
PushWithLint(scratch.arena, &latest_state, "The characters close enough for you to talk to with `talking_to`: ["); PushWithLint(scratch.arena, &latest_state, "The characters close enough for you to talk to with `talking_to`: [");
BUFF_ITER(NpcKind, &can_talk_to) BUFF_ITER(NpcKind, &can_talk_to)
{ {
@ -659,15 +671,19 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e, CanTalkTo can_tal
// last thought explanation and re-prompt // last thought explanation and re-prompt
{ {
MD_String8 last_thought_string = {0}; Memory *last_memory_that_was_me = 0;
for(Memory *cur = e->memories_first; cur; cur = cur->next) for(Memory *cur = e->memories_first; cur; cur = cur->next)
{ {
if(cur->internal_monologue_length > 0) if(cur->context.i_said_this)
{ {
last_thought_string = MD_S8(cur->internal_monologue, cur->internal_monologue_length); last_memory_that_was_me = cur;
} }
} }
PushWithLint(scratch.arena, &latest_state, "Your last thought was: %.*s\n", MD_S8VArg(last_thought_string)); if(last_memory_that_was_me)
{
MD_String8 last_thought_string = MD_S8(last_memory_that_was_me->internal_monologue, last_memory_that_was_me->internal_monologue_length);
PushWithLint(scratch.arena, &latest_state, "Your last thought was: %.*s\nYour current mood is %s, make sure you act like it!", MD_S8VArg(last_thought_string), moods[last_memory_that_was_me->mood]);
}
} }
MD_String8 latest_state_string = MD_S8ListJoin(scratch.arena, latest_state, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit("")}); MD_String8 latest_state_string = MD_S8ListJoin(scratch.arena, latest_state, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit("")});
@ -714,6 +730,7 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
MD_String8 action_arg_str = {0}; MD_String8 action_arg_str = {0};
MD_String8 who_i_am_str = {0}; MD_String8 who_i_am_str = {0};
MD_String8 talking_to_str = {0}; MD_String8 talking_to_str = {0};
MD_String8 mood_str = {0};
if(error_message.size == 0) if(error_message.size == 0)
{ {
action_str = get_field(message_obj, MD_S8Lit("action")); action_str = get_field(message_obj, MD_S8Lit("action"));
@ -722,6 +739,7 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
thoughts_str = get_field(message_obj, MD_S8Lit("thoughts")); thoughts_str = get_field(message_obj, MD_S8Lit("thoughts"));
action_arg_str = get_field(message_obj, MD_S8Lit("action_arg")); action_arg_str = get_field(message_obj, MD_S8Lit("action_arg"));
talking_to_str = get_field(message_obj, MD_S8Lit("talking_to")); talking_to_str = get_field(message_obj, MD_S8Lit("talking_to"));
mood_str = get_field(message_obj, MD_S8Lit("mood"));
} }
if(error_message.size == 0 && who_i_am_str.size == 0) if(error_message.size == 0 && who_i_am_str.size == 0)
{ {
@ -735,6 +753,10 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
{ {
error_message = MD_S8Lit("You must have a field named `talking_to` in your message"); error_message = MD_S8Lit("You must have a field named `talking_to` in your message");
} }
if(error_message.size == 0 && mood_str.size == 0)
{
error_message = MD_S8Lit("You must have a field named `mood` in your message");
}
if(error_message.size == 0 && thoughts_str.size == 0) if(error_message.size == 0 && thoughts_str.size == 0)
{ {
error_message = MD_S8Lit("You must have a field named `thoughts` in your message, and it must have nonzero size. Like { ... thoughts: \"<your thoughts>\" ... }"); error_message = MD_S8Lit("You must have a field named `thoughts` in your message, and it must have nonzero size. Like { ... thoughts: \"<your thoughts>\" ... }");
@ -846,6 +868,24 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc
} }
} }
if(error_message.size == 0)
{
bool found = false;
for(int i = 0; i < ARRLEN(moods); i++)
{
if(MD_S8Match(MD_S8CString(moods[i]), mood_str, 0))
{
out->mood = i;
found = true;
break;
}
}
if(!found)
{
error_message = FmtWithLint(arena, "Game does not recognize the mood '%.*s', you must use an available mood from the list provided.", MD_S8VArg(mood_str));
}
}
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
return error_message; return error_message;
} }

Loading…
Cancel
Save