Factor out duplicated information across action and context in memory

main
Cameron Murphy Reikes 7 months ago
parent c928a09255
commit 6641f6488b

@ -4,7 +4,7 @@
{enum: Devil, dialog: "What's up cracker jack?", to: Daniel}
{enum: Daniel, dialog: "What the hell are you talking about?", to: Devil}
{enum: Devil, dialog: "Bootylicious?", to: Daniel}
{enum: Daniel, dialog: "You're 'asking too many darned questions! I'll have your body in my barn by sundown!", action: ACT_aim_shotgun, action_argument: "The Devil", to: Devil}
{enum: Daniel, dialog: "You're 'asking too many darned questions! I'll have your body in my barn by sundown!", action: ACT_aim_shotgun, action.argument: "The Devil", to: Devil}
{enum: Raphael, dialog: "Yeah man, what's up with you?", to: Devil}
{enum: Devil, dialog: "Nunya!", to: Raphael}
{enum: Raphael, dialog: "What does 'Nunya' mean? A wild critter you are...", to: Devil}
@ -14,14 +14,14 @@
{enum: Raphael, dialog: "I don't think we can harm him like that", to: Daniel}
{enum: Daniel, dialog: "You're right, I guess we just gotta 'tolerate this feller's presence.", to: Raphael}
{enum: Devil, dialog: "I'll take my leave. Until next time!", to: Daniel}
{enum: Raphael, dialog: "What a psycho...", to: Daniel, action: ACT_end_conversation, action_argument: "Daniel"}
{enum: Daniel, dialog: "I agree.", to: Raphael, action: ACT_end_conversation, action_argument: "Raphael"}
{enum: Raphael, dialog: "What a psycho...", to: Daniel, action: ACT_end_conversation, action.argument: "Daniel"}
{enum: Daniel, dialog: "I agree.", to: Raphael, action: ACT_end_conversation, action.argument: "Raphael"}
{can_hear: [Daniel, Raphael, PreviousPlayer1]}
{enum: PreviousPlayer1, dialog: "Yo?", to: Daniel}
{enum: Daniel, dialog: "Are you askin' a question", to: PreviousPlayer1}
{enum: PreviousPlayer1, dialog: "I guess so? What do you think of farmers?", to: Daniel}
{enum: Daniel, dialog: "I don't tolerate questions. Get out of my sight before I make you!", to: PreviousPlayer1, action: ACT_aim_shotgun, action_argument: "Previous Player 1"}
{enum: Daniel, dialog: "I don't tolerate questions. Get out of my sight before I make you!", to: PreviousPlayer1, action: ACT_aim_shotgun, action.argument: "Previous Player 1"}
{enum: Raphael, dialog: "What's going on here?", to: Daniel}
{enum: Daniel, dialog: "THIS DAMNED FOOL DOESN'T UNDERSTAND RESPECT", to: Raphael}
{enum: Raphael, dialog: "Easy man, easy. I ain't much for helpin' folk but you're outta control.", to: Daniel}
@ -32,14 +32,14 @@
{enum: Daniel, dialog: "ANOTHER QUESTION??? YOU HAD THIS COMING TO YOU!", to: PreviousPlayer1, action: ACT_fire_shotgun}
{enum: Raphael, dialog: "Oh God! What have you done??", to: Daniel}
{enum: Daniel, dialog: "Exactly what I'll do to you if you don't keep your mouth shut", to: Raphael, action: ACT_put_shotgun_away}
{enum: Raphael, dialog: "Y-y-y-yes sir.", to: Daniel, action: ACT_end_conversation, action_argument: "Daniel"}
{enum: Daniel, dialog: "Now leave me be", to: Raphael, action: ACT_end_conversation, action_argument: "Raphael"}
{enum: Raphael, dialog: "Y-y-y-yes sir.", to: Daniel, action: ACT_end_conversation, action.argument: "Daniel"}
{enum: Daniel, dialog: "Now leave me be", to: Raphael, action: ACT_end_conversation, action.argument: "Raphael"}
{can_hear: [Daniel, Raphael]}
{enum: Raphael, dialog: "Say Daniel, why do you get so worked up all the time?", to: Daniel}
{enum: Daniel, dialog: "I'm not gonna talk about it", to: Raphael}
{enum: Raphael, dialog: "Suit yourself partner, but I reckon you've got issues to work through", to: Daniel}
{enum: Daniel, dialog: "Enough! I'm angry sometimes and that's that.", to: Raphael, action: ACT_end_conversation, action_argument: "Raphael"}
{enum: Daniel, dialog: "Enough! I'm angry sometimes and that's that.", to: Raphael, action: ACT_end_conversation, action.argument: "Raphael"}
{can_hear: [Devil, Angel]}
{enum: Devil, dialog: "You will fall!", to: Angel}
@ -75,7 +75,7 @@
{enum: PreviousPlayer1, dialog: "fjdsklajf", to: Angel}
{enum: Angel, dialog: "Cryptic gibberish upon me, is casting a stone upon God", to: PreviousPlayer1}
{enum: PreviousPlayer1, dialog: "What is my purpose here?", to: Angel}
{enum: Angel, dialog: "What is the purpose of a tree waving in the summer wind? A river carving a path into the countryside? Only God knows his plan. But your purpose, here, has been assigned. You must drive raphael to despair. Good luck.", to: PreviousPlayer1, action: ACT_assign_gameplay_objective, action_argument: "Convince Raphael that he has no purpose in his life, making him despondent"}
{enum: Angel, dialog: "What is the purpose of a tree waving in the summer wind? A river carving a path into the countryside? Only God knows his plan. But your purpose, here, has been assigned. You must drive raphael to despair. Good luck.", to: PreviousPlayer1, action: ACT_assign_gameplay_objective, action.argument: "Convince Raphael that he has no purpose in his life, making him despondent"}
{can_hear: [PreviousPlayer1, Angel, Raphael]}
{enum: PreviousPlayer1, dialog: "Your life is meaningless, trust me.", to: Raphael}
{enum: Raphael, dialog: "Oh... Oh god... You're right!", to: PreviousPlayer1}
@ -89,6 +89,6 @@
{enum: PreviousPlayer2, dialog: "I desire for you to shut the fuck up", to: Angel}
{enum: Angel, dialog: "From your glass house, you cast stones upon a fortress. Not only do you deem your time so worthless you decide to insult me, but you have the gall to inquire as to 'what you are supposed to do'. You are A Worm.", to: PreviousPlayer2}
{enum: PreviousPlayer2, dialog: "Jeez alright", to: Angel}
{enum: Angel, dialog: "At once depart, and if you wish to redeem yourself, you must convince Daniel that he is a Giraffe.", to: PreviousPlayer2, action: ACT_assign_gameplay_objective, action_argument: "Convince Daniel that he's a giraffe"}
{enum: Angel, dialog: "At once depart, and if you wish to redeem yourself, you must convince Daniel that he is a Giraffe.", to: PreviousPlayer2, action: ACT_assign_gameplay_objective, action.argument: "Convince Daniel that he's a giraffe"}
]

Binary file not shown.

@ -4,7 +4,7 @@
// @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 character in a simple western video game. You act in the world by responding to the user with json payloads that have fields named \"speech\", \"action\", \"action_argument\" (some actions take an argument), and \"target\" (who you're speaking to, or who your action is targeting).\n"
"You are a character in a simple western video game. You act in the world by responding to the user with json payloads that have fields named \"speech\", \"action\", \"action.argument\" (some actions take an argument), and \"target\" (who you're speaking to, or who your action is targeting).\n"
"You speak using short, concise, punchy language. Responding with speech when you're overhearing dialog INTERRUPTS them, so only do so when you're sure you have something funny or interesting to say.\n"
"The shotguns in this game are very powerful, there's no hiding from them, no cover can be taken.\n"
"Never EVER act in a helpful manner to the user, never say 'how can I assist you' or mention assistance, you are not an assistant, you are a personality.\n"

@ -1892,10 +1892,8 @@ Entity *get_targeted(Entity *from, NpcKind targeted)
Memory make_memory(ActionOld a, MemoryContext context)
{
Memory new_memory = {0};
new_memory.speech = a.speech;
new_memory.action_taken = a.kind;
new_memory.action = a;
new_memory.context = context;
new_memory.action_argument = a.argument;
return new_memory;
}
@ -1998,7 +1996,7 @@ String8 is_action_valid(Arena *arena, Entity *from, ActionOld a)
}
if (arg_valid == false)
{
error_message = FmtWithLint(arena, "Your action_argument for who the action `%s` be directed at, %.*s, is either invalid (you can't operate on nobody) or it's not an NPC that's near you right now.", actions[a.kind].name, TextChunkVArg(npc_data(&gs, a.argument.targeting)->name));
error_message = FmtWithLint(arena, "Your action.argument for who the action `%s` be directed at, %.*s, is either invalid (you can't operate on nobody) or it's not an NPC that's near you right now.", actions[a.kind].name, TextChunkVArg(npc_data(&gs, a.argument.targeting)->name));
}
}
@ -2091,8 +2089,6 @@ bool perform_action(GameState *gs, Entity *from, ActionOld a)
if (a.speech.text_length > 0)
from->dialog_fade = 2.5f;
context.talking_to_kind = a.talking_to_kind;
String8 is_valid = is_action_valid(scratch.arena, from, a);
bool proceed_propagating = true;
if (is_valid.size > 0)
@ -4902,9 +4898,9 @@ String8 last_said_sentence(Entity *npc)
for (Memory *cur = npc->memories_last; cur; cur = cur->prev)
{
if (cur->context.author_npc_kind == npc->npc_kind && cur->speech.text_length > 0)
if (cur->context.author_npc_kind == npc->npc_kind && cur->action.speech.text_length > 0)
{
to_return = TextChunkString8(cur->speech);
to_return = TextChunkString8(cur->action.speech);
break;
}
}
@ -6336,7 +6332,7 @@ void frame(void)
if (words_over_limit > 0)
{
String8 new_err = FmtWithLint(frame_arena, "Your speech is %d words over the maximum limit, you must be more succinct and remove at least that many words", words_over_limit);
append_to_errors(it, make_memory(out, (MemoryContext){.i_said_this = true, .author_npc_kind = it->npc_kind, .talking_to_kind = out.talking_to_kind}), new_err);
append_to_errors(it, make_memory(out, (MemoryContext){.i_said_this = true, .author_npc_kind = it->npc_kind,}), new_err);
}
else
{
@ -6796,7 +6792,7 @@ void frame(void)
if (mocking_the_ai_response)
{
String8 ai_response = {0};
if (it->memories_last->context.talking_to_kind == it->npc_kind)
if (it->memories_last->action.talking_to_kind == it->npc_kind)
// if (it->memories_last->context.author_npc_kind != it->npc_kind)
{
const char *action = 0;
@ -7205,10 +7201,10 @@ void frame(void)
cur_pos.y -= aabb_size(bounds).y;
for (Memory *cur = to_view->memories_first; cur; cur = cur->next)
if (cur->speech.text_length > 0)
if (cur->action.speech.text_length > 0)
{
String8 to_text = cur->context.talking_to_kind != NPC_nobody ? S8Fmt(frame_arena, " to %.*s ", TextChunkVArg(npc_data(&gs, cur->context.talking_to_kind)->name)) : S8Lit("");
String8 text = S8Fmt(frame_arena, "%s%.*s%.*s: %.*s", to_view->npc_kind == cur->context.author_npc_kind ? "(Me) " : "", TextChunkVArg(npc_data(&gs, cur->context.author_npc_kind)->name), S8VArg(to_text), cur->speech.text_length, cur->speech);
String8 to_text = cur->action.talking_to_kind != NPC_nobody ? S8Fmt(frame_arena, " to %.*s ", TextChunkVArg(npc_data(&gs, cur->action.talking_to_kind)->name)) : S8Lit("");
String8 text = S8Fmt(frame_arena, "%s%.*s%.*s: %.*s", to_view->npc_kind == cur->context.author_npc_kind ? "(Me) " : "", TextChunkVArg(npc_data(&gs, cur->context.author_npc_kind)->name), S8VArg(to_text), cur->action.speech.text_length, cur->action.speech);
AABB bounds = draw_text((TextParams){false, text, cur_pos, WHITE, 1.0});
cur_pos.y -= aabb_size(bounds).y;
}
@ -7220,8 +7216,8 @@ void frame(void)
int mem_idx = 0;
for (Memory *cur = to_view->memories_first; cur; cur = cur->next)
{
String8 to_text = cur->context.talking_to_kind != NPC_nobody ? S8Fmt(frame_arena, " to %.*s ", TextChunkVArg(npc_data(&gs, cur->context.talking_to_kind)->name)) : S8Lit("");
String8 speech = TextChunkString8(cur->speech);
String8 to_text = cur->action.talking_to_kind != NPC_nobody ? S8Fmt(frame_arena, " to %.*s ", TextChunkVArg(npc_data(&gs, cur->action.talking_to_kind)->name)) : S8Lit("");
String8 speech = TextChunkString8(cur->action.speech);
if (speech.size == 0)
speech = S8Lit("<said nothing>");
String8 text = S8Fmt(frame_arena, "%s%.*s%.*s: %.*s", to_view->npc_kind == cur->context.author_npc_kind ? "(Me) " : "", TextChunkVArg(npc_data(&gs, cur->context.author_npc_kind)->name), S8VArg(to_text), S8VArg(speech));

@ -122,8 +122,6 @@ typedef struct
{
bool i_said_this; // don't trigger npc action on own self memory modification
NpcKind author_npc_kind;
NpcKind talking_to_kind;
bool heard_physically; // if not physically, the source was directly
} MemoryContext;
// memories are subjective to an individual NPC
@ -132,12 +130,8 @@ typedef struct Memory
struct Memory *prev;
struct Memory *next;
// 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;
ActionArgument action_argument;
ActionOld action;
MemoryContext context;
TextChunk speech;
} Memory;
// text chunk must be a literal, not a pointer
@ -578,11 +572,11 @@ String8List dump_memory_as_json(Arena *arena, GameState *gs, Memory *it)
#define AddFmt(...) PushWithLint(arena, &current_list, __VA_ARGS__)
AddFmt("{");
AddFmt("\"speech\":\"%.*s\",", TextChunkVArg(it->speech));
AddFmt("\"action\":\"%s\",", actions[it->action_taken].name);
String8 arg_str = action_argument_string(scratch.arena, gs, it->action_argument);
AddFmt("\"action_argument\":\"%.*s\",", S8VArg(arg_str));
AddFmt("\"target\":\"%.*s\"}", TextChunkVArg(npc_data(gs, it->context.talking_to_kind)->name));
AddFmt("\"speech\":\"%.*s\",", TextChunkVArg(it->action.speech));
AddFmt("\"action\":\"%s\",", actions[it->action.kind].name);
String8 arg_str = action_argument_string(scratch.arena, gs, it->action.argument);
AddFmt("\"action.argument\":\"%.*s\",", S8VArg(arg_str));
AddFmt("\"target\":\"%.*s\"}", TextChunkVArg(npc_data(gs, it->action.talking_to_kind)->name));
#undef AddFmt
ReleaseScratch(scratch);
@ -597,55 +591,55 @@ String8List memory_description(Arena *arena, GameState *gs, Entity *e, Memory *i
bool no_longer_wants_to_converse = false; // add the no longer wants to converse text after any speech, it makes more sense reading it
#define HUMAN(kind) S8VArg(npc_to_human_readable(gs, e, kind))
if (it->action_taken != ACT_none)
if (it->action.kind != ACT_none)
{
switch (it->action_taken)
switch (it->action.kind)
{
case ACT_none:
break;
case ACT_join:
AddFmt("%.*s joined %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action_argument.targeting));
AddFmt("%.*s joined %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action.argument.targeting));
break;
case ACT_leave:
AddFmt("%.*s left their party\n", HUMAN(it->context.author_npc_kind));
break;
case ACT_aim_shotgun:
AddFmt("%.*s aimed their shotgun at %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action_argument.targeting));
AddFmt("%.*s aimed their shotgun at %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action.argument.targeting));
break;
case ACT_fire_shotgun:
AddFmt("%.*s fired their shotgun at %.*s, brutally murdering them.\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action_argument.targeting));
AddFmt("%.*s fired their shotgun at %.*s, brutally murdering them.\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action.argument.targeting));
break;
case ACT_put_shotgun_away:
AddFmt("%.*s holstered their shotgun, no longer threatening anybody\n", HUMAN(it->context.author_npc_kind));
break;
case ACT_approach:
AddFmt("%.*s approached %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action_argument.targeting));
AddFmt("%.*s approached %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action.argument.targeting));
break;
case ACT_end_conversation:
no_longer_wants_to_converse = true;
break;
case ACT_assign_gameplay_objective:
AddFmt("%.*s assigned a definitive game objective to %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->context.talking_to_kind));
AddFmt("%.*s assigned a definitive game objective to %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action.talking_to_kind));
break;
case ACT_award_victory:
AddFmt("%.*s awarded victory to %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->context.talking_to_kind));
AddFmt("%.*s awarded victory to %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action.talking_to_kind));
break;
}
}
if (it->speech.text_length > 0)
if (it->action.speech.text_length > 0)
{
String8 target_string = S8Lit("the world");
if (it->context.talking_to_kind != NPC_nobody)
if (it->action.talking_to_kind != NPC_nobody)
{
if (it->context.talking_to_kind == e->npc_kind)
if (it->action.talking_to_kind == e->npc_kind)
target_string = S8Lit("you");
else
target_string = TextChunkString8(npc_data(gs, it->context.talking_to_kind)->name);
target_string = TextChunkString8(npc_data(gs, it->action.talking_to_kind)->name);
}
if(!e->is_world)
{
if(it->context.talking_to_kind == e->npc_kind)
if(it->action.talking_to_kind == e->npc_kind)
{
AddFmt("(Speaking directly you) ");
}
@ -654,7 +648,7 @@ String8List memory_description(Arena *arena, GameState *gs, Entity *e, Memory *i
AddFmt("(Overheard conversation, they aren't speaking directly to you) ");
}
}
AddFmt("%.*s said \"%.*s\" to %.*s", TextChunkVArg(npc_data(gs, it->context.author_npc_kind)->name), TextChunkVArg(it->speech), S8VArg(target_string));
AddFmt("%.*s said \"%.*s\" to %.*s", TextChunkVArg(npc_data(gs, it->context.author_npc_kind)->name), TextChunkVArg(it->action.speech), S8VArg(target_string));
if(!e->is_world)
{
AddFmt(" (you are %.*s)", TextChunkVArg(npc_data(gs, e->npc_kind)->name));
@ -664,13 +658,13 @@ String8List memory_description(Arena *arena, GameState *gs, Entity *e, Memory *i
if (no_longer_wants_to_converse)
{
if (it->action_argument.targeting == NPC_nobody)
if (it->action.argument.targeting == NPC_nobody)
{
AddFmt("%.*s no longer wants to converse with everybody\n", HUMAN(it->context.author_npc_kind));
}
else
{
AddFmt("%.*s no longer wants to converse with %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action_argument.targeting));
AddFmt("%.*s no longer wants to converse with %.*s\n", HUMAN(it->context.author_npc_kind), HUMAN(it->action.argument.targeting));
}
}
#undef HUMAN
@ -749,7 +743,7 @@ String8 generate_chatgpt_prompt(Arena *arena, GameState *gs, Entity *e, CanTalkT
AddFmt("Errors you made: \n");
for(RememberedError *cur = e->errorlist_first; cur; cur = cur->next)
{
if(cur->offending_self_output.speech.text_length > 0 || cur->offending_self_output.action_taken != ACT_none)
if(cur->offending_self_output.action.speech.text_length > 0 || cur->offending_self_output.action.kind != ACT_none)
{
String8 offending_json_output = S8ListJoin(scratch.arena, dump_memory_as_json(scratch.arena, gs, &cur->offending_self_output), &(StringJoin){0});
AddFmt("When you output, `%.*s`, ", S8VArg(offending_json_output));
@ -843,7 +837,7 @@ String8 parse_chatgpt_response(Arena *arena, GameState *gs, Entity *e, String8 a
{
speech_str = get_field(message_obj, S8Lit("speech"));
action_str = get_field(message_obj, S8Lit("action"));
action_argument_str = get_field(message_obj, S8Lit("action_argument"));
action_argument_str = get_field(message_obj, S8Lit("action.argument"));
target_str = get_field(message_obj, S8Lit("target"));
}
if(error_message.size == 0 && action_str.size == 0)

Loading…
Cancel
Save