Angel behavior tuning, errors remember the memory that caused them

main
parent fc4b415970
commit b3b89c9835

@ -16,19 +16,19 @@
{enum: Raphael, dialog: "What a psycho...", to: Daniel, action: ACT_end_conversation, action_argument: "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: Daniel, dialog: "I agree.", to: Raphael, action: ACT_end_conversation, action_argument: "Raphael"}
{can_hear: [Daniel, Raphael, Passerby]} {can_hear: [Daniel, Raphael, PreviousPlayer1]}
{enum: Passerby, dialog: "Yo?", to: Daniel} {enum: PreviousPlayer1, dialog: "Yo?", to: Daniel}
{enum: Daniel, dialog: "Are you askin' a question", to: Passerby} {enum: Daniel, dialog: "Are you askin' a question", to: PreviousPlayer1}
{enum: Passerby, dialog: "I guess so? What do you think of farmers?", to: Daniel} {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: Passerby, action: ACT_aim_shotgun, action_argument: "Passerby"} {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: "PreviousPlayer1"}
{enum: Raphael, dialog: "What's going on here?", to: Daniel} {enum: Raphael, dialog: "What's going on here?", to: Daniel}
{enum: Daniel, dialog: "THIS DAMNED FOOL DOESN'T UNDERSTAND RESPECT", to: Raphael} {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} {enum: Raphael, dialog: "Easy man, easy. I ain't much for helpin' folk but you're outta control.", to: Daniel}
{enum: Daniel, dialog: "I WON'T TOLERATE IT", to: Raphael} {enum: Daniel, dialog: "I WON'T TOLERATE IT", to: Raphael}
{enum: Passerby, dialog: "Hey man woah, just put down the gun", to: Daniel} {enum: PreviousPlayer1, dialog: "Hey man woah, just put down the gun", to: Daniel}
{enum: Daniel, dialog: "YOU BETTER MAKE ME!", to: Passerby} {enum: Daniel, dialog: "YOU BETTER MAKE ME!", to: PreviousPlayer1}
{enum: Passerby, dialog: "Why do you have the gun anyways?", to: Daniel} {enum: PreviousPlayer1, dialog: "Why do you have the gun anyways?", to: Daniel}
{enum: Daniel, dialog: "ANOTHER QUESTION??? YOU HAD THIS COMING TO YOU!", to: Passerby, action: ACT_fire_shotgun} {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: 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: 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: Raphael, dialog: "Y-y-y-yes sir.", to: Daniel, action: ACT_end_conversation, action_argument: "Daniel"}
@ -44,11 +44,19 @@
{enum: Devil, dialog: "You will fall!", to: Angel} {enum: Devil, dialog: "You will fall!", to: Angel}
{enum: Angel, dialog: "Perhaps. And then I will rise as He has, from the corpse and ashes, the sun rises.", to: Devil} {enum: Angel, dialog: "Perhaps. And then I will rise as He has, from the corpse and ashes, the sun rises.", to: Devil}
{can_hear: [Passerby, Angel]} {can_hear: [PreviousPlayer1, Angel]}
{enum: Passerby, dialog: "fjdsklajf", to: Angel} {enum: PreviousPlayer1, dialog: "fjdsklajf", to: Angel}
{enum: Angel, dialog: "Cryptic gibberish upon me, is casting a stone upon God", to: Passerby} {enum: Angel, dialog: "Cryptic gibberish upon me, is casting a stone upon God", to: PreviousPlayer1}
{enum: Passerby, dialog: "What is my purpose here?", to: Angel} {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 kill raphael. Good luck.", to: Passerby, action: ACT_assign_gameplay_objective, action_argument: "KILL Raphael"} {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 kill raphael. Good luck.", to: PreviousPlayer1, action: ACT_assign_gameplay_objective, action_argument: "KILL Raphael"}
{can_hear: [PreviousPlayer2, Angel]}
{enum: PreviousPlayer2, dialog: "What am I supposed to do?", to: Angel}
{enum: Angel, dialog: "What do you most desire?", to: PreviousPlayer2}
{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 kill Daniel.", to: PreviousPlayer2, action: ACT_assign_gameplay_objective, action_argument: "KILL Daniel"}
/* /*
{can_hear: [WellDweller, Farmer, ManInBlack]}, {can_hear: [WellDweller, Farmer, ManInBlack]},

@ -7,6 +7,7 @@ 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" "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" "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"
; ;
const char *top_of_header = "" const char *top_of_header = ""
@ -110,7 +111,7 @@ CharacterGen characters[] = {
.name = "Raphael", .name = "Raphael",
.enum_name = "Raphael", .enum_name = "Raphael",
.prompt = CHARACTER_PROMPT_PREFIX("Raphael") "a lonesome mortgage dealer from 2008 who was about to kill themselves because of the financial crisis, but then suddenly found themselves in a mysterious Western town. They don't know why they're in this town, but they're terrified.", .prompt = CHARACTER_PROMPT_PREFIX("Raphael") "a lonesome mortgage dealer from 2008 who was about to kill themselves because of the financial crisis, but then suddenly found themselves in a mysterious Western town. They don't know why they're in this town, but they're terrified.",
.silence_factor = 0.5f, .silence_factor = 0.8f,
}, },
{ {
.name = "The Devil", .name = "The Devil",
@ -118,14 +119,21 @@ CharacterGen characters[] = {
.prompt = CHARACTER_PROMPT_PREFIX("The Devil") "strange red beast, the devil himself, evil incarnate. You mercilessly mock everybody who talks to you, and are intending to instill absolute chaos.", .prompt = CHARACTER_PROMPT_PREFIX("The Devil") "strange red beast, the devil himself, evil incarnate. You mercilessly mock everybody who talks to you, and are intending to instill absolute chaos.",
}, },
{ {
.name = "Passerby", .name = "PreviousPlayer1",
.enum_name = "Passerby", .enum_name = "PreviousPlayer1",
.prompt = CHARACTER_PROMPT_PREFIX("Random Passerby") "random person, just passing by", .prompt = CHARACTER_PROMPT_PREFIX("Previous Player 1") "random person, just passing by",
},
{
.name = "PreviousPlayer2",
.enum_name = "PreviousPlayer2",
.prompt = CHARACTER_PROMPT_PREFIX("Previous Player 2") "random person, just passing by",
}, },
{ {
.name = "Angel", .name = "Angel",
.enum_name = "Angel", .enum_name = "Angel",
.prompt = CHARACTER_PROMPT_PREFIX("Angel") "mysterious, radiant, mystical creature the player first talks to. You guide the entire game: deciding on an objective for the player to fulfill until you believe they've learned their lesson, whatever that means to them. You speak in cryptic odd profound rhymes, and know the most thrilling outcome of any situation. Your purpose it to thrill the player, but you will never admit this.", .prompt = CHARACTER_PROMPT_PREFIX("Angel") "mysterious, radiant, mystical creature the player first talks to. You guide the entire game: deciding on an objective for the player to fulfill until you believe they've learned their lesson, whatever that means to them. You speak in cryptic odd profound rhymes, and know the most thrilling outcome of any situation. Your purpose it to thrill the player, but you will never admit this.\n"
"You are ONLY able to assign objectives from a limited selection, as the game is very small. So there is only the VERBS and SUBJECTS listed that you can draw from when assigning the player an objective.\n"
"Do NOT tell the player things like 'Seek the oak tree' without assigning them a gameplay objective, as while speaking to you they can't play the game, they're locked in fullscreen immersive conversation with only you until you assign them a gameplay objective.",
.silence_factor = 0.0, .silence_factor = 0.0,
}, },
}; };

297
main.c

@ -576,6 +576,12 @@ void generation_thread(void* my_request_voidptr)
int make_generation_request(MD_String8 post_req_body) int make_generation_request(MD_String8 post_req_body)
{ {
// checking for taken characters, pipe should only occur at the beginning
for(MD_u64 i = 1; i < post_req_body.size; i++)
{
assert(post_req_body.str[i] != '|');
}
ChatRequest *to_return = 0; ChatRequest *to_return = 0;
if(requests_free_list) if(requests_free_list)
{ {
@ -634,54 +640,46 @@ ISANERROR("Only know how to do desktop http requests on windows")
#endif // DESKTOP #endif // DESKTOP
Memory *memories_free_list = 0; Memory *memories_free_list = 0;
RememberedError *remembered_error_free_list = 0;
TextChunkList *text_chunk_free_list = 0;
// s.size must be less than MAX_SENTENCE_LENGTH, or assert fails // s.size must be less than MAX_SENTENCE_LENGTH, or assert fails
void into_chunk(TextChunk *t, MD_String8 s)
{ RememberedError *allocate_remembered_error(MD_Arena *arena)
assert(s.size < MAX_SENTENCE_LENGTH);
memcpy(t->text, s.str, s.size);
t->text_length = (int)s.size;
}
TextChunkList *allocate_text_chunk(MD_Arena *arena)
{ {
TextChunkList *to_return = 0; RememberedError *to_return = 0;
if(text_chunk_free_list) if(remembered_error_free_list)
{ {
to_return = text_chunk_free_list; to_return = remembered_error_free_list;
MD_StackPop(text_chunk_free_list); MD_StackPop(remembered_error_free_list);
} }
else else
{ {
to_return = MD_PushArray(arena, TextChunkList, 1); to_return = MD_PushArray(arena, RememberedError, 1);
} }
*to_return = (TextChunkList){0}; *to_return = (RememberedError){0};
return to_return; return to_return;
} }
void remove_text_chunk_from(TextChunkList **first, TextChunkList **last, TextChunkList *chunk) void remove_remembered_error_from(RememberedError **first, RememberedError **last, RememberedError *chunk)
{ {
MD_DblRemove(*first, *last, chunk); MD_DblRemove(*first, *last, chunk);
MD_StackPush(text_chunk_free_list, chunk); MD_StackPush(remembered_error_free_list, chunk);
} }
int text_chunk_list_count(TextChunkList *first)
{ void append_to_errors(Entity *from, Memory incorrect_memory, MD_String8 s)
int ret = 0;
for(TextChunkList *cur = first; cur != 0; cur = cur->next)
{
ret++;
}
return ret;
}
void append_to_errors(Entity *from, MD_String8 s)
{ {
TextChunkList *error_chunk = allocate_text_chunk(persistent_arena); RememberedError *err = allocate_remembered_error(persistent_arena);
into_chunk(&error_chunk->text, s); chunk_from_s8(&err->reason_why_its_bad, s);
while(text_chunk_list_count(from->errorlist_first) > REMEMBERED_ERRORS) err->offending_self_output = incorrect_memory;
while(true)
{ {
remove_text_chunk_from(&from->errorlist_first, &from->errorlist_last, from->errorlist_first); int count = 0;
for(RememberedError *cur = from->errorlist_first; cur; cur = cur->next) count++;
if(count < REMEMBERED_ERRORS) break;
remove_remembered_error_from(&from->errorlist_first, &from->errorlist_last, from->errorlist_first);
} }
MD_DblPushBack(from->errorlist_first, from->errorlist_last, error_chunk); MD_DblPushBack(from->errorlist_first, from->errorlist_last, err);
from->perceptions_dirty = true; from->perceptions_dirty = true;
} }
@ -853,6 +851,7 @@ SER_MAKE_FOR_TYPE(double);
SER_MAKE_FOR_TYPE(float); SER_MAKE_FOR_TYPE(float);
SER_MAKE_FOR_TYPE(PropKind); SER_MAKE_FOR_TYPE(PropKind);
SER_MAKE_FOR_TYPE(NpcKind); SER_MAKE_FOR_TYPE(NpcKind);
SER_MAKE_FOR_TYPE(ActionKind);
SER_MAKE_FOR_TYPE(Memory); SER_MAKE_FOR_TYPE(Memory);
SER_MAKE_FOR_TYPE(Vec2); SER_MAKE_FOR_TYPE(Vec2);
SER_MAKE_FOR_TYPE(Vec3); SER_MAKE_FOR_TYPE(Vec3);
@ -1705,14 +1704,19 @@ Entity *get_targeted(Entity *from, NpcKind targeted)
return 0; return 0;
} }
void remember_action(GameState *gs, Entity *to_modify, Action a, MemoryContext context) Memory make_memory(Action a, MemoryContext context)
{ {
Memory new_memory = {0}; Memory new_memory = {0};
new_memory.speech = a.speech; new_memory.speech = a.speech;
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;
return new_memory;
}
void remember_action(GameState *gs, Entity *to_modify, Action a, MemoryContext context)
{
Memory new_memory = make_memory(a, context);
push_memory(gs, to_modify, new_memory); push_memory(gs, to_modify, new_memory);
if(context.i_said_this && (a.speech.text_length > 0 || a.kind != ACT_none)) if(context.i_said_this && (a.speech.text_length > 0 || a.kind != ACT_none))
@ -1872,6 +1876,7 @@ typedef struct PropagatingAction
Action a; Action a;
MemoryContext context; MemoryContext context;
TextChunk in_room_name;
Vec2 from; Vec2 from;
bool already_propagated_to[MAX_ENTITIES]; // tracks by index of entity bool already_propagated_to[MAX_ENTITIES]; // tracks by index of entity
float progress; // if greater than or equal to 1.0, is freed float progress; // if greater than or equal to 1.0, is freed
@ -1937,18 +1942,18 @@ bool perform_action(GameState *gs, Entity *from, Action a)
if(is_valid.size > 0) if(is_valid.size > 0)
{ {
assert(from->npc_kind != NPC_Player); assert(from->npc_kind != NPC_Player);
append_to_errors(from, is_valid); append_to_errors(from, make_memory(a, context), is_valid);
proceed_propagating = false; proceed_propagating = false;
} }
bool angel_heard_action = false;
Entity *targeted = 0; Entity *targeted = 0;
if(proceed_propagating) if(proceed_propagating)
{ {
targeted = get_targeted(from, a.talking_to_kind); targeted = get_targeted(from, a.talking_to_kind);
if(from->errorlist_first) if(from->errorlist_first)
MD_StackPush(text_chunk_free_list, from->errorlist_first); MD_StackPush(remembered_error_free_list, from->errorlist_first);
from->errorlist_first = 0; from->errorlist_first = 0;
from->errorlist_last = 0; from->errorlist_last = 0;
@ -1959,12 +1964,13 @@ bool perform_action(GameState *gs, Entity *from, Action a)
{ {
MemoryContext my_context = context; MemoryContext my_context = context;
my_context.i_said_this = true; my_context.i_said_this = true;
angel_heard_action = angel_heard_action || from->npc_kind == NPC_Angel;
remember_action(gs, from, a, my_context); remember_action(gs, from, a, my_context);
} }
if(a.speech.text_length == 0 && a.kind == ACT_none) if(a.speech.text_length == 0 && a.kind == ACT_none)
{ {
proceed_propagating = false; // didn't say anything proceed_propagating = false; // didn't say or do anything
} }
} }
@ -1973,11 +1979,13 @@ bool perform_action(GameState *gs, Entity *from, Action a)
// memory of target // memory of target
if(targeted) if(targeted)
{ {
angel_heard_action = angel_heard_action || targeted->npc_kind == NPC_Angel;
remember_action(gs, targeted, a, context); remember_action(gs, targeted, a, context);
} }
// propagate physically // propagate physically
PropagatingAction to_propagate = {0}; PropagatingAction to_propagate = {0};
chunk_from_s8(&to_propagate.in_room_name, from->current_room_name);
to_propagate.a = a; to_propagate.a = a;
to_propagate.context = context; to_propagate.context = context;
to_propagate.from = from->pos; to_propagate.from = from->pos;
@ -1989,6 +1997,14 @@ bool perform_action(GameState *gs, Entity *from, Action a)
push_propagating(to_propagate); push_propagating(to_propagate);
} }
// the angel knows all
if(!MD_S8Match(gs->player->current_room_name, MD_S8Lit("StartingRoom"), 0) && !angel_heard_action)
{
MemoryContext angel_context = context;
angel_context.i_said_this = false;
remember_action(gs, gs->angel, a, angel_context);
}
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
return proceed_propagating; return proceed_propagating;
} }
@ -2156,6 +2172,11 @@ void transition_to_room(GameState *gs, ThreeDeeLevel *level, MD_String8 new_room
(void)level; (void)level;
gs->player->current_room_name = new_room_name; gs->player->current_room_name = new_room_name;
if(MD_S8Match(new_room_name, MD_S8Lit("StartingLevel"), 0))
{
gs->angel->perceptions_dirty = true;
}
} }
@ -2165,7 +2186,6 @@ void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *leve
rnd_gamerand_seed(&gs->random, RANDOM_SEED); rnd_gamerand_seed(&gs->random, RANDOM_SEED);
// make entities for all rooms // make entities for all rooms
bool found_player = false;
for(Room *cur_room = level->room_list; cur_room; cur_room = cur_room->next) for(Room *cur_room = level->room_list; cur_room; cur_room = cur_room->next)
{ {
for (PlacedEntity *cur = cur_room->placed_entity_list; cur; cur = cur->next) for (PlacedEntity *cur = cur_room->placed_entity_list; cur; cur = cur->next)
@ -2177,13 +2197,20 @@ void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *leve
cur_entity->current_room_name = cur_room->name; cur_entity->current_room_name = cur_room->name;
if (cur_entity->npc_kind == NPC_Player) if (cur_entity->npc_kind == NPC_Player)
{ {
assert(!found_player); assert(!gs->player);
found_player = true;
gs->player = cur_entity; gs->player = cur_entity;
} }
if (cur_entity->npc_kind == NPC_Angel)
{
assert(!gs->angel);
gs->angel = cur_entity;
}
} }
} }
assert(gs->player);
assert(gs->angel);
gs->world_entity = new_entity(gs); gs->world_entity = new_entity(gs);
gs->world_entity->is_world = true; gs->world_entity->is_world = true;
@ -2442,14 +2469,17 @@ void ser_entity(SerState *ser, Entity *e)
ser_bool(ser, &e->being_hovered); ser_bool(ser, &e->being_hovered);
ser_bool(ser, &e->perceptions_dirty); ser_bool(ser, &e->perceptions_dirty);
/* rememboring errorlist is disabled for being a bad idea because as game is improved the errors go out of date, and to begin with it's not even that necessary
But also it's too hard to maintain
if(ser->serializing) if(ser->serializing)
{ {
TextChunkList *cur = e->errorlist_first; RememberedError *cur = e->errorlist_first;
bool more_errors = cur != 0; bool more_errors = cur != 0;
ser_bool(ser, &more_errors); ser_bool(ser, &more_errors);
while(more_errors) while(more_errors)
{ {
ser_TextChunk(ser, &cur->text); ser_TextChunk(ser, &cur->reason_why_its_bad);
ser_Memory(ser, &cur->offending_self_output)
cur = cur->next; cur = cur->next;
more_errors = cur != 0; more_errors = cur != 0;
ser_bool(ser, &more_errors); ser_bool(ser, &more_errors);
@ -2467,6 +2497,7 @@ void ser_entity(SerState *ser, Entity *e)
ser_bool(ser, &more_errors); ser_bool(ser, &more_errors);
} }
} }
*/
if(ser->serializing) if(ser->serializing)
{ {
@ -6292,13 +6323,20 @@ void frame(void)
{ {
for(PropagatingAction *cur = propagating; cur; cur = cur->next) for(PropagatingAction *cur = propagating; cur; cur = cur->next)
{ {
assert(cur->in_room_name.text_length > 0);
if(cur->progress < 1.0f) if(cur->progress < 1.0f)
{ {
cur->progress += dt; cur->progress += dt;
float effective_radius = propagating_radius(cur); float effective_radius = propagating_radius(cur);
ENTITIES_ITER(gs.entities) ENTITIES_ITER(gs.entities)
{ {
if(it->is_npc && LenV2(SubV2(it->pos, cur->from)) < effective_radius) bool should_propagate = true
&& it->is_npc
&& LenV2(SubV2(it->pos, cur->from)) < effective_radius
&& MD_S8Match(TextChunkString8(cur->in_room_name), it->current_room_name, 0)
&& it->npc_kind != NPC_Angel // angels already hear everything, this would duplicate the hearing of the action
;
if(should_propagate)
{ {
if(!cur->already_propagated_to[frome(it).index]) if(!cur->already_propagated_to[frome(it).index])
{ {
@ -6402,13 +6440,8 @@ ISANERROR("Don't know how to do this stuff on this platform.")
if(words_over_limit > 0) if(words_over_limit > 0)
{ {
// trim what the npc said so that the error message is never more than the text chunk, which without this would be super possible MD_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);
// if the speech the npc already made was too big 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);
int max_words_of_original_speech = MAX_SENTENCE_LENGTH / 2;
MD_String8 original_speech = TextChunkString8(out.speech);
MD_String8 trimmed_original_speech = original_speech.size < max_words_of_original_speech ? original_speech : FmtWithLint(frame_arena, "...%.*s", MD_S8VArg(MD_S8Substring(original_speech, original_speech.size - max_words_of_original_speech, original_speech.size)));
MD_String8 new_err = FmtWithLint(frame_arena, "You said '%.*s' which is %d words over the maximum limit, you must be more succinct and remove at least that many words", MD_S8VArg(trimmed_original_speech), words_over_limit);
append_to_errors(it, new_err);
} }
else else
{ {
@ -6422,7 +6455,7 @@ ISANERROR("Don't know how to do this stuff on this platform.")
else else
{ {
Log("There was a parse error: `%.*s`\n", MD_S8VArg(parse_response)); Log("There was a parse error: `%.*s`\n", MD_S8VArg(parse_response));
append_to_errors(it, parse_response); append_to_errors(it, (Memory){0}, parse_response);
} }
} }
@ -6859,124 +6892,120 @@ ISANERROR("Don't know how to do this stuff on this platform.")
} }
ENTITIES_ITER(gs.entities) ENTITIES_ITER(gs.entities)
if(it->is_npc)
{ {
if (it->perceptions_dirty && !npc_does_dialog(it)) bool doesnt_prompt_on_dirty_perceptions = false
|| it->npc_kind == NPC_Player
|| (it->npc_kind == NPC_Angel && gs.no_angel_screen)
|| !npc_does_dialog(it) // not sure what's up with this actually, potentially remove
|| !MD_S8Match(it->current_room_name, gs.player->current_room_name, 0)
;
if (it->perceptions_dirty && doesnt_prompt_on_dirty_perceptions)
{ {
it->perceptions_dirty = false; it->perceptions_dirty = false;
} }
if (it->perceptions_dirty) if (it->perceptions_dirty)
{ {
if(it->npc_kind == NPC_Player) if (!gs.stopped_time)
{
it->perceptions_dirty = false;
}
else if(it->is_npc)
{ {
if (!gs.stopped_time) it->perceptions_dirty = false; // needs to be in beginning because they might be redirtied by the new perception
{ MD_String8 prompt_str = {0};
it->perceptions_dirty = false; // needs to be in beginning because they might be redirtied by the new perception
MD_String8 prompt_str = {0};
#ifdef DO_CHATGPT_PARSING #ifdef DO_CHATGPT_PARSING
prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it)); prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it));
#else #else
generate_prompt(it, &prompt); generate_prompt(it, &prompt);
#endif #endif
Log("Sending request with prompt `%.*s`\n", MD_S8VArg(prompt_str)); Log("Sending request with prompt `%.*s`\n", MD_S8VArg(prompt_str));
#ifdef WEB #ifdef WEB
// fire off generation request, save id // fire off generation request, save id
MD_ArenaTemp scratch = MD_GetScratch(0, 0); MD_ArenaTemp scratch = MD_GetScratch(0, 0);
MD_String8 terminated_completion_url = nullterm(scratch.arena, FmtWithLint(scratch.arena, "%s://%s:%d/completion", IS_SERVER_SECURE ? "https" : "http", SERVER_DOMAIN, SERVER_PORT)); MD_String8 terminated_completion_url = nullterm(scratch.arena, FmtWithLint(scratch.arena, "%s://%s:%d/completion", IS_SERVER_SECURE ? "https" : "http", SERVER_DOMAIN, SERVER_PORT));
int req_id = EM_ASM_INT({ int req_id = EM_ASM_INT({
return make_generation_request(UTF8ToString($0, $1), UTF8ToString($2, $3)); return make_generation_request(UTF8ToString($0, $1), UTF8ToString($2, $3));
}, },
prompt_str.str, (int)prompt_str.size, terminated_completion_url.str, (int)terminated_completion_url.size); prompt_str.str, (int)prompt_str.size, terminated_completion_url.str, (int)terminated_completion_url.size);
it->gen_request_id = req_id; it->gen_request_id = req_id;
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
#endif #endif
#ifdef DESKTOP #ifdef DESKTOP
MD_ArenaTemp scratch = MD_GetScratch(0, 0); MD_ArenaTemp scratch = MD_GetScratch(0, 0);
MD_String8 ai_response = {0}; MD_String8 ai_response = {0};
bool mocking_the_ai_response = false; bool mocking_the_ai_response = false;
#ifdef DEVTOOLS #ifdef DEVTOOLS
#ifdef MOCK_AI_RESPONSE #ifdef MOCK_AI_RESPONSE
mocking_the_ai_response = true; mocking_the_ai_response = true;
#endif #endif
#endif #endif
bool succeeded = true; // couldn't get AI response if false bool succeeded = true; // couldn't get AI response if false
if (mocking_the_ai_response) if (mocking_the_ai_response)
{
if (it->memories_last->context.talking_to_kind == it->npc_kind)
//if (it->memories_last->context.author_npc_kind != it->npc_kind)
{ {
if (it->memories_last->context.talking_to_kind == it->npc_kind) const char *action = 0;
//if (it->memories_last->context.author_npc_kind != it->npc_kind) const char *action_argument = "Raphael";
if(it->npc_kind == NPC_Daniel)
{ {
const char *action = 0; if (gete(it->aiming_shotgun_at))
const char *action_argument = "Raphael";
if(it->npc_kind == NPC_Daniel)
{ {
if (gete(it->aiming_shotgun_at)) action = "fire_shotgun";
{
action = "fire_shotgun";
}
else
{
action = "aim_shotgun";
}
} }
else if(it->npc_kind == NPC_Angel) else
{ {
action = "assign_gameplay_objective"; action = "aim_shotgun";
action_argument = "KILL Raphael";
} }
char *rigged_dialog[] = {
"Repeated amounts of testing dialog overwhelmingly in support of the mulaney brothers",
};
char *next_dialog = rigged_dialog[it->times_talked_to % ARRLEN(rigged_dialog)];
char *target = characters[it->memories_last->context.author_npc_kind].name;
target = characters[NPC_Player].name;
ai_response = FmtWithLint(frame_arena, "{\"target\": \"%s\", \"action\": \"%s\", \"action_argument\": \"%s\", \"speech\": \"%s\"}", target, action, action_argument, next_dialog);
it->times_talked_to += 1;
} }
else else if(it->npc_kind == NPC_Angel)
{ {
ai_response = MD_S8Lit("{\"target\": \"nobody\", \"action\": \"none\", \"speech\": \"\"}"); action = "assign_gameplay_objective";
action_argument = "KILL Raphael";
} }
char *rigged_dialog[] = {
"Repeated amounts of testing dialog overwhelmingly in support of the mulaney brothers",
};
char *next_dialog = rigged_dialog[it->times_talked_to % ARRLEN(rigged_dialog)];
char *target = characters[it->memories_last->context.author_npc_kind].name;
target = characters[NPC_Player].name;
ai_response = FmtWithLint(frame_arena, "{\"target\": \"%s\", \"action\": \"%s\", \"action_argument\": \"%s\", \"speech\": \"%s\"}", target, action, action_argument, next_dialog);
it->times_talked_to += 1;
}
else
{
ai_response = MD_S8Lit("{\"target\": \"nobody\", \"action\": \"none\", \"speech\": \"\"}");
}
// something to mock // something to mock
if (ai_response.size > 0) if (ai_response.size > 0)
{
Log("Mocking...\n");
Action a = {0};
MD_String8 error_message = MD_S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
{ {
Log("Mocking...\n"); error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
Action a = {0}; }
MD_String8 error_message = MD_S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
{
error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
}
assert(succeeded); assert(succeeded);
assert(error_message.size == 0); assert(error_message.size == 0);
MD_String8 valid_str = is_action_valid(frame_arena, it, a); MD_String8 valid_str = is_action_valid(frame_arena, it, a);
assert(valid_str.size == 0); assert(valid_str.size == 0);
perform_action(&gs, it, a); perform_action(&gs, it, a);
}
}
else
{
MD_String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", MD_S8VArg(prompt_str));
it->gen_request_id = make_generation_request(post_request_body);
} }
}
else
{
MD_String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", MD_S8VArg(prompt_str));
it->gen_request_id = make_generation_request(post_request_body);
}
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
#undef SAY #undef SAY
#endif // desktop endif #endif // desktop endif
}
}
else
{
assert(false);
} }
} }
} }
@ -7262,10 +7291,12 @@ ISANERROR("Don't know how to do this stuff on this platform.")
float shake_speed = 9.0f; float shake_speed = 9.0f;
Vec2 win_offset = V2(sinf((float)unwarped_elapsed_time * shake_speed * 1.5f + 0.1f), sinf((float)unwarped_elapsed_time * shake_speed + 0.3f)); Vec2 win_offset = V2(sinf((float)unwarped_elapsed_time * shake_speed * 1.5f + 0.1f), sinf((float)unwarped_elapsed_time * shake_speed + 0.3f));
win_offset = MulV2F(win_offset, 10.0f); win_offset = MulV2F(win_offset, 10.0f);
draw_centered_text((TextParams){false, MD_S8Lit("YOU WERE KILLED"), AddV2(MulV2F(screen_size(), 0.5f), win_offset), WHITE, 3.0f*visible}); draw_centered_text((TextParams){false, MD_S8Lit("YOU WERE KILLED"), AddV2(MulV2F(screen_size(), 0.5f), win_offset), WHITE, 3.0f*visible}); // YOU DIED
if(imbutton(aabb_centered(V2(screen_size().x/2.0f, screen_size().y*0.25f), MulV2F(V2(170.0f, 60.0f), visible)), 1.5f*visible, MD_S8Lit("Restart"))) if(imbutton(aabb_centered(V2(screen_size().x/2.0f, screen_size().y*0.25f), MulV2F(V2(170.0f, 60.0f), visible)), 1.5f*visible, MD_S8Lit("Continue")))
{ {
gs.player->killed = false;
transition_to_room(&gs, &level_threedee, MD_S8Lit("StartingRoom"));
reset_level(); reset_level();
} }
} }

@ -220,6 +220,14 @@ void chunk_from_s8(TextChunk *into, MD_String8 from)
into->text_length = (int)from.size; into->text_length = (int)from.size;
} }
typedef struct RememberedError
{
struct RememberedError *next;
struct RememberedError *prev;
Memory offending_self_output; // sometimes is just zero value if reason why it's bad is severe enough.
TextChunk reason_why_its_bad;
} RememberedError;
typedef struct Entity typedef struct Entity
{ {
bool exists; bool exists;
@ -229,6 +237,7 @@ typedef struct Entity
// the kinds are at the top so you can quickly see what kind an entity is in the debugger // the kinds are at the top so you can quickly see what kind an entity is in the debugger
bool is_world; // the static world. An entity is always returned when you collide with something so support that here bool is_world; // the static world. An entity is always returned when you collide with something so support that here
bool is_npc; bool is_npc;
NpcKind npc_kind;
// fields for all gs.entities // fields for all gs.entities
Vec2 pos; Vec2 pos;
@ -241,7 +250,6 @@ typedef struct Entity
MD_String8 current_room_name; MD_String8 current_room_name;
// npcs // npcs
NpcKind npc_kind;
EntityRef joined; EntityRef joined;
EntityRef aiming_shotgun_at; EntityRef aiming_shotgun_at;
EntityRef looking_at; // aiming shotgun at takes facing priority over this EntityRef looking_at; // aiming shotgun at takes facing priority over this
@ -250,8 +258,8 @@ typedef struct Entity
bool being_hovered; bool being_hovered;
bool perceptions_dirty; bool perceptions_dirty;
float dialog_fade; float dialog_fade;
TextChunkList *errorlist_first; RememberedError *errorlist_first;
TextChunkList *errorlist_last; RememberedError *errorlist_last;
#ifdef DESKTOP #ifdef DESKTOP
int times_talked_to; // for better mocked response string int times_talked_to; // for better mocked response string
#endif #endif
@ -302,6 +310,7 @@ typedef struct GameState {
// these must point entities in its own array. // these must point entities in its own array.
Entity *player; Entity *player;
Entity *angel;
Entity *world_entity; Entity *world_entity;
Entity entities[MAX_ENTITIES]; Entity entities[MAX_ENTITIES];
rnd_gamerand_t random; rnd_gamerand_t random;
@ -347,6 +356,11 @@ void fill_available_actions(GameState *gs, Entity *it, AvailableActions *a)
BUFF_APPEND(a, ACT_assign_gameplay_objective); BUFF_APPEND(a, ACT_assign_gameplay_objective);
} }
if(it->npc_kind != NPC_Angel)
{
BUFF_APPEND(a, ACT_end_conversation);
}
bool has_shotgun = it->npc_kind == NPC_Daniel; bool has_shotgun = it->npc_kind == NPC_Daniel;
if(has_shotgun) if(has_shotgun)
{ {
@ -401,6 +415,25 @@ MD_String8 npc_to_human_readable(Entity *me, NpcKind kind)
} }
} }
MD_String8List dump_memory_as_json(MD_Arena *arena, Memory *it)
{
MD_ArenaTemp scratch = MD_GetScratch(&arena, 1);
MD_String8List current_list = {0};
#define AddFmt(...) PushWithLint(arena, &current_list, __VA_ARGS__)
AddFmt("{");
AddFmt("\"speech\":\"%.*s\",", TextChunkVArg(it->speech));
AddFmt("\"action\":\"%s\",", actions[it->action_taken].name);
MD_String8 arg_str = action_argument_string(scratch.arena, it->action_argument);
AddFmt("\"action_argument\":\"%.*s\",", MD_S8VArg(arg_str));
AddFmt("\"target\":\"%s\"}", characters[it->context.talking_to_kind].name);
#undef AddFmt
MD_ReleaseScratch(scratch);
return current_list;
}
// outputs json which is parsed by the server // outputs json which is parsed by the server
MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, CanTalkTo can_talk_to) MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, CanTalkTo can_talk_to)
{ {
@ -438,6 +471,8 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
// @TODO unhardcode this, this will be a description of where the character is right now // @TODO unhardcode this, this will be a description of where the character is right now
AddFmt("You're currently standing in Daniel's farm's barn, a run-down structure that barely serves its purpose. Daniel's mighty protective of it though.\n"); AddFmt("You're currently standing in Daniel's farm's barn, a run-down structure that barely serves its purpose. Daniel's mighty protective of it though.\n");
AddFmt("\n");
if(e->npc_kind == NPC_Angel) if(e->npc_kind == NPC_Angel)
{ {
AddFmt("Acceptable verbs for assigning a gameplay objective:\n"); AddFmt("Acceptable verbs for assigning a gameplay objective:\n");
@ -555,9 +590,14 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
if(it == e->memories_last && e->errorlist_first) if(it == e->memories_last && e->errorlist_first)
{ {
AddFmt("Errors you made: \n"); AddFmt("Errors you made: \n");
for(TextChunkList *cur = e->errorlist_first; cur; cur = cur->next) for(RememberedError *cur = e->errorlist_first; cur; cur = cur->next)
{ {
AddFmt("%.*s\n", TextChunkVArg(cur->text)); if(cur->offending_self_output.speech.text_length > 0 || cur->offending_self_output.action_taken != ACT_none)
{
MD_String8 offending_json_output = MD_S8ListJoin(scratch.arena, dump_memory_as_json(scratch.arena, &cur->offending_self_output), &(MD_StringJoin){0});
AddFmt("When you output, `%.*s`, ", MD_S8VArg(offending_json_output));
}
AddFmt("%.*s\n", TextChunkVArg(cur->reason_why_its_bad));
} }
} }
if(current_list.node_count > 0) if(current_list.node_count > 0)
@ -567,12 +607,7 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
if(it->context.i_said_this) if(it->context.i_said_this)
{ {
MD_String8List current_list = {0}; // shadow the list of human understandable sentences to quickly flush MD_String8List current_list = {0}; // shadow the list of human understandable sentences to quickly flush
AddFmt("{"); current_list = dump_memory_as_json(scratch.arena, it);
AddFmt("\"speech\":\"%.*s\",", TextChunkVArg(it->speech));
AddFmt("\"action\":\"%s\",", actions[it->action_taken].name);
MD_String8 arg_str = action_argument_string(scratch.arena, it->action_argument);
AddFmt("\"action_argument\":\"%.*s\",", MD_S8VArg(arg_str));
AddFmt("\"target\":\"%s\"}", characters[it->context.talking_to_kind].name);
AddNewNode(MSG_ASSISTANT); AddNewNode(MSG_ASSISTANT);
} }
} }
@ -583,9 +618,11 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, Ca
MD_ReleaseScratch(scratch); MD_ReleaseScratch(scratch);
#undef AddFmt
return to_return; return to_return;
} }
MD_String8 get_field(MD_Node *parent, MD_String8 name) MD_String8 get_field(MD_Node *parent, MD_String8 name)
{ {
return MD_ChildFromString(parent, name, 0)->first_child->string; return MD_ChildFromString(parent, name, 0)->first_child->string;

@ -27,7 +27,7 @@
#ifdef DEVTOOLS #ifdef DEVTOOLS
#define PROFILING_SAVE_FILENAME "rpgpt.spall" #define PROFILING_SAVE_FILENAME "rpgpt.spall"
// server url cannot have trailing slash // server url cannot have trailing slash
#define MOCK_AI_RESPONSE //#define MOCK_AI_RESPONSE
#define SERVER_DOMAIN "localhost" #define SERVER_DOMAIN "localhost"
#define SERVER_PORT 8090 #define SERVER_PORT 8090
#define IS_SERVER_SECURE 0 #define IS_SERVER_SECURE 0

Loading…
Cancel
Save