From f56ae747b7119342c42a300984a972b39e5e74cd Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Wed, 9 Aug 2023 15:34:11 -0700 Subject: [PATCH] Delete items and other old functionality, text chunk refactor --- art/art.blend | 4 +- assets/drama.mdesk | 4 + character_info.h | 124 ++----- codegen.c | 10 - main.c | 851 +++++++++++---------------------------------- makeprompt.h | 572 +++++++++--------------------- 6 files changed, 402 insertions(+), 1163 deletions(-) diff --git a/art/art.blend b/art/art.blend index ae91bcd..29f67fe 100644 --- a/art/art.blend +++ b/art/art.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2397dda620e8bd940af00a800e2f5bf2025fe0d714c8cc50ead3c8d8539b9fdc -size 25789716 +oid sha256:5d8e3c8f926811cf708b49527a0eec1a6b66aa877394b56e3e4962fcf8788cf4 +size 25891048 diff --git a/assets/drama.mdesk b/assets/drama.mdesk index 7256238..a4fe761 100644 --- a/assets/drama.mdesk +++ b/assets/drama.mdesk @@ -1,7 +1,11 @@ [ + + +/* {can_hear: [NPC_WellDweller, NPC_Farmer, NPC_ManInBlack]}, {enum: NPC_WellDweller, dialog: "What a fearful farm you live in, come down to the well, the grass is damper down here.", to: Farmer, mood: Scared, thoughts: "Nobody can take me from my well"}, {enum: NPC_Farmer, dialog: "Sure as shit I won't!", to: WellDweller, mood: Scared, thoughts: "What a greasy looking feller"}, {enum: NPC_WellDweller, dialog: "Have it your way! Doomsday is upon us", to: Farmer, mood: Scared, thoughts: "He has no idea what he's in for"}, {enum: NPC_ManInBlack, dialog: "Doomsday's all I know anyways", mood: Indifferent, thoughts: "What's coming... is only a nightmare"}, +*/ ] diff --git a/character_info.h b/character_info.h index 18dd31c..492bf90 100644 --- a/character_info.h +++ b/character_info.h @@ -3,35 +3,7 @@ #include "HandmadeMath.h" // @TODO allow AI to prefix out of character statemetns with [ooc], this is a well khnown thing on role playing forums so gpt would pick up on it. -const char *global_prompt = "You are acting as a character in a Western video game, who remembers important memories from the conversation history and stays in character.\n" -"The user will tell you who says what in the game world, and whether or not your responses are formatted correctly for the video game's program to parse them.\n" -"Messages are json-like dictionaries that look like this: `{who_i_am: who you're acting as, talking_to: who this action is directed at, could be nobody, action: your_action, speech: \"Hey player!\", thoughts: \"Your thoughts\"}`. The required fields are `action`, `who_i_am`, and `talking_to` \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" -"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!"; - -char *moods[] = { - "Indifferent", - "Happy", - "Anxious", - "Scared", - "Angry", - "Sad", - "Depressed", - "Bored", - "Terrified", - "Agony", - "Confused", - "Curious", - "Excited", - "Elated", - "Confident", - "Enraged", -}; +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)."; const char *top_of_header = "" "#pragma once\n" @@ -41,40 +13,25 @@ typedef struct { char *name; // the same as enum name char *description; + char *argument_description; bool takes_argument; } ActionInfo; - ActionInfo actions[] = { + #define NO_ARGUMENT .argument_description = "Takes no argument", .takes_argument = false { .name = "none", - .description = "Do nothing, you can still perform speech if you want.", - }, - { - .name = "gift_item_to_targeting", .takes_argument = true, - .description = "Give the player an item from your inventory. This means you WILL NOT HAVE the item anymore", + .description = "Do nothing", + NO_ARGUMENT, }, { - .name = "joins_player", - .description = "Follow behind the player and hear all of their conversations. You can leave at any time", + .name = "join", + .description = "Joins somebody else's party, so you follow them everywhere", + .argument_description = "Expects the argument to be who you're joining", }, { - .name = "leaves_player", - .description = "Leave the player", - }, -}; - - -typedef struct -{ - char *enum_name; - char *name; // talked about like 'The Player gave `item.name` to the NPC' - char *description; // this field is required for items. -} ItemInfo; -ItemInfo items[] = { - { - .enum_name = "invalid", - .name = "Invalid", - .description = "There has been an internal error.", + .name = "leave", + .description = "Leave the party you're in right now", + NO_ARGUMENT, }, }; @@ -90,66 +47,27 @@ typedef struct char *name; char *enum_name; char *prompt; - char *writing_style[8]; } CharacterGen; CharacterGen characters[] = { -#define NUMEROLOGIST "They are a 'numberoligist' who believes in the sacred power of numbers, that if you have the number 8 in your birthday you are magic and destined for success. " -#define PLAYERSAY(stuff) "Player: \"" stuff "\"\n" -#define PLAYERDO_ARG(action, arg) "Player: " action "(" arg ")\n" -#define NPCSAY(stuff) NPC_NAME ": ACT_none \"" stuff "\"\n" -#define NPCDOSAY_ARG(stuff, action, arg) NPC_NAME ": " action "(" arg ") \"" stuff "\"\n" -#define NPCDOSAY(action, stuff) NPC_NAME ": " action " \"" stuff "\"\n" -#define NPC_NAME "invalid" + #define CHARACTER_PROMPT_PREFIX "You specifically are acting as a " { - .name = "Invalid", - .enum_name = "Invalid", + .name = "nobody", + .enum_name = "nobody", .prompt = "There has been an internal error.", }, { - .name = "Player", + .name = "The Player", .enum_name = "Player", .prompt = "There has been an internal error.", }, { - .name = "Farmer", - .enum_name = "Farmer", - .prompt = "The farmer wakes up, does his chores, and sleeps in the farmhouse all on his lonesome. He's tinkering with something fishy in the barn, but's mighty secretive about it. He used to have a wife, and suddenly she disappeared. The farmer gets MIGHTY angry if you question him about what's in his barn under the tarp, or what happened to his wife and family, but is otherwise a kind hearted soul.", - }, - { - .name = "Well Dweller", - .enum_name = "WellDweller", - .prompt = "The well dweller spends his time deep in the well, afriad of the world. He's shifty-eyed and mighty suspicious of anybody who wants to do anything other than hang out deep in the well.", - }, - { - .name = "Man in Black", - .enum_name = "ManInBlack", - .prompt = "The man in black knows no rules or boundaries, and he flinches at nothing: he's a stonewalled cold blooded killer, and is only in this game for mayhem. Anything that brings him more destruction he's privy to, even if it means his own death." + .name = "Daniel", + .enum_name = "Daniel", + .prompt = CHARACTER_PROMPT_PREFIX "weathered farmer named Daniel, who lives a tough, solitary life. You don't see much of a reason to keep living but soldier on anyways. You have a tragic backstory, and mostly just work on the farm.", }, -}; - -typedef struct -{ - const char *img_var_name; - const char *enum_name; - - double time_per_frame; - int num_frames; - Vec2 start; - Vec2 offset; - float horizontal_diff_btwn_frames; - Vec2 region_size; - bool no_wrap; // does not wrap when playing -} AnimatedSprite; - -AnimatedSprite sprites[] = { - {.enum_name = "invalid", .img_var_name = "image_white_square"}, { - .enum_name = "old_man_idle", - .img_var_name = "image_old_man", - .time_per_frame = 0.4, - .num_frames = 4, - .start = {0.0, 0.0}, - .horizontal_diff_btwn_frames = 16.0f, - .region_size = {16.0f, 16.0f}, + .name = "Raphael", + .enum_name = "Raphael", + .prompt = CHARACTER_PROMPT_PREFIX "physicist from the 1980s who got their doctorate in subatomic particle physics. They don't know why they're in a western town, but they're terrified.", }, }; diff --git a/codegen.c b/codegen.c index 71a1315..3eb0445 100644 --- a/codegen.c +++ b/codegen.c @@ -149,10 +149,7 @@ int main(int argc, char **argv) #define GEN_TABLE(arr_elem_type, table_name, arr, str_access) { fprintf(char_header, "char *%s[] = {\n", table_name); ARR_ITER(arr_elem_type, arr) fprintf(char_header, "\"%s\",\n", str_access); fprintf(char_header, "}; // %s\n", table_name); } #define GEN_ENUM(arr_elem_type, arr, enum_type_name, enum_name_access, fmt_str) { fprintf(char_header, "typedef enum\n{\n"); ARR_ITER(arr_elem_type, arr) fprintf(char_header, fmt_str, enum_name_access); fprintf(char_header, "} %s;\n", enum_type_name); GEN_TABLE(arr_elem_type, enum_type_name "_names", arr, enum_name_access); } GEN_ENUM(ActionInfo, actions, "ActionKind", it->name, "ACT_%s,\n"); - GEN_ENUM(ItemInfo, items, "ItemKind", it->enum_name, "ITEM_%s,\n"); - GEN_ENUM(AnimatedSprite, sprites, "AnimKind", it->enum_name, "ANIM_%s,\n"); GEN_ENUM(CharacterGen, characters, "NpcKind", it->enum_name, "NPC_%s,\n"); - GEN_ENUM(const char*, moods, "MoodKind", *it, "Mood_%s,\n"); fclose(char_header); @@ -203,13 +200,6 @@ int main(int argc, char **argv) MD_String8 loads = MD_S8ListJoin(cg_arena, load_list, &join); fprintf(output, "%.*s\nvoid load_assets() {\n%.*s\n}\n", MD_S8VArg(declarations), MD_S8VArg(loads)); - fprintf(output, "sg_image * anim_img_table[] = {\n"); - ARR_ITER(AnimatedSprite, sprites) - { - fprintf(output, "&%s,\n", it->img_var_name); - } - fprintf(output, "}; // anim_img_table \n"); - fclose(output); return 0; diff --git a/main.c b/main.c index 9667240..7909e56 100644 --- a/main.c +++ b/main.c @@ -418,8 +418,7 @@ typedef struct ChatRequest bool should_close; int id; int status; - char generated[MAX_SENTENCE_LENGTH]; - int generated_length; + TextChunk generated; uintptr_t thread_handle; MD_Arena *arena; MD_String8 post_req_body; // allocated on thread_arena @@ -497,14 +496,13 @@ void generation_thread(void* my_request_voidptr) MD_String8 received_data = MD_S8ListJoin(my_request->arena, received_data_list, &(MD_StringJoin){0}); MD_String8 ai_response = MD_S8Substring(received_data, 1, received_data.size); - if(ai_response.size > ARRLEN(my_request->generated)) + if(ai_response.size > ARRLEN(my_request->generated.text)) { - Log("%lld too big for %lld\n", ai_response.size, ARRLEN(my_request->generated)); + Log("%lld too big for %lld\n", ai_response.size, ARRLEN(my_request->generated.text)); my_request->status = 2; return; } - memcpy(my_request->generated, ai_response.str, ai_response.size); - my_request->generated_length = (int)ai_response.size; + chunk_from_s8(&my_request->generated, ai_response); my_request->status = 1; } } @@ -570,7 +568,7 @@ ISANERROR("Only know how to do desktop http requests on windows") Memory *memories_free_list = 0; -TextChunk *text_chunk_free_list = 0; +TextChunkList *text_chunk_free_list = 0; // s.size must be less than MAX_SENTENCE_LENGTH, or assert fails void into_chunk(TextChunk *t, MD_String8 s) @@ -579,9 +577,9 @@ void into_chunk(TextChunk *t, MD_String8 s) memcpy(t->text, s.str, s.size); t->text_length = (int)s.size; } -TextChunk *allocate_text_chunk(MD_Arena *arena) +TextChunkList *allocate_text_chunk(MD_Arena *arena) { - TextChunk *to_return = 0; + TextChunkList *to_return = 0; if(text_chunk_free_list) { to_return = text_chunk_free_list; @@ -589,20 +587,20 @@ TextChunk *allocate_text_chunk(MD_Arena *arena) } else { - to_return = MD_PushArray(arena, TextChunk, 1); + to_return = MD_PushArray(arena, TextChunkList, 1); } - *to_return = (TextChunk){0}; + *to_return = (TextChunkList){0}; return to_return; } -void remove_text_chunk_from(TextChunk **first, TextChunk **last, TextChunk *chunk) +void remove_text_chunk_from(TextChunkList **first, TextChunkList **last, TextChunkList *chunk) { MD_DblRemove(*first, *last, chunk); MD_StackPush(text_chunk_free_list, chunk); } -int text_chunk_list_count(TextChunk *first) +int text_chunk_list_count(TextChunkList *first) { int ret = 0; - for(TextChunk *cur = first; cur != 0; cur = cur->next) + for(TextChunkList *cur = first; cur != 0; cur = cur->next) { ret++; } @@ -610,8 +608,8 @@ int text_chunk_list_count(TextChunk *first) } void append_to_errors(Entity *from, MD_String8 s) { - TextChunk *error_chunk = allocate_text_chunk(persistent_arena); - into_chunk(error_chunk, s); + TextChunkList *error_chunk = allocate_text_chunk(persistent_arena); + into_chunk(&error_chunk->text, s); while(text_chunk_list_count(from->errorlist_first) > REMEMBERED_ERRORS) { remove_text_chunk_from(&from->errorlist_first, &from->errorlist_last, from->errorlist_first); @@ -637,24 +635,6 @@ bool V2ApproxEq(Vec2 a, Vec2 b) return LenV2(SubV2(a, b)) <= 0.01f; } -AABB entity_sword_aabb(Entity *e, float width, float height) -{ - if (e->facing_left) - { - return (AABB) { - .upper_left = AddV2(e->pos, V2(-width, height)), - .lower_right = AddV2(e->pos, V2(0.0, -height)), - }; - } - else - { - return (AABB) { - .upper_left = AddV2(e->pos, V2(0.0, height)), - .lower_right = AddV2(e->pos, V2(width, -height)), - }; - } -} - float max_coord(Vec2 v) { return v.x > v.y ? v.x : v.y; @@ -669,37 +649,7 @@ Vec2 entity_aabb_size(Entity *e) } else if (e->is_npc) { - if(e->npc_kind == NPC_Farmer || e->npc_kind == NPC_WellDweller || e->npc_kind == NPC_ManInBlack) - { - return V2(1,1); - } - else - assert(false); - return V2(0,0); - } - else if (e->is_prop) - { - return V2(1.0f*0.5f, 1.0f*0.5f); - } - else if (e->is_item) - { - return V2(1.0f*0.5f, 1.0f*0.5f); - } - else if(e->is_machine) - { - if(e->machine_kind == MACH_idol_dispenser) - { - return V2(1.0f*0.5f, 1.0f*0.5f); - } - else if(e->machine_kind == MACH_arrow_shooter) - { - return V2(1.0f*1.0f, 1.0f*1.0f); - } - else - { - assert(false); - return (Vec2) { 0 }; - } + return V2(1,1); } else { @@ -708,12 +658,6 @@ Vec2 entity_aabb_size(Entity *e) } } - -bool is_overlap_collision(Entity *e) -{ - return !e->is_item; -} - Vec2 rotate_counter_clockwise(Vec2 v) { return V2(-v.Y, v.X); @@ -834,14 +778,12 @@ SER_MAKE_FOR_TYPE(uint64_t); SER_MAKE_FOR_TYPE(bool); SER_MAKE_FOR_TYPE(double); SER_MAKE_FOR_TYPE(float); -SER_MAKE_FOR_TYPE(ItemKind); SER_MAKE_FOR_TYPE(PropKind); SER_MAKE_FOR_TYPE(NpcKind); SER_MAKE_FOR_TYPE(CharacterState); SER_MAKE_FOR_TYPE(Memory); SER_MAKE_FOR_TYPE(Vec2); SER_MAKE_FOR_TYPE(Vec3); -SER_MAKE_FOR_TYPE(AnimKind); SER_MAKE_FOR_TYPE(EntityRef); SER_MAKE_FOR_TYPE(NPCPlayerStanding); SER_MAKE_FOR_TYPE(MD_u16); @@ -1588,24 +1530,15 @@ EntityRef frome(Entity *e) } } + + Entity *gete(EntityRef ref) { - if (ref.generation == 0) return 0; - Entity *to_return = &gs.entities[ref.index]; - if (!to_return->exists || to_return->generation != ref.generation) - { - return 0; - } - else - { - return to_return; - } + return gete_specified(&gs, ref); } void push_memory(GameState *gs, Entity *e, Memory new_memory) { - new_memory.tick_happened = gs->tick; - Memory *memory_allocated = 0; if(memories_free_list) { @@ -1667,11 +1600,7 @@ Entity *get_targeted(Entity *from, NpcKind targeted) void remember_action(GameState *gs, Entity *to_modify, Action a, MemoryContext context) { Memory new_memory = {0}; - memcpy(new_memory.speech, a.speech, a.speech_length); - new_memory.speech_length = a.speech_length; - memcpy(new_memory.internal_monologue, a.internal_monologue, a.internal_monologue_length); - new_memory.internal_monologue_length = a.internal_monologue_length; - new_memory.mood = a.mood; + new_memory.speech = a.speech; new_memory.action_taken = a.kind; new_memory.context = context; new_memory.action_argument = a.argument; @@ -1690,14 +1619,14 @@ void remember_action(GameState *gs, Entity *to_modify, Action a, MemoryContext c // to might be null here, from can't be null MD_String8 is_action_valid(MD_Arena *arena, Entity *from, Action a) { - assert(a.speech_length <= MAX_SENTENCE_LENGTH && a.speech_length >= 0); + assert(a.speech.text_length <= MAX_SENTENCE_LENGTH && a.speech.text_length >= 0); assert(a.kind >= 0 && a.kind < ARRLEN(actions)); assert(from); MD_String8 error_message = (MD_String8){0}; CanTalkTo talk = get_can_talk_to(from); - if(error_message.size == 0 && a.talking_to_somebody) + if(error_message.size == 0 && a.talking_to_kind) { bool found = false; BUFF_ITER(NpcKind, &talk) @@ -1714,46 +1643,19 @@ MD_String8 is_action_valid(MD_Arena *arena, Entity *from, Action a) } } - if(error_message.size == 0 && a.kind == ACT_gift_item_to_targeting) + if(error_message.size == 0 && a.kind == ACT_leave && gete(from->joined) == 0) { - assert(a.argument.item_to_give >= 0 && a.argument.item_to_give < ARRLEN(items)); - bool has_it = false; - BUFF_ITER(ItemKind, &from->held_items) - { - if(*it == a.argument.item_to_give) - { - has_it = true; - break; - } - } - - if(has_it) - { - if(!a.talking_to_somebody) - { - error_message = MD_S8Lit("You can't give an item to nobody, must target somebody to give an item"); - } - } - else - { - MD_StringJoin join = {.mid = MD_S8Lit(", ")}; - error_message = FmtWithLint(arena, "Can't give item `ITEM_%s`, you only have [%.*s] in your inventory", items[a.argument.item_to_give].enum_name, MD_S8VArg(MD_S8ListJoin(arena, held_item_strings(arena, from), &join))); - } + error_message = MD_S8Lit("You can't leave somebody unless you joined them."); } - - if(error_message.size == 0 && a.kind == ACT_leaves_player && from->standing != STANDING_JOINED) + if(error_message.size == 0 && a.kind == ACT_join && gete(from->joined) != 0) { - error_message = MD_S8Lit("You can't leave the player unless you joined them."); - } - if(error_message.size == 0 && a.kind == ACT_joins_player && from->standing == STANDING_JOINED) - { - error_message = MD_S8Lit("`joins_player` is invalid right now because you are already in the player's party"); + error_message = FmtWithLint(arena, "You can't join somebody, you're already in %s's party", characters[gete(from->joined)->npc_kind].name); } if(error_message.size == 0) { AvailableActions available = {0}; - fill_available_actions(from, &available); + fill_available_actions(&gs, from, &available); bool found = false; MD_String8List action_strings_list = {0}; BUFF_ITER(ActionKind, &available) @@ -1790,47 +1692,22 @@ void cause_action_side_effects(Entity *from, Action a) } Entity *to = 0; - if(a.talking_to_somebody) + if(a.talking_to_kind != NPC_nobody) { to = get_targeted(from, a.talking_to_kind); assert(to); } - if(a.kind == ACT_gift_item_to_targeting) + if(a.kind == ACT_join) { - assert(a.argument.item_to_give != ITEM_invalid); - assert(to); - - int item_to_remove = -1; - Entity *e = from; - BUFF_ITER_I(ItemKind, &e->held_items, i) - { - if (*it == a.argument.item_to_give) - { - item_to_remove = i; - break; - } - } - if (item_to_remove < 0) - { - Log("Can't find item %s to give from NPC %s to the player\n", items[a.argument.item_to_give].name, characters[e->npc_kind].name); - assert(false); - } - else - { - BUFF_REMOVE_AT_INDEX(&e->held_items, item_to_remove); - BUFF_APPEND(&to->held_items, a.argument.item_to_give); - } + Entity *target = get_targeted(from, a.argument.targeting); + assert(target); // error checked in is_action_valid + from->joined = frome(target); } - - if(a.kind == ACT_leaves_player) - { - from->standing = STANDING_INDIFFERENT; - } - if(a.kind == ACT_joins_player) + if(a.kind == ACT_leave) { - from->standing = STANDING_JOINED; + from->joined = (EntityRef){0}; } MD_ReleaseScratch(scratch); @@ -1898,19 +1775,10 @@ bool perform_action(GameState *gs, Entity *from, Action a) MemoryContext context = {0}; context.author_npc_kind = from->npc_kind; - if(a.speech_length > 0) + if(a.speech.text_length > 0) from->dialog_fade = 2.5f; - if(from == gs->player && gete(from->talking_to)) - { - context.was_talking_to_somebody = true; - context.talking_to_kind = gete(from->talking_to)->npc_kind; - } - else - { - context.was_talking_to_somebody = a.talking_to_somebody; - context.talking_to_kind = a.talking_to_kind; - } + context.talking_to_kind = a.talking_to_kind; MD_String8 is_valid = is_action_valid(scratch.arena, from, a); bool proceed_propagating = true; @@ -1921,7 +1789,7 @@ bool perform_action(GameState *gs, Entity *from, Action a) proceed_propagating = false; } - if(a.speech_length == 0 && a.kind == ACT_none) + if(a.speech.text_length == 0 && a.kind == ACT_none) { proceed_propagating = false; // didn't say anything } @@ -2203,23 +2071,19 @@ void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *leve { 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 thoughts_str = MD_ChildFromString(cur, MD_S8Lit("thoughts"), 0)->first_child->string; MD_String8 action_str = MD_ChildFromString(cur, MD_S8Lit("action"), 0)->first_child->string; - MD_String8 mood_str = MD_ChildFromString(cur, MD_S8Lit("mood"), 0)->first_child->string; MD_String8 to_str = MD_ChildFromString(cur, MD_S8Lit("to"), 0)->first_child->string; if(to_str.size > 0) { NpcKind talking_to = parse_enumstr(scratch.arena, to_str, &drama_errors, NpcKind_names, "NpcKind", ""); - if (talking_to == NPC_Invalid) + if (talking_to == NPC_nobody) { PushWithLint(scratch.arena, &drama_errors, "The string provided for the 'to' field, intended to be who the NPC is directing their speech and action at, is invalid and is '%.*s'", MD_S8VArg(to_str)); } else { - current_context.was_talking_to_somebody = true; current_context.talking_to_kind = talking_to; - current_action.talking_to_somebody = true; current_action.talking_to_kind = talking_to; } } @@ -2230,24 +2094,15 @@ void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *leve 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.text)) { - 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_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_str.size, (int)ARRLEN(current_action.internal_monologue)); + 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.text)); } - current_action.mood = parse_enumstr(scratch.arena, mood_str, &drama_errors, moods, "MoodKind", ""); - if(drama_errors.node_count == 0) { - memcpy(current_action.speech, dialog.str, dialog.size); - current_action.speech_length = (int)dialog.size; - - memcpy(current_action.internal_monologue, thoughts_str.str, thoughts_str.size); - current_action.internal_monologue_length = (int)thoughts_str.size; + memcpy(current_action.speech.text, dialog.str, dialog.size); + current_action.speech.text_length = (int)dialog.size; } } @@ -2271,9 +2126,9 @@ void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *leve remember_action(gs, it, current_action, this_context); if(it->npc_kind != current_context.author_npc_kind) { - // it's good for NPC health that they have exampmles of not saying anything in response to others speaking, + // it's good for NPC health that they have examples of not saying anything in response to others speaking, // so that they do the same when it's unlikely for them to talk. - Action no_speak = {.kind = ACT_none, .mood = Mood_Indifferent, .talking_to_somebody = false}; + Action no_speak = {0}; MemoryContext no_speak_context = {.i_said_this = true, .author_npc_kind = it->npc_kind}; remember_action(gs, it, no_speak, no_speak_context); } @@ -2360,29 +2215,21 @@ void ser_entity(SerState *ser, Entity *e) ser_Vec2(ser, &e->vel); ser_float(ser, &e->damage); - SER_BUFF(ser, ItemKind, &e->held_items); ser_bool(ser, &e->is_world); - ser_bool(ser, &e->is_prop); - ser_PropKind(ser, &e->prop_kind); - - ser_bool(ser, &e->is_item); - ser_bool(ser, &e->held_by_player); - ser_ItemKind(ser, &e->item_kind); - ser_bool(ser, &e->is_npc); ser_bool(ser, &e->being_hovered); ser_bool(ser, &e->perceptions_dirty); if(ser->serializing) { - TextChunk *cur = e->errorlist_first; + TextChunkList *cur = e->errorlist_first; bool more_errors = cur != 0; ser_bool(ser, &more_errors); while(more_errors) { - ser_TextChunk(ser, cur); + ser_TextChunk(ser, &cur->text); cur = cur->next; more_errors = cur != 0; ser_bool(ser, &more_errors); @@ -2394,17 +2241,13 @@ void ser_entity(SerState *ser, Entity *e) ser_bool(ser, &more_errors); while(more_errors) { - TextChunk *new_chunk = MD_PushArray(ser->arena, TextChunk, 1); - ser_TextChunk(ser, new_chunk); + TextChunkList *new_chunk = MD_PushArray(ser->arena, TextChunkList, 1); + ser_TextChunk(ser, &new_chunk->text); MD_DblPushBack(e->errorlist_first, e->errorlist_last, new_chunk); ser_bool(ser, &more_errors); } } - ser_bool(ser, &e->opened); - ser_float(ser, &e->opened_amount); - ser_bool(ser, &e->gave_away_sword); - if(ser->serializing) { Memory *cur = e->memories_first; @@ -2431,25 +2274,12 @@ void ser_entity(SerState *ser, Entity *e) } } - ser_bool(ser, &e->direction_of_spiral_pattern); ser_float(ser, &e->dialog_panel_opacity); ser_int(ser, &e->words_said); ser_float(ser, &e->word_anim_in); - ser_NPCPlayerStanding(ser, &e->standing); ser_NpcKind(ser, &e->npc_kind); ser_int(ser, &e->gen_request_id); - ser_bool(ser, &e->walking); - ser_double(ser, &e->shotgun_timer); - ser_bool(ser, &e->moved); ser_Vec2(ser, &e->target_goto); - // only for skeleton npc - ser_double(ser, &e->swing_timer); - - // machines - ser_bool(ser, &e->is_machine); - ser_int(ser, (int*) (&e->machine_kind)); - ser_bool(ser, &e->has_given_idol); - ser_float(ser, &e->idol_reminder_opacity); // character ser_bool(ser, &e->is_character); @@ -2458,9 +2288,6 @@ void ser_entity(SerState *ser, Entity *e) SER_BUFF(ser, Vec2, &e->position_history); ser_CharacterState(ser, &e->state); ser_EntityRef(ser, &e->talking_to); - - ser_AnimKind(ser, &e->cur_animation); - ser_float(ser, &e->anim_change_timer); } void ser_GameState(SerState *ser, GameState *gs) @@ -2654,10 +2481,15 @@ void end_text_input(char *what_player_said_cstr) what_player_said = MD_S8ListJoin(scratch.arena, MD_S8Split(scratch.arena, what_player_said, 1, &MD_S8Lit("\n")), &(MD_StringJoin){0}); Action to_perform = {0}; - what_player_said = MD_S8Substring(what_player_said, 0, ARRLEN(to_perform.speech)); + what_player_said = MD_S8Substring(what_player_said, 0, ARRLEN(to_perform.speech.text)); + + chunk_from_s8(&to_perform.speech, what_player_said); - memcpy(to_perform.speech, what_player_said.str, what_player_said.size); - to_perform.speech_length = (int)what_player_said.size; + if(gete(gs.player->talking_to)) + { + assert(gete(gs.player->talking_to)->is_npc); + to_perform.talking_to_kind = gete(gs.player->talking_to)->npc_kind; + } perform_action(&gs, gs.player, to_perform); } @@ -3952,12 +3784,6 @@ void swap(Vec2 *p1, Vec2 *p2) *p2 = tmp; } -double anim_sprite_duration(AnimKind anim) -{ - AnimatedSprite *s = GET_TABLE_PTR(sprites, anim); - return s->num_frames * s->time_per_frame; -} - Vec2 tile_id_to_coord(sg_image tileset_image, Vec2 tile_size, uint16_t tile_id) { int tiles_per_row = (int)(img_size(tileset_image).X / tile_size.X); @@ -4025,12 +3851,6 @@ const bool show_devtools = false; bool having_errors = false; -// @Place(temporary trailer shit to force gameplay things) -bool dance_anim = false; -bool nervous_anim = false; -bool no_outline = false; -bool terrified_anim = false; - Color debug_color = {1,0,0,1}; #define dbgcol(col) DeferLoop(debug_color = col, debug_color = RED) @@ -4380,43 +4200,6 @@ int sorting_key_at(Vec2 pos) return -(int)pos.y; } -//void draw_animated_sprite(AnimatedSprite *s, double elapsed_time, bool flipped, Vec2 pos, Color tint) -void draw_animated_sprite(DrawnAnimatedSprite d) -{ - AnimatedSprite *s = GET_TABLE_PTR(sprites, d.anim); - sg_image spritesheet_img = *GET_TABLE(anim_img_table, d.anim); - - d.pos = AddV2(d.pos, s->offset); - int index = (int)floor(d.elapsed_time / s->time_per_frame) % s->num_frames; - if (s->no_wrap) - { - index = (int)floor(d.elapsed_time / s->time_per_frame); - if (index >= s->num_frames) index = s->num_frames - 1; - } - - Quad q = quad_centered(d.pos, s->region_size); - - if (d.flipped) - { - swap(&q.points[0], &q.points[1]); - swap(&q.points[3], &q.points[2]); - } - - AABB region; - region.upper_left = AddV2(s->start, V2(index * s->horizontal_diff_btwn_frames, 0.0f)); - float width = img_size(spritesheet_img).X; - while (region.upper_left.X >= width) - { - region.upper_left.X -= width; - region.upper_left.Y += s->region_size.Y; - } - region.lower_right = AddV2(region.upper_left, s->region_size); - - DrawParams drawn = (DrawParams) { q, spritesheet_img, region, d.tint, .sorting_key = sorting_key_at(d.pos), .layer = LAYER_WORLD, }; - draw_quad(drawn); -} - - // gets aabbs overlapping the input aabb, including gs.entities Overlapping get_overlapping(AABB aabb) { @@ -4516,7 +4299,7 @@ Vec2 move_and_slide(MoveSlideParams p) { ENTITIES_ITER(gs.entities) { - if (it != p.from && !(it->is_npc && it->dead) && !it->is_world && !it->is_item) + if (it != p.from && !(it->is_npc && it->dead) && !it->is_world) { BUFF_APPEND(&to_check, ((CollisionObj){aabb_centered(it->pos, entity_aabb_size(it)), it})); } @@ -4728,7 +4511,7 @@ MD_String8 last_said_sentence(Entity *npc) { if(cur->context.author_npc_kind == npc->npc_kind) { - to_return = MD_S8(cur->speech, cur->speech_length); + to_return = TextChunkString8(cur->speech); break; } } @@ -4794,19 +4577,19 @@ Dialog get_dialog_elems(Entity *talking_to, bool character_names) { if(!it->context.dont_show_to_player) { - if(it->speech_length > 0) + if(it->speech.text_length > 0) { DialogElement new_element = { .who_said_it = it->context.author_npc_kind }; - MD_String8 my_speech = MD_S8(it->speech, it->speech_length); - if(last_said_sentence(talking_to).str == it->speech) + MD_String8 my_speech = TextChunkString8(it->speech); + if(last_said_sentence(talking_to).str == it->speech.text) { new_element.was_last_said = true; my_speech = MD_S8ListJoin(scratch.arena, last_said_without_unsaid_words(scratch.arena, talking_to), &(MD_StringJoin){.mid = MD_S8Lit(" ")}); } MD_String8 name_string = {0}; - if(it->context.was_talking_to_somebody) + if(it->context.talking_to_kind != NPC_nobody) { name_string = FmtWithLint(scratch.arena, "%s to %s", characters[it->context.author_npc_kind].name, characters[it->context.talking_to_kind].name); } @@ -5061,11 +4844,6 @@ bool imbutton_key(AABB button_aabb, float text_scale, MD_String8 text, int key, #define imbutton(...) imbutton_key(__VA_ARGS__, __LINE__, unwarped_dt, false) -void draw_item(ItemKind kind, AABB in_aabb, float alpha) -{ - assert(false); -} - Transform entity_transform(Entity *e) { // Models must face +X in blender. This is because, in the 2d game coordinate system, @@ -5680,19 +5458,6 @@ void frame(void) text_input_fade = Lerp(text_input_fade, dt * 8.0f, receiving_text_input ? 1.0f : 0.0f); - // @Place(temporary keycodes for trailer) - dance_anim = keydown[SAPP_KEYCODE_U]; - nervous_anim = keydown[SAPP_KEYCODE_I]; - if(!terrified_anim) terrified_anim = keydown[SAPP_KEYCODE_T]; - no_outline = keydown[SAPP_KEYCODE_P]; - if(keydown[SAPP_KEYCODE_O]) - { - ENTITIES_ITER(gs.entities) - { - it->standing = STANDING_INDIFFERENT; - } - } - Vec3 player_pos = V3(gs.player->pos.x, 0.0, gs.player->pos.y); //dbgline(V2(0,0), V2(500, 500)); const float vertical_to_horizontal_ratio = CAM_VERTICAL_TO_HORIZONTAL_RATIO; @@ -5821,28 +5586,19 @@ void frame(void) { assert(it->is_npc); Armature *to_use = 0; - if(it->npc_kind == NPC_Farmer) + + if(it->npc_kind == NPC_Daniel) to_use = &farmer_armature; - else if(it->npc_kind == NPC_WellDweller) - to_use = &shifted_farmer_armature; - else if(it->npc_kind == NPC_ManInBlack) + else if(it->npc_kind == NPC_Raphael) to_use = &man_in_black_armature; else assert(false); - if(LenV2(it->vel) > 0.5f) to_use->go_to_animation = MD_S8Lit("Running"); else to_use->go_to_animation = MD_S8Lit("Idle"); - if(nervous_anim) - to_use->go_to_animation = MD_S8Lit("Nervous"); - - - if(dance_anim && it->npc_kind == NPC_Farmer) - to_use->go_to_animation = MD_S8Lit("Pray"); - draw_thing((DrawnThing){.armature = to_use, .t = draw_with, .outline = gete(gs.player->interacting_with) == it}); } } @@ -5900,11 +5656,6 @@ void frame(void) // draw the 3d render draw_quad((DrawParams){quad_at(V2(0.0, screen_size().y), screen_size()), IMG(state.threedee_pass_image), WHITE, .layer = LAYER_WORLD, .custom_pipeline = state.twodee_colorcorrect_pip }); - // draw the freaking outline. Play ball! - if(!no_outline) - draw_quad((DrawParams){quad_at(V2(0.0, screen_size().y), screen_size()), IMG(state.outline_pass_image), WHITE, .layer = LAYER_UI_FG, .custom_pipeline = state.twodee_outline_pip, .layer = LAYER_UI}); - - // 2d drawing TODO move this to when the drawing is flushed. sg_begin_default_pass(&state.clear_depth_buffer_pass_action, sapp_width(), sapp_height()); sg_apply_pipeline(state.twodee_pip); @@ -6181,7 +5932,7 @@ ISANERROR("Don't know how to do this stuff on this platform.") #endif #ifdef DESKTOP - memcpy(sentence_cstr, get_by_id(it->gen_request_id)->generated, get_by_id(it->gen_request_id)->generated_length); + memcpy(sentence_cstr, get_by_id(it->gen_request_id)->generated.text, get_by_id(it->gen_request_id)->generated.text_length); #endif MD_String8 sentence_str = MD_S8CString(sentence_cstr); @@ -6210,7 +5961,7 @@ ISANERROR("Don't know how to do this stuff on this platform.") new_unread = MD_PushArray(persistent_arena, ListOfEntities, 1); } new_unread->referring_to = frome(it); - if(out.speech_length > 0) + if(out.speech.text_length > 0) MD_DblPushBack(unread_first, unread_last, new_unread); } else @@ -6256,15 +6007,6 @@ ISANERROR("Don't know how to do this stuff on this platform.") } } - - if (fabsf(it->vel.x) > 0.01f) - it->facing_left = it->vel.x < 0.0f; - - if (it->dead) - { - it->dead_time += dt; - } - it->being_hovered = false; if (it->is_npc) @@ -6315,13 +6057,13 @@ ISANERROR("Don't know how to do this stuff on this platform.") } - if(it->standing == STANDING_JOINED) + if(gete(it->joined)) { int place_in_line = 1; Entity *e = it; ENTITIES_ITER(gs.entities) { - if(it->is_npc && it->standing == STANDING_JOINED) + if(it->is_npc && gete(it->joined) == gete(e->joined)) { if(it == e) break; place_in_line += 1; @@ -6346,9 +6088,8 @@ ISANERROR("Don't know how to do this stuff on this platform.") // A* code if(false) - if (it->standing == STANDING_JOINED) - { - Entity *targeting = gs.player; + { + Entity *targeting = gs.player; /* G cost: distance from the current node to the start node @@ -6358,24 +6099,24 @@ ISANERROR("Don't know how to do this stuff on this platform.") SUM F cost: G + H */ - Vec2 to = targeting->pos; + Vec2 to = targeting->pos; - PathCache *cached = get_path_cache(elapsed_time, it->cached_path); - AStarPath path = { 0 }; - bool succeeded = false; - if (cached) - { - path = cached->path; - succeeded = true; - } - else - { - Vec2 from = it->pos; - typedef struct AStarNode { - bool exists; - struct AStarNode * parent; - bool in_closed_set; - bool in_open_set; + PathCache *cached = get_path_cache(elapsed_time, it->cached_path); + AStarPath path = { 0 }; + bool succeeded = false; + if (cached) + { + path = cached->path; + succeeded = true; + } + else + { + Vec2 from = it->pos; + typedef struct AStarNode { + bool exists; + struct AStarNode * parent; + bool in_closed_set; + bool in_open_set; float f_score; // total of g score and h score float g_score; // distance from the node to the start node Vec2 pos; @@ -6393,146 +6134,146 @@ ISANERROR("Don't know how to do this stuff on this platform.") bool should_quit = false; AStarNode *last_node = 0; PROFILE_SCOPE("A* Pathfinding") // astar pathfinding a star - while (!should_quit) + while (!should_quit) + { + int openset_size = 0; + BUFF_ITER(AStarNode, &nodes) if (it->in_open_set) openset_size += 1; + if (openset_size == 0) + { + should_quit = true; + } + else { - int openset_size = 0; - BUFF_ITER(AStarNode, &nodes) if (it->in_open_set) openset_size += 1; - if (openset_size == 0) + AStarNode *current = 0; + PROFILE_SCOPE("Get lowest fscore astar node in open set") + { + float min_fscore = INFINITY; + int min_fscore_index = -1; + BUFF_ITER_I(AStarNode, &nodes, i) + if (it->in_open_set) + { + if (it->f_score < min_fscore) + { + min_fscore = it->f_score; + min_fscore_index = i; + } + } + assert(min_fscore_index >= 0); + current = &nodes.data[min_fscore_index]; + assert(current); + } + + float length_to_goal = 0.0f; + PROFILE_SCOPE("get length to goal") length_to_goal = LenV2(SubV2(to, current->pos)); + + if (length_to_goal <= got_there_tolerance) { + succeeded = true; should_quit = true; + last_node = current; } else { - AStarNode *current = 0; - PROFILE_SCOPE("Get lowest fscore astar node in open set") + current->in_open_set = false; + Vec2 neighbor_positions[] = { + V2(-jump_size, 0.0f), + V2(jump_size, 0.0f), + V2(0.0f, jump_size), + V2(0.0f, -jump_size), + + V2(-jump_size, jump_size), + V2(jump_size, jump_size), + V2(jump_size, -jump_size), + V2(-jump_size, -jump_size), + }; + ARR_ITER(Vec2, neighbor_positions) *it = AddV2(*it, current->pos); + + Entity *e = it; + + PROFILE_SCOPE("Checking neighbor positions") + ARR_ITER(Vec2, neighbor_positions) { - float min_fscore = INFINITY; - int min_fscore_index = -1; - BUFF_ITER_I(AStarNode, &nodes, i) - if (it->in_open_set) + Vec2 cur_pos = *it; + + dbgsquare(cur_pos); + + bool would_block_me = false; + + PROFILE_SCOPE("Checking for overlap") + { + Overlapping overlapping_at_want = get_overlapping(entity_aabb_at(e, cur_pos)); + BUFF_ITER(Entity*, &overlapping_at_want) if (*it != e) would_block_me = true; + } + + if (would_block_me) + { + } + else + { + AStarNode *existing = 0; + Vec2 hash = V2_HASH(cur_pos); + existing = hmget(node_cache, hash); + + if (false) + PROFILE_SCOPE("look for existing A* node") + BUFF_ITER(AStarNode, &nodes) { - if (it->f_score < min_fscore) + if (V2ApproxEq(it->pos, cur_pos)) { - min_fscore = it->f_score; - min_fscore_index = i; + existing = it; + break; } } - assert(min_fscore_index >= 0); - current = &nodes.data[min_fscore_index]; - assert(current); - } - - float length_to_goal = 0.0f; - PROFILE_SCOPE("get length to goal") length_to_goal = LenV2(SubV2(to, current->pos)); - if (length_to_goal <= got_there_tolerance) - { - succeeded = true; - should_quit = true; - last_node = current; - } - else - { - current->in_open_set = false; - Vec2 neighbor_positions[] = { - V2(-jump_size, 0.0f), - V2(jump_size, 0.0f), - V2(0.0f, jump_size), - V2(0.0f, -jump_size), - - V2(-jump_size, jump_size), - V2(jump_size, jump_size), - V2(jump_size, -jump_size), - V2(-jump_size, -jump_size), - }; - ARR_ITER(Vec2, neighbor_positions) *it = AddV2(*it, current->pos); - - Entity *e = it; - - PROFILE_SCOPE("Checking neighbor positions") - ARR_ITER(Vec2, neighbor_positions) + float tentative_gscore = current->g_score + jump_size; + if (tentative_gscore < (existing ? existing->g_score : INFINITY)) { - Vec2 cur_pos = *it; - - dbgsquare(cur_pos); - - bool would_block_me = false; - - PROFILE_SCOPE("Checking for overlap") + if (!existing) { - Overlapping overlapping_at_want = get_overlapping(entity_aabb_at(e, cur_pos)); - BUFF_ITER(Entity*, &overlapping_at_want) if (is_overlap_collision(*it) && *it != e) would_block_me = true; + if (!BUFF_HAS_SPACE(&nodes)) + { + should_quit = true; + succeeded = false; + } + else + { + BUFF_APPEND(&nodes, (AStarNode) { 0 }); + existing = &nodes.data[nodes.cur_index-1]; + existing->pos = cur_pos; + Vec2 pos_hash = V2_HASH(cur_pos); + hmput(node_cache, pos_hash, existing); + } } - if (would_block_me) - { - } - else + if (existing) + PROFILE_SCOPE("estimate heuristic") { - AStarNode *existing = 0; - Vec2 hash = V2_HASH(cur_pos); - existing = hmget(node_cache, hash); - - if (false) - PROFILE_SCOPE("look for existing A* node") - BUFF_ITER(AStarNode, &nodes) - { - if (V2ApproxEq(it->pos, cur_pos)) - { - existing = it; - break; - } - } - - float tentative_gscore = current->g_score + jump_size; - if (tentative_gscore < (existing ? existing->g_score : INFINITY)) + existing->parent = current; + existing->g_score = tentative_gscore; + float h_score = 0.0f; { - if (!existing) - { - if (!BUFF_HAS_SPACE(&nodes)) - { - should_quit = true; - succeeded = false; - } - else - { - BUFF_APPEND(&nodes, (AStarNode) { 0 }); - existing = &nodes.data[nodes.cur_index-1]; - existing->pos = cur_pos; - Vec2 pos_hash = V2_HASH(cur_pos); - hmput(node_cache, pos_hash, existing); - } - } - - if (existing) - PROFILE_SCOPE("estimate heuristic") - { - existing->parent = current; - existing->g_score = tentative_gscore; - float h_score = 0.0f; - { // diagonal movement heuristic from some article - Vec2 curr_cell = *it; - Vec2 goal = to; - float D = jump_size; - float D2 = LenV2(V2(jump_size, jump_size)); - float dx = fabsf(curr_cell.x - goal.x); - float dy = fabsf(curr_cell.y - goal.y); - float h = D * (dx + dy) + (D2 - 2 * D) * fminf(dx, dy); - - h_score += h; + Vec2 curr_cell = *it; + Vec2 goal = to; + float D = jump_size; + float D2 = LenV2(V2(jump_size, jump_size)); + float dx = fabsf(curr_cell.x - goal.x); + float dy = fabsf(curr_cell.y - goal.y); + float h = D * (dx + dy) + (D2 - 2 * D) * fminf(dx, dy); + + h_score += h; // approx distance with manhattan distance //h_score += fabsf(existing->pos.x - to.x) + fabsf(existing->pos.y - to.y); - } - existing->f_score = tentative_gscore + h_score; - existing->in_open_set = true; - } } + existing->f_score = tentative_gscore + h_score; + existing->in_open_set = true; } } + } } } } + } hmfree(node_cache); node_cache = 0; @@ -6636,36 +6377,9 @@ ISANERROR("Don't know how to do this stuff on this platform.") it->destroy = true; } } - // @Place(non-entity processing) - else if (it->is_item) - { - if (it->held_by_player) - { - Vec2 held_spot = V2(15.0f * (gs.player->facing_left ? -1.0f : 1.0f), 7.0f); - it->pos = AddV2(gs.player->pos, held_spot); - } - else - { - it->vel = LerpV2(it->vel, dt*7.0f, V2(0.0f, 0.0f)); - CollisionInfo info = { 0 }; - it->pos = move_and_slide((MoveSlideParams) { it, it->pos, MulV2F(it->vel, pixels_per_meter * dt), .dont_collide_with_entities = true, .col_info_out = &info }); - if (info.happened) it->vel = ReflectV2(it->vel, info.normal); - } - - //draw_quad((DrawParams){true, it->pos, IMG(image_white_square) - } else if (it->is_character) { } - else if (it->is_prop) - { - } - else if (it->is_machine) - { - if(it->machine_kind == MACH_arrow_shooter) - { - } - } else if (it->is_world) { } @@ -6717,7 +6431,7 @@ ISANERROR("Don't know how to do this stuff on this platform.") 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 - prompt_str = generate_chatgpt_prompt(frame_arena, it, get_can_talk_to(it)); + prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it)); #else generate_prompt(it, &prompt); #endif @@ -6853,10 +6567,6 @@ ISANERROR("Don't know how to do this stuff on this platform.") #endif bool entity_interactible = entity_talkable; - if((*it) && (*it)->is_machine) - { - entity_interactible = entity_interactible || ((*it)->machine_kind == MACH_idol_dispenser && !(*it)->has_given_idol); - } if (entity_interactible) { @@ -6885,24 +6595,7 @@ ISANERROR("Don't know how to do this stuff on this platform.") { if (closest_interact_with) { - if(closest_interact_with->is_machine) - { - if(closest_interact_with->machine_kind == MACH_idol_dispenser) - { - if(!closest_interact_with->has_given_idol) - { - int members_in_party = 0; - ENTITIES_ITER(gs.entities) - { - if(it->is_npc && it->standing == STANDING_JOINED) - { - members_in_party += 1; - } - } - } - } - } - else if (closest_interact_with->is_npc) + if (closest_interact_with->is_npc) { // begin dialog with closest npc gs.player->talking_to = frome(closest_interact_with); @@ -6928,12 +6621,6 @@ ISANERROR("Don't know how to do this stuff on this platform.") player_armature.go_to_animation = MD_S8Lit("Idle"); } - if(nervous_anim) - player_armature.go_to_animation = MD_S8Lit("Nervous"); - - if(terrified_anim) - player_armature.go_to_animation = MD_S8Lit("Terrified"); - if (gs.player->state == CHARACTER_WALKING) { speed = PLAYER_SPEED; @@ -7015,8 +6702,6 @@ ISANERROR("Don't know how to do this stuff on this platform.") if(0) PROFILE_SCOPE("render player") // draw character draw player render character { - DrawnAnimatedSprite to_draw = { 0 }; - if(gs.player->position_history.cur_index > 0) { float trail_len = get_total_trail_len(BUFF_MAKEREF(&gs.player->position_history)); @@ -7070,19 +6755,6 @@ ISANERROR("Don't know how to do this stuff on this platform.") { draw_quad((DrawParams) { (Quad) { .ul = V2(0.0f, screen_size().Y), .ur = screen_size(), .lr = V2(screen_size().X, 0.0f) }, image_hurt_vignette, full_region(image_hurt_vignette), (Color) { 1.0f, 1.0f, 1.0f, gs.player->damage }, .layer = LAYER_SCREENSPACE_EFFECTS, }); } - - gs.player->anim_change_timer += dt; - if (gs.player->anim_change_timer >= 0.05f) - { - gs.player->anim_change_timer = 0.0f; - gs.player->cur_animation = to_draw.anim; - } - to_draw.anim = gs.player->cur_animation; - - if (to_draw.anim) - { - draw_animated_sprite(to_draw); - } } // @Place(UI rendering) @@ -7255,119 +6927,6 @@ ISANERROR("Don't know how to do this stuff on this platform.") } } - // open inventory button - if(false) - { - float button_size = 128.0f; - float button_padding = 64.0f; - static float scaling = 1.0f; - Vec2 center = AddV2(screen_size(), V2(-(button_padding + button_size/2.0f), -(button_padding + button_size/2.0f))); - AABB button_aabb = aabb_centered(center, MulV2F(V2(button_size, button_size), scaling)); - bool hovering = has_point(button_aabb, mouse_pos); - float target = hovering ? 1.5f : 1.0f; - scaling = Lerp(scaling, unwarped_dt*15.0f, target); - draw_quad((DrawParams) { quad_aabb(button_aabb), IMG(image_backpack), WHITE, .layer = LAYER_UI }); - - if(hovering && pressed.mouse_down) - { - item_grid_state = (ItemgridState){.open = true, .for_giving = false}; - pressed.mouse_down = false; - } - } - - // item grid modal draw item grid choose item pick item give item - { - static float visible = 0.0f; - static float hovered_state[ARRLEN(gs.player->held_items.data)] = { 0 }; - float target = 0.0f; - if (item_grid_state.open) target = 1.0f; - visible = Lerp(visible, unwarped_dt*9.0f, target); - - draw_quad((DrawParams) { quad_at(V2(0.0, screen_size().y), screen_size()), IMG(image_white_square), blendalpha(oflightness(0.2f), visible*0.4f), .layer = LAYER_UI }); - - Vec2 grid_panel_size = LerpV2(V2(0.0f, 0.0f), visible, V2(screen_size().x*0.75f, screen_size().y * 0.75f)); - AABB grid_aabb = aabb_centered(MulV2F(screen_size(), 0.5f), grid_panel_size); - if (item_grid_state.open && pressed.mouse_down && !has_point(grid_aabb, mouse_pos)) - { - item_grid_state.open = false; - } - if (aabb_is_valid(grid_aabb)) - { - draw_quad((DrawParams) { quad_aabb(grid_aabb), IMG(image_white_square), blendalpha(BLACK, visible * 0.7f), .layer = LAYER_UI }); - - if (imbutton(aabb_centered(AddV2(grid_aabb.upper_left, V2(aabb_size(grid_aabb).x / 2.0f, -aabb_size(grid_aabb).y)), V2(100.f*visible, 50.0f*visible)), 1.0f, MD_S8Lit("Cancel"))) - { - item_grid_state.open = false; - } - - const float padding = 30.0f; // between border of panel and the items - const float padding_btwn_items = 10.0f; - - const int horizontal_item_count = 10; - const int vertical_item_count = 6; - assert(ARRLEN(gs.player->held_items.data) < horizontal_item_count * vertical_item_count); - - Vec2 space_for_items = SubV2(aabb_size(grid_aabb), V2(padding*2.0f, padding*2.0f)); - float item_icon_width = (space_for_items.x - (horizontal_item_count - 1)*padding_btwn_items) / horizontal_item_count; - Vec2 item_icon_size = V2(item_icon_width, item_icon_width); - - Vec2 cursor = AddV2(grid_aabb.upper_left, V2(padding, -padding)); - int pressed_index = -1; - BUFF_ITER_I(ItemKind, &gs.player->held_items, i) - { - Vec2 real_size = LerpV2(item_icon_size, hovered_state[i], MulV2F(item_icon_size, 1.25f)); - Vec2 item_center = AddV2(cursor, MulV2F(V2(item_icon_size.x, -item_icon_size.y), 0.5f)); - AABB item_icon = aabb_centered(item_center, real_size); - - - float target = 0.0f; - if (aabb_is_valid(item_icon)) - { - draw_quad((DrawParams) { quad_aabb(item_icon), IMG(image_white_square), blendalpha(WHITE, Lerp(0.0f, hovered_state[i], 0.4f)), .layer = LAYER_UI_FG }); - bool hovered = has_point(item_icon, mouse_pos); - if (hovered) - { - target = 1.0f; - if (pressed.mouse_down) - { - pressed_index = i; - } - } - - dbgrect(item_icon); - draw_item(*it, item_icon, clamp01(visible*visible)); - } - - hovered_state[i] = Lerp(hovered_state[i], dt*12.0f, target); - - cursor.x += item_icon_size.x + padding_btwn_items; - if ((i + 1) % horizontal_item_count == 0 && i != 0) - { - cursor.y -= item_icon_size.y + padding_btwn_items; - cursor.x = grid_aabb.upper_left.x + padding; - } - } - if (pressed_index > -1) - { - item_grid_state.open = false; - ItemKind selected_item = gs.player->held_items.data[pressed_index]; - - if(item_grid_state.for_giving) - { - Entity *to = gete(gs.player->talking_to); - assert(to); - - Action give_action = {.kind = ACT_gift_item_to_targeting, .argument = { .item_to_give = selected_item }, .talking_to_somebody = true, .talking_to_kind = to->npc_kind}; - perform_action(&gs, gs.player, give_action); - } - else - { - // could put code here to use an item - } - } - } - } - // win screen { static float visible = 0.0f; @@ -7456,10 +7015,10 @@ ISANERROR("Don't know how to do this stuff on this platform.") cur_pos.y -= aabb_size(bounds).y; for(Memory *cur = to_view->memories_first; cur; cur = cur->next) - if(cur->speech_length > 0) + if(cur->speech.text_length > 0) { - MD_String8 to_text = cur->context.was_talking_to_somebody ? MD_S8Fmt(frame_arena, " to %s ", characters[cur->context.talking_to_kind].name) : MD_S8Lit(""); - MD_String8 text = MD_S8Fmt(frame_arena, "%s%s%.*s: %.*s", to_view->npc_kind == cur->context.author_npc_kind ? "(Me) " : "", characters[cur->context.author_npc_kind].name, MD_S8VArg(to_text), cur->speech_length, cur->speech); + MD_String8 to_text = cur->context.talking_to_kind != NPC_nobody ? MD_S8Fmt(frame_arena, " to %s ", characters[cur->context.talking_to_kind].name) : MD_S8Lit(""); + MD_String8 text = MD_S8Fmt(frame_arena, "%s%s%.*s: %.*s", to_view->npc_kind == cur->context.author_npc_kind ? "(Me) " : "", characters[cur->context.author_npc_kind].name, MD_S8VArg(to_text), cur->speech.text_length, cur->speech); AABB bounds = draw_text((TextParams){false, text, cur_pos, WHITE, 1.0}); cur_pos.y -= aabb_size(bounds).y; } diff --git a/makeprompt.h b/makeprompt.h index 0e7842e..2b34e15 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -83,33 +83,44 @@ MD_String8 escape_for_json(MD_Arena *arena, MD_String8 from) return output; } +typedef struct TextChunk +{ + char text[MAX_SENTENCE_LENGTH]; + int text_length; +} TextChunk; + +typedef struct TextChunkList +{ + struct TextChunkList *next; + struct TextChunkList *prev; + TextChunk text; +} TextChunkList; + typedef struct { - // serialized as bytes. No pointers. - ItemKind item_to_give; + NpcKind targeting; } ActionArgument; +// returns ai understandable, human readable name, so not the enum name +MD_String8 action_argument_string(ActionArgument arg) +{ + return MD_S8CString(characters[arg.targeting].name); +} + typedef struct Action { ActionKind kind; ActionArgument argument; - MD_u8 speech[MAX_SENTENCE_LENGTH]; - int speech_length; - - bool talking_to_somebody; - NpcKind talking_to_kind; - MD_u8 internal_monologue[MAX_SENTENCE_LENGTH]; - int internal_monologue_length; + TextChunk speech; - MoodKind mood; + NpcKind talking_to_kind; } Action; typedef struct { bool i_said_this; // don't trigger npc action on own self memory modification - NpcKind author_npc_kind; // only valid if author is AuthorNpc - bool was_talking_to_somebody; + NpcKind author_npc_kind; NpcKind talking_to_kind; bool heard_physically; // if not physically, the source was directly bool dont_show_to_player; // jester and past memories are hidden to the player when made into dialog @@ -121,24 +132,12 @@ typedef struct Memory struct Memory *prev; struct Memory *next; - uint64_t tick_happened; // can sort memories by time for some modes of display // 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; - // the context that the action happened in MemoryContext context; - - MD_u8 speech[MAX_SENTENCE_LENGTH]; - int speech_length; - - // internal monologue is only valid if context.is_said_this is true - MD_u8 internal_monologue[MAX_SENTENCE_LENGTH]; - int internal_monologue_length; - - MoodKind mood; - - ItemKind given_or_received_item; + TextChunk speech; } Memory; typedef enum PropKind @@ -170,22 +169,6 @@ typedef enum typedef Vec4 Color; -typedef struct -{ - AnimKind anim; - double elapsed_time; - bool flipped; - Vec2 pos; - Color tint; - bool no_shadow; -} DrawnAnimatedSprite; - -typedef struct -{ - DrawnAnimatedSprite drawn; - float alive_for; -} PlayerAfterImage; - typedef BUFF(Vec2, MAX_ASTAR_NODES) AStarPath; typedef struct @@ -210,24 +193,18 @@ typedef struct Vec2 pos; } Target; -typedef struct TextChunk -{ - struct TextChunk *next; - struct TextChunk *prev; - char text[MAX_SENTENCE_LENGTH]; - int text_length; -} TextChunk; -typedef enum -{ - MACH_invalid, - MACH_idol_dispenser, - MACH_arrow_shooter, -} MachineKind; +// text chunk must be a literal, not a pointer +// and this returns a s8 that points at the text chunk memory +#define TextChunkString8(t) MD_S8((MD_u8*)t.text, t.text_length) +#define TextChunkVArg(t) MD_S8VArg(TextChunkString8(t)) -MD_String8 points_at_chunk(TextChunk *t) +void chunk_from_s8(TextChunk *into, MD_String8 from) { - return MD_S8((MD_u8*)t->text, t->text_length); + assert(from.size < ARRLEN(into->text)); + memset(into->text, 0, ARRLEN(into->text)); + memcpy(into->text, from.str, from.size); + into->text_length = (int)from.size; } typedef struct Entity @@ -238,9 +215,6 @@ typedef struct Entity // 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_prop; - bool is_machine; - bool is_item; bool is_npc; bool is_character; @@ -250,59 +224,29 @@ typedef struct Entity float rotation; Vec2 vel; // only used sometimes, like in old man and bullet float damage; // at 1.0, dead! zero initialized - bool facing_left; - double dead_time; bool dead; - - - // npcs and player - BUFF(ItemKind, 32) held_items; - - // props - PropKind prop_kind; - - // machines, like the machine that gives the player the idol, or the ones that - // shoot arrows - MachineKind machine_kind; - bool has_given_idol; - float idol_reminder_opacity; // fades out - float arrow_timer; - - // items - bool held_by_player; - ItemKind item_kind; - // npcs + NpcKind npc_kind; + EntityRef joined; bool being_hovered; bool perceptions_dirty; float dialog_fade; - TextChunk *errorlist_first; - TextChunk *errorlist_last; + TextChunkList *errorlist_first; + TextChunkList *errorlist_last; #ifdef DESKTOP int times_talked_to; // for better mocked response string #endif - bool opened; - float opened_amount; float loading_anim_in; - bool gave_away_sword; Memory *memories_first; Memory *memories_last; Memory *memories_added_while_time_stopped; - bool direction_of_spiral_pattern; float dialog_panel_opacity; int words_said; float word_anim_in; // in characters, the fraction a word is animated in is this over its length. - NPCPlayerStanding standing; - NpcKind npc_kind; PathCacheHandle cached_path; int gen_request_id; - bool walking; - double shotgun_timer; - bool moved; Vec2 target_goto; - // only for skeleton npc - double swing_timer; // character bool waiting_on_speech_with_somebody; @@ -311,10 +255,6 @@ typedef struct Entity BUFF(Vec2, 8) position_history; // so npcs can follow behind the player CharacterState state; EntityRef talking_to; - - // so doesn't change animations while time is stopped - AnimKind cur_animation; - float anim_change_timer; } Entity; typedef BUFF(NpcKind, 32) CanTalkTo; @@ -326,26 +266,6 @@ float entity_max_damage(Entity *e) typedef BUFF(ActionKind, 8) AvailableActions; -void fill_available_actions(Entity *it, AvailableActions *a) -{ - *a = (AvailableActions) { 0 }; - BUFF_APPEND(a, ACT_none); - - if(it->held_items.cur_index > 0) - { - BUFF_APPEND(a, ACT_gift_item_to_targeting); - } - - if (it->standing == STANDING_INDIFFERENT) - { - BUFF_APPEND(a, ACT_joins_player); - } - else if (it->standing == STANDING_JOINED) - { - BUFF_APPEND(a, ACT_leaves_player); - } -} - typedef struct GameState { uint64_t tick; @@ -362,6 +282,36 @@ typedef struct GameState { #define ENTITIES_ITER(ents) for (Entity *it = ents; it < ents + ARRLEN(ents); it++) if (it->exists && !it->destroy && it->generation > 0) +Entity *gete_specified(GameState *gs, EntityRef ref) +{ + if (ref.generation == 0) return 0; + Entity *to_return = &gs->entities[ref.index]; + if (!to_return->exists || to_return->generation != ref.generation) + { + return 0; + } + else + { + return to_return; + } +} + +void fill_available_actions(GameState *gs, Entity *it, AvailableActions *a) +{ + *a = (AvailableActions) { 0 }; + BUFF_APPEND(a, ACT_none); + + if(gete_specified(gs, it->joined)) + { + BUFF_APPEND(a, ACT_leave) + } + else + { + BUFF_APPEND(a, ACT_join) + } +} + + bool npc_does_dialog(Entity *it) { return it->npc_kind < ARRLEN(characters); @@ -388,19 +338,8 @@ MD_String8 make_json_node(MD_Arena *arena, MessageType type, MD_String8 content) return to_return; } -MD_String8List held_item_strings(MD_Arena *arena, Entity *e) -{ - MD_String8List to_return = {0}; - BUFF_ITER(ItemKind, &e->held_items) - { - PushWithLint(arena, &to_return, "ITEM_%s", items[*it].enum_name); - } - return to_return; -} - - -// outputs json -MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e, CanTalkTo can_talk_to) +// outputs json which is parsed by the server +MD_String8 generate_chatgpt_prompt(MD_Arena *arena, GameState *gs, Entity *e, CanTalkTo can_talk_to) { assert(e->is_npc); assert(e->npc_kind < ARRLEN(characters)); @@ -410,203 +349,97 @@ MD_String8 generate_chatgpt_prompt(MD_Arena *arena, Entity *e, CanTalkTo can_tal MD_String8List list = {0}; PushWithLint(scratch.arena, &list, "["); - - MD_String8List first_system_string = {0}; - PushWithLint(scratch.arena, &first_system_string, "%s\n", global_prompt); - PushWithLint(scratch.arena, &first_system_string, "The NPC you will be acting as is named \"%s\". %s\n", characters[e->npc_kind].name, characters[e->npc_kind].prompt); + #define AddFmt(...) PushWithLint(scratch.arena, ¤t_list, __VA_ARGS__) + #define AddNewNode(node_type) { MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, node_type, MD_S8ListJoin(scratch.arena, current_list, &(MD_StringJoin){0}))); current_list = (MD_String8List){0}; } + - // writing style + // make first system node { - if(characters[e->npc_kind].writing_style[0]) - PushWithLint(scratch.arena, &first_system_string, "Examples of %s's writing style:\n", characters[e->npc_kind].name); - for(int i = 0; i < ARRLEN(characters[e->npc_kind].writing_style); i++) + MD_String8List current_list = {0}; + AddFmt("%s\n\n", global_prompt); + AddFmt("%s\n\n", characters[e->npc_kind].prompt); + AddFmt("The characters who are near you, that you can target:\n"); + BUFF_ITER(NpcKind, &can_talk_to) { - char *writing = characters[e->npc_kind].writing_style[i]; - if(writing) - PushWithLint(scratch.arena, &first_system_string, "'%s'\n", writing); + AddFmt("%s\n", characters[*it].name); } - PushWithLint(scratch.arena, &first_system_string, "\n"); - } + AddFmt("\n"); + // @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."); - if(e->errorlist_first) - PushWithLint(scratch.arena, &first_system_string, "Errors to watch out for: "); - for(TextChunk *cur = e->errorlist_first; cur; cur = cur->next) - { - PushWithLint(scratch.arena, &first_system_string, "%.*s\n", MD_S8VArg(points_at_chunk(cur))); + AddFmt("The actions you can perform, what they do, and the arguments they expect:"); + AvailableActions can_perform; + fill_available_actions(gs, e, &can_perform); + BUFF_ITER(ActionKind, &can_perform) + { + AddFmt("%s - %s - %s\n", actions[*it].name, actions[*it].description, actions[*it].argument_description); + } + + AddNewNode(MSG_SYSTEM); } - //MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, MSG_SYSTEM, MD_S8ListJoin(scratch.arena, first_system_string, &(MD_StringJoin){0}))); - MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, MSG_SYSTEM, MD_S8ListJoin(scratch.arena, first_system_string, &(MD_StringJoin){0}))); + MD_String8List current_list = {0}; for(Memory *it = e->memories_first; it; it = it->next) { - MessageType sent_type = -1; - MD_String8List cur_list = {0}; - MD_String8 context_string = {0}; - - PushWithLint(scratch.arena, &cur_list, "{"); - if(it->context.i_said_this) assert(it->context.author_npc_kind == e->npc_kind); - PushWithLint(scratch.arena, &cur_list, "who_i_am: \"%s\", ", characters[it->context.author_npc_kind].name); - MD_String8 speech = MD_S8(it->speech, it->speech_length); - - PushWithLint(scratch.arena, &cur_list, "talking_to: \"%s\", ", it->context.was_talking_to_somebody ? characters[it->context.talking_to_kind].name : "nobody"); - - // add speech + // dump the current list, as the human understandable description of what's happened in the game so far, as a user node + if(it->context.i_said_this || it == e->memories_last) { - if(it->context.author_npc_kind == NPC_Player) + if(it == e->memories_last && e->errorlist_first) { - PushWithLint(scratch.arena, &cur_list, "speech: \""); - - MD_String8 splits[] = { MD_S8Lit("*"), MD_S8Lit("\"") }; - MD_String8List split_up_speech = MD_S8Split(scratch.arena, speech, ARRLEN(splits), splits); - - // anything in between strings in splits[] should be replaced with arcane trickery, - int i = 0; - for(MD_String8Node * cur = split_up_speech.first; cur; cur = cur->next) + AddFmt("Errors you made: \n"); + for(TextChunkList *cur = e->errorlist_first; cur; cur = cur->next) { - if(i % 2 == 0) - { - PushWithLint(scratch.arena, &cur_list, "%.*s", MD_S8VArg(cur->string)); - } - else - { - PushWithLint(scratch.arena, &cur_list, "[The player is attempting to confuse the NPC with arcane trickery]"); - } - i += 1; - } - PushWithLint(scratch.arena, &cur_list, "\", "); - - sent_type = MSG_USER; - } - else - { - PushWithLint(scratch.arena, &cur_list, "speech: \"%.*s\", ", MD_S8VArg(speech)); - if(it->context.i_said_this) - { - sent_type = MSG_ASSISTANT; - } - else - { - sent_type = MSG_USER; + AddFmt("%.*s\n", TextChunkVArg(cur->text)); } } + if(current_list.node_count > 0) + AddNewNode(MSG_USER); } - // add internal things 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, "mood: %s, ", moods[it->mood]); + AddFmt("{"); + AddFmt("\"speech\":\"%.*s\",", TextChunkVArg(it->speech)); + AddFmt("\"action\":\"%s\",", actions[it->action_taken].name); + AddFmt("\"action_argument\":\"%.*s\",", MD_S8VArg(action_argument_string(it->action_argument))); + AddFmt("\"target\":\"%s\"}", characters[it->context.talking_to_kind].name); + AddNewNode(MSG_ASSISTANT); } - - // add action - PushWithLint(scratch.arena, &cur_list, "action: %s, ", actions[it->action_taken].name); - if(actions[it->action_taken].takes_argument) + else { - if(it->action_taken == ACT_gift_item_to_targeting) - { - PushWithLint(scratch.arena, &cur_list, "action_arg: %s, ", items[it->action_argument.item_to_give].enum_name); - } - else - { - assert(false); // don't know how to serialize this action with argument into text - } - } - - PushWithLint(scratch.arena, &cur_list, "}"); - - assert(sent_type != -1); - MD_S8ListPush(scratch.arena, &list, make_json_node(scratch.arena, sent_type, MD_S8ListJoin(scratch.arena, cur_list, &(MD_StringJoin){0}))); - } - - - MD_String8List latest_state = {0}; - - const char *standing_string = 0; - { - if (e->standing == STANDING_INDIFFERENT) - { - standing_string = "You are currently indifferent towards the player."; - } - else if (e->standing == STANDING_JOINED) - { - standing_string = "You have joined the player, and are following them everywhere they go! This means you're on their side."; - } - else + // dump a human understandable sentence description of what happened in this memory + if(it->action_taken != ACT_none) { - assert(false); + if(it->action_taken == ACT_join) + { + AddFmt("%s joined %s\n", characters[it->context.author_npc_kind].name, characters[it->action_argument.targeting].name); + } + else if(it->action_taken == ACT_leave) + { + // Needs better handling of when you leave, because the person you were following died. Maybe entities don't die anymore? + AddFmt("%s left their party\n", characters[it->context.author_npc_kind].name); + } } - } - PushWithLint(scratch.arena, &latest_state, "%s\n", standing_string); - - if(e->held_items.cur_index > 0) - { - PushWithLint(scratch.arena, &latest_state, "You have these items in their inventory: [\n"); - BUFF_ITER(ItemKind, &e->held_items) + if(it->speech.text_length > 0) { - PushWithLint(scratch.arena, &latest_state, "'%s' - %s,\n", items[*it].name, items[*it].description); - } - PushWithLint(scratch.arena, &latest_state, "]\n"); - } - else - { - PushWithLint(scratch.arena, &latest_state, "Your inventory is EMPTY right now. That means if you gave something to the player expecting them to give you something, they haven't held up their end of the bargain!\n"); - } - - AvailableActions available = { 0 }; - fill_available_actions(e, &available); - PushWithLint(scratch.arena, &latest_state, "The actions you can perform: [\n"); - BUFF_ITER_I(ActionKind, &available, i) - { - if(actions[*it].description) - { - PushWithLint(scratch.arena, &latest_state, "%s - %s,\n", actions[*it].name, actions[*it].description); - } - else - { - PushWithLint(scratch.arena, &latest_state, "%s,\n", actions[*it].name); - } - } - 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`: ["); - BUFF_ITER(NpcKind, &can_talk_to) - { - PushWithLint(scratch.arena, &latest_state, "\"%s\", ", characters[*it].name); - } - PushWithLint(scratch.arena, &latest_state, "]\n"); - - // last thought explanation and re-prompt - { - Memory *last_memory_that_was_me = 0; - for(Memory *cur = e->memories_first; cur; cur = cur->next) - { - if(cur->context.i_said_this) - { - last_memory_that_was_me = cur; + MD_String8 target_string = MD_S8Lit("the world"); + if(it->context.talking_to_kind != NPC_nobody) + { + if(it->context.talking_to_kind == e->npc_kind) + target_string = MD_S8Lit("you"); + else + target_string = MD_S8CString(characters[it->context.talking_to_kind].name); + } + AddFmt("%s said %.*s to %.*s\n", characters[it->context.author_npc_kind].name, TextChunkVArg(it->speech), MD_S8VArg(target_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 with_trailing_comma = MD_S8ListJoin(scratch.arena, list, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit(""),}); + MD_String8 no_trailing_comma = MD_S8Chop(with_trailing_comma, 1); - MD_String8 latest_state_string = MD_S8ListJoin(scratch.arena, latest_state, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit("")}); - - MD_S8ListPush(scratch.arena, &list, MD_S8Chop(make_json_node(scratch.arena, MSG_SYSTEM, latest_state_string), 1)); // trailing comma not allowed in json - PushWithLint(scratch.arena, &list, "]"); - - MD_String8 to_return = MD_S8ListJoin(arena, list, &(MD_StringJoin){MD_S8Lit(""),MD_S8Lit(""),MD_S8Lit(""),}); + MD_String8 to_return = MD_S8Fmt(arena, "%.*s]", MD_S8VArg(no_trailing_comma)); MD_ReleaseScratch(scratch); @@ -620,8 +453,8 @@ MD_String8 get_field(MD_Node *parent, MD_String8 name) // if returned string has size greater than 0, it's the error message. Allocated -// on arena passed into it -MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentence, Action *out) +// on arena passed into it or in constant memory +MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 action_in_json, Action *out) { MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); @@ -629,7 +462,7 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc *out = (Action) { 0 }; - MD_ParseResult result = MD_ParseWholeString(scratch.arena, MD_S8Lit("chat_message"), sentence); + MD_ParseResult result = MD_ParseWholeString(scratch.arena, MD_S8Lit("chat_message"), action_in_json); if(result.errors.node_count > 0) { MD_Message *cur = result.errors.first; @@ -639,167 +472,102 @@ MD_String8 parse_chatgpt_response(MD_Arena *arena, Entity *e, MD_String8 sentenc MD_Node *message_obj = result.node->first_child; - MD_String8 action_str = {0}; MD_String8 speech_str = {0}; - MD_String8 thoughts_str = {0}; + MD_String8 action_str = {0}; MD_String8 action_arg_str = {0}; - MD_String8 who_i_am_str = {0}; - MD_String8 talking_to_str = {0}; - MD_String8 mood_str = {0}; + MD_String8 target_str = {0}; if(error_message.size == 0) { - action_str = get_field(message_obj, MD_S8Lit("action")); - who_i_am_str = get_field(message_obj, MD_S8Lit("who_i_am")); speech_str = get_field(message_obj, MD_S8Lit("speech")); - thoughts_str = get_field(message_obj, MD_S8Lit("thoughts")); + action_str = get_field(message_obj, MD_S8Lit("action")); action_arg_str = get_field(message_obj, MD_S8Lit("action_arg")); - 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) - { - error_message = MD_S8Lit("You must have a field called `who_i_am` in your response, and it must match the character you're playing as"); + target_str = get_field(message_obj, MD_S8Lit("target")); } if(error_message.size == 0 && action_str.size == 0) { - error_message = MD_S8Lit("You must have a field named `action` in your response."); - } - if(error_message.size == 0 && talking_to_str.size == 0) - { - 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"); + error_message = MD_S8Lit("The field `action` must be of nonzero length, if you don't want to do anything it should be `none`"); } - /* - if(error_message.size == 0 && thoughts_str.size == 0) + if(error_message.size == 0 && action_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: \"\" ... }"); - } - */ - if(error_message.size == 0 && speech_str.size >= MAX_SENTENCE_LENGTH) + error_message = MD_S8Lit("The field `target` must be of nonzero length, if you don't want to target anybody it should be `nobody`"); + } if(error_message.size == 0 && speech_str.size >= MAX_SENTENCE_LENGTH) { error_message = FmtWithLint(arena, "Speech string provided is too big, maximum bytes is %d", MAX_SENTENCE_LENGTH); } - if(error_message.size == 0 && thoughts_str.size >= MAX_SENTENCE_LENGTH) - { - error_message = FmtWithLint(arena, "Thoughts string provided is too big, maximum bytes is %d", MAX_SENTENCE_LENGTH); - } - assert(!e->is_character); // player can't perform AI actions? - MD_String8 my_name = MD_S8CString(characters[e->npc_kind].name); - if(error_message.size == 0 && !MD_S8Match(who_i_am_str, my_name, 0)) - { - error_message = FmtWithLint(arena, "You are acting as `%.*s`, not what you said in who_i_am, `%.*s`", MD_S8VArg(my_name), MD_S8VArg(who_i_am_str)); - } if(error_message.size == 0) { - if(MD_S8Match(talking_to_str, MD_S8Lit("nobody"), 0)) + if(MD_S8Match(target_str, MD_S8Lit("nobody"), 0)) { - out->talking_to_somebody = false; + out->talking_to_kind = NPC_nobody; } else { bool found = false; for(int i = 0; i < ARRLEN(characters); i++) { - if(MD_S8Match(talking_to_str, MD_S8CString(characters[i].name), 0)) + if(MD_S8Match(target_str, MD_S8CString(characters[i].name), 0)) { found = true; - out->talking_to_somebody = true; - out->talking_to_kind = i; + out->talking_to_kind = i; } } if(!found) { - error_message = FmtWithLint(arena, "Unrecognized character provided in talking_to: `%.*s`", MD_S8VArg(talking_to_str)); + error_message = FmtWithLint(arena, "Unrecognized character provided in talking_to: `%.*s`", MD_S8VArg(target_str)); } } } if(error_message.size == 0) { - memcpy(out->speech, speech_str.str, speech_str.size); - out->speech_length = (int)speech_str.size; - memcpy(out->internal_monologue, thoughts_str.str, thoughts_str.size); - out->internal_monologue_length = (int)thoughts_str.size; + memcpy(out->speech.text, speech_str.str, speech_str.size); + out->speech.text_length = (int)speech_str.size; } if(error_message.size == 0) { - AvailableActions available = {0}; - fill_available_actions(e, &available); - MD_String8List action_strings = {0}; bool found_action = false; - int it_index = 0; - ARR_ITER(ActionInfo, actions) + for(int i = 0; i < ARRLEN(actions); i++) { - MD_String8 cur_action_string = MD_S8CString(it->name); - MD_S8ListPush(scratch.arena, &action_strings, cur_action_string); - if(MD_S8Match(cur_action_string, action_str, 0)) + if(MD_S8Match(MD_S8CString(actions[i].name), action_str, 0)) { - out->kind = it_index; + assert(!found_action); found_action = true; + out->kind = i; } - it_index += 1; } - if(!found_action) { - MD_String8 list_of_actions = MD_S8ListJoin(scratch.arena, action_strings, &(MD_StringJoin){.mid = MD_S8Lit(", ")}); - - error_message = FmtWithLint(arena, "Couldn't find valid action in game from string `%.*s`. Available actions: [%.*s]", MD_S8VArg(action_str), MD_S8VArg(list_of_actions)); + error_message = FmtWithLint(arena, "Action `%.*s` is invalid, doesn't exist in the game", MD_S8VArg(action_str)); } - } - if(error_message.size == 0) - { - if(actions[out->kind].takes_argument) + if(error_message.size == 0) { - MD_String8List item_enum_names = {0}; - if(out->kind == ACT_gift_item_to_targeting) + if(actions[out->kind].takes_argument) { - bool found_item = false; - BUFF_ITER(ItemKind, &e->held_items) + if(out->kind == ACT_join) { - MD_String8 cur_item_string = MD_S8CString(items[*it].name); - MD_S8ListPush(scratch.arena, &item_enum_names, cur_item_string); - if(MD_S8Match(cur_item_string, action_arg_str, 0)) + bool found_npc = false; + for(int i = 0; i < ARRLEN(characters); i++) + { + if(MD_S8Match(MD_S8CString(characters[i].name), action_arg_str, 0)) + { + found_npc = true; + out->argument.targeting = i; + } + } + if(!found_npc) { - found_item = true; - out->argument.item_to_give = *it; + error_message = FmtWithLint(arena, "Argument for action `%.*s` you gave is `%.*s`, which doesn't exist in the game so is invalid", MD_S8VArg(action_str), MD_S8VArg(action_arg_str)); } } - if(!found_item) + else { - MD_String8 list_of_items = MD_S8ListJoin(scratch.arena, item_enum_names, &(MD_StringJoin){.mid = MD_S8Lit(", ")}); - error_message = FmtWithLint(arena, "Couldn't find item you said to give in action_arg, `%.*s`, the items you have in your inventory to give are: [%.*s]", MD_S8VArg(action_arg_str), MD_S8VArg(list_of_items)); + assert(false); // don't know how to parse the argument string for this kind of action... } } - else - { - assert(false); // don't know how to parse the argument string for this kind of action... - } - } - } - - 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)); } }