From d2e0f2596e2a78a5e91f7ead9252960fd23c0089 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sun, 30 Apr 2023 21:23:50 -0700 Subject: [PATCH] Giving items back and forth and REFORMAT EVERYTHING. SUCKS --- 10x.10x | 69 + assets.mdesk | 4 + assets/chalice_of_gold.png | Bin 0 -> 1065 bytes assets/new_level.json | 5 + character_info.h | 585 +-- codegen.c | 543 ++- main.c | 7015 ++++++++++++++++++------------------ makeprompt.h | 1406 ++++---- 8 files changed, 4881 insertions(+), 4746 deletions(-) create mode 100644 10x.10x create mode 100644 assets/chalice_of_gold.png diff --git a/10x.10x b/10x.10x new file mode 100644 index 0000000..8536aa3 --- /dev/null +++ b/10x.10x @@ -0,0 +1,69 @@ + + + + *.c,*.cc,*.cpp,*.c++,*.cp,*.cxx,*.h,*.hh,*.hpp,*.h++,*.hp,*.hxx,*.inl,*.cs,*.rs,*.java,*.jav,*.js,*.jsc,*.jsx,*.json,*.cls,*.py,*.rpy,*.php,*.php3,*.phl,*.phtml,*.rhtml,*.tpl,*.phps,*.lua,*.html,*.html5,*.htm,*.xml,*.xaml,*.css,*.ssi,*.haml,*.yaml,*.bat,*.wbf,*.wbt,*.txt,*.cmake,*.make,*.makefile,*.mak,*.mk,*.sh,*.bash,*.csv,*.asp,*.pl,*.mac,*.ws,*.vbs,*.perl,*.src,*.rss,*.inc,*.f,*.go,*.prl,*.plx,*.rb,*.lsp,*.lpx,*.ps1,*.command,*.cbl,*.cob,*.qs,*.wxs,*.ph,*.msc,*.glsl,*.hlsl,*.fx,*.vert,*.tesc,*.tese,*.geom,*.frag,*.comp,*.pssl,*.scons,*.cu, + + true + true + true + false + false + + + + + + + + + + + + false + + Debug + Release + + + x64 + Win32 + + + C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\include + C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\ATLMFC\include + C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include + C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\um + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\shared + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\winrt + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\cppwinrt + C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um + C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\include + C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\ATLMFC\include + C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include + C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\um + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\shared + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\winrt + C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\cppwinrt + C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um + + + + + Debug:x64 + + + + + Debug + + + + x64 + + + + + + diff --git a/assets.mdesk b/assets.mdesk index 67131d6..b245001 100644 --- a/assets.mdesk +++ b/assets.mdesk @@ -26,6 +26,10 @@ { filepath: "boots.png", } +@image chalice: +{ + filepath: "chalice_of_gold.png", +} @image new_knight_idle: { filepath: "copyrighted/knight_idle.png", diff --git a/assets/chalice_of_gold.png b/assets/chalice_of_gold.png new file mode 100644 index 0000000000000000000000000000000000000000..16cd869c683544913c19b24c734b2b659e68dd25 GIT binary patch literal 1065 zcmV+^1lIeBP)EX>4Tx04R}tkv&MmP!xqvQ%glF4t5Z6$WWauh!$~_Di*;)X)CnqVDi#GXws0R zxHt-~1qXi?s}3&Cx;nTDg5VE`tBaGOiPeE zNGIZAF25=UUlD*0e#Fo(F;h=w7PIiIuY2mHx{LBG@4i24Kq;6E@QK88OgAjz4dR(i zOXs{#9A;%nAwDObFzABBk6f2se&bwnSm2pqBb%Nl4ik%|E|$BPl?|0RMjTO8jq-(@ z%L?Z$&T6&J+V|uy3>CDM4A*ImB7r3&k%9;rbyQG=g(&SBDJIf%9{2E%IQ|s5WO7x& z$gzMLR7j2={11N5)+|m>xk;fo5O}fek1-&)3$z-x{e5iPtrNii3|wg)f2|43ev;nk zXptjea2vR|?r8EJaJd5vKk1SoIg+22P%HxPXY@^ZVCWX;TXW~uKF8?;kfm8I-v9@P zz<7zW*FE0d+da2`d)o8+0Y#5;mCeK#YybcN24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j>L=12r@@3gOiN000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}00062NklZrE5Jg!;j*z5D4v~sTxj+tpass*( zoW%v5Pk|I+iW9h^vVDXUNd+Q!7j}o4-DR0CS+Zn-nKyrD*E5bH7K_DVu~;k? zi?u}w$VES2&%eF|aP#^ttOD>UcnlbENOp!KM}(4ns1t&603=Zf#uuK>7wbXZ-`%3z zC#JF%wNB?t|FQ;PDjQfCvF(Hyb8WeTu%r$M>>Jvw4LZQ*mThA50WpDKxh-+IULio{ z0)Kn|=-*dmM!N*+rj1^n9=%!j!{9r89v=khE>JgZR5$Hv4^0UM?IKTm2(BCfO#)~M z$gEc6sq$J%C|%&Y{o(4fs?6N(0;=p_02$s>6}*#OVE8a9SNK#jKme*TyQ%@QOzifD ze-L!wKtMYwf}O>5vpEIF6wF-ky7sqz5TXPl3tH#bD{4bz{E$m*E(1d$Z2&5o*uxoTlO7om z7n7$ThocOD0WbgtzyQ_=z{Np|9k6u(N$3_wJqoYoe}X2*YY}qrIuN)BZXJM=8Zfpb jstring)); - int d = 0; - for(MD_EachNode(child, from->first_child)) - { - printf("|-- Child %d Tag [%.*s] string[%.*s] first child string[%.*s]\n", d, MD_S8VArg(child->first_tag->string), MD_S8VArg(child->string), MD_S8VArg(child->first_child->string)); - d += 1; - } + printf("/ %.*s\n", MD_S8VArg(from->string)); + int d = 0; + for (MD_EachNode(child, from->first_child)) + { + printf("|-- Child %d Tag [%.*s] string[%.*s] first child string[%.*s]\n", d, MD_S8VArg(child->first_tag->string), MD_S8VArg(child->string), MD_S8VArg(child->first_child->string)); + d += 1; + } } bool has_decimal(MD_String8 s) { - for(int i = 0; i < s.size; i++) - { - if(s.str[i] == '.') return true; - } - return false; + for (int i = 0; i < s.size; i++) + { + if (s.str[i] == '.') return true; + } + return false; } MD_Arena *cg_arena = NULL; @@ -40,33 +40,33 @@ MD_Arena *cg_arena = NULL; #define S8V(s) MD_S8VArg(s) MD_String8 ChildValue(MD_Node *n, MD_String8 name) { - MD_Node *child_with_value = MD_ChildFromString(n, name, 0); - assert(child_with_value); - assert(!MD_NodeIsNil(child_with_value->first_child)); // MD_S8Lit("Must have child")); - return child_with_value->first_child->string; + MD_Node *child_with_value = MD_ChildFromString(n, name, 0); + assert(child_with_value); + assert(!MD_NodeIsNil(child_with_value->first_child)); // MD_S8Lit("Must have child")); + return child_with_value->first_child->string; } MD_String8 asset_file_path(MD_String8 filename) { - return MD_S8Fmt(cg_arena, "%.*s/%.*s", MD_S8VArg(S8("assets")), MD_S8VArg(filename)); + return MD_S8Fmt(cg_arena, "%.*s/%.*s", MD_S8VArg(S8("assets")), MD_S8VArg(filename)); } char *nullterm(MD_String8 s) { - char *to_return = malloc(s.size + 1); - memcpy(to_return, s.str, s.size); - to_return[s.size] = '\0'; - return to_return; + char *to_return = malloc(s.size + 1); + memcpy(to_return, s.str, s.size); + to_return[s.size] = '\0'; + return to_return; } char* fillnull(char *s, char c) { - while(*s != '\0') { - if(*s == c) { - *s = '\0'; - return s + 1; - } - s++; - } - assert(false); // MD_S8Lit("Couldn't find char")); - return NULL; + while (*s != '\0') { + if (*s == c) { + *s = '\0'; + return s + 1; + } + s++; + } + assert(false); // MD_S8Lit("Couldn't find char")); + return NULL; } #define StrSame(s1, s2) MD_S8Match((s1), (s2), 0) @@ -75,268 +75,261 @@ char* fillnull(char *s, char c) { typedef BUFF(MD_Node*, 256) Nodes; MD_Node* find_by_name(Nodes *n, MD_String8 name) { - MD_Node *node_with = 0; - BUFF_ITER(MD_Node *, n) - { - if(StrSame((*it)->string, name)) - { - assert(node_with == 0); - node_with = (*it); - } - } - assert(node_with); - return node_with; + MD_Node *node_with = 0; + BUFF_ITER(MD_Node *, n) + { + if (StrSame((*it)->string, name)) + { + assert(node_with == 0); + node_with = (*it); + } + } + assert(node_with); + return node_with; } char* goto_end_of(char *tomove, size_t max_move, char *pattern) { - size_t pattern_len = strlen(pattern); - for(int i = 0; i < max_move; i++) { - if(strncmp(tomove, pattern, pattern_len) == 0) { - tomove += pattern_len; - return tomove; - } - tomove++; - } - return NULL; + size_t pattern_len = strlen(pattern); + for (int i = 0; i < max_move; i++) { + if (strncmp(tomove, pattern, pattern_len) == 0) { + tomove += pattern_len; + return tomove; + } + tomove++; + } + return NULL; } #define list_printf(list_ptr, ...) MD_S8ListPush(cg_arena, list_ptr, MD_S8Fmt(cg_arena, __VA_ARGS__)) void dump_full(MD_Node* from) { - for(MD_EachNode(node, from)) - { - printf("@%.*s %.*s\n", MD_S8VArg(node->first_tag->string), MD_S8VArg(node->string)); - } -/* MD_String8List output_list = {0}; - MD_DebugDumpFromNode(cg_arena, &output_list, from, 4, S8(" "), 0); - MD_StringJoin join = (MD_StringJoin){0}; - MD_String8 debugged = MD_S8ListJoin(cg_arena, output_list , &join); - printf("%.*s\n", MD_S8VArg(debugged));*/ + for (MD_EachNode(node, from)) + { + printf("@%.*s %.*s\n", MD_S8VArg(node->first_tag->string), MD_S8VArg(node->string)); + } + /* MD_String8List output_list = {0}; + MD_DebugDumpFromNode(cg_arena, &output_list, from, 4, S8(" "), 0); + MD_StringJoin join = (MD_StringJoin){0}; + MD_String8 debugged = MD_S8ListJoin(cg_arena, output_list , &join); + printf("%.*s\n", MD_S8VArg(debugged));*/ } #include "character_info.h" int main(int argc, char **argv) { - cg_arena = MD_ArenaAlloc(); - assert(cg_arena); + cg_arena = MD_ArenaAlloc(); + assert(cg_arena); - // do characters + // do characters - FILE *char_header = fopen("gen/characters.gen.h", "w"); - fprintf(char_header, top_of_header); + FILE *char_header = fopen("gen/characters.gen.h", "w"); + fprintf(char_header, top_of_header); #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, "Action", 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"); - - - fprintf(char_header, "typedef enum\n{\n"); - ARR_ITER(CharacterGen, characters) - { - fprintf(char_header, " NPC_%s,\n", it->enum_name); - } - // characters enum can be extended at site of include, not ending the enum here - - fclose(char_header); - - // do assets - - MD_String8 writeto = MD_S8Fmt(cg_arena, "gen/assets.gen.c"); - Log("Writing to %.*s\n", MD_S8VArg(writeto)); - FILE *output = fopen(nullterm(writeto), "w"); - - MD_ParseResult parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("assets.mdesk")); - - MD_String8List declarations_list = {0}; - MD_String8List load_list = {0}; - MD_String8List level_decl_list = {0}; - MD_String8List tileset_decls = {0}; - for(MD_EachNode(node, parse.node->first_child)) { - if(MD_S8Match(node->first_tag->string, MD_S8Lit("sound"), 0)) { - MD_String8 variable_name = MD_S8Fmt(cg_arena, "sound_%.*s", MD_S8VArg(node->string)); - Log("New sound variable %.*s\n", MD_S8VArg(variable_name)); - MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath")); - filepath = asset_file_path(filepath); - assert(filepath.str != 0); // MD_S8Fmt(cg_arena, "No filepath specified for sound '%.*s'", MD_S8VArg(node->string))); - FILE *asset_file = fopen(filepath.str, "r"); - assert(asset_file); // MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string))); - fclose(asset_file); - - MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "AudioSample %.*s = {0};\n", MD_S8VArg(variable_name))); - MD_S8ListPush(cg_arena, &load_list, MD_S8Fmt(cg_arena, "%.*s = load_wav_audio(\"%.*s\");\n", MD_S8VArg(variable_name), MD_S8VArg(filepath))); - } - if(MD_S8Match(node->first_tag->string, MD_S8Lit("image"), 0)) { - MD_String8 variable_name = MD_S8Fmt(cg_arena, "image_%.*s", MD_S8VArg(node->string)); - Log("New image variable %.*s\n", MD_S8VArg(variable_name)); - MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath")); - filepath = asset_file_path(filepath); - assert(filepath.str != 0); // , MD_S8Fmt(cg_arena, "No filepath specified for image '%.*s'", MD_S8VArg(node->string))); - FILE *asset_file = fopen(filepath.str, "r"); - assert(asset_file); // , MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string))); - fclose(asset_file); - - MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "sg_image %.*s = {0};\n", MD_S8VArg(variable_name))); - MD_S8ListPush(cg_arena, &load_list, MD_S8Fmt(cg_arena, "%.*s = load_image(\"%.*s\");\n", MD_S8VArg(variable_name), MD_S8VArg(filepath))); - } - if(MD_S8Match(node->first_tag->string, MD_S8Lit("tileset"), 0)) { - // not a variable anymore - MD_String8 variable_name = MD_S8Fmt(cg_arena, "tileset_%.*s", MD_S8VArg(node->string)); - Log("New tileset %.*s\n", MD_S8VArg(variable_name)); - MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath"))); - - MD_String8 tileset_file_contents = MD_LoadEntireFile(cg_arena, filepath); - list_printf(&tileset_decls, "{\n", MD_S8VArg(variable_name)); - list_printf(&tileset_decls, ".first_gid = %.*s,\n", MD_S8VArg(ChildValue(node, MD_S8Lit("firstgid")))); - list_printf(&tileset_decls, ".img = &%.*s,\n", MD_S8VArg(ChildValue(node, MD_S8Lit("image")))); - - list_printf(&tileset_decls, ".animated = {\n"); - char *end = tileset_file_contents.str + tileset_file_contents.size; - char *cur = tileset_file_contents.str; - int num_animated_tiles = 0; - while(cur < end) { - cur = goto_end_of(cur, end - cur, ""); - if(end_of_anim == NULL) break; - char *new_cur = fillnull(cur, '"'); - int frame_from = atoi(cur); - cur = new_cur; - list_printf(&tileset_decls, "{ .exists = true, .id_from = %d, .frames = { ", frame_from); - - int num_frames = 0; - while(true) { - char *next_frame = goto_end_of(cur, end - cur, " end_of_anim) break; - char *new_cur = fillnull(next_frame, '"'); - int frame = atoi(next_frame); - - list_printf(&tileset_decls, "%d, ", frame); - num_frames++; - - cur = new_cur; - } - list_printf(&tileset_decls, "}, .num_frames = %d },\n", num_frames); - num_animated_tiles++; - } - if(num_animated_tiles == 0) list_printf(&tileset_decls, "0"); - list_printf(&tileset_decls, "}},\n"); - } - if(MD_S8Match(node->first_tag->string, MD_S8Lit("level"), 0)) { - MD_String8 variable_name = MD_S8Fmt(cg_arena, "level_%.*s", MD_S8VArg(node->string)); - Log("New level variable %.*s\n", MD_S8VArg(variable_name)); - MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath"))); - MD_ParseResult level_parse = MD_ParseWholeFile(cg_arena, filepath); - assert(!MD_NodeIsNil(level_parse.node->first_child)); // , MD_S8Lit("Failed to load level file")); - - MD_Node *layers = MD_ChildFromString(level_parse.node->first_child, MD_S8Lit("layers"), 0); - fprintf(output, "Level %.*s = {\n", MD_S8VArg(variable_name)); - MD_String8List tile_layer_decls = {0}; - for(MD_EachNode(lay, layers->first_child)) { - MD_String8 type = MD_ChildFromString(lay, MD_S8Lit("type"), 0)->first_child->string; - if(MD_S8Match(type, MD_S8Lit("objectgroup"), 0)) { - fprintf(output, ".initial_entities = {\n"); - for(MD_EachNode(object, MD_ChildFromString(lay, MD_S8Lit("objects"), 0)->first_child)) { - //dump(object); - // negative numbers for object position aren't supported here - MD_String8 name = MD_ChildFromString(object, MD_S8Lit("name"), 0)->first_child->string; - MD_String8 x_string = MD_ChildFromString(object, MD_S8Lit("x"), 0)->first_child->string; - MD_String8 y_string = MD_ChildFromString(object, MD_S8Lit("y"), 0)->first_child->string; - y_string = MD_S8Fmt(cg_arena, "-%.*s", MD_S8VArg(y_string)); - - MD_String8List props = {0}; - for(MD_EachNode(prop_object, MD_ChildFromString(object, S8("properties"), 0)->first_child)) - { - list_printf(&props, ".%.*s = %.*s, ", S8V(ChildValue(prop_object, S8("name"))), S8V(ChildValue(prop_object, S8("value")))); - } - MD_StringJoin join = (MD_StringJoin){0}; - MD_String8 props_string = MD_S8ListJoin(cg_arena, props, &join); - - if(has_decimal(x_string)) x_string = MD_S8Fmt(cg_arena, "%.*sf", MD_S8VArg(x_string)); - if(has_decimal(y_string)) y_string = MD_S8Fmt(cg_arena, "%.*sf", MD_S8VArg(y_string)); - - MD_String8 class = MD_ChildFromString(object, MD_S8Lit("class"), 0)->first_child->string; - if(MD_S8Match(class, MD_S8Lit("PROP"), 0)) - { - fprintf(output, "{ .exists = true, .is_prop = true, .prop_kind = %.*s, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); - } - else if(MD_S8Match(class, MD_S8Lit("ITEM"), 0)) - { - fprintf(output, "{ .exists = true, .is_item = true, .item_kind = ITEM_%.*s, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); - } - else if(MD_S8Match(name, MD_S8Lit("PLAYER"), 0)) - { - fprintf(output, "{ .exists = true, .is_character = true, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); - } - else - { - fprintf(output, "{ .exists = true, .is_npc = true, .npc_kind = NPC_%.*s, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); - } - } - fprintf(output, "\n}, // entities\n"); - } - if(MD_S8Match(type, MD_S8Lit("tilelayer"), 0)) { - int width = atoi(nullterm(MD_ChildFromString(layers->first_child, MD_S8Lit("width"), 0)->first_child->string)); - int height = atoi(nullterm(MD_ChildFromString(layers->first_child, MD_S8Lit("height"), 0)->first_child->string)); - MD_Node *data = MD_ChildFromString(lay, MD_S8Lit("data"), 0); - - int num_index = 0; - MD_String8List cur_layer_decl = {0}; - list_printf(&cur_layer_decl, "{ \n"); - list_printf(&cur_layer_decl, "{ "); - for(MD_EachNode(tile_id_node, data->first_child)) { - list_printf(&cur_layer_decl, "%.*s, ", MD_S8VArg(tile_id_node->string)); - - if(num_index % width == width - 1) { - if(MD_NodeIsNil(tile_id_node->next)) { - list_printf(&cur_layer_decl, "},\n}, // tiles for this layer\n"); - } else { - list_printf(&cur_layer_decl, "},\n{ "); - } - } - num_index += 1; - } - - MD_StringJoin join = MD_ZERO_STRUCT; - MD_String8 layer_decl_string = MD_S8ListJoin(cg_arena, cur_layer_decl, &join); - MD_S8ListPush(cg_arena, &tile_layer_decls, layer_decl_string); - } - } - - fprintf(output, ".tiles = {\n"); - // layer decls - { - - MD_StringJoin join = MD_ZERO_STRUCT; - MD_String8 layers_string = MD_S8ListJoin(cg_arena, tile_layer_decls, &join); - fprintf(output, "%.*s\n", MD_S8VArg(layers_string)); - //MD_S8ListPush(cg_arena, &tile_layer_delcs, layer_decl_string); - - } - fprintf(output, "} // tiles\n"); - - fprintf(output, "\n}; // %.*s\n", MD_S8VArg(variable_name)); - } - } - - - MD_StringJoin join = {0}; - MD_String8 declarations = MD_S8ListJoin(cg_arena, declarations_list, &join); - 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, "TileSet tilesets[] = { %.*s\n };\n", MD_S8VArg(MD_S8ListJoin(cg_arena, tileset_decls, &join))); - - 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; + GEN_ENUM(ActionInfo, actions, "Action", 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"); + + fclose(char_header); + + // do assets + + MD_String8 writeto = MD_S8Fmt(cg_arena, "gen/assets.gen.c"); + Log("Writing to %.*s\n", MD_S8VArg(writeto)); + FILE *output = fopen(nullterm(writeto), "w"); + + MD_ParseResult parse = MD_ParseWholeFile(cg_arena, MD_S8Lit("assets.mdesk")); + + MD_String8List declarations_list = { 0 }; + MD_String8List load_list = { 0 }; + MD_String8List level_decl_list = { 0 }; + MD_String8List tileset_decls = { 0 }; + for (MD_EachNode(node, parse.node->first_child)) { + if (MD_S8Match(node->first_tag->string, MD_S8Lit("sound"), 0)) { + MD_String8 variable_name = MD_S8Fmt(cg_arena, "sound_%.*s", MD_S8VArg(node->string)); + Log("New sound variable %.*s\n", MD_S8VArg(variable_name)); + MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath")); + filepath = asset_file_path(filepath); + assert(filepath.str != 0); // MD_S8Fmt(cg_arena, "No filepath specified for sound '%.*s'", MD_S8VArg(node->string))); + FILE *asset_file = fopen(filepath.str, "r"); + assert(asset_file); // MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string))); + fclose(asset_file); + + MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "AudioSample %.*s = {0};\n", MD_S8VArg(variable_name))); + MD_S8ListPush(cg_arena, &load_list, MD_S8Fmt(cg_arena, "%.*s = load_wav_audio(\"%.*s\");\n", MD_S8VArg(variable_name), MD_S8VArg(filepath))); + } + if (MD_S8Match(node->first_tag->string, MD_S8Lit("image"), 0)) { + MD_String8 variable_name = MD_S8Fmt(cg_arena, "image_%.*s", MD_S8VArg(node->string)); + Log("New image variable %.*s\n", MD_S8VArg(variable_name)); + MD_String8 filepath = ChildValue(node, MD_S8Lit("filepath")); + filepath = asset_file_path(filepath); + assert(filepath.str != 0); // , MD_S8Fmt(cg_arena, "No filepath specified for image '%.*s'", MD_S8VArg(node->string))); + FILE *asset_file = fopen(filepath.str, "r"); + assert(asset_file); // , MD_S8Fmt(cg_arena, "Could not open filepath %.*s for asset '%.*s'", MD_S8VArg(filepath), MD_S8VArg(node->string))); + fclose(asset_file); + + MD_S8ListPush(cg_arena, &declarations_list, MD_S8Fmt(cg_arena, "sg_image %.*s = {0};\n", MD_S8VArg(variable_name))); + MD_S8ListPush(cg_arena, &load_list, MD_S8Fmt(cg_arena, "%.*s = load_image(\"%.*s\");\n", MD_S8VArg(variable_name), MD_S8VArg(filepath))); + } + if (MD_S8Match(node->first_tag->string, MD_S8Lit("tileset"), 0)) { + // not a variable anymore + MD_String8 variable_name = MD_S8Fmt(cg_arena, "tileset_%.*s", MD_S8VArg(node->string)); + Log("New tileset %.*s\n", MD_S8VArg(variable_name)); + MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath"))); + + MD_String8 tileset_file_contents = MD_LoadEntireFile(cg_arena, filepath); + list_printf(&tileset_decls, "{\n", MD_S8VArg(variable_name)); + list_printf(&tileset_decls, ".first_gid = %.*s,\n", MD_S8VArg(ChildValue(node, MD_S8Lit("firstgid")))); + list_printf(&tileset_decls, ".img = &%.*s,\n", MD_S8VArg(ChildValue(node, MD_S8Lit("image")))); + + list_printf(&tileset_decls, ".animated = {\n"); + char *end = tileset_file_contents.str + tileset_file_contents.size; + char *cur = tileset_file_contents.str; + int num_animated_tiles = 0; + while (cur < end) { + cur = goto_end_of(cur, end - cur, ""); + if (end_of_anim == NULL) break; + char *new_cur = fillnull(cur, '"'); + int frame_from = atoi(cur); + cur = new_cur; + list_printf(&tileset_decls, "{ .exists = true, .id_from = %d, .frames = { ", frame_from); + + int num_frames = 0; + while (true) { + char *next_frame = goto_end_of(cur, end - cur, " end_of_anim) break; + char *new_cur = fillnull(next_frame, '"'); + int frame = atoi(next_frame); + + list_printf(&tileset_decls, "%d, ", frame); + num_frames++; + + cur = new_cur; + } + list_printf(&tileset_decls, "}, .num_frames = %d },\n", num_frames); + num_animated_tiles++; + } + if (num_animated_tiles == 0) list_printf(&tileset_decls, "0"); + list_printf(&tileset_decls, "}},\n"); + } + if (MD_S8Match(node->first_tag->string, MD_S8Lit("level"), 0)) { + MD_String8 variable_name = MD_S8Fmt(cg_arena, "level_%.*s", MD_S8VArg(node->string)); + Log("New level variable %.*s\n", MD_S8VArg(variable_name)); + MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath"))); + MD_ParseResult level_parse = MD_ParseWholeFile(cg_arena, filepath); + assert(!MD_NodeIsNil(level_parse.node->first_child)); // , MD_S8Lit("Failed to load level file")); + + MD_Node *layers = MD_ChildFromString(level_parse.node->first_child, MD_S8Lit("layers"), 0); + fprintf(output, "Level %.*s = {\n", MD_S8VArg(variable_name)); + MD_String8List tile_layer_decls = { 0 }; + for (MD_EachNode(lay, layers->first_child)) { + MD_String8 type = MD_ChildFromString(lay, MD_S8Lit("type"), 0)->first_child->string; + if (MD_S8Match(type, MD_S8Lit("objectgroup"), 0)) { + fprintf(output, ".initial_entities = {\n"); + for (MD_EachNode(object, MD_ChildFromString(lay, MD_S8Lit("objects"), 0)->first_child)) { + //dump(object); + // negative numbers for object position aren't supported here + MD_String8 name = MD_ChildFromString(object, MD_S8Lit("name"), 0)->first_child->string; + MD_String8 x_string = MD_ChildFromString(object, MD_S8Lit("x"), 0)->first_child->string; + MD_String8 y_string = MD_ChildFromString(object, MD_S8Lit("y"), 0)->first_child->string; + y_string = MD_S8Fmt(cg_arena, "-%.*s", MD_S8VArg(y_string)); + + MD_String8List props = { 0 }; + for (MD_EachNode(prop_object, MD_ChildFromString(object, S8("properties"), 0)->first_child)) + { + list_printf(&props, ".%.*s = %.*s, ", S8V(ChildValue(prop_object, S8("name"))), S8V(ChildValue(prop_object, S8("value")))); + } + MD_StringJoin join = (MD_StringJoin) { 0 }; + MD_String8 props_string = MD_S8ListJoin(cg_arena, props, &join); + + if (has_decimal(x_string)) x_string = MD_S8Fmt(cg_arena, "%.*sf", MD_S8VArg(x_string)); + if (has_decimal(y_string)) y_string = MD_S8Fmt(cg_arena, "%.*sf", MD_S8VArg(y_string)); + + MD_String8 class = MD_ChildFromString(object, MD_S8Lit("class"), 0)->first_child->string; + if (MD_S8Match(class , MD_S8Lit("PROP"), 0)) + { + fprintf(output, "{ .exists = true, .is_prop = true, .prop_kind = %.*s, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); + } + else if (MD_S8Match(class , MD_S8Lit("ITEM"), 0)) + { + fprintf(output, "{ .exists = true, .is_item = true, .item_kind = ITEM_%.*s, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); + } + else if (MD_S8Match(name, MD_S8Lit("PLAYER"), 0)) + { + fprintf(output, "{ .exists = true, .is_character = true, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); + } + else + { + fprintf(output, "{ .exists = true, .is_npc = true, .npc_kind = NPC_%.*s, .pos = { .X=%.*s, .Y=%.*s }, %.*s }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string), MD_S8VArg(props_string)); + } + } + fprintf(output, "\n}, // entities\n"); + } + if (MD_S8Match(type, MD_S8Lit("tilelayer"), 0)) { + int width = atoi(nullterm(MD_ChildFromString(layers->first_child, MD_S8Lit("width"), 0)->first_child->string)); + int height = atoi(nullterm(MD_ChildFromString(layers->first_child, MD_S8Lit("height"), 0)->first_child->string)); + MD_Node *data = MD_ChildFromString(lay, MD_S8Lit("data"), 0); + + int num_index = 0; + MD_String8List cur_layer_decl = { 0 }; + list_printf(&cur_layer_decl, "{ \n"); + list_printf(&cur_layer_decl, "{ "); + for (MD_EachNode(tile_id_node, data->first_child)) { + list_printf(&cur_layer_decl, "%.*s, ", MD_S8VArg(tile_id_node->string)); + + if (num_index % width == width - 1) { + if (MD_NodeIsNil(tile_id_node->next)) { + list_printf(&cur_layer_decl, "},\n}, // tiles for this layer\n"); + } else { + list_printf(&cur_layer_decl, "},\n{ "); + } + } + num_index += 1; + } + + MD_StringJoin join = MD_ZERO_STRUCT; + MD_String8 layer_decl_string = MD_S8ListJoin(cg_arena, cur_layer_decl, &join); + MD_S8ListPush(cg_arena, &tile_layer_decls, layer_decl_string); + } + } + + fprintf(output, ".tiles = {\n"); + // layer decls + { + + MD_StringJoin join = MD_ZERO_STRUCT; + MD_String8 layers_string = MD_S8ListJoin(cg_arena, tile_layer_decls, &join); + fprintf(output, "%.*s\n", MD_S8VArg(layers_string)); + //MD_S8ListPush(cg_arena, &tile_layer_delcs, layer_decl_string); + + } + fprintf(output, "} // tiles\n"); + + fprintf(output, "\n}; // %.*s\n", MD_S8VArg(variable_name)); + } + } + + + MD_StringJoin join = { 0 }; + MD_String8 declarations = MD_S8ListJoin(cg_arena, declarations_list, &join); + 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, "TileSet tilesets[] = { %.*s\n };\n", MD_S8VArg(MD_S8ListJoin(cg_arena, tileset_decls, &join))); + + 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 fff1f9e..62be544 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ #define CURRENT_VERSION 10 // wehenver you change Entity increment this boz -// you will die someday + // you will die someday #define SOKOL_IMPL #if defined(WIN32) || defined(_WIN32) @@ -41,22 +41,22 @@ #endif #include "profiling.h" -#define ENTITIES_ITER(ents) for(Entity *it = ents; it < ents + ARRLEN(ents); it++) if(it->exists) +#define ENTITIES_ITER(ents) for (Entity *it = ents; it < ents + ARRLEN(ents); it++) if (it->exists) double clamp(double d, double min, double max) { - const double t = d < min ? min : d; - return t > max ? max : t; + const double t = d < min ? min : d; + return t > max ? max : t; } float clampf(float d, float min, float max) { - const float t = d < min ? min : d; - return t > max ? max : t; + const float t = d < min ? min : d; + return t > max ? max : t; } float clamp01(float f) { - return clampf(f, 0.0f, 1.0f); + return clampf(f, 0.0f, 1.0f); } #ifdef min @@ -65,69 +65,69 @@ float clamp01(float f) int min(int a, int b) { - if(a < b) return a; - else return b; + if (a < b) return a; + else return b; } // so can be grep'd and removed #define dbgprint(...) { printf("Debug | %s:%d | ", __FILE__, __LINE__); printf(__VA_ARGS__); } Vec2 RotateV2(Vec2 v, float theta) { - return V2( - v.X * cosf(theta) - v.Y * sinf(theta), - v.X * sinf(theta) + v.Y * cosf(theta) - ); + return V2( + v.X * cosf(theta) - v.Y * sinf(theta), + v.X * sinf(theta) + v.Y * cosf(theta) + ); } Vec2 ReflectV2(Vec2 v, Vec2 normal) { - assert(fabsf(LenV2(normal) - 1.0f) < 0.01f); // must be normalized - Vec2 to_return = SubV2(v, MulV2F(normal, 2.0f * DotV2(v, normal))); + assert(fabsf(LenV2(normal) - 1.0f) < 0.01f); // must be normalized + Vec2 to_return = SubV2(v, MulV2F(normal, 2.0f * DotV2(v, normal))); - assert(!isnan(to_return.x)); - assert(!isnan(to_return.y)); - return to_return; + assert(!isnan(to_return.x)); + assert(!isnan(to_return.y)); + return to_return; } typedef struct AABB { - Vec2 upper_left; - Vec2 lower_right; + Vec2 upper_left; + Vec2 lower_right; } AABB; typedef struct Quad { - union - { - struct - { - Vec2 ul; // upper left - Vec2 ur; // upper right - Vec2 lr; // lower right - Vec2 ll; // lower left - }; - Vec2 points[4]; - }; + union + { + struct + { + Vec2 ul; // upper left + Vec2 ur; // upper right + Vec2 lr; // lower right + Vec2 ll; // lower left + }; + Vec2 points[4]; + }; } Quad; typedef struct TileInstance { - uint16_t kind; + uint16_t kind; } TileInstance; typedef struct AnimatedTile { - uint16_t id_from; - bool exists; - int num_frames; - uint16_t frames[32]; + uint16_t id_from; + bool exists; + int num_frames; + uint16_t frames[32]; } AnimatedTile; typedef struct TileSet { - sg_image *img; - uint16_t first_gid; - AnimatedTile animated[128]; + sg_image *img; + uint16_t first_gid; + AnimatedTile animated[128]; } TileSet; #ifdef DEVTOOLS @@ -140,9 +140,9 @@ typedef struct TileSet typedef struct Overlap { - bool is_tile; // in which case e will be null, naturally - TileInstance t; - Entity *e; + bool is_tile; // in which case e will be null, naturally + TileInstance t; + Entity *e; } Overlap; typedef BUFF(Overlap, 16) Overlapping; @@ -156,84 +156,84 @@ typedef BUFF(Overlap, 16) Overlapping; #define PLAYER_ROLL_SPEED 7.0f typedef struct Level { - TileInstance tiles[LAYERS][LEVEL_TILES][LEVEL_TILES]; - Entity initial_entities[MAX_ENTITIES]; // shouldn't be directly modified, only used to initialize gs.entities on loading of level + TileInstance tiles[LAYERS][LEVEL_TILES][LEVEL_TILES]; + Entity initial_entities[MAX_ENTITIES]; // shouldn't be directly modified, only used to initialize gs.entities on loading of level } Level; typedef struct TileCoord { - int x; // column - int y; // row + int x; // column + int y; // row } TileCoord; // no alignment etc because lazy typedef struct Arena { - char *data; - size_t data_size; - size_t cur; + char *data; + size_t data_size; + size_t cur; } Arena; Entity *player = 0; // up here, used in text backend callback typedef struct AudioSample { - float *pcm_data; // allocated by loader, must be freed - uint64_t pcm_data_length; + float *pcm_data; // allocated by loader, must be freed + uint64_t pcm_data_length; } AudioSample; typedef struct AudioPlayer { - AudioSample *sample; // if not 0, exists - double volume; // ZII, 1.0 + this again - double pitch; // zero initialized, the pitch used to play is 1.0 + this - double cursor_time; // in seconds, current audio sample is cursor_time * sample_rate + AudioSample *sample; // if not 0, exists + double volume; // ZII, 1.0 + this again + double pitch; // zero initialized, the pitch used to play is 1.0 + this + double cursor_time; // in seconds, current audio sample is cursor_time * sample_rate } AudioPlayer; -AudioPlayer playing_audio[128] = {0}; +AudioPlayer playing_audio[128] = { 0 }; #define SAMPLE_RATE 44100 AudioSample load_wav_audio(const char *path) { - unsigned int channels; - unsigned int sampleRate; - AudioSample to_return = {0}; - to_return.pcm_data = drwav_open_file_and_read_pcm_frames_f32(path, &channels, &sampleRate, &to_return.pcm_data_length, 0); - assert(channels == 1); - assert(sampleRate == SAMPLE_RATE); - return to_return; + unsigned int channels; + unsigned int sampleRate; + AudioSample to_return = { 0 }; + to_return.pcm_data = drwav_open_file_and_read_pcm_frames_f32(path, &channels, &sampleRate, &to_return.pcm_data_length, 0); + assert(channels == 1); + assert(sampleRate == SAMPLE_RATE); + return to_return; } uint64_t cursor_pcm(AudioPlayer *p) { - return (uint64_t)(p->cursor_time * SAMPLE_RATE); + return (uint64_t)(p->cursor_time * SAMPLE_RATE); } -float float_rand( float min, float max ) +float float_rand(float min, float max) { - float scale = rand() / (float) RAND_MAX; /* [0, 1.0] */ - return min + scale * ( max - min ); /* [min, max] */ + float scale = rand() / (float) RAND_MAX; /* [0, 1.0] */ + return min + scale * (max - min); /* [min, max] */ } void play_audio(AudioSample *sample, float volume) { - AudioPlayer *to_use = 0; - for(int i = 0; i < ARRLEN(playing_audio); i++) - { - if(playing_audio[i].sample == 0) - { - to_use = &playing_audio[i]; - break; - } - } - assert(to_use); - *to_use = (AudioPlayer){0}; - to_use->sample = sample; - to_use->volume = volume; - to_use->pitch = float_rand(0.9f, 1.1f); + AudioPlayer *to_use = 0; + for (int i = 0; i < ARRLEN(playing_audio); i++) + { + if (playing_audio[i].sample == 0) + { + to_use = &playing_audio[i]; + break; + } + } + assert(to_use); + *to_use = (AudioPlayer) { 0 }; + to_use->sample = sample; + to_use->volume = volume; + to_use->pitch = float_rand(0.9f, 1.1f); } // keydown needs to be referenced when begin text input, // on web it disables event handling so the button up event isn't received -bool keydown[SAPP_KEYCODE_MENU] = {0}; +bool keydown[SAPP_KEYCODE_MENU] = { 0 }; bool choosing_item_grid = false; @@ -244,24 +244,24 @@ bool receiving_text_input = false; // called from the web to see if should do the text input modal bool is_receiving_text_input() { - return receiving_text_input; + return receiving_text_input; } #ifdef DESKTOP -Sentence text_input_buffer = {0}; +Sentence text_input_buffer = { 0 }; #else #ifdef WEB - EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void stop_controlling_input() { - _sapp_emsc_unregister_eventhandlers(); // stop getting input, hand it off to text input + _sapp_emsc_unregister_eventhandlers(); // stop getting input, hand it off to text input } - EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void start_controlling_input() { - memset(keydown, 0, ARRLEN(keydown)); - _sapp_emsc_register_eventhandlers(); + memset(keydown, 0, ARRLEN(keydown)); + _sapp_emsc_register_eventhandlers(); } #else #error "No platform defined for text input! @@ -271,572 +271,571 @@ void start_controlling_input() void begin_text_input() { - receiving_text_input = true; + receiving_text_input = true; #ifdef DESKTOP - BUFF_CLEAR(&text_input_buffer); + BUFF_CLEAR(&text_input_buffer); #endif } Vec2 FloorV2(Vec2 v) { - return V2(floorf(v.x), floorf(v.y)); + return V2(floorf(v.x), floorf(v.y)); } Arena make_arena(size_t max_size) { - return (Arena) - { - .data = calloc(max_size, 1), - .data_size = max_size, - .cur = 0, - }; + return (Arena) + { + .data = calloc(max_size, 1), + .data_size = max_size, + .cur = 0, + }; } void reset(Arena *a) { - memset(a->data, 0, a->data_size); - a->cur = 0; + memset(a->data, 0, a->data_size); + a->cur = 0; } char *get(Arena *a, size_t of_size) { - assert(a->data != NULL); - char *to_return = a->data + a->cur; - a->cur += of_size; - assert(a->cur < a->data_size); - return to_return; + assert(a->data != NULL); + char *to_return = a->data + a->cur; + a->cur += of_size; + assert(a->cur < a->data_size); + return to_return; } -Arena scratch = {0}; +Arena scratch = { 0 }; char *tprint(const char *format, ...) { - va_list argptr; - va_start(argptr, format); + va_list argptr; + va_start(argptr, format); - int size = vsnprintf(NULL, 0, format, argptr) + 1; // for null terminator + int size = vsnprintf(NULL, 0, format, argptr) + 1; // for null terminator - char *to_return = get(&scratch, size); + char *to_return = get(&scratch, size); - vsnprintf(to_return, size, format, argptr); + vsnprintf(to_return, size, format, argptr); - va_end(argptr); + va_end(argptr); - return to_return; + return to_return; } bool V2ApproxEq(Vec2 a, Vec2 b) { - return LenV2(SubV2(a, b)) <= 0.01f; + 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)), - }; - } + 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; + return v.x > v.y ? v.x : v.y; } // aabb advice by iRadEntertainment Vec2 entity_aabb_size(Entity *e) { - if(e->is_character) - { - return V2(TILE_SIZE*0.9f, TILE_SIZE*0.5f); - } - else if(e->is_npc) - { - if(npc_is_knight_sprite(e)) - { - return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); - } - else if(e->npc_kind == NPC_GodRock) - { - return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); - } - else if(e->npc_kind == NPC_OldMan) - { - return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); - } - else if(e->npc_kind == NPC_Death) - { - return V2(TILE_SIZE*1.10f, TILE_SIZE*1.10f); - } - else if(npc_is_skeleton(e)) - { - return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f); - } - else if(e->npc_kind == NPC_MOOSE) - { - return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f); - } - else if(e->npc_kind == NPC_TheGuard) - { - return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); - } - else - { - assert(false); - return (Vec2){0}; - } - } - else if(e->is_bullet) - { - return V2(TILE_SIZE*0.25f, TILE_SIZE*0.25f); - } - else if(e->is_prop) - { - return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); - } - else if(e->is_item) - { - return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); - } - else - { - assert(false); - return (Vec2){0}; - } + if (e->is_character) + { + return V2(TILE_SIZE*0.9f, TILE_SIZE*0.5f); + } + else if (e->is_npc) + { + if (npc_is_knight_sprite(e)) + { + return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); + } + else if (e->npc_kind == NPC_GodRock) + { + return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); + } + else if (e->npc_kind == NPC_OldMan) + { + return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); + } + else if (e->npc_kind == NPC_Death) + { + return V2(TILE_SIZE*1.10f, TILE_SIZE*1.10f); + } + else if (npc_is_skeleton(e)) + { + return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f); + } + else if (e->npc_kind == NPC_TheGuard) + { + return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); + } + else + { + assert(false); + return (Vec2) { 0 }; + } + } + else if (e->is_bullet) + { + return V2(TILE_SIZE*0.25f, TILE_SIZE*0.25f); + } + else if (e->is_prop) + { + return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); + } + else if (e->is_item) + { + return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f); + } + else + { + assert(false); + return (Vec2) { 0 }; + } } bool is_tile_solid(TileInstance t) { - uint16_t tile_id = t.kind; - uint16_t collideable[] = { - 57 , 58 , 59 , - 121, 122, 123, - 185, 186, 187, - 249, 250, 251, - 313, 314, 315, - 377, 378, 379, - }; - for(int i = 0; i < ARRLEN(collideable); i++) - { - if(tile_id == collideable[i]+1) return true; - } - return false; - //return tile_id == 53 || tile_id == 0 || tile_id == 367 || tile_id == 317 || tile_id == 313 || tile_id == 366 || tile_id == 368; + uint16_t tile_id = t.kind; + uint16_t collideable[] = { + 57 , 58 , 59 , + 121, 122, 123, + 185, 186, 187, + 249, 250, 251, + 313, 314, 315, + 377, 378, 379, + }; + for (int i = 0; i < ARRLEN(collideable); i++) + { + if (tile_id == collideable[i] + 1) return true; + } + return false; + //return tile_id == 53 || tile_id == 0 || tile_id == 367 || tile_id == 317 || tile_id == 313 || tile_id == 366 || tile_id == 368; } bool is_overlap_collision(Overlap o) { - if(o.is_tile) - { - return is_tile_solid(o.t); - } - else - { - assert(o.e); - return !o.e->is_item; - } + if (o.is_tile) + { + return is_tile_solid(o.t); + } + else + { + assert(o.e); + return !o.e->is_item; + } } // tilecoord is integer tile position, not like tile coord Vec2 tilecoord_to_world(TileCoord t) { - return V2( (float)t.x * (float)TILE_SIZE * 1.0f, -(float)t.y * (float)TILE_SIZE * 1.0f ); + return V2((float)t.x * (float)TILE_SIZE * 1.0f, -(float)t.y * (float)TILE_SIZE * 1.0f); } // points from tiled editor have their own strange and alien coordinate system (local to the tilemap Y+ down) Vec2 tilepoint_to_world(Vec2 tilepoint) { - Vec2 tilecoord = MulV2F(tilepoint, 1.0/TILE_SIZE); - return tilecoord_to_world((TileCoord){(int)tilecoord.X, (int)tilecoord.Y}); + Vec2 tilecoord = MulV2F(tilepoint, 1.0 / TILE_SIZE); + return tilecoord_to_world((TileCoord) { (int)tilecoord.X, (int)tilecoord.Y }); } TileCoord world_to_tilecoord(Vec2 w) { - // world = V2(tilecoord.x * tile_size, -tilecoord.y * tile_size) - // world.x = tilecoord.x * tile_size - // world.x / tile_size = tilecoord.x - // world.y = -tilecoord.y * tile_size - // - world.y / tile_size = tilecoord.y - return (TileCoord){ (int)floorf(w.X / TILE_SIZE), (int)floorf(-w.Y / TILE_SIZE) }; + // world = V2(tilecoord.x * tile_size, -tilecoord.y * tile_size) + // world.x = tilecoord.x * tile_size + // world.x / tile_size = tilecoord.x + // world.y = -tilecoord.y * tile_size + // - world.y / tile_size = tilecoord.y + return (TileCoord) { (int)floorf(w.X / TILE_SIZE), (int)floorf(-w.Y / TILE_SIZE) }; } AABB tile_aabb(TileCoord t) { - return (AABB) - { - .upper_left = tilecoord_to_world(t), - .lower_right = AddV2(tilecoord_to_world(t), V2(TILE_SIZE, -TILE_SIZE)), - }; + return (AABB) + { + .upper_left = tilecoord_to_world(t), + .lower_right = AddV2(tilecoord_to_world(t), V2(TILE_SIZE, -TILE_SIZE)), + }; } Vec2 rotate_counter_clockwise(Vec2 v) { - return V2(-v.Y, v.X); + return V2(-v.Y, v.X); } Vec2 rotate_clockwise(Vec2 v) { - return V2(v.y, -v.x); + return V2(v.y, -v.x); } Vec2 aabb_center(AABB aabb) { - return MulV2F(AddV2(aabb.upper_left, aabb.lower_right), 0.5f); + return MulV2F(AddV2(aabb.upper_left, aabb.lower_right), 0.5f); } AABB centered_aabb(Vec2 at, Vec2 size) { - return (AABB){ - .upper_left = AddV2(at, V2(-size.X/2.0f, size.Y/2.0f)), - .lower_right = AddV2(at, V2( size.X/2.0f, -size.Y/2.0f)), - }; + return (AABB) { + .upper_left = AddV2(at, V2(-size.X / 2.0f, size.Y / 2.0f)), + .lower_right = AddV2(at, V2(size.X / 2.0f, -size.Y / 2.0f)), + }; } AABB entity_aabb_at(Entity *e, Vec2 at) { - return centered_aabb(at, entity_aabb_size(e)); + return centered_aabb(at, entity_aabb_size(e)); } AABB entity_aabb(Entity *e) { - Vec2 at = e->pos; - /* following doesn't work because in move_and_slide I'm not using this function - if(e->is_character) // aabb near feet - { - at = AddV2(at, V2(0.0f, -50.0f)); - } - */ - return entity_aabb_at(e, at); + Vec2 at = e->pos; + /* following doesn't work because in move_and_slide I'm not using this function + if(e->is_character) // aabb near feet + { + at = AddV2(at, V2(0.0f, -50.0f)); + } + */ + return entity_aabb_at(e, at); } TileInstance get_tile_layer(Level *l, int layer, TileCoord t) { - bool out_of_bounds = false; - out_of_bounds |= t.x < 0; - out_of_bounds |= t.x >= LEVEL_TILES; - out_of_bounds |= t.y < 0; - out_of_bounds |= t.y >= LEVEL_TILES; - //assert(!out_of_bounds); - if(out_of_bounds) return (TileInstance){0}; - return l->tiles[layer][t.y][t.x]; + bool out_of_bounds = false; + out_of_bounds |= t.x < 0; + out_of_bounds |= t.x >= LEVEL_TILES; + out_of_bounds |= t.y < 0; + out_of_bounds |= t.y >= LEVEL_TILES; + //assert(!out_of_bounds); + if (out_of_bounds) return (TileInstance) { 0 }; + return l->tiles[layer][t.y][t.x]; } TileInstance get_tile(Level *l, TileCoord t) { - return get_tile_layer(l, 0, t); + return get_tile_layer(l, 0, t); } sg_image load_image(const char *path) { - sg_image to_return = {0}; - - int png_width, png_height, num_channels; - const int desired_channels = 4; - stbi_uc* pixels = stbi_load( - path, - &png_width, &png_height, - &num_channels, 0); - assert(pixels); - Log("Pah %s | Loading image with dimensions %d %d\n", path, png_width, png_height); - to_return = sg_make_image(&(sg_image_desc) - { - .width = png_width, - .height = png_height, - .pixel_format = SG_PIXELFORMAT_RGBA8, - .min_filter = SG_FILTER_NEAREST, - .num_mipmaps = 0, - .wrap_u = SG_WRAP_CLAMP_TO_EDGE, - .wrap_v = SG_WRAP_CLAMP_TO_EDGE, - .mag_filter = SG_FILTER_NEAREST, - .data.subimage[0][0] = - { - .ptr = pixels, - .size = (size_t)(png_width * png_height * 4), - } - }); - stbi_image_free(pixels); - return to_return; + sg_image to_return = { 0 }; + + int png_width, png_height, num_channels; + const int desired_channels = 4; + stbi_uc* pixels = stbi_load( + path, + &png_width, &png_height, + &num_channels, 0); + assert(pixels); + Log("Pah %s | Loading image with dimensions %d %d\n", path, png_width, png_height); + to_return = sg_make_image(&(sg_image_desc) + { + .width = png_width, + .height = png_height, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .min_filter = SG_FILTER_NEAREST, + .num_mipmaps = 0, + .wrap_u = SG_WRAP_CLAMP_TO_EDGE, + .wrap_v = SG_WRAP_CLAMP_TO_EDGE, + .mag_filter = SG_FILTER_NEAREST, + .data.subimage[0][0] = + { + .ptr = pixels, + .size = (size_t)(png_width * png_height * 4), + } + }); + stbi_image_free(pixels); + return to_return; } #include "assets.gen.c" #include "quad-sapp.glsl.h" -AABB level_aabb = { .upper_left = {0.0f, 0.0f}, .lower_right = {TILE_SIZE * LEVEL_TILES, -(TILE_SIZE * LEVEL_TILES)} }; +AABB level_aabb = { .upper_left = { 0.0f, 0.0f }, .lower_right = { TILE_SIZE * LEVEL_TILES, -(TILE_SIZE * LEVEL_TILES) } }; typedef struct GameState { - int version; - Entity entities[MAX_ENTITIES]; + int version; + Entity entities[MAX_ENTITIES]; } GameState; -GameState gs = {0}; +GameState gs = { 0 }; -PathCache cached_paths[32] = {0}; +PathCache cached_paths[32] = { 0 }; bool is_path_cache_old(double elapsed_time, PathCache *cache) { - double time_delta = elapsed_time - cache->elapsed_time; - if(time_delta < 0.0) - { - // path was cached in the future... likely from old save or something. Always invalidate - return true; - } - else - { - return time_delta >= TIME_BETWEEN_PATH_GENS; - } + double time_delta = elapsed_time - cache->elapsed_time; + if (time_delta < 0.0) + { + // path was cached in the future... likely from old save or something. Always invalidate + return true; + } + else + { + return time_delta >= TIME_BETWEEN_PATH_GENS; + } } PathCacheHandle cache_path(double elapsed_time, AStarPath *path) { - ARR_ITER_I(PathCache, cached_paths, i) - { - if(!it->exists || is_path_cache_old(elapsed_time, it)) - { - int gen = it->generation; - *it = (PathCache){0}; - it->generation = gen + 1; - - it->path = *path; - it->elapsed_time = elapsed_time; - it->exists = true; - return (PathCacheHandle){.generation = it->generation, .index = i}; - } - } - return (PathCacheHandle){0}; + ARR_ITER_I(PathCache, cached_paths, i) + { + if (!it->exists || is_path_cache_old(elapsed_time, it)) + { + int gen = it->generation; + *it = (PathCache) { 0 }; + it->generation = gen + 1; + + it->path = *path; + it->elapsed_time = elapsed_time; + it->exists = true; + return (PathCacheHandle) { .generation = it->generation, .index = i }; + } + } + return (PathCacheHandle) { 0 }; } // passes in the time to return 0 and invalidate if too old PathCache *get_path_cache(double elapsed_time, PathCacheHandle handle) { - if(handle.generation == 0) - { - return 0; - } - else - { - assert(handle.index >= 0); - assert(handle.index < ARRLEN(cached_paths)); - PathCache *to_return = &cached_paths[handle.index]; - if(to_return->exists && to_return->generation == handle.generation) - { - if(is_path_cache_old(elapsed_time, to_return)) - { - to_return->exists = false; - return 0; - } - else - { - return to_return; - } - } - else - { - return 0; - } - } + if (handle.generation == 0) + { + return 0; + } + else + { + assert(handle.index >= 0); + assert(handle.index < ARRLEN(cached_paths)); + PathCache *to_return = &cached_paths[handle.index]; + if (to_return->exists && to_return->generation == handle.generation) + { + if (is_path_cache_old(elapsed_time, to_return)) + { + to_return->exists = false; + return 0; + } + else + { + return to_return; + } + } + else + { + return 0; + } + } } double unprocessed_gameplay_time = 0.0; -#define MINIMUM_TIMESTEP (1.0/60.0) +#define MINIMUM_TIMESTEP (1.0 / 60.0) EntityRef frome(Entity *e) { - EntityRef to_return = { - .index = (int)(e - gs.entities), - .generation = e->generation, - }; - assert(to_return.index >= 0); - assert(to_return.index < ARRLEN(gs.entities)); - return to_return; + EntityRef to_return = { + .index = (int)(e - gs.entities), + .generation = e->generation, + }; + assert(to_return.index >= 0); + assert(to_return.index < ARRLEN(gs.entities)); + return to_return; } 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; - } + 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; + } } bool eq(EntityRef ref1, EntityRef ref2) { - return ref1.index == ref2.index && ref1.generation == ref2.generation; + return ref1.index == ref2.index && ref1.generation == ref2.generation; } Entity *new_entity() { - for(int i = 0; i < ARRLEN(gs.entities); i++) - { - if(!gs.entities[i].exists) - { - Entity *to_return = &gs.entities[i]; - int gen = to_return->generation; - *to_return = (Entity){0}; - to_return->exists = true; - to_return->generation = gen + 1; - return to_return; - } - } - assert(false); - return NULL; + for (int i = 0; i < ARRLEN(gs.entities); i++) + { + if (!gs.entities[i].exists) + { + Entity *to_return = &gs.entities[i]; + int gen = to_return->generation; + *to_return = (Entity) { 0 }; + to_return->exists = true; + to_return->generation = gen + 1; + return to_return; + } + } + assert(false); + return NULL; } void update_player_from_entities() { - player = 0; - ENTITIES_ITER(gs.entities) - { - if(it->is_character) - { - assert(player == 0); - player = it; - } - } - assert(player != 0); + player = 0; + ENTITIES_ITER(gs.entities) + { + if (it->is_character) + { + assert(player == 0); + player = it; + } + } + assert(player != 0); } void reset_level() { - // load level - Level *to_load = &level_level0; - { - assert(ARRLEN(to_load->initial_entities) == ARRLEN(gs.entities)); - memcpy(gs.entities, to_load->initial_entities, sizeof(Entity) * MAX_ENTITIES); - gs.version = CURRENT_VERSION; - ENTITIES_ITER(gs.entities) - { - if(it->generation == 0) it->generation = 1; // zero value generation means doesn't exist - } - } - update_player_from_entities(); - - BUFF_APPEND(&player->held_items, ITEM_WhiteSquare); - for(int i = 0; i < 20; i++) - BUFF_APPEND(&player->held_items, ITEM_Boots); - - ENTITIES_ITER(gs.entities) - { - if(it->npc_kind == NPC_TheBlacksmith) - { - BUFF_APPEND(&it->remembered_perceptions, ((Perception){.type = PlayerDialog, .player_dialog = SENTENCE_CONST("Testing dialog")})); - - BUFF_APPEND(&it->held_items, ITEM_Tripod); - } - } + // load level + Level *to_load = &level_level0; + { + assert(ARRLEN(to_load->initial_entities) == ARRLEN(gs.entities)); + memcpy(gs.entities, to_load->initial_entities, sizeof(Entity) * MAX_ENTITIES); + gs.version = CURRENT_VERSION; + ENTITIES_ITER(gs.entities) + { + if (it->generation == 0) it->generation = 1; // zero value generation means doesn't exist + } + } + update_player_from_entities(); + + if(false) + { + BUFF_APPEND(&player->held_items, ITEM_WhiteSquare); + for (int i = 0; i < 20; i++) + BUFF_APPEND(&player->held_items, ITEM_Boots); + } + + ENTITIES_ITER(gs.entities) + { + if (it->npc_kind == NPC_TheBlacksmith) + { + //BUFF_APPEND(&it->remembered_perceptions, ((Perception) { .type = PlayerDialog, .player_dialog = SENTENCE_CONST("Testing dialog") })); + + BUFF_APPEND(&it->held_items, ITEM_Chalice); + } + } } #ifdef WEB -EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void dump_save_data() { - EM_ASM({ - save_game_data = new Int8Array(Module.HEAP8.buffer, $0, $1); - }, (char*)(&gs), sizeof(gs)); + EM_ASM( { + save_game_data = new Int8Array(Module.HEAP8.buffer, $0, $1); + }, (char*)(&gs), sizeof(gs)); } - EMSCRIPTEN_KEEPALIVE + EMSCRIPTEN_KEEPALIVE void read_from_save_data(char *data, size_t length) { - GameState read_data = {0}; - memcpy((char*)(&read_data), data, length); - if(read_data.version != CURRENT_VERSION) - { - Log("Bad gamestate, has version %d expected version %d\n", read_data.version, CURRENT_VERSION); - } - else - { - gs = read_data; - update_player_from_entities(); - } + GameState read_data = { 0 }; + memcpy((char*)(&read_data), data, length); + if (read_data.version != CURRENT_VERSION) + { + Log("Bad gamestate, has version %d expected version %d\n", read_data.version, CURRENT_VERSION); + } + else + { + gs = read_data; + update_player_from_entities(); + } } #endif // a callback, when 'text backend' has finished making text. End dialog void end_text_input(char *what_player_said) { - receiving_text_input = false; - // avoid double ending text input - if(player->state != CHARACTER_TALKING) - { - return; - } - - size_t player_said_len = strlen(what_player_said); - int actual_len = 0; - for(int i = 0; i < player_said_len; i++) if(what_player_said[i] != '\n') actual_len++; - if(actual_len == 0) - { - // this just means cancel the dialog - } - else - { - Sentence what_player_said_sentence = {0}; - assert(player_said_len < ARRLEN(what_player_said_sentence.data)); // should be made sure of in the html5 layer - for(int i = 0; i < player_said_len; i++) - { - char c = what_player_said[i]; - if(!BUFF_HAS_SPACE(&what_player_said_sentence)) - { - break; - } - else if(c == '\n') - { - break; - } - else - { - BUFF_APPEND(&what_player_said_sentence, c); - } - } - - Entity *talking = gete(player->talking_to); - assert(talking); - ItemKind player_holding = ITEM_none; - if(gete(player->holding_item) != 0) - { - player_holding = gete(player->holding_item)->item_kind; - } - if(talking->last_seen_holding_kind != player_holding) - { - process_perception(talking, (Perception){.type = PlayerHeldItemChanged, .holding = player_holding,}, player); - - } - process_perception(talking, (Perception){.type = PlayerDialog, .player_dialog = what_player_said_sentence,}, player); - } + // avoid double ending text input + if (!receiving_text_input) + { + return; + } + receiving_text_input = false; + + size_t player_said_len = strlen(what_player_said); + int actual_len = 0; + for (int i = 0; i < player_said_len; i++) if (what_player_said[i] != '\n') actual_len++; + if (actual_len == 0) + { + // this just means cancel the dialog + } + else + { + Sentence what_player_said_sentence = { 0 }; + assert(player_said_len < ARRLEN(what_player_said_sentence.data)); // should be made sure of in the html5 layer + for (int i = 0; i < player_said_len; i++) + { + char c = what_player_said[i]; + if (!BUFF_HAS_SPACE(&what_player_said_sentence)) + { + break; + } + else if (c == '\n') + { + break; + } + else + { + BUFF_APPEND(&what_player_said_sentence, c); + } + } + + Entity *talking = gete(player->talking_to); + assert(talking); + ItemKind player_holding = ITEM_none; + if (gete(player->holding_item) != 0) + { + player_holding = gete(player->holding_item)->item_kind; + } + if (talking->last_seen_holding_kind != player_holding) + { + process_perception(talking, (Perception) { .type = PlayerHeldItemChanged, .holding = player_holding, }, player); + + } + process_perception(talking, (Perception) { .type = PlayerDialog, .player_dialog = what_player_said_sentence, }, player); + } } /* - AnimatedSprite moose_idle = - { - .img = &image_moose, - .time_per_frame = 0.15, - .num_frames = 8, - .start = {0.0, 0.0}, - .horizontal_diff_btwn_frames = 347.0f, - .region_size = {347.0f, 160.0f}, - .offset = {-1.5f, -10.0f}, - }; - */ - - -sg_image image_font = {0}; + AnimatedSprite moose_idle = + { + .img = &image_moose, + .time_per_frame = 0.15, + .num_frames = 8, + .start = {0.0, 0.0}, + .horizontal_diff_btwn_frames = 347.0f, + .region_size = {347.0f, 160.0f}, + .offset = {-1.5f, -10.0f}, + }; + */ + + +sg_image image_font = { 0 }; float font_line_advance = 0.0f; const float font_size = 32.0; stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs @@ -844,344 +843,343 @@ stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs static struct { - sg_pass_action pass_action; - sg_pipeline pip; - sg_bindings bind; + sg_pass_action pass_action; + sg_pipeline pip; + sg_bindings bind; } state; - void audio_stream_callback(float *buffer, int num_frames, int num_channels) { - assert(num_channels == 1); - const int num_samples = num_frames * num_channels; - double time_to_play = (double)num_frames / (double)SAMPLE_RATE; - double time_per_sample = 1.0 / (double)SAMPLE_RATE; - for(int i = 0; i < num_samples; i++) - { - float output_frame = 0.0f; - for(int audio_i = 0; audio_i < ARRLEN(playing_audio); audio_i++) - { - AudioPlayer *it = &playing_audio[audio_i]; - if(it->sample != 0) - { - if(cursor_pcm(it) >= it->sample->pcm_data_length) - { - it->sample = 0; - } - else - { - output_frame += it->sample->pcm_data[cursor_pcm(it)]*(float)(it->volume + 1.0); - it->cursor_time += time_per_sample*(it->pitch + 1.0); - } - } - } - buffer[i] = output_frame; - } + assert(num_channels == 1); + const int num_samples = num_frames * num_channels; + double time_to_play = (double)num_frames / (double)SAMPLE_RATE; + double time_per_sample = 1.0 / (double)SAMPLE_RATE; + for (int i = 0; i < num_samples; i++) + { + float output_frame = 0.0f; + for (int audio_i = 0; audio_i < ARRLEN(playing_audio); audio_i++) + { + AudioPlayer *it = &playing_audio[audio_i]; + if (it->sample != 0) + { + if (cursor_pcm(it) >= it->sample->pcm_data_length) + { + it->sample = 0; + } + else + { + output_frame += it->sample->pcm_data[cursor_pcm(it)]*(float)(it->volume + 1.0); + it->cursor_time += time_per_sample*(it->pitch + 1.0); + } + } + } + buffer[i] = output_frame; + } } typedef BUFF(Entity*, 16) SwordToDamage; SwordToDamage entity_sword_to_do_damage(Entity *from, Overlapping o) { - SwordToDamage to_return = {0}; - BUFF_ITER(Overlap, &o) - { - if(!it->is_tile && it->e != from) - { - bool done_damage = false; - Entity *looking_for = it->e; - BUFF_ITER(EntityRef, &from->done_damage_to_this_swing) - { - EntityRef ref = *it; - Entity *it = gete(ref); - if(it == looking_for) done_damage = true; - } - if(!done_damage) - { - if(!BUFF_HAS_SPACE(&from->done_damage_to_this_swing)) - { - BUFF_REMOVE_FRONT(&from->done_damage_to_this_swing); - Log("Too many things to do damage to...\n"); - assert(false); - } - BUFF_APPEND(&to_return, looking_for); - BUFF_APPEND(&from->done_damage_to_this_swing, frome(looking_for)); - } - } - } - return to_return; + SwordToDamage to_return = { 0 }; + BUFF_ITER(Overlap, &o) + { + if (!it->is_tile && it->e != from) + { + bool done_damage = false; + Entity *looking_for = it->e; + BUFF_ITER(EntityRef, &from->done_damage_to_this_swing) + { + EntityRef ref = *it; + Entity *it = gete(ref); + if (it == looking_for) done_damage = true; + } + if (!done_damage) + { + if (!BUFF_HAS_SPACE(&from->done_damage_to_this_swing)) + { + BUFF_REMOVE_FRONT(&from->done_damage_to_this_swing); + Log("Too many things to do damage to...\n"); + assert(false); + } + BUFF_APPEND(&to_return, looking_for); + BUFF_APPEND(&from->done_damage_to_this_swing, frome(looking_for)); + } + } + } + return to_return; } -#define WHITE ((Color){1.0f, 1.0f, 1.0f, 1.0f}) -#define BLACK ((Color){0.0f, 0.0f, 0.0f, 1.0f}) -#define RED ((Color){1.0f, 0.0f, 0.0f, 1.0f}) -#define PINK ((Color){1.0f, 0.0f, 1.0f, 1.0f}) -#define BLUE ((Color){0.0f, 0.0f, 1.0f, 1.0f}) -#define GREEN ((Color){0.0f, 1.0f, 0.0f, 1.0f}) +#define WHITE ((Color) { 1.0f, 1.0f, 1.0f, 1.0f }) +#define BLACK ((Color) { 0.0f, 0.0f, 0.0f, 1.0f }) +#define RED ((Color) { 1.0f, 0.0f, 0.0f, 1.0f }) +#define PINK ((Color) { 1.0f, 0.0f, 1.0f, 1.0f }) +#define BLUE ((Color) { 0.0f, 0.0f, 1.0f, 1.0f }) +#define GREEN ((Color) { 0.0f, 1.0f, 0.0f, 1.0f }) #define BROWN (colhex(0x4d3d25)) Color oflightness(float dark) { - return (Color){dark, dark, dark, 1.0f}; + return (Color) { dark, dark, dark, 1.0f }; } Color colhex(uint32_t hex) { - int r = (hex & 0xff0000) >> 16; - int g = (hex & 0x00ff00) >> 8; - int b = (hex & 0x0000ff) >> 0; + int r = (hex & 0xff0000) >> 16; + int g = (hex & 0x00ff00) >> 8; + int b = (hex & 0x0000ff) >> 0; - return (Color){ (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, 1.0f }; + return (Color) { (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, 1.0f }; } Color blendalpha(Color c, float alpha) { - Color to_return = c; - to_return.a = alpha; - return to_return; + Color to_return = c; + to_return.a = alpha; + return to_return; } void init(void) { #ifdef WEB - EM_ASM({ - set_server_url(UTF8ToString($0)); - }, SERVER_URL); + EM_ASM( { + set_server_url(UTF8ToString($0)); + }, SERVER_URL); #endif - Log("Size of entity struct: %zu\n", sizeof(Entity)); - Log("Size of %d gs.entities: %zu kb\n", (int)ARRLEN(gs.entities), sizeof(gs.entities)/1024); - sg_setup(&(sg_desc){ - .context = sapp_sgcontext(), - }); - stm_setup(); - saudio_setup(&(saudio_desc){ - .stream_cb = audio_stream_callback, - .logger.func = slog_func, - }); - - scratch = make_arena(1024 * 10); - - load_assets(); - reset_level(); + Log("Size of entity struct: %zu\n", sizeof(Entity)); + Log("Size of %d gs.entities: %zu kb\n", (int)ARRLEN(gs.entities), sizeof(gs.entities) / 1024); + sg_setup(&(sg_desc) { + .context = sapp_sgcontext(), + }); + stm_setup(); + saudio_setup(&(saudio_desc) { + .stream_cb = audio_stream_callback, + .logger.func = slog_func, + }); + + scratch = make_arena(1024 * 10); + + load_assets(); + reset_level(); #ifdef WEB - EM_ASM({ - load_all(); - }); + EM_ASM( { + load_all(); + }); #endif - // load font - { - FILE* fontFile = fopen("assets/orange kid.ttf", "rb"); - fseek(fontFile, 0, SEEK_END); - size_t size = ftell(fontFile); /* how long is the file ? */ - fseek(fontFile, 0, SEEK_SET); /* reset */ - - unsigned char *fontBuffer = malloc(size); - - fread(fontBuffer, size, 1, fontFile); - fclose(fontFile); - - unsigned char *font_bitmap = calloc(1, 512*512); - stbtt_BakeFontBitmap(fontBuffer, 0, font_size, font_bitmap, 512, 512, 32, 96, cdata); - - unsigned char *font_bitmap_rgba = malloc(4 * 512 * 512); // stack would be too big if allocated on stack (stack overflow) - for(int i = 0; i < 512 * 512; i++) - { - font_bitmap_rgba[i*4 + 0] = 255; - font_bitmap_rgba[i*4 + 1] = 255; - font_bitmap_rgba[i*4 + 2] = 255; - font_bitmap_rgba[i*4 + 3] = font_bitmap[i]; - } - - image_font = sg_make_image( &(sg_image_desc){ - .width = 512, - .height = 512, - .pixel_format = SG_PIXELFORMAT_RGBA8, - .min_filter = SG_FILTER_NEAREST, - .mag_filter = SG_FILTER_NEAREST, - .data.subimage[0][0] = - { - .ptr = font_bitmap_rgba, - .size = (size_t)(512 * 512 * 4), - } - } ); - - stbtt_fontinfo font; - stbtt_InitFont(&font, fontBuffer, 0); - int ascent = 0; - int descent = 0; - int lineGap = 0; - float scale = stbtt_ScaleForPixelHeight(&font, font_size); - stbtt_GetFontVMetrics(&font, &ascent, &descent, &lineGap); - font_line_advance = (float)(ascent - descent + lineGap) * scale * 0.75f; - - free(font_bitmap_rgba); - free(fontBuffer); - } - - state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc) - { - .usage = SG_USAGE_STREAM, - //.data = SG_RANGE(vertices), + // load font + { + FILE* fontFile = fopen("assets/orange kid.ttf", "rb"); + fseek(fontFile, 0, SEEK_END); + size_t size = ftell(fontFile); /* how long is the file ? */ + fseek(fontFile, 0, SEEK_SET); /* reset */ + + unsigned char *fontBuffer = malloc(size); + + fread(fontBuffer, size, 1, fontFile); + fclose(fontFile); + + unsigned char *font_bitmap = calloc(1, 512*512); + stbtt_BakeFontBitmap(fontBuffer, 0, font_size, font_bitmap, 512, 512, 32, 96, cdata); + + unsigned char *font_bitmap_rgba = malloc(4 * 512 * 512); // stack would be too big if allocated on stack (stack overflow) + for (int i = 0; i < 512 * 512; i++) + { + font_bitmap_rgba[i*4 + 0] = 255; + font_bitmap_rgba[i*4 + 1] = 255; + font_bitmap_rgba[i*4 + 2] = 255; + font_bitmap_rgba[i*4 + 3] = font_bitmap[i]; + } + + image_font = sg_make_image(&(sg_image_desc) { + .width = 512, + .height = 512, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .min_filter = SG_FILTER_NEAREST, + .mag_filter = SG_FILTER_NEAREST, + .data.subimage[0][0] = + { + .ptr = font_bitmap_rgba, + .size = (size_t)(512 * 512 * 4), + } + }); + + stbtt_fontinfo font; + stbtt_InitFont(&font, fontBuffer, 0); + int ascent = 0; + int descent = 0; + int lineGap = 0; + float scale = stbtt_ScaleForPixelHeight(&font, font_size); + stbtt_GetFontVMetrics(&font, &ascent, &descent, &lineGap); + font_line_advance = (float)(ascent - descent + lineGap) * scale * 0.75f; + + free(font_bitmap_rgba); + free(fontBuffer); + } + + state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc) + { + .usage = SG_USAGE_STREAM, + //.data = SG_RANGE(vertices), #ifdef DEVTOOLS - .size = 1024*2500, + .size = 1024*2500, #else - .size = 1024*700, + .size = 1024*700, #endif - .label = "quad-vertices" - }); - - const sg_shader_desc *desc = quad_program_shader_desc(sg_query_backend()); - assert(desc); - sg_shader shd = sg_make_shader(desc); - - Color clearcol = colhex(0x98734c); - state.pip = sg_make_pipeline(&(sg_pipeline_desc) - { - .shader = shd, - .depth = { - .compare = SG_COMPAREFUNC_LESS_EQUAL, - .write_enabled = true - }, - .layout = { - .attrs = - { - [ATTR_quad_vs_position].format = SG_VERTEXFORMAT_FLOAT3, - [ATTR_quad_vs_texcoord0].format = SG_VERTEXFORMAT_FLOAT2, - } - }, - .colors[0].blend = (sg_blend_state) { // allow transparency - .enabled = true, - .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, - .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - .op_rgb = SG_BLENDOP_ADD, - .src_factor_alpha = SG_BLENDFACTOR_ONE, - .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - .op_alpha = SG_BLENDOP_ADD, - }, - .label = "quad-pipeline", - }); - - state.pass_action = (sg_pass_action) - { - //.colors[0] = { .action=SG_ACTION_CLEAR, .value={12.5f/255.0f, 12.5f/255.0f, 12.5f/255.0f, 1.0f } } - //.colors[0] = { .action=SG_ACTION_CLEAR, .value={255.5f/255.0f, 255.5f/255.0f, 255.5f/255.0f, 1.0f } } - // 0x898989 is the color in tiled - .colors[0] = - { .action=SG_ACTION_CLEAR, .value={clearcol.r, clearcol.g, clearcol.b, 1.0f } } - }; + .label = "quad-vertices" + }); + + const sg_shader_desc *desc = quad_program_shader_desc(sg_query_backend()); + assert(desc); + sg_shader shd = sg_make_shader(desc); + + Color clearcol = colhex(0x98734c); + state.pip = sg_make_pipeline(&(sg_pipeline_desc) + { + .shader = shd, + .depth = { + .compare = SG_COMPAREFUNC_LESS_EQUAL, + .write_enabled = true + }, + .layout = { + .attrs = + { + [ATTR_quad_vs_position].format = SG_VERTEXFORMAT_FLOAT3, + [ATTR_quad_vs_texcoord0].format = SG_VERTEXFORMAT_FLOAT2, + } + }, + .colors[0].blend = (sg_blend_state) { // allow transparency + .enabled = true, + .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, + .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .op_rgb = SG_BLENDOP_ADD, + .src_factor_alpha = SG_BLENDFACTOR_ONE, + .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .op_alpha = SG_BLENDOP_ADD, + }, + .label = "quad-pipeline", + }); + + state.pass_action = (sg_pass_action) + { + //.colors[0] = { .action=SG_ACTION_CLEAR, .value={12.5f/255.0f, 12.5f/255.0f, 12.5f/255.0f, 1.0f } } + //.colors[0] = { .action=SG_ACTION_CLEAR, .value={255.5f/255.0f, 255.5f/255.0f, 255.5f/255.0f, 1.0f } } + // 0x898989 is the color in tiled + .colors[0] = + { .action = SG_ACTION_CLEAR, .value = { clearcol.r, clearcol.g, clearcol.b, 1.0f } } + }; } Vec2 screen_size() { - return V2((float)sapp_width(), (float)sapp_height()); + return V2((float)sapp_width(), (float)sapp_height()); } typedef struct Camera { - Vec2 pos; - float scale; + Vec2 pos; + float scale; } Camera; bool mobile_controls = false; -Vec2 thumbstick_base_pos = {0}; -Vec2 thumbstick_nub_pos = {0}; +Vec2 thumbstick_base_pos = { 0 }; +Vec2 thumbstick_nub_pos = { 0 }; typedef struct TouchMemory { - // need this because uintptr_t = 0 *doesn't* mean no touching! - bool active; - uintptr_t identifier; + // need this because uintptr_t = 0 *doesn't* mean no touching! + bool active; + uintptr_t identifier; } TouchMemory; TouchMemory activate(uintptr_t by) { - //Log("Activating %ld\n", by); - return (TouchMemory){.active = true, .identifier = by}; + //Log("Activating %ld\n", by); + return (TouchMemory) { .active = true, .identifier = by }; } // returns if deactivated bool maybe_deactivate(TouchMemory *memory, uintptr_t ended_identifier) { - if(memory->active) - { - if(memory->identifier == ended_identifier) - { - //Log("Deactivating %ld\n", memory->identifier); - *memory = (TouchMemory){0}; - return true; - } - } - else - { - return false; - } - return false; + if (memory->active) + { + if (memory->identifier == ended_identifier) + { + //Log("Deactivating %ld\n", memory->identifier); + *memory = (TouchMemory) { 0 }; + return true; + } + } + else + { + return false; + } + return false; } -TouchMemory movement_touch = {0}; -TouchMemory roll_pressed_by = {0}; -TouchMemory attack_pressed_by = {0}; -TouchMemory interact_pressed_by = {0}; +TouchMemory movement_touch = { 0 }; +TouchMemory roll_pressed_by = { 0 }; +TouchMemory attack_pressed_by = { 0 }; +TouchMemory interact_pressed_by = { 0 }; bool mobile_roll_pressed = false; bool mobile_attack_pressed = false; bool mobile_interact_pressed = false; float thumbstick_base_size() { - if(screen_size().x < screen_size().y) - { - return screen_size().x * 0.24f; - } - else - { - return screen_size().x * 0.14f; - } + if (screen_size().x < screen_size().y) + { + return screen_size().x * 0.24f; + } + else + { + return screen_size().x * 0.14f; + } } float mobile_button_size() { - if(screen_size().x < screen_size().y) - { - return screen_size().x * 0.2f; - } - else - { - return screen_size().x * 0.09f; - } + if (screen_size().x < screen_size().y) + { + return screen_size().x * 0.2f; + } + else + { + return screen_size().x * 0.09f; + } } Vec2 roll_button_pos() { - return V2(screen_size().x - mobile_button_size(), screen_size().y * 0.4f); + return V2(screen_size().x - mobile_button_size(), screen_size().y * 0.4f); } Vec2 interact_button_pos() { - return V2(screen_size().x - mobile_button_size()*2.0f, screen_size().y * (0.4f + (0.4f - 0.25f))); + return V2(screen_size().x - mobile_button_size()*2.0f, screen_size().y * (0.4f + (0.4f - 0.25f))); } Vec2 attack_button_pos() { - return V2(screen_size().x - mobile_button_size()*2.0f, screen_size().y * 0.25f); + return V2(screen_size().x - mobile_button_size()*2.0f, screen_size().y * 0.25f); } // everything is in pixels in world space, 43 pixels is approx 1 meter measured from // merchant sprite being 5'6" const float pixels_per_meter = 43.0f; -Camera cam = {.scale = 2.0f }; +Camera cam = { .scale = 2.0f }; Vec2 cam_offset() { - Vec2 to_return = AddV2(cam.pos, MulV2F(screen_size(), 0.5f)); - to_return = FloorV2(to_return); // avoid pixel glitching on tilemap atlas - return to_return; + Vec2 to_return = AddV2(cam.pos, MulV2F(screen_size(), 0.5f)); + to_return = FloorV2(to_return); // avoid pixel glitching on tilemap atlas + return to_return; } // in pixels Vec2 img_size(sg_image img) { - sg_image_info info = sg_query_image_info(img); - return V2((float)info.width, (float)info.height); + sg_image_info info = sg_query_image_info(img); + return V2((float)info.width, (float)info.height); } #define IMG(img) img, full_region(img) @@ -1189,301 +1187,301 @@ Vec2 img_size(sg_image img) // full region in pixels AABB full_region(sg_image img) { - return (AABB) - { - .upper_left = V2(0.0f, 0.0f), - .lower_right = img_size(img), - }; + return (AABB) + { + .upper_left = V2(0.0f, 0.0f), + .lower_right = img_size(img), + }; } // screen coords are in pixels counting from bottom left as (0,0), Y+ is up Vec2 world_to_screen(Vec2 world) { - Vec2 to_return = world; - to_return = MulV2F(to_return, cam.scale); - to_return = AddV2(to_return, cam_offset()); - return to_return; + Vec2 to_return = world; + to_return = MulV2F(to_return, cam.scale); + to_return = AddV2(to_return, cam_offset()); + return to_return; } Vec2 screen_to_world(Vec2 screen) { - Vec2 to_return = screen; - to_return = SubV2(to_return, cam_offset()); - to_return = MulV2F(to_return, 1.0f/cam.scale); - return to_return; + Vec2 to_return = screen; + to_return = SubV2(to_return, cam_offset()); + to_return = MulV2F(to_return, 1.0f / cam.scale); + return to_return; } AABB aabb_at(Vec2 at, Vec2 size) { - return (AABB){ - .upper_left = at, - .lower_right = AddV2(at, V2(size.x, -size.y)), - }; + return (AABB) { + .upper_left = at, + .lower_right = AddV2(at, V2(size.x, -size.y)), + }; } AABB aabb_at_yplusdown(Vec2 at, Vec2 size) { - return (AABB){ - .upper_left = at, - .lower_right = AddV2(at, V2(size.x, size.y)), - }; + return (AABB) { + .upper_left = at, + .lower_right = AddV2(at, V2(size.x, size.y)), + }; } Quad quad_at(Vec2 at, Vec2 size) { - Quad to_return; + Quad to_return; - to_return.points[0] = V2(0.0, 0.0); - to_return.points[1] = V2(size.X, 0.0); - to_return.points[2] = V2(size.X, -size.Y); - to_return.points[3] = V2(0.0, -size.Y); + to_return.points[0] = V2(0.0, 0.0); + to_return.points[1] = V2(size.X, 0.0); + to_return.points[2] = V2(size.X, -size.Y); + to_return.points[3] = V2(0.0, -size.Y); - for(int i = 0; i < 4; i++) - { - to_return.points[i] = AddV2(to_return.points[i], at); - } - return to_return; + for (int i = 0; i < 4; i++) + { + to_return.points[i] = AddV2(to_return.points[i], at); + } + return to_return; } Quad tile_quad(TileCoord coord) { - Quad to_return = quad_at(tilecoord_to_world(coord), V2(TILE_SIZE, TILE_SIZE)); + Quad to_return = quad_at(tilecoord_to_world(coord), V2(TILE_SIZE, TILE_SIZE)); - return to_return; + return to_return; } // out must be of at least length 4 Quad quad_centered(Vec2 at, Vec2 size) { - Quad to_return = quad_at(at, size); - for(int i = 0; i < 4; i++) - { - to_return.points[i] = AddV2(to_return.points[i], V2(-size.X*0.5f, size.Y*0.5f)); - } - return to_return; + Quad to_return = quad_at(at, size); + for (int i = 0; i < 4; i++) + { + to_return.points[i] = AddV2(to_return.points[i], V2(-size.X*0.5f, size.Y*0.5f)); + } + return to_return; } bool aabb_is_valid(AABB aabb) { - Vec2 size_vec = SubV2(aabb.lower_right, aabb.upper_left); // negative in vertical direction - return size_vec.Y < 0.0f && size_vec.X > 0.0f; + Vec2 size_vec = SubV2(aabb.lower_right, aabb.upper_left); // negative in vertical direction + return size_vec.Y < 0.0f && size_vec.X > 0.0f; } // positive in both directions Vec2 aabb_size(AABB aabb) { - assert(aabb_is_valid(aabb)); - Vec2 size_vec = SubV2(aabb.lower_right, aabb.upper_left); // negative in vertical direction - size_vec.y *= -1.0; - return size_vec; + assert(aabb_is_valid(aabb)); + Vec2 size_vec = SubV2(aabb.lower_right, aabb.upper_left); // negative in vertical direction + size_vec.y *= -1.0; + return size_vec; } Quad quad_aabb(AABB aabb) { - Vec2 size_vec = SubV2(aabb.lower_right, aabb.upper_left); // negative in vertical direction + Vec2 size_vec = SubV2(aabb.lower_right, aabb.upper_left); // negative in vertical direction - assert(aabb_is_valid(aabb)); - return (Quad) { - .ul = aabb.upper_left, - .ur = AddV2(aabb.upper_left, V2(size_vec.X, 0.0f)), - .lr = AddV2(aabb.upper_left, size_vec), - .ll = AddV2(aabb.upper_left, V2(0.0f, size_vec.Y)), - }; + assert(aabb_is_valid(aabb)); + return (Quad) { + .ul = aabb.upper_left, + .ur = AddV2(aabb.upper_left, V2(size_vec.X, 0.0f)), + .lr = AddV2(aabb.upper_left, size_vec), + .ll = AddV2(aabb.upper_left, V2(0.0f, size_vec.Y)), + }; } // both segment_a and segment_b must be arrays of length 2 bool segments_overlapping(float *a_segment, float *b_segment) { - assert(a_segment[1] >= a_segment[0]); - assert(b_segment[1] >= b_segment[0]); - float total_length = (a_segment[1] - a_segment[0]) + (b_segment[1] - b_segment[0]); - float farthest_to_left = fminf(a_segment[0], b_segment[0]); - float farthest_to_right = fmaxf(a_segment[1], b_segment[1]); - if (farthest_to_right - farthest_to_left < total_length) - { - return true; - } - else - { - return false; - } + assert(a_segment[1] >= a_segment[0]); + assert(b_segment[1] >= b_segment[0]); + float total_length = (a_segment[1] - a_segment[0]) + (b_segment[1] - b_segment[0]); + float farthest_to_left = fminf(a_segment[0], b_segment[0]); + float farthest_to_right = fmaxf(a_segment[1], b_segment[1]); + if (farthest_to_right - farthest_to_left < total_length) + { + return true; + } + else + { + return false; + } } bool overlapping(AABB a, AABB b) { - // x axis - - { - float a_segment[2] = - { a.upper_left.X, a.lower_right.X }; - float b_segment[2] = - { b.upper_left.X, b.lower_right.X }; - if(segments_overlapping(a_segment, b_segment)) - { - } - else - { - return false; - } - } - - // y axis - - { - float a_segment[2] = - { a.lower_right.Y, a.upper_left.Y }; - float b_segment[2] = - { b.lower_right.Y, b.upper_left.Y }; - if(segments_overlapping(a_segment, b_segment)) - { - } - else - { - return false; - } - } - - return true; // both segments overlapping + // x axis + + { + float a_segment[2] = + { a.upper_left.X, a.lower_right.X }; + float b_segment[2] = + { b.upper_left.X, b.lower_right.X }; + if (segments_overlapping(a_segment, b_segment)) + { + } + else + { + return false; + } + } + + // y axis + + { + float a_segment[2] = + { a.lower_right.Y, a.upper_left.Y }; + float b_segment[2] = + { b.lower_right.Y, b.upper_left.Y }; + if (segments_overlapping(a_segment, b_segment)) + { + } + else + { + return false; + } + } + + return true; // both segments overlapping } bool has_point(AABB aabb, Vec2 point) { - return - (aabb.upper_left.X < point.X && point.X < aabb.lower_right.X) && - (aabb.upper_left.Y > point.Y && point.Y > aabb.lower_right.Y); + return + (aabb.upper_left.X < point.X && point.X < aabb.lower_right.X) && + (aabb.upper_left.Y > point.Y && point.Y > aabb.lower_right.Y); } AABB screen_cam_aabb() { - return (AABB){ .upper_left = V2(0.0, screen_size().Y), .lower_right = V2(screen_size().X, 0.0) }; + return (AABB) { .upper_left = V2(0.0, screen_size().Y), .lower_right = V2(screen_size().X, 0.0) }; } AABB world_cam_aabb() { - AABB to_return = screen_cam_aabb(); - to_return.upper_left = screen_to_world(to_return.upper_left); - to_return.lower_right = screen_to_world(to_return.lower_right); - return to_return; + AABB to_return = screen_cam_aabb(); + to_return.upper_left = screen_to_world(to_return.upper_left); + to_return.lower_right = screen_to_world(to_return.lower_right); + return to_return; } int num_draw_calls = 0; #define FLOATS_PER_VERTEX (3 + 2) -float cur_batch_data[1024*10] = {0}; +float cur_batch_data[1024*10] = { 0 }; int cur_batch_data_index = 0; // @TODO check last tint as well, do this when factor into drawing parameters -sg_image cur_batch_image = {0}; -quad_fs_params_t cur_batch_params = {0}; +sg_image cur_batch_image = { 0 }; +quad_fs_params_t cur_batch_params = { 0 }; void flush_quad_batch() { - if(cur_batch_image.id == 0 || cur_batch_data_index == 0) return; // flush called when image changes, image starts out null! - state.bind.vertex_buffer_offsets[0] = sg_append_buffer(state.bind.vertex_buffers[0], &(sg_range){cur_batch_data, cur_batch_data_index*sizeof(*cur_batch_data)}); - state.bind.fs_images[SLOT_quad_tex] = cur_batch_image; - sg_apply_bindings(&state.bind); - sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_quad_fs_params, &SG_RANGE(cur_batch_params)); - assert(cur_batch_data_index % FLOATS_PER_VERTEX == 0); - sg_draw(0, cur_batch_data_index/FLOATS_PER_VERTEX, 1); - num_draw_calls += 1; - memset(cur_batch_data, 0, cur_batch_data_index*sizeof(*cur_batch_data)); - cur_batch_data_index = 0; + if (cur_batch_image.id == 0 || cur_batch_data_index == 0) return; // flush called when image changes, image starts out null! + state.bind.vertex_buffer_offsets[0] = sg_append_buffer(state.bind.vertex_buffers[0], &(sg_range) { cur_batch_data, cur_batch_data_index*sizeof(*cur_batch_data) }); + state.bind.fs_images[SLOT_quad_tex] = cur_batch_image; + sg_apply_bindings(&state.bind); + sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_quad_fs_params, &SG_RANGE(cur_batch_params)); + assert(cur_batch_data_index % FLOATS_PER_VERTEX == 0); + sg_draw(0, cur_batch_data_index / FLOATS_PER_VERTEX, 1); + num_draw_calls += 1; + memset(cur_batch_data, 0, cur_batch_data_index*sizeof(*cur_batch_data)); + cur_batch_data_index = 0; } typedef enum { - LAYER_TILEMAP, - LAYER_WORLD, - LAYER_UI, - LAYER_UI_FG, - LAYER_SCREENSPACE_EFFECTS, + LAYER_TILEMAP, + LAYER_WORLD, + LAYER_UI, + LAYER_UI_FG, + LAYER_SCREENSPACE_EFFECTS, - LAYER_LAST + LAYER_LAST } Layer; typedef struct DrawParams { - bool world_space; - Quad quad; - sg_image image; - AABB image_region; - Color tint; + bool world_space; + Quad quad; + sg_image image; + AABB image_region; + Color tint; - AABB clip_to; // if world space is in world space, if screen space is in screen space - Lao Tzu - int sorting_key; - float alpha_clip_threshold; + AABB clip_to; // if world space is in world space, if screen space is in screen space - Lao Tzu + int sorting_key; + float alpha_clip_threshold; - bool do_clipping; - Layer layer; + bool do_clipping; + Layer layer; } DrawParams; Vec2 into_clip_space(Vec2 screen_space_point) { - Vec2 zero_to_one = DivV2(screen_space_point, screen_size()); - Vec2 in_clip_space = SubV2(MulV2F(zero_to_one, 2.0), V2(1.0, 1.0)); - return in_clip_space; + Vec2 zero_to_one = DivV2(screen_space_point, screen_size()); + Vec2 in_clip_space = SubV2(MulV2F(zero_to_one, 2.0), V2(1.0, 1.0)); + return in_clip_space; } typedef BUFF(DrawParams, 1024*5) RenderingQueue; -RenderingQueue rendering_queues[LAYER_LAST] = {0}; +RenderingQueue rendering_queues[LAYER_LAST] = { 0 }; // The image region is in pixel space of the image void draw_quad(DrawParams d) { - Vec2 *points = d.quad.points; - if(d.world_space) - { - for(int i = 0; i < 4; i++) - { - points[i] = world_to_screen(points[i]); - } - } - // we've aplied the world space transform - d.world_space = false; - - assert(d.layer >= 0 && d.layer < ARRLEN(rendering_queues)); - BUFF_APPEND(&rendering_queues[(int)d.layer], d); + Vec2 *points = d.quad.points; + if (d.world_space) + { + for (int i = 0; i < 4; i++) + { + points[i] = world_to_screen(points[i]); + } + } + // we've aplied the world space transform + d.world_space = false; + + assert(d.layer >= 0 && d.layer < ARRLEN(rendering_queues)); + BUFF_APPEND(&rendering_queues[(int)d.layer], d); } int rendering_compare(const void *a, const void *b) { - DrawParams *a_draw = (DrawParams*)a; - DrawParams *b_draw = (DrawParams*)b; + DrawParams *a_draw = (DrawParams*)a; + DrawParams *b_draw = (DrawParams*)b; - return (int)((a_draw->sorting_key - b_draw->sorting_key)); + return (int)((a_draw->sorting_key - b_draw->sorting_key)); } void swap(Vec2 *p1, Vec2 *p2) { - Vec2 tmp = *p1; - *p1 = *p2; - *p2 = tmp; + Vec2 tmp = *p1; + *p1 = *p2; + *p2 = tmp; } double anim_sprite_duration(AnimKind anim) { - AnimatedSprite *s = GET_TABLE_PTR(sprites, anim); - return s->num_frames * s->time_per_frame; + 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); - int tile_index = tile_id - 1; - int tile_image_row = tile_index / tiles_per_row; - int tile_image_col = tile_index - tile_image_row*tiles_per_row; - Vec2 tile_image_coord = V2((float)tile_image_col * tile_size.X, (float)tile_image_row*tile_size.Y); - return tile_image_coord; + int tiles_per_row = (int)(img_size(tileset_image).X / tile_size.X); + int tile_index = tile_id - 1; + int tile_image_row = tile_index / tiles_per_row; + int tile_image_col = tile_index - tile_image_row*tiles_per_row; + Vec2 tile_image_coord = V2((float)tile_image_col * tile_size.X, (float)tile_image_row*tile_size.Y); + return tile_image_coord; } void colorquad(bool world_space, Quad q, Color col) { - bool queue = false; - if(col.A < 1.0f) - { - queue = true; - } - // y coord sorting for colorquad puts it below text for dialog panel - draw_quad((DrawParams){world_space, q, image_white_square, full_region(image_white_square), col, .layer = LAYER_UI}); + bool queue = false; + if (col.A < 1.0f) + { + queue = true; + } + // y coord sorting for colorquad puts it below text for dialog panel + draw_quad((DrawParams) { world_space, q, image_white_square, full_region(image_white_square), col, .layer = LAYER_UI }); } @@ -1491,16 +1489,16 @@ void colorquad(bool world_space, Quad q, Color col) bool in_screen_space = false; void line(Vec2 from, Vec2 to, float line_width, Color color) { - Vec2 normal = rotate_counter_clockwise(NormV2(SubV2(to, from))); - Quad line_quad = { - .points = { - AddV2(from, MulV2F(normal, line_width)), // upper left - AddV2(to, MulV2F(normal, line_width)), // upper right - AddV2(to, MulV2F(normal, -line_width)), // lower right - AddV2(from, MulV2F(normal, -line_width)), // lower left - } - }; - colorquad(!in_screen_space, line_quad, color); + Vec2 normal = rotate_counter_clockwise(NormV2(SubV2(to, from))); + Quad line_quad = { + .points = { + AddV2(from, MulV2F(normal, line_width)), // upper left + AddV2(to, MulV2F(normal, line_width)), // upper right + AddV2(to, MulV2F(normal, -line_width)), // lower right + AddV2(from, MulV2F(normal, -line_width)), // lower left + } + }; + colorquad(!in_screen_space, line_quad, color); } #ifdef DEVTOOLS @@ -1512,576 +1510,576 @@ bool profiling; #endif #endif -Color debug_color = {1.0f, 0.0f, 0.0f, 0.0f}; +Color debug_color = { 1.0f, 0.0f, 0.0f, 0.0f }; #define dbgcol(col) DeferLoop(debug_color = col, debug_color = RED) void dbgsquare(Vec2 at) { #ifdef DEVTOOLS - if(!show_devtools) return; - colorquad(true, quad_centered(at, V2(3.0, 3.0)), debug_color); + if (!show_devtools) return; + colorquad(true, quad_centered(at, V2(3.0, 3.0)), debug_color); #else - (void)at; + (void)at; #endif } void dbgline(Vec2 from, Vec2 to) { #ifdef DEVTOOLS - if(!show_devtools) return; - line(from, to, 0.5f, debug_color); + if (!show_devtools) return; + line(from, to, 0.5f, debug_color); #else - (void)from; - (void)to; + (void)from; + (void)to; #endif } void dbgvec(Vec2 from, Vec2 vec) { - Vec2 to = AddV2(from, vec); - dbgline(from, to); + Vec2 to = AddV2(from, vec); + dbgline(from, to); } // in world space void dbgrect(AABB rect) { #ifdef DEVTOOLS - if(!show_devtools) return; - if(!aabb_is_valid(rect)) - { - dbgsquare(rect.upper_left); - } - else - { - const float line_width = 0.5; - Color col = debug_color; - Quad q = quad_aabb(rect); - line(q.ul, q.ur, line_width, col); - line(q.ur, q.lr, line_width, col); - line(q.lr, q.ll, line_width, col); - line(q.ll, q.ul, line_width, col); - } + if (!show_devtools) return; + if (!aabb_is_valid(rect)) + { + dbgsquare(rect.upper_left); + } + else + { + const float line_width = 0.5; + Color col = debug_color; + Quad q = quad_aabb(rect); + line(q.ul, q.ur, line_width, col); + line(q.ur, q.lr, line_width, col); + line(q.lr, q.ll, line_width, col); + line(q.ll, q.ul, line_width, col); + } #else - (void)rect; + (void)rect; #endif } // from_point is for knockback void request_do_damage(Entity *to, Entity *from, float damage) { - Vec2 from_point = from->pos; - if(to == NULL) return; - if(to->is_bullet) - { - Vec2 norm = NormV2(SubV2(to->pos, from_point)); - dbgvec(from_point, norm); - to->vel = ReflectV2(to->vel, norm); - dbgprint("deflecitng\n"); - } - else if(true) - { - // damage processing is done in process perception so in training, has accurate values for - // NPC health - if(to->is_character) - { - to->damage += damage; - } - else - { - if(from->is_character) - { - process_perception(to, (Perception){.type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage,}, player); - } - else - { - process_perception(to, (Perception){.type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage,}, player); - } - } - to->vel = MulV2F(NormV2(SubV2(to->pos, from_point)), 15.0f); - } - else - { - Log("Can't do damage to npc...\n"); - } + Vec2 from_point = from->pos; + if (to == NULL) return; + if (to->is_bullet) + { + Vec2 norm = NormV2(SubV2(to->pos, from_point)); + dbgvec(from_point, norm); + to->vel = ReflectV2(to->vel, norm); + dbgprint("deflecitng\n"); + } + else if (true) + { + // damage processing is done in process perception so in training, has accurate values for + // NPC health + if (to->is_character) + { + to->damage += damage; + } + else + { + if (from->is_character) + { + process_perception(to, (Perception) { .type = PlayerAction, .player_action_type = ACT_hits_npc, .damage_done = damage, }, player); + } + else + { + process_perception(to, (Perception) { .type = EnemyAction, .enemy_action_type = ACT_hits_npc, .damage_done = damage, }, player); + } + } + to->vel = MulV2F(NormV2(SubV2(to->pos, from_point)), 15.0f); + } + else + { + Log("Can't do damage to npc...\n"); + } } typedef struct TextParams { - bool world_space; - bool dry_run; - const char *text; - Vec2 pos; - Color color; - float scale; - AABB clip_to; // if in world space, in world space. In space of pos given - Color *colors; // color per character, if not null must be array of same length as text - bool do_clipping; + bool world_space; + bool dry_run; + const char *text; + Vec2 pos; + Color color; + float scale; + AABB clip_to; // if in world space, in world space. In space of pos given + Color *colors; // color per character, if not null must be array of same length as text + bool do_clipping; } TextParams; // returns bounds. To measure text you can set dry run to true and get the bounds AABB draw_text(TextParams t) { - size_t text_len = strlen(t.text); - AABB bounds = {0}; - float y = 0.0; - float x = 0.0; - for(int i = 0; i < text_len; i++) - { - stbtt_aligned_quad q; - float old_y = y; - stbtt_GetBakedQuad(cdata, 512, 512, t.text[i]-32, &x, &y, &q, 1); - float difference = y - old_y; - y = old_y + difference; - - Vec2 size = V2(q.x1 - q.x0, q.y1 - q.y0); - if(t.text[i] == '\n') - { + size_t text_len = strlen(t.text); + AABB bounds = { 0 }; + float y = 0.0; + float x = 0.0; + for (int i = 0; i < text_len; i++) + { + stbtt_aligned_quad q; + float old_y = y; + stbtt_GetBakedQuad(cdata, 512, 512, t.text[i]-32, &x, &y, &q, 1); + float difference = y - old_y; + y = old_y + difference; + + Vec2 size = V2(q.x1 - q.x0, q.y1 - q.y0); + if (t.text[i] == '\n') + { #ifdef DEVTOOLS - y += font_size*0.75f; // arbitrary, only debug t.text has newlines - x = 0.0; + y += font_size*0.75f; // arbitrary, only debug t.text has newlines + x = 0.0; #else - assert(false); + assert(false); #endif - } - if(size.Y > 0.0 && size.X > 0.0) - { // spaces (and maybe other characters) produce quads of size 0 - Quad to_draw = { - .points = { - AddV2(V2(q.x0, -q.y0), V2(0.0f, 0.0f)), - AddV2(V2(q.x0, -q.y0), V2(size.X, 0.0f)), - AddV2(V2(q.x0, -q.y0), V2(size.X, -size.Y)), - AddV2(V2(q.x0, -q.y0), V2(0.0f, -size.Y)), - }, - }; - - for(int i = 0; i < 4; i++) - { - to_draw.points[i] = MulV2F(to_draw.points[i], t.scale); - } - - AABB font_atlas_region = (AABB) - { - .upper_left = V2(q.s0, q.t0), - .lower_right = V2(q.s1, q.t1), - }; - font_atlas_region.upper_left.X *= img_size(image_font).X; - font_atlas_region.lower_right.X *= img_size(image_font).X; - font_atlas_region.upper_left.Y *= img_size(image_font).Y; - font_atlas_region.lower_right.Y *= img_size(image_font).Y; - - for(int i = 0; i < 4; i++) - { - bounds.upper_left.X = fminf(bounds.upper_left.X, to_draw.points[i].X); - bounds.upper_left.Y = fmaxf(bounds.upper_left.Y, to_draw.points[i].Y); - bounds.lower_right.X = fmaxf(bounds.lower_right.X, to_draw.points[i].X); - bounds.lower_right.Y = fminf(bounds.lower_right.Y, to_draw.points[i].Y); - } - - for(int i = 0; i < 4; i++) - { - to_draw.points[i] = AddV2(to_draw.points[i], t.pos); - } - - if(!t.dry_run) - { - Color col = t.color; - if(t.colors) - { - col = t.colors[i]; - } - - if(false) // drop shadow, don't really like it - if(t.world_space) - { - Quad shadow_quad = to_draw; - for(int i = 0; i < 4; i++) - { - shadow_quad.points[i] = AddV2(shadow_quad.points[i], V2(0.0, -1.0)); - } - draw_quad((DrawParams){t.world_space, shadow_quad, image_font, font_atlas_region, (Color){0.0f,0.0f,0.0f,0.4f}, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping}); - } - - draw_quad((DrawParams){t.world_space, to_draw, image_font, font_atlas_region, col, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping}); - } - } - } - - bounds.upper_left = AddV2(bounds.upper_left, t.pos); - bounds.lower_right = AddV2(bounds.lower_right, t.pos); - return bounds; + } + if (size.Y > 0.0 && size.X > 0.0) + { // spaces (and maybe other characters) produce quads of size 0 + Quad to_draw = { + .points = { + AddV2(V2(q.x0, -q.y0), V2(0.0f, 0.0f)), + AddV2(V2(q.x0, -q.y0), V2(size.X, 0.0f)), + AddV2(V2(q.x0, -q.y0), V2(size.X, -size.Y)), + AddV2(V2(q.x0, -q.y0), V2(0.0f, -size.Y)), + }, + }; + + for (int i = 0; i < 4; i++) + { + to_draw.points[i] = MulV2F(to_draw.points[i], t.scale); + } + + AABB font_atlas_region = (AABB) + { + .upper_left = V2(q.s0, q.t0), + .lower_right = V2(q.s1, q.t1), + }; + font_atlas_region.upper_left.X *= img_size(image_font).X; + font_atlas_region.lower_right.X *= img_size(image_font).X; + font_atlas_region.upper_left.Y *= img_size(image_font).Y; + font_atlas_region.lower_right.Y *= img_size(image_font).Y; + + for (int i = 0; i < 4; i++) + { + bounds.upper_left.X = fminf(bounds.upper_left.X, to_draw.points[i].X); + bounds.upper_left.Y = fmaxf(bounds.upper_left.Y, to_draw.points[i].Y); + bounds.lower_right.X = fmaxf(bounds.lower_right.X, to_draw.points[i].X); + bounds.lower_right.Y = fminf(bounds.lower_right.Y, to_draw.points[i].Y); + } + + for (int i = 0; i < 4; i++) + { + to_draw.points[i] = AddV2(to_draw.points[i], t.pos); + } + + if (!t.dry_run) + { + Color col = t.color; + if (t.colors) + { + col = t.colors[i]; + } + + if (false) // drop shadow, don't really like it + if (t.world_space) + { + Quad shadow_quad = to_draw; + for (int i = 0; i < 4; i++) + { + shadow_quad.points[i] = AddV2(shadow_quad.points[i], V2(0.0, -1.0)); + } + draw_quad((DrawParams) { t.world_space, shadow_quad, image_font, font_atlas_region, (Color) { 0.0f, 0.0f, 0.0f, 0.4f }, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping }); + } + + draw_quad((DrawParams) { t.world_space, to_draw, image_font, font_atlas_region, col, t.clip_to, .layer = LAYER_UI_FG, .do_clipping = t.do_clipping }); + } + } + } + + bounds.upper_left = AddV2(bounds.upper_left, t.pos); + bounds.lower_right = AddV2(bounds.lower_right, t.pos); + return bounds; } AABB draw_centered_text(TextParams t) { - t.dry_run = true; - AABB text_aabb = draw_text(t); - t.dry_run = false; - Vec2 center_pos = t.pos; - t.pos = AddV2(center_pos, MulV2F(aabb_size(text_aabb), -0.5f)); - return draw_text(t); + t.dry_run = true; + AABB text_aabb = draw_text(t); + t.dry_run = false; + Vec2 center_pos = t.pos; + t.pos = AddV2(center_pos, MulV2F(aabb_size(text_aabb), -0.5f)); + return draw_text(t); } int sorting_key_at(Vec2 pos) { - return -(int)pos.y; + return -(int)pos.y; } void draw_shadow_for(DrawParams d) { - Quad sheared_quad = d.quad; - float height = d.quad.ur.y - d.quad.lr.y; - Vec2 shear_addition = V2(-height*0.35f, -height*0.2f); - sheared_quad.ul = AddV2(sheared_quad.ul, shear_addition); - sheared_quad.ur = AddV2(sheared_quad.ur, shear_addition); - d.quad = sheared_quad; - d.tint = (Color){0,0,0,0.2f}; - d.sorting_key -= 1; - d.alpha_clip_threshold = 0.0f; - dbgline(sheared_quad.ul, sheared_quad.ur); - dbgline(sheared_quad.ur, sheared_quad.lr); - dbgline(sheared_quad.lr, sheared_quad.ll); - dbgline(sheared_quad.ll, sheared_quad.ul); - draw_quad(d); + Quad sheared_quad = d.quad; + float height = d.quad.ur.y - d.quad.lr.y; + Vec2 shear_addition = V2(-height*0.35f, -height*0.2f); + sheared_quad.ul = AddV2(sheared_quad.ul, shear_addition); + sheared_quad.ur = AddV2(sheared_quad.ur, shear_addition); + d.quad = sheared_quad; + d.tint = (Color) { 0, 0, 0, 0.2f }; + d.sorting_key -= 1; + d.alpha_clip_threshold = 0.0f; + dbgline(sheared_quad.ul, sheared_quad.ur); + dbgline(sheared_quad.ur, sheared_quad.lr); + dbgline(sheared_quad.lr, sheared_quad.ll); + dbgline(sheared_quad.ll, sheared_quad.ul); + draw_quad(d); } //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){true, q, spritesheet_img, region, d.tint, .sorting_key = sorting_key_at(d.pos), .layer = LAYER_WORLD, }; - if(!d.no_shadow) draw_shadow_for(drawn); - draw_quad(drawn); + 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) { true, q, spritesheet_img, region, d.tint, .sorting_key = sorting_key_at(d.pos), .layer = LAYER_WORLD, }; + if (!d.no_shadow) draw_shadow_for(drawn); + draw_quad(drawn); } // gets aabbs overlapping the input aabb, including gs.entities and tiles Overlapping get_overlapping(Level *l, AABB aabb) { - Overlapping to_return = {0}; - - Quad q = quad_aabb(aabb); - // the corners, jessie - PROFILE_SCOPE("checking the corners") - for(int i = 0; i < 4; i++) - { - TileCoord to_check = world_to_tilecoord(q.points[i]); - TileInstance t = get_tile_layer(l, 2, to_check); - if(is_tile_solid(t)) - { - Overlap element = ((Overlap){.is_tile = true, .t = t}); - //{ (&to_return)[(&to_return)->cur_index++] = element; assert((&to_return)->cur_index < ARRLEN((&to_return)->data)); } - BUFF_APPEND(&to_return, element); - } - } - - // the gs.entities jessie - PROFILE_SCOPE("checking the entities") - ENTITIES_ITER(gs.entities) - { - if(!(it->is_character && it->is_rolling) && overlapping(aabb, entity_aabb(it))) - { - BUFF_APPEND(&to_return, (Overlap){.e = it}); - } - } - - return to_return; + Overlapping to_return = { 0 }; + + Quad q = quad_aabb(aabb); + // the corners, jessie + PROFILE_SCOPE("checking the corners") + for (int i = 0; i < 4; i++) + { + TileCoord to_check = world_to_tilecoord(q.points[i]); + TileInstance t = get_tile_layer(l, 2, to_check); + if (is_tile_solid(t)) + { + Overlap element = ((Overlap) { .is_tile = true, .t = t }); + //{ (&to_return)[(&to_return)->cur_index++] = element; assert((&to_return)->cur_index < ARRLEN((&to_return)->data)); } + BUFF_APPEND(&to_return, element); + } + } + + // the gs.entities jessie + PROFILE_SCOPE("checking the entities") + ENTITIES_ITER(gs.entities) + { + if (!(it->is_character && it->is_rolling) && overlapping(aabb, entity_aabb(it))) + { + BUFF_APPEND(&to_return, (Overlap) { .e = it }); + } + } + + return to_return; } typedef struct CollisionInfo { - bool happened; - Vec2 normal; + bool happened; + Vec2 normal; }CollisionInfo; typedef struct MoveSlideParams { - Entity *from; - Vec2 position; - Vec2 movement_this_frame; + Entity *from; + Vec2 position; + Vec2 movement_this_frame; - // optional - bool dont_collide_with_entities; - CollisionInfo *col_info_out; + // optional + bool dont_collide_with_entities; + CollisionInfo *col_info_out; } MoveSlideParams; // returns new pos after moving and sliding against collidable things Vec2 move_and_slide(MoveSlideParams p) { - Vec2 collision_aabb_size = entity_aabb_size(p.from); - Vec2 new_pos = AddV2(p.position, p.movement_this_frame); - assert(collision_aabb_size.x > 0.0f); - assert(collision_aabb_size.y > 0.0f); - AABB at_new = centered_aabb(new_pos, collision_aabb_size); - BUFF(AABB, 256) to_check = {0}; - - // add tilemap boxes - { - Vec2 at_new_size_vector = SubV2(at_new.lower_right, at_new.upper_left); - Vec2 points_to_check[] = { - AddV2(at_new.upper_left, V2(0.0, 0.0)), - AddV2(at_new.upper_left, V2(at_new_size_vector.X, 0.0)), - AddV2(at_new.upper_left, V2(at_new_size_vector.X, at_new_size_vector.Y)), - AddV2(at_new.upper_left, V2(0.0, at_new_size_vector.Y)), - }; - for(int i = 0; i < ARRLEN(points_to_check); i++) - { - Vec2 *it = &points_to_check[i]; - TileCoord tilecoord_to_check = world_to_tilecoord(*it); - - if(is_tile_solid(get_tile_layer(&level_level0, 2, tilecoord_to_check))) - { - AABB t = tile_aabb(tilecoord_to_check); - BUFF_APPEND(&to_check, t); - } - } - } - - // add entity boxes - if(!p.dont_collide_with_entities && !(p.from->is_character && p.from->is_rolling)) - { - ENTITIES_ITER(gs.entities) - { - if(!(it->is_character && it->is_rolling) && it != p.from && !(it->is_npc && it->dead) && !it->is_item) - { - BUFF_APPEND(&to_check, centered_aabb(it->pos, entity_aabb_size(it))); - } - } - } - - // here we do some janky C stuff to resolve collisions with the closest - // box first, because doing so is a simple heuristic to avoid depenetrating and losing - // sideways velocity. It's visual and I can't put diagrams in code so uh oh! - - typedef BUFF(AABB, 32) OverlapBuff; - OverlapBuff actually_overlapping = {0}; - - BUFF_ITER(AABB, &to_check) - { - if(overlapping(at_new, *it)) - { - BUFF_APPEND(&actually_overlapping, *it); - } - } - - - float smallest_distance = FLT_MAX; - int smallest_aabb_index = 0; - int i = 0; - BUFF_ITER(AABB, &actually_overlapping) - { - float cur_dist = LenV2(SubV2(aabb_center(at_new), aabb_center(*it))); - if(cur_dist < smallest_distance){ - smallest_distance = cur_dist; - smallest_aabb_index = i; - } - i++; - } - - - OverlapBuff overlapping_smallest_first = {0}; - if(actually_overlapping.cur_index > 0) - { - BUFF_APPEND(&overlapping_smallest_first, actually_overlapping.data[smallest_aabb_index]); - } - BUFF_ITER_I(AABB, &actually_overlapping, i) - { - if(i == smallest_aabb_index) - { - } - else - { - BUFF_APPEND(&overlapping_smallest_first, *it); - } - } - - // overlapping - BUFF_ITER(AABB, &overlapping_smallest_first) - { - dbgcol(GREEN) - { - dbgrect(*it); - } - } - - //overlapping_smallest_first = actually_overlapping; - - BUFF_ITER(AABB, &actually_overlapping) - dbgcol(WHITE) - dbgrect(*it); - - BUFF_ITER(AABB, &overlapping_smallest_first) - dbgcol(WHITE) - dbgsquare(aabb_center(*it)); - - CollisionInfo info = {0}; - for(int col_iter_i = 0; col_iter_i < 1; col_iter_i++) - BUFF_ITER(AABB, &overlapping_smallest_first) - { - AABB to_depenetrate_from = *it; - int iters_tried_to_push_apart = 0; - while(overlapping(to_depenetrate_from, at_new) && iters_tried_to_push_apart < 500) - { - const float move_dist = 0.1f; - - info.happened = true; - Vec2 from_point = aabb_center(to_depenetrate_from); - Vec2 to_player = NormV2(SubV2(aabb_center(at_new), from_point)); - Vec2 compass_dirs[4] = { - V2( 1.0, 0.0), - V2(-1.0, 0.0), - V2(0.0, 1.0), - V2(0.0, -1.0), - }; - int closest_index = -1; - float closest_dot = -99999999.0f; - for(int i = 0; i < 4; i++) - { - float dot = DotV2(compass_dirs[i], to_player); - if(dot > closest_dot) - { - closest_index = i; - closest_dot = dot; - } - } - assert(closest_index != -1); - Vec2 move_dir = compass_dirs[closest_index]; - info.normal = move_dir; - dbgvec(from_point, MulV2F(move_dir, 30.0f)); - Vec2 move = MulV2F(move_dir, move_dist); - at_new.upper_left = AddV2(at_new.upper_left,move); - at_new.lower_right = AddV2(at_new.lower_right,move); - iters_tried_to_push_apart++; - } - } - - if(p.col_info_out) *p.col_info_out = info; - - Vec2 result_pos = aabb_center(at_new); - dbgrect(centered_aabb(result_pos, collision_aabb_size)); - return result_pos; + Vec2 collision_aabb_size = entity_aabb_size(p.from); + Vec2 new_pos = AddV2(p.position, p.movement_this_frame); + assert(collision_aabb_size.x > 0.0f); + assert(collision_aabb_size.y > 0.0f); + AABB at_new = centered_aabb(new_pos, collision_aabb_size); + BUFF(AABB, 256) to_check = { 0 }; + + // add tilemap boxes + { + Vec2 at_new_size_vector = SubV2(at_new.lower_right, at_new.upper_left); + Vec2 points_to_check[] = { + AddV2(at_new.upper_left, V2(0.0, 0.0)), + AddV2(at_new.upper_left, V2(at_new_size_vector.X, 0.0)), + AddV2(at_new.upper_left, V2(at_new_size_vector.X, at_new_size_vector.Y)), + AddV2(at_new.upper_left, V2(0.0, at_new_size_vector.Y)), + }; + for (int i = 0; i < ARRLEN(points_to_check); i++) + { + Vec2 *it = &points_to_check[i]; + TileCoord tilecoord_to_check = world_to_tilecoord(*it); + + if (is_tile_solid(get_tile_layer(&level_level0, 2, tilecoord_to_check))) + { + AABB t = tile_aabb(tilecoord_to_check); + BUFF_APPEND(&to_check, t); + } + } + } + + // add entity boxes + if (!p.dont_collide_with_entities && !(p.from->is_character && p.from->is_rolling)) + { + ENTITIES_ITER(gs.entities) + { + if (!(it->is_character && it->is_rolling) && it != p.from && !(it->is_npc && it->dead) && !it->is_item) + { + BUFF_APPEND(&to_check, centered_aabb(it->pos, entity_aabb_size(it))); + } + } + } + + // here we do some janky C stuff to resolve collisions with the closest + // box first, because doing so is a simple heuristic to avoid depenetrating and losing + // sideways velocity. It's visual and I can't put diagrams in code so uh oh! + + typedef BUFF(AABB, 32) OverlapBuff; + OverlapBuff actually_overlapping = { 0 }; + + BUFF_ITER(AABB, &to_check) + { + if (overlapping(at_new, *it)) + { + BUFF_APPEND(&actually_overlapping, *it); + } + } + + + float smallest_distance = FLT_MAX; + int smallest_aabb_index = 0; + int i = 0; + BUFF_ITER(AABB, &actually_overlapping) + { + float cur_dist = LenV2(SubV2(aabb_center(at_new), aabb_center(*it))); + if (cur_dist < smallest_distance) { + smallest_distance = cur_dist; + smallest_aabb_index = i; + } + i++; + } + + + OverlapBuff overlapping_smallest_first = { 0 }; + if (actually_overlapping.cur_index > 0) + { + BUFF_APPEND(&overlapping_smallest_first, actually_overlapping.data[smallest_aabb_index]); + } + BUFF_ITER_I(AABB, &actually_overlapping, i) + { + if (i == smallest_aabb_index) + { + } + else + { + BUFF_APPEND(&overlapping_smallest_first, *it); + } + } + + // overlapping + BUFF_ITER(AABB, &overlapping_smallest_first) + { + dbgcol(GREEN) + { + dbgrect(*it); + } + } + + //overlapping_smallest_first = actually_overlapping; + + BUFF_ITER(AABB, &actually_overlapping) + dbgcol(WHITE) + dbgrect(*it); + + BUFF_ITER(AABB, &overlapping_smallest_first) + dbgcol(WHITE) + dbgsquare(aabb_center(*it)); + + CollisionInfo info = { 0 }; + for (int col_iter_i = 0; col_iter_i < 1; col_iter_i++) + BUFF_ITER(AABB, &overlapping_smallest_first) + { + AABB to_depenetrate_from = *it; + int iters_tried_to_push_apart = 0; + while (overlapping(to_depenetrate_from, at_new) && iters_tried_to_push_apart < 500) + { + const float move_dist = 0.1f; + + info.happened = true; + Vec2 from_point = aabb_center(to_depenetrate_from); + Vec2 to_player = NormV2(SubV2(aabb_center(at_new), from_point)); + Vec2 compass_dirs[4] = { + V2(1.0, 0.0), + V2(-1.0, 0.0), + V2(0.0, 1.0), + V2(0.0, -1.0), + }; + int closest_index = -1; + float closest_dot = -99999999.0f; + for (int i = 0; i < 4; i++) + { + float dot = DotV2(compass_dirs[i], to_player); + if (dot > closest_dot) + { + closest_index = i; + closest_dot = dot; + } + } + assert(closest_index != -1); + Vec2 move_dir = compass_dirs[closest_index]; + info.normal = move_dir; + dbgvec(from_point, MulV2F(move_dir, 30.0f)); + Vec2 move = MulV2F(move_dir, move_dist); + at_new.upper_left = AddV2(at_new.upper_left, move); + at_new.lower_right = AddV2(at_new.lower_right, move); + iters_tried_to_push_apart++; + } + } + + if (p.col_info_out) *p.col_info_out = info; + + Vec2 result_pos = aabb_center(at_new); + dbgrect(centered_aabb(result_pos, collision_aabb_size)); + return result_pos; } typedef struct { - bool dry_run; - Vec2 at_point; - float max_width; - char *text; - Color *colors; - float text_scale; - AABB clip_to; + bool dry_run; + Vec2 at_point; + float max_width; + char *text; + Color *colors; + float text_scale; + AABB clip_to; - bool screen_space; + bool screen_space; } WrappedTextParams; // returns next vertical cursor position float draw_wrapped_text(WrappedTextParams p) { - char *sentence_to_draw = p.text; - size_t sentence_len = strlen(sentence_to_draw); - - Vec2 cursor = p.at_point; - while(sentence_len > 0) - { - char line_to_draw[MAX_SENTENCE_LENGTH] = {0}; - Color colors_to_draw[MAX_SENTENCE_LENGTH] = {0}; - size_t chars_from_sentence = 0; - AABB line_bounds = {0}; - while(chars_from_sentence <= sentence_len) - { - memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); - memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); - - line_bounds = draw_text((TextParams){!p.screen_space, true, line_to_draw, cursor, BLACK, p.text_scale, p.clip_to}); - if(line_bounds.lower_right.X > p.at_point.X + p.max_width) - { - // too big - if(chars_from_sentence <= 0) chars_from_sentence = 1; // @CREDIT(warehouse56) always draw at least one character, if there's not enough room - chars_from_sentence -= 1; - break; - } - chars_from_sentence += 1; - } - if(chars_from_sentence > sentence_len) chars_from_sentence--; - memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); - memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); - memcpy(colors_to_draw, p.colors, chars_from_sentence*sizeof(Color)); - - //float line_height = line_bounds.upper_left.Y - line_bounds.lower_right.Y; - float line_height = font_line_advance * p.text_scale; - AABB drawn_bounds = draw_text((TextParams){!p.screen_space, p.dry_run, line_to_draw, AddV2(cursor, V2(0.0f, -line_height)), BLACK, p.text_scale, p.clip_to, colors_to_draw}); - if(!p.dry_run) dbgrect(drawn_bounds); - - // caught a random infinite loop in the debugger, this will stop it - assert(chars_from_sentence >= 0); // defensive programming - if(chars_from_sentence == 0) - { - break; - } - - sentence_len -= chars_from_sentence; - sentence_to_draw += chars_from_sentence; - p.colors += chars_from_sentence; - cursor = V2(drawn_bounds.upper_left.X, drawn_bounds.lower_right.Y); - } - - return cursor.Y; + char *sentence_to_draw = p.text; + size_t sentence_len = strlen(sentence_to_draw); + + Vec2 cursor = p.at_point; + while (sentence_len > 0) + { + char line_to_draw[MAX_SENTENCE_LENGTH] = { 0 }; + Color colors_to_draw[MAX_SENTENCE_LENGTH] = { 0 }; + size_t chars_from_sentence = 0; + AABB line_bounds = { 0 }; + while (chars_from_sentence <= sentence_len) + { + memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); + memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); + + line_bounds = draw_text((TextParams) { !p.screen_space, true, line_to_draw, cursor, BLACK, p.text_scale, p.clip_to }); + if (line_bounds.lower_right.X > p.at_point.X + p.max_width) + { + // too big + if (chars_from_sentence <= 0) chars_from_sentence = 1; // @CREDIT(warehouse56) always draw at least one character, if there's not enough room + chars_from_sentence -= 1; + break; + } + chars_from_sentence += 1; + } + if (chars_from_sentence > sentence_len) chars_from_sentence--; + memset(line_to_draw, 0, MAX_SENTENCE_LENGTH); + memcpy(line_to_draw, sentence_to_draw, chars_from_sentence); + memcpy(colors_to_draw, p.colors, chars_from_sentence*sizeof(Color)); + + //float line_height = line_bounds.upper_left.Y - line_bounds.lower_right.Y; + float line_height = font_line_advance * p.text_scale; + AABB drawn_bounds = draw_text((TextParams) { !p.screen_space, p.dry_run, line_to_draw, AddV2(cursor, V2(0.0f, -line_height)), BLACK, p.text_scale, p.clip_to, colors_to_draw }); + if (!p.dry_run) dbgrect(drawn_bounds); + + // caught a random infinite loop in the debugger, this will stop it + assert(chars_from_sentence >= 0); // defensive programming + if (chars_from_sentence == 0) + { + break; + } + + sentence_len -= chars_from_sentence; + sentence_to_draw += chars_from_sentence; + p.colors += chars_from_sentence; + cursor = V2(drawn_bounds.upper_left.X, drawn_bounds.lower_right.Y); + } + + return cursor.Y; } Sentence *last_said_sentence(Entity *npc) { - BUFF_ITER_I(Perception, &npc->remembered_perceptions, i) - { - bool is_last_said = i == npc->remembered_perceptions.cur_index - 1; - if(is_last_said && it->type == NPCDialog) - { - return &it->npc_dialog; - } - } - return 0; + BUFF_ITER_I(Perception, &npc->remembered_perceptions, i) + { + bool is_last_said = i == npc->remembered_perceptions.cur_index - 1; + if (is_last_said && it->type == NPCDialog) + { + return &it->npc_dialog; + } + } + return 0; } typedef enum { - DELEM_NPC, - DELEM_PLAYER, - DELEM_NPC_ACTION_DESCRIPTION, + DELEM_NPC, + DELEM_PLAYER, + DELEM_ACTION_DESCRIPTION, } DialogElementKind; typedef struct { - Sentence s; - DialogElementKind kind; + Sentence s; + DialogElementKind kind; } DialogElement; // Some perceptions can have multiple dialog elements. @@ -2091,139 +2089,149 @@ typedef struct typedef BUFF(DialogElement, REMEMBERED_PERCEPTIONS*2) Dialog; Dialog produce_dialog(Entity *talking_to, bool character_names) { - assert(talking_to->is_npc); - Dialog to_return = {0}; - BUFF_ITER(Perception, &talking_to->remembered_perceptions) - { - if(it->type == NPCDialog) - { - Sentence to_say = (Sentence){0}; - - if(it->npc_action_type == ACT_give_item) - { - DialogElement new = {0}; - printf_buff(&new.s, "%s gave %s to you", characters[talking_to->npc_kind].name, items[it->given_item].name); - new.kind = DELEM_NPC_ACTION_DESCRIPTION; - BUFF_APPEND(&to_return, new); - } - - if(character_names) - { - append_str(&to_say, characters[talking_to->npc_kind].name); - append_str(&to_say, ": "); - } - - Sentence *last_said = last_said_sentence(talking_to); - if(last_said == &it->npc_dialog) - { - for(int i = 0; i < min(it->npc_dialog.cur_index, (int)talking_to->characters_said); i++) - { - BUFF_APPEND(&to_say, it->npc_dialog.data[i]); - } - } - else - { - append_str(&to_say, it->npc_dialog.data); - } - BUFF_APPEND(&to_return, ((DialogElement){ .s = to_say, .kind = DELEM_NPC }) ); - } - else if(it->type == PlayerDialog) - { - Sentence to_say = (Sentence){0}; - if(character_names) - { - append_str(&to_say, "Player: "); - } - append_str(&to_say, it->player_dialog.data); - BUFF_APPEND(&to_return, ((DialogElement){ .s = to_say, .kind = DELEM_PLAYER }) ); - } - } - return to_return; + assert(talking_to->is_npc); + Dialog to_return = { 0 }; + BUFF_ITER(Perception, &talking_to->remembered_perceptions) + { + if (it->type == NPCDialog) + { + Sentence to_say = (Sentence) { 0 }; + + if (it->npc_action_type == ACT_give_item) + { + DialogElement new = { 0 }; + printf_buff(&new.s, "%s gave %s to you", characters[talking_to->npc_kind].name, items[it->given_item].name); + new.kind = DELEM_ACTION_DESCRIPTION; + BUFF_APPEND(&to_return, new); + } + + if (character_names) + { + append_str(&to_say, characters[talking_to->npc_kind].name); + append_str(&to_say, ": "); + } + + Sentence *last_said = last_said_sentence(talking_to); + if (last_said == &it->npc_dialog) + { + for (int i = 0; i < min(it->npc_dialog.cur_index, (int)talking_to->characters_said); i++) + { + BUFF_APPEND(&to_say, it->npc_dialog.data[i]); + } + } + else + { + append_str(&to_say, it->npc_dialog.data); + } + BUFF_APPEND(&to_return, ((DialogElement) { .s = to_say, .kind = DELEM_NPC })); + } + else if (it->type == PlayerAction) + { + if (it->player_action_type == ACT_give_item) + { + DialogElement new = { 0 }; + printf_buff(&new.s, "You gave %s to the NPC", items[it->given_item].name); + new.kind = DELEM_ACTION_DESCRIPTION; + BUFF_APPEND(&to_return, new); + } + } + else if (it->type == PlayerDialog) + { + Sentence to_say = (Sentence) { 0 }; + if (character_names) + { + append_str(&to_say, "Player: "); + } + append_str(&to_say, it->player_dialog.data); + BUFF_APPEND(&to_return, ((DialogElement) { .s = to_say, .kind = DELEM_PLAYER })); + } + } + return to_return; } void draw_dialog_panel(Entity *talking_to, float alpha) { - float panel_width = 250.0f; - float panel_height = 150.0f; - float panel_vert_offset = 30.0f; - AABB dialog_panel = (AABB){ - .upper_left = AddV2(talking_to->pos, V2(-panel_width/2.0f, panel_vert_offset+panel_height)), - .lower_right = AddV2(talking_to->pos, V2(panel_width/2.0f, panel_vert_offset)), - }; - AABB constrict_to = world_cam_aabb(); - dialog_panel.upper_left.x = fmaxf(constrict_to.upper_left.x, dialog_panel.upper_left.x); - dialog_panel.lower_right.y = fmaxf(constrict_to.lower_right.y, dialog_panel.lower_right.y); - dialog_panel.upper_left.y = fminf(constrict_to.upper_left.y, dialog_panel.upper_left.y); - dialog_panel.lower_right.x = fminf(constrict_to.lower_right.x, dialog_panel.lower_right.x); - - if(aabb_is_valid(dialog_panel)) - { - Quad dialog_quad = quad_aabb(dialog_panel); - float line_width = 2.0f; - Quad panel_quad = dialog_quad; - { - float inset = line_width; - panel_quad.ul = AddV2(panel_quad.ul, V2(inset, -inset)); - panel_quad.ll = AddV2(panel_quad.ll, V2(inset, inset)); - panel_quad.lr = AddV2(panel_quad.lr, V2(-inset, inset)); - panel_quad.ur = AddV2(panel_quad.ur, V2(-inset, -inset)); - } - colorquad(true, panel_quad, (Color){1.0f, 1.0f, 1.0f, 0.7f*alpha}); - Color line_color = (Color){0,0,0,alpha}; - line(AddV2(dialog_quad.ul, V2(-line_width,0.0)), AddV2(dialog_quad.ur, V2(line_width,0.0)), line_width, line_color); - line(dialog_quad.ur, dialog_quad.lr, line_width, line_color); - line(AddV2(dialog_quad.lr, V2(line_width,0.0)), AddV2(dialog_quad.ll, V2(-line_width,0.0)), line_width, line_color); - line(dialog_quad.ll, dialog_quad.ul, line_width, line_color); - - float padding = 5.0f; - dialog_panel.upper_left = AddV2(dialog_panel.upper_left, V2(padding, -padding)); - dialog_panel.lower_right = AddV2(dialog_panel.lower_right, V2(-padding, padding)); - - if(aabb_is_valid(dialog_panel)) - { - float new_line_height = dialog_panel.lower_right.Y; - - Dialog dialog = produce_dialog(talking_to, false); - if(dialog.cur_index > 0) - { - for(int i = dialog.cur_index - 1; i >= 0; i--) - { - DialogElement *it = &dialog.data[i]; - { - Color *colors = calloc(sizeof(*colors), it->s.cur_index); - for(int char_i = 0; char_i < it->s.cur_index; char_i++) - { - if(it->kind == DELEM_PLAYER) - { - colors[char_i] = BLACK; - } - else if(it->kind == DELEM_NPC) - { - colors[char_i] = colhex(0x345e22); - } - else if(it->kind == DELEM_NPC_ACTION_DESCRIPTION) - { - colors[char_i] = colhex(0xb5910e); - } - else - { - assert(false); - } - colors[char_i] = blendalpha(colors[char_i], alpha); - } - float measured_line_height = draw_wrapped_text((WrappedTextParams){true, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, dialog_panel}); - new_line_height += (new_line_height - measured_line_height); - draw_wrapped_text((WrappedTextParams){false, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, dialog_panel}); - - free(colors); - } - } - } - - dbgrect(dialog_panel); - } - } + float panel_width = 250.0f; + float panel_height = 150.0f; + float panel_vert_offset = 30.0f; + AABB dialog_panel = (AABB) { + .upper_left = AddV2(talking_to->pos, V2(-panel_width / 2.0f, panel_vert_offset + panel_height)), + .lower_right = AddV2(talking_to->pos, V2(panel_width / 2.0f, panel_vert_offset)), + }; + AABB constrict_to = world_cam_aabb(); + dialog_panel.upper_left.x = fmaxf(constrict_to.upper_left.x, dialog_panel.upper_left.x); + dialog_panel.lower_right.y = fmaxf(constrict_to.lower_right.y, dialog_panel.lower_right.y); + dialog_panel.upper_left.y = fminf(constrict_to.upper_left.y, dialog_panel.upper_left.y); + dialog_panel.lower_right.x = fminf(constrict_to.lower_right.x, dialog_panel.lower_right.x); + + if (aabb_is_valid(dialog_panel)) + { + Quad dialog_quad = quad_aabb(dialog_panel); + float line_width = 2.0f; + Quad panel_quad = dialog_quad; + { + float inset = line_width; + panel_quad.ul = AddV2(panel_quad.ul, V2(inset, -inset)); + panel_quad.ll = AddV2(panel_quad.ll, V2(inset, inset)); + panel_quad.lr = AddV2(panel_quad.lr, V2(-inset, inset)); + panel_quad.ur = AddV2(panel_quad.ur, V2(-inset, -inset)); + } + colorquad(true, panel_quad, (Color) { 1.0f, 1.0f, 1.0f, 0.7f*alpha }); + Color line_color = (Color) { 0, 0, 0, alpha }; + line(AddV2(dialog_quad.ul, V2(-line_width, 0.0)), AddV2(dialog_quad.ur, V2(line_width, 0.0)), line_width, line_color); + line(dialog_quad.ur, dialog_quad.lr, line_width, line_color); + line(AddV2(dialog_quad.lr, V2(line_width, 0.0)), AddV2(dialog_quad.ll, V2(-line_width, 0.0)), line_width, line_color); + line(dialog_quad.ll, dialog_quad.ul, line_width, line_color); + + float padding = 5.0f; + dialog_panel.upper_left = AddV2(dialog_panel.upper_left, V2(padding, -padding)); + dialog_panel.lower_right = AddV2(dialog_panel.lower_right, V2(-padding, padding)); + + if (aabb_is_valid(dialog_panel)) + { + float new_line_height = dialog_panel.lower_right.Y; + + Dialog dialog = produce_dialog(talking_to, false); + if (dialog.cur_index > 0) + { + for (int i = dialog.cur_index - 1; i >= 0; i--) + { + DialogElement *it = &dialog.data[i]; + { + Color *colors = calloc(sizeof(*colors), it->s.cur_index); + for (int char_i = 0; char_i < it->s.cur_index; char_i++) + { + if (it->kind == DELEM_PLAYER) + { + colors[char_i] = BLACK; + } + else if (it->kind == DELEM_NPC) + { + colors[char_i] = colhex(0x345e22); + } + else if (it->kind == DELEM_ACTION_DESCRIPTION) + { + colors[char_i] = colhex(0xb5910e); + } + else + { + assert(false); + } + colors[char_i] = blendalpha(colors[char_i], alpha); + } + float measured_line_height = draw_wrapped_text((WrappedTextParams) { true, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, dialog_panel }); + new_line_height += (new_line_height - measured_line_height); + draw_wrapped_text((WrappedTextParams) { false, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->s.data, colors, 0.5f, dialog_panel }); + + free(colors); + } + } + } + + dbgrect(dialog_panel); + } + } } @@ -2231,16 +2239,16 @@ void draw_dialog_panel(Entity *talking_to, float alpha) double elapsed_time = 0.0; double last_frame_processing_time = 0.0; uint64_t last_frame_time; -Vec2 mouse_pos = {0}; // in screen space +Vec2 mouse_pos = { 0 }; // in screen space typedef struct { - bool interact; - bool mouse_down; - bool mouse_up; + bool interact; + bool mouse_down; + bool mouse_up; } PressedState; -PressedState pressed = {0}; +PressedState pressed = { 0 }; bool mouse_down = false; float learned_shift = 0.0; float learned_space = 0.0; @@ -2251,2090 +2259,2089 @@ bool mouse_frozen = false; typedef struct { - float pressed_amount; // for buttons, 0.0 is completely unpressed (up), 1.0 is completely depressed (down) - bool is_being_pressed; + float pressed_amount; // for buttons, 0.0 is completely unpressed (up), 1.0 is completely depressed (down) + bool is_being_pressed; } IMState; struct { int key; IMState value; } *imui_state = 0; bool imbutton_key(AABB button_aabb, float text_scale, const char *text, int key, float dt, bool force_down) { - IMState state = hmget(imui_state, key); + IMState state = hmget(imui_state, key); - float raise = Lerp(0.0f, state.pressed_amount, 5.0f); - button_aabb.upper_left.y += raise; - button_aabb.lower_right.y += raise; + float raise = Lerp(0.0f, state.pressed_amount, 5.0f); + button_aabb.upper_left.y += raise; + button_aabb.lower_right.y += raise; - bool to_return = false; - float pressed_target = 0.5f; - if(has_point(button_aabb, mouse_pos)) - { - if(pressed.mouse_down) - { - state.is_being_pressed = true; - } + bool to_return = false; + float pressed_target = 0.5f; + if (has_point(button_aabb, mouse_pos)) + { + if (pressed.mouse_down) + { + state.is_being_pressed = true; + } - pressed_target = 1.0f; // when hovering button like pops out a bit + pressed_target = 1.0f; // when hovering button like pops out a bit - if(pressed.mouse_up) to_return = true; // when mouse released, and hovering over button, this is a button press - Lao Tzu - } - if(pressed.mouse_up) state.is_being_pressed = false; + if (pressed.mouse_up) to_return = true; // when mouse released, and hovering over button, this is a button press - Lao Tzu + } + if (pressed.mouse_up) state.is_being_pressed = false; - if(state.is_being_pressed || force_down) pressed_target = 0.0f; + if (state.is_being_pressed || force_down) pressed_target = 0.0f; - state.pressed_amount = Lerp(state.pressed_amount, dt*20.0f, pressed_target); + state.pressed_amount = Lerp(state.pressed_amount, dt*20.0f, pressed_target); - float button_alpha = Lerp(0.5f, state.pressed_amount, 1.0f); + float button_alpha = Lerp(0.5f, state.pressed_amount, 1.0f); - if(aabb_is_valid(button_aabb)) - { - draw_quad((DrawParams){false, quad_aabb(button_aabb), IMG(image_white_square), blendalpha(WHITE, button_alpha), .layer = LAYER_UI, }); - draw_centered_text((TextParams){false, false, text, aabb_center(button_aabb), BLACK, text_scale, .clip_to = button_aabb, .do_clipping = true}); - } + if (aabb_is_valid(button_aabb)) + { + draw_quad((DrawParams) { false, quad_aabb(button_aabb), IMG(image_white_square), blendalpha(WHITE, button_alpha), .layer = LAYER_UI, }); + draw_centered_text((TextParams) { false, false, text, aabb_center(button_aabb), BLACK, text_scale, .clip_to = button_aabb, .do_clipping = true }); + } - hmput(imui_state, key, state); - return to_return; + hmput(imui_state, key, state); + return to_return; } #define imbutton(...) imbutton_key(__VA_ARGS__, __LINE__, unwarped_dt, false) void draw_item(bool world_space, ItemKind kind, AABB in_aabb, float alpha) { - Quad drawn = quad_aabb(in_aabb); - if(kind == ITEM_Tripod) - { - draw_quad((DrawParams){world_space, drawn, IMG(image_tripod), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG}); - } - else if(kind == ITEM_Boots) - { - draw_quad((DrawParams){world_space, drawn, IMG(image_boots), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG}); - } - else if(kind == ITEM_WhiteSquare) - { - colorquad(world_space, drawn, blendalpha(WHITE, alpha)); - } - else - { - assert(false); - } + Quad drawn = quad_aabb(in_aabb); + if (kind == ITEM_Tripod) + { + draw_quad((DrawParams) { world_space, drawn, IMG(image_tripod), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG }); + } + else if (kind == ITEM_Boots) + { + draw_quad((DrawParams) { world_space, drawn, IMG(image_boots), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG }); + } + else if (kind == ITEM_Chalice) + { + draw_quad((DrawParams) { world_space, drawn, IMG(image_chalice), blendalpha(WHITE, alpha), .layer = LAYER_UI_FG }); + } + else if (kind == ITEM_WhiteSquare) + { + colorquad(world_space, drawn, blendalpha(WHITE, alpha)); + } + else + { + assert(false); + } } void frame(void) { - static float speed_factor = 1.0f; - // elapsed_time - double unwarped_dt_double = 0.0; - { - unwarped_dt_double = stm_sec(stm_diff(stm_now(), last_frame_time)); - unwarped_dt_double = fmin(unwarped_dt_double, MINIMUM_TIMESTEP * 5.0); // clamp dt at maximum 5 frames, avoid super huge dt - elapsed_time += unwarped_dt_double*speed_factor; - last_frame_time = stm_now(); - } - double dt_double = unwarped_dt_double*speed_factor; - float unwarped_dt = (float)unwarped_dt_double; - float dt = (float)dt_double; + static float speed_factor = 1.0f; + // elapsed_time + double unwarped_dt_double = 0.0; + { + unwarped_dt_double = stm_sec(stm_diff(stm_now(), last_frame_time)); + unwarped_dt_double = fmin(unwarped_dt_double, MINIMUM_TIMESTEP * 5.0); // clamp dt at maximum 5 frames, avoid super huge dt + elapsed_time += unwarped_dt_double*speed_factor; + last_frame_time = stm_now(); + } + double dt_double = unwarped_dt_double*speed_factor; + float unwarped_dt = (float)unwarped_dt_double; + float dt = (float)dt_double; #if 0 - { - printf("Frametime: %.1f ms\n", dt*1000.0); - sg_begin_default_pass(&state.pass_action, sapp_width(), sapp_height()); - sg_apply_pipeline(state.pip); - - //colorquad(false, quad_at(V2(0.0, 100.0), V2(100.0f, 100.0f)), RED); - sg_image img = image_white_square; - AABB region = full_region(img); - //region.lower_right.X *= 0.5f; - draw_quad((DrawParams){false,quad_at(V2(0.0, 100.0), V2(100.0f, 100.0f)), img, region, WHITE}); - - - flush_quad_batch(); - sg_end_pass(); - sg_commit(); - reset(&scratch); - } - return; + { + printf("Frametime: %.1f ms\n", dt*1000.0); + sg_begin_default_pass(&state.pass_action, sapp_width(), sapp_height()); + sg_apply_pipeline(state.pip); + + //colorquad(false, quad_at(V2(0.0, 100.0), V2(100.0f, 100.0f)), RED); + sg_image img = image_white_square; + AABB region = full_region(img); + //region.lower_right.X *= 0.5f; + draw_quad((DrawParams) { false, quad_at(V2(0.0, 100.0), V2(100.0f, 100.0f)), img, region, WHITE }); + + + flush_quad_batch(); + sg_end_pass(); + sg_commit(); + reset(&scratch); + } + return; #endif - PROFILE_SCOPE("frame") - { - - // better for vertical aspect ratios - if(screen_size().x < 0.7f*screen_size().y) - { - cam.scale = 2.3f; - } - else - { - cam.scale = 2.0f; - } - - - uint64_t time_start_frame = stm_now(); - - Vec2 movement = {0}; - bool attack = false; - bool roll = false; - bool interact = false; - if(mobile_controls) - { - movement = SubV2(thumbstick_nub_pos, thumbstick_base_pos); - if(LenV2(movement) > 0.0f) - { - movement = MulV2F(NormV2(movement), LenV2(movement)/(thumbstick_base_size()*0.5f)); - } - attack = mobile_attack_pressed; - roll = mobile_roll_pressed; - interact = pressed.interact; - } - else - { - movement = V2( - (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A], - (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S] - ); - attack = keydown[SAPP_KEYCODE_SPACE]; + PROFILE_SCOPE("frame") + { + + // better for vertical aspect ratios + if (screen_size().x < 0.7f*screen_size().y) + { + cam.scale = 2.3f; + } + else + { + cam.scale = 2.0f; + } + + + uint64_t time_start_frame = stm_now(); + + Vec2 movement = { 0 }; + bool attack = false; + bool roll = false; + bool interact = false; + if (mobile_controls) + { + movement = SubV2(thumbstick_nub_pos, thumbstick_base_pos); + if (LenV2(movement) > 0.0f) + { + movement = MulV2F(NormV2(movement), LenV2(movement) / (thumbstick_base_size()*0.5f)); + } + attack = mobile_attack_pressed; + roll = mobile_roll_pressed; + interact = pressed.interact; + } + else + { + movement = V2( + (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A], + (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S] + ); + attack = keydown[SAPP_KEYCODE_SPACE]; #ifdef DEVTOOLS - attack = attack || keydown[SAPP_KEYCODE_LEFT_CONTROL]; + attack = attack || keydown[SAPP_KEYCODE_LEFT_CONTROL]; #endif - roll = keydown[ROLL_KEY]; - interact = pressed.interact; - } - if(LenV2(movement) > 1.0) - { - movement = NormV2(movement); - } + roll = keydown[ROLL_KEY]; + interact = pressed.interact; + } + if (LenV2(movement) > 1.0) + { + movement = NormV2(movement); + } - sg_begin_default_pass(&state.pass_action, sapp_width(), sapp_height()); - sg_apply_pipeline(state.pip); + sg_begin_default_pass(&state.pass_action, sapp_width(), sapp_height()); + sg_apply_pipeline(state.pip); - Level *cur_level = &level_level0; + Level *cur_level = &level_level0; - // Draw Tilemap draw tilemap tilemap drawing + // Draw Tilemap draw tilemap tilemap drawing #if 1 - PROFILE_SCOPE("tilemap") - { - Vec2 starting_world = AddV2(world_cam_aabb().upper_left, V2(-TILE_SIZE, TILE_SIZE)); - Vec2 ending_world = AddV2(world_cam_aabb().lower_right, V2(TILE_SIZE, -TILE_SIZE)); - - TileCoord starting_point = world_to_tilecoord(starting_world); - TileCoord ending_point = world_to_tilecoord(ending_world); - - int starting_row = starting_point.y; - int ending_row = ending_point.y; - - int starting_col = starting_point.x; - int ending_col = ending_point.x; - - for(int layer = 0; layer < LAYERS; layer++) - { - for(int row = starting_row; row < ending_row; row++) - { - for(int col = starting_col; col < ending_col; col++) - { - TileCoord cur_coord = { col, row }; - TileInstance cur = get_tile_layer(cur_level, layer, cur_coord); - - int tileset_i = 0; - uint16_t max_gid = 0; - for(int i = 0; i < ARRLEN(tilesets); i++) - { - TileSet tileset = tilesets[i]; - if(cur.kind > tileset.first_gid && tileset.first_gid > max_gid) - { - tileset_i = i; - max_gid = tileset.first_gid; - } - } - - TileSet tileset = tilesets[tileset_i]; - cur.kind -= tileset.first_gid - 1; - - if(cur.kind != 0) - { - Vec2 tile_size = V2(TILE_SIZE, TILE_SIZE); - - sg_image tileset_image = *tileset.img; - - Vec2 tile_image_coord = tile_id_to_coord(tileset_image, tile_size, cur.kind); - - AnimatedTile *anim = NULL; - for(int i = 0; i < sizeof(tileset.animated)/sizeof(*tileset.animated); i++) - { - if(tileset.animated[i].exists && tileset.animated[i].id_from == cur.kind-1) - { - anim = &tileset.animated[i]; - } - } - if(anim) - { - double time_per_frame = 0.1; - int frame_index = (int)(elapsed_time/time_per_frame) % anim->num_frames; - tile_image_coord = tile_id_to_coord(tileset_image, tile_size, anim->frames[frame_index]+1); - } - - AABB region; - region.upper_left = tile_image_coord; - region.lower_right = AddV2(region.upper_left, tile_size); - - draw_quad((DrawParams){true, tile_quad(cur_coord), tileset_image, region, WHITE, .layer = LAYER_TILEMAP}); - } - } - } - } - } + PROFILE_SCOPE("tilemap") + { + Vec2 starting_world = AddV2(world_cam_aabb().upper_left, V2(-TILE_SIZE, TILE_SIZE)); + Vec2 ending_world = AddV2(world_cam_aabb().lower_right, V2(TILE_SIZE, -TILE_SIZE)); + + TileCoord starting_point = world_to_tilecoord(starting_world); + TileCoord ending_point = world_to_tilecoord(ending_world); + + int starting_row = starting_point.y; + int ending_row = ending_point.y; + + int starting_col = starting_point.x; + int ending_col = ending_point.x; + + for (int layer = 0; layer < LAYERS; layer++) + { + for (int row = starting_row; row < ending_row; row++) + { + for (int col = starting_col; col < ending_col; col++) + { + TileCoord cur_coord = { col, row }; + TileInstance cur = get_tile_layer(cur_level, layer, cur_coord); + + int tileset_i = 0; + uint16_t max_gid = 0; + for (int i = 0; i < ARRLEN(tilesets); i++) + { + TileSet tileset = tilesets[i]; + if (cur.kind > tileset.first_gid && tileset.first_gid > max_gid) + { + tileset_i = i; + max_gid = tileset.first_gid; + } + } + + TileSet tileset = tilesets[tileset_i]; + cur.kind -= tileset.first_gid - 1; + + if (cur.kind != 0) + { + Vec2 tile_size = V2(TILE_SIZE, TILE_SIZE); + + sg_image tileset_image = *tileset.img; + + Vec2 tile_image_coord = tile_id_to_coord(tileset_image, tile_size, cur.kind); + + AnimatedTile *anim = NULL; + for (int i = 0; i < sizeof(tileset.animated) / sizeof(*tileset.animated); i++) + { + if (tileset.animated[i].exists && tileset.animated[i].id_from == cur.kind-1) + { + anim = &tileset.animated[i]; + } + } + if (anim) + { + double time_per_frame = 0.1; + int frame_index = (int)(elapsed_time / time_per_frame) % anim->num_frames; + tile_image_coord = tile_id_to_coord(tileset_image, tile_size, anim->frames[frame_index] + 1); + } + + AABB region; + region.upper_left = tile_image_coord; + region.lower_right = AddV2(region.upper_left, tile_size); + + draw_quad((DrawParams) { true, tile_quad(cur_coord), tileset_image, region, WHITE, .layer = LAYER_TILEMAP }); + } + } + } + } + } #endif - assert(player != NULL); - - // gameplay processing loop, do multiple if lagging - // these are static so that, on frames where no gameplay processing is necessary and just rendering, the rendering uses values from last frame - static Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on - static bool player_in_combat = false; - const float dialog_interact_size = 2.5f * TILE_SIZE; - - float speed_target; - if(player->in_conversation_mode) - { - speed_target = 0.0f; - } - else - { - speed_target = 1.0f; - } - speed_factor = Lerp(speed_factor, unwarped_dt*10.0f, speed_target); - if(fabsf(speed_factor - speed_target) <= 0.05f) - { - speed_factor = speed_target; - } - int num_timestep_loops = 0; - // restore the pressed state after gameplay loop so pressed input events can be processed in the - // rendering correctly as well - PressedState before_gameplay_loops = pressed; - { - unprocessed_gameplay_time += unwarped_dt; - float timestep = fminf(unwarped_dt, (float)MINIMUM_TIMESTEP); - while(unprocessed_gameplay_time >= timestep) - { - num_timestep_loops++; - unprocessed_gameplay_time -= timestep; - float unwarped_dt = timestep; - float dt = unwarped_dt*speed_factor; - - // process gs.entities - player_in_combat = false; // in combat set by various enemies when they fight the player - PROFILE_SCOPE("entity processing") - { - ENTITIES_ITER(gs.entities) - { - assert(!(it->exists && it->generation == 0)); + assert(player != NULL); + + // gameplay processing loop, do multiple if lagging + // these are static so that, on frames where no gameplay processing is necessary and just rendering, the rendering uses values from last frame + static Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on + static bool player_in_combat = false; + const float dialog_interact_size = 2.5f * TILE_SIZE; + + float speed_target; + if (player->in_conversation_mode) + { + speed_target = 0.0f; + } + else + { + speed_target = 1.0f; + } + speed_factor = Lerp(speed_factor, unwarped_dt*10.0f, speed_target); + if (fabsf(speed_factor - speed_target) <= 0.05f) + { + speed_factor = speed_target; + } + int num_timestep_loops = 0; + // restore the pressed state after gameplay loop so pressed input events can be processed in the + // rendering correctly as well + PressedState before_gameplay_loops = pressed; + { + unprocessed_gameplay_time += unwarped_dt; + float timestep = fminf(unwarped_dt, (float)MINIMUM_TIMESTEP); + while (unprocessed_gameplay_time >= timestep) + { + num_timestep_loops++; + unprocessed_gameplay_time -= timestep; + float unwarped_dt = timestep; + float dt = unwarped_dt*speed_factor; + + // process gs.entities + player_in_combat = false; // in combat set by various enemies when they fight the player + PROFILE_SCOPE("entity processing") + { + ENTITIES_ITER(gs.entities) + { + assert(!(it->exists && it->generation == 0)); #ifdef WEB - if(it->is_npc) - { - if(it->gen_request_id != 0) - { - assert(it->gen_request_id > 0); - int status = EM_ASM_INT({ - return get_generation_request_status($0); - }, it->gen_request_id); - if(status == 0) - { - // simply not done yet - } - else - { - if(status == 1) - { - // done! we can get the string - char sentence_str[MAX_SENTENCE_LENGTH] = {0}; - EM_ASM({ - let generation = get_generation_request_content($0); - stringToUTF8(generation, $1, $2); - }, it->gen_request_id, sentence_str, ARRLEN(sentence_str)); - - - // parse out from the sentence NPC action and dialog - Perception out = {0}; + if (it->is_npc) + { + if (it->gen_request_id != 0) + { + assert(it->gen_request_id > 0); + int status = EM_ASM_INT( { + return get_generation_request_status($0); + }, it->gen_request_id); + if (status == 0) + { + // simply not done yet + } + else + { + if (status == 1) + { + // done! we can get the string + char sentence_str[MAX_SENTENCE_LENGTH] = { 0 }; + EM_ASM( { + let generation = get_generation_request_content($0); + stringToUTF8(generation, $1, $2); + }, it->gen_request_id, sentence_str, ARRLEN(sentence_str)); + + + // parse out from the sentence NPC action and dialog + Perception out = { 0 }; #ifdef DO_CHATGPT_PARSING - bool text_was_well_formatted = parse_chatgpt_response(it, sentence_str, &out); + bool text_was_well_formatted = parse_chatgpt_response(it, sentence_str, &out); #else - bool text_was_well_formatted = parse_ai_response(it, sentence_str, &out); + bool text_was_well_formatted = parse_ai_response(it, sentence_str, &out); #endif - if(text_was_well_formatted) - { - process_perception(it, out); - } - else - { - it->perceptions_dirty = true; // on poorly formatted AI, just retry request. - } - - EM_ASM({ - done_with_generation_request($0); - }, it->gen_request_id); - } - else if(status == 2) - { - Log("Failed to generate dialog! Fuck!\n"); - // need somethin better here. Maybe each sentence has to know if it's player or NPC, that way I can remove the player's dialog - process_perception(it, (Perception){.type = NPCDialog, .npc_action_type = ACT_none, .npc_dialog = SENTENCE_CONST("I'm not sure...")}); - } - else if(status == -1) - { - Log("Generation request doesn't exist anymore, that's fine...\n"); - } - else - { - Log("Unknown generation request status: %d\n", status); - } - it->gen_request_id = 0; - } - } - } + if (text_was_well_formatted) + { + process_perception(it, out, player); + } + else + { + it->perceptions_dirty = true; // on poorly formatted AI, just retry request. + } + + EM_ASM( { + done_with_generation_request($0); + }, it->gen_request_id); + } + else if (status == 2) + { + Log("Failed to generate dialog! Fuck!\n"); + // need somethin better here. Maybe each sentence has to know if it's player or NPC, that way I can remove the player's dialog + process_perception(it, (Perception) { .type = NPCDialog, .npc_action_type = ACT_none, .npc_dialog = SENTENCE_CONST("I'm not sure...") }, player); + } + else if (status == -1) + { + Log("Generation request doesn't exist anymore, that's fine...\n"); + } + else + { + Log("Unknown generation request status: %d\n", status); + } + it->gen_request_id = 0; + } + } + } #endif - 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(player->in_conversation_mode) - { - - if(has_point(entity_aabb(it), screen_to_world(mouse_pos))) - { - it->being_hovered = true; - if(pressed.mouse_down) - { - player->talking_to = frome(it); - player->state = CHARACTER_TALKING; - } - } - } - - if(it->is_npc) - { - // character speech animation text input - if(true) - { - const float characters_per_sec = 35.0f; - double before = it->characters_said; - - int length = 0; - if(last_said_sentence(it)) length = last_said_sentence(it)->cur_index; - if((int)before < length) - { - it->characters_said += characters_per_sec*unwarped_dt; - } - else - { - it->characters_said = (double)length; - } - - if( (int)it->characters_said > (int)before ) - { - float dist = LenV2(SubV2(it->pos, player->pos)); - float volume = Lerp(-0.6f, clamp01(dist/70.0f), -1.0f); - play_audio(&sound_simple_talk, volume); - } - } - - - if(it->standing == STANDING_FIGHTING || it->standing == STANDING_JOINED) - { - Entity *targeting = player; - - /* - G cost: distance from the current node to the start node - H cost: distance from the current node to the target node - - G H - SUM - F cost: G + H - */ - 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; - float f_score; // total of g score and h score - float g_score; // distance from the node to the start node - Vec2 pos; - } AStarNode; - - BUFF(AStarNode, MAX_ASTAR_NODES) nodes = {0}; - struct { Vec2 key; AStarNode *value; } *node_cache = 0; + 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 (player->in_conversation_mode) + { + + if (has_point(entity_aabb(it), screen_to_world(mouse_pos))) + { + it->being_hovered = true; + if (pressed.mouse_down) + { + player->talking_to = frome(it); + player->state = CHARACTER_TALKING; + } + } + } + + if (it->is_npc) + { + // character speech animation text input + if (true) + { + const float characters_per_sec = 35.0f; + double before = it->characters_said; + + int length = 0; + if (last_said_sentence(it)) length = last_said_sentence(it)->cur_index; + if ((int)before < length) + { + it->characters_said += characters_per_sec*unwarped_dt; + } + else + { + it->characters_said = (double)length; + } + + if ((int)it->characters_said > (int)before) + { + float dist = LenV2(SubV2(it->pos, player->pos)); + float volume = Lerp(-0.6f, clamp01(dist / 70.0f), -1.0f); + play_audio(&sound_simple_talk, volume); + } + } + + + if (it->standing == STANDING_FIGHTING || it->standing == STANDING_JOINED) + { + Entity *targeting = player; + + /* + G cost: distance from the current node to the start node + H cost: distance from the current node to the target node + + G H + SUM + F cost: G + H + */ + 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; + float f_score; // total of g score and h score + float g_score; // distance from the node to the start node + Vec2 pos; + } AStarNode; + + BUFF(AStarNode, MAX_ASTAR_NODES) nodes = { 0 }; + struct { Vec2 key; AStarNode *value; } *node_cache = 0; #define V2_HASH(v) (FloorV2(v)) - const float jump_size = TILE_SIZE/2.0f; - BUFF_APPEND(&nodes, ((AStarNode){.in_open_set = true, .pos = from})); - Vec2 from_hash = V2_HASH(from); - float got_there_tolerance = max_coord(entity_aabb_size(player))*1.5f; - hmput(node_cache, from_hash, &nodes.data[0]); - - bool should_quit = false; - AStarNode *last_node = 0; - PROFILE_SCOPE("A* Pathfinding") // astar pathfinding a star - 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 - { - 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 - { - 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) - { - Vec2 cur_pos = *it; - - dbgsquare(cur_pos); - - bool would_block_me = false; - - PROFILE_SCOPE("Checking for overlap") - { - Overlapping overlapping_at_want = get_overlapping(&level_level0, entity_aabb_at(e, cur_pos)); - BUFF_ITER(Overlap, &overlapping_at_want) if(is_overlap_collision(*it) && !(it->e && it->e == 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(V2ApproxEq(it->pos, cur_pos)) - { - existing = it; - break; - } - } - - float tentative_gscore = current->g_score + jump_size; - if(tentative_gscore < (existing ? existing->g_score : INFINITY)) - { - 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; - // 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; - } - } - } - } - } - } - } - - hmfree(node_cache); - node_cache = 0; - - // reconstruct path - if(succeeded) - { - assert(last_node); - AStarNode *cur = last_node; - while(cur) - { - BUFF_PUSH_FRONT(&path, cur->pos); - cur = cur->parent; - } - } - - if(succeeded) - it->cached_path = cache_path(elapsed_time, &path); - } - - Vec2 next_point_on_path = {0}; - if(succeeded) - { - float nearest_dist = INFINITY; - int nearest_index = -1; - Entity *from = it; - BUFF_ITER_I(Vec2, &path, i) - { - float dist = LenV2(SubV2(*it, from->pos)); - if(dist < nearest_dist) - { - nearest_dist = dist; - nearest_index = i; - } - } - assert(nearest_index >= 0); - int target_index = (nearest_index + 1); - - if(target_index >= path.cur_index) - { - next_point_on_path = to; - } - else - { - next_point_on_path = path.data[target_index]; - } - } - - BUFF_ITER_I(Vec2, &path, i) - { - if(i == 0) - { - } - else - { - dbgcol(BLUE) dbgline(*it, path.data[i-1]); - } - } - - { - if(npc_attacks_with_sword(it)) - { - if(fabsf(it->vel.x) > 0.01f) - it->facing_left = it->vel.x < 0.0f; - - it->pos = move_and_slide((MoveSlideParams){it, it->pos, MulV2F(it->vel, pixels_per_meter * dt)}); - AABB weapon_aabb = entity_sword_aabb(it, 30.0f, 18.0f); - Vec2 target_vel = {0}; - Overlapping overlapping_weapon = get_overlapping(cur_level, weapon_aabb); - if(it->swing_timer > 0.0) - { - player_in_combat = true; - it->swing_timer += dt; - if(it->swing_timer >= anim_sprite_duration(ANIM_skeleton_swing_sword)) - { - it->swing_timer = 0.0; - } - if(it->swing_timer >= 0.4f) - { - SwordToDamage to_damage = entity_sword_to_do_damage(it, overlapping_weapon); - Entity *from = it; - BUFF_ITER(Entity *, &to_damage) - { - request_do_damage(*it, from, DAMAGE_SWORD); - } - } - } - else - { - // in huntin' range - //it->walking = LenV2(SubV2(player->pos, it->pos)) < 250.0f; - it->walking = true; - if(it->walking) - { - player_in_combat = true; - Entity *skele = it; - BUFF_ITER(Overlap, &overlapping_weapon) - { - if(it->e && it->e->is_character) - { - skele->swing_timer += dt; - BUFF_CLEAR(&skele->done_damage_to_this_swing); - } - } - target_vel = MulV2F(NormV2(SubV2(next_point_on_path, it->pos)), PLAYER_ROLL_SPEED); - } - else - { - } - } - it->vel = LerpV2(it->vel, dt*8.0f, target_vel); - } - - if(npc_attacks_with_shotgun(it)) - if(succeeded) - { - Vec2 to_player = NormV2(SubV2(targeting->pos, it->pos)); - Vec2 rotate_direction; - if(it->direction_of_spiral_pattern) - { - rotate_direction = rotate_counter_clockwise(to_player); - } - else - { - rotate_direction = rotate_clockwise(to_player); - } - Vec2 target_vel = NormV2(SubV2(next_point_on_path, it->pos)); - target_vel = MulV2F(target_vel, 3.0f); - it->vel = LerpV2(it->vel, 15.0f * dt, target_vel); - CollisionInfo col = {0}; - it->pos = move_and_slide((MoveSlideParams){it, it->pos, MulV2F(it->vel, pixels_per_meter * dt), .col_info_out = &col}); - if(col.happened) - { - it->direction_of_spiral_pattern = !it->direction_of_spiral_pattern; - } - - if(it->standing == STANDING_FIGHTING) - { - it->shotgun_timer += dt; - Vec2 to_player = NormV2(SubV2(targeting->pos, it->pos)); - if(it->shotgun_timer >= 1.0f) - { - it->shotgun_timer = 0.0f; - const float spread = (float)PI/4.0f; - // shoot shotgun - int num_bullets = 5; - for(int i = 0; i < num_bullets; i++) - { - Vec2 dir = to_player; - float theta = Lerp(-spread/2.0f, ((float)i / (float)(num_bullets - 1)), spread/2.0f); - dir = RotateV2(dir, theta); - Entity *new_bullet = new_entity(); - new_bullet->is_bullet = true; - new_bullet->pos = AddV2(it->pos, MulV2F(dir, 20.0f)); - new_bullet->vel = MulV2F(dir, 15.0f); - it->vel = AddV2(it->vel, MulV2F(dir, -3.0f)); - } - } - } - - } - } - } - if(it->npc_kind == NPC_OldMan) - { - /* - draw_dialog_panel(it); - Entity *targeting = player; - it->shotgun_timer += dt; - Vec2 to_player = NormV2(SubV2(targeting->pos, it->pos)); - if(it->shotgun_timer >= 1.0f) - { - it->shotgun_timer = 0.0f; - const float spread = (float)PI/4.0f; - // shoot shotgun - int num_bullets = 5; - for(int i = 0; i < num_bullets; i++) - { - Vec2 dir = to_player; - float theta = Lerp(-spread/2.0f, ((float)i / (float)(num_bullets - 1)), spread/2.0f); - dir = RotateV2(dir, theta); - Entity *new_bullet = new_entity(); - new_bullet->is_bullet = true; - new_bullet->pos = AddV2(it->pos, MulV2F(dir, 20.0f)); - new_bullet->vel = MulV2F(dir, 15.0f); - it->vel = AddV2(it->vel, MulV2F(dir, -3.0f)); - } - } - - Vec2 target_vel = NormV2(AddV2(rotate_counter_clockwise(to_player), MulV2F(to_player, 0.5f))); - target_vel = MulV2F(target_vel, 3.0f); - it->vel = LerpV2(it->vel, 15.0f * dt, target_vel); - it->pos = move_and_slide((MoveSlideParams){it, it->pos, MulV2F(it->vel, pixels_per_meter * dt)}); - */ - } - else if(npc_is_skeleton(it)) - { - if(it->dead) - { - } - else - { - } // skelton combat and movement - } - else if(it->npc_kind == NPC_Death) - { - } + const float jump_size = TILE_SIZE / 2.0f; + BUFF_APPEND(&nodes, ((AStarNode) { .in_open_set = true, .pos = from })); + Vec2 from_hash = V2_HASH(from); + float got_there_tolerance = max_coord(entity_aabb_size(player))*1.5f; + hmput(node_cache, from_hash, &nodes.data[0]); + + bool should_quit = false; + AStarNode *last_node = 0; + PROFILE_SCOPE("A* Pathfinding") // astar pathfinding a star + 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 + { + 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 + { + 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) + { + Vec2 cur_pos = *it; + + dbgsquare(cur_pos); + + bool would_block_me = false; + + PROFILE_SCOPE("Checking for overlap") + { + Overlapping overlapping_at_want = get_overlapping(&level_level0, entity_aabb_at(e, cur_pos)); + BUFF_ITER(Overlap, &overlapping_at_want) if (is_overlap_collision(*it) && !(it->e && it->e == 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 (V2ApproxEq(it->pos, cur_pos)) + { + existing = it; + break; + } + } + + float tentative_gscore = current->g_score + jump_size; + if (tentative_gscore < (existing ? existing->g_score : INFINITY)) + { + 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; + // 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; + } + } + } + } + } + } + } + + hmfree(node_cache); + node_cache = 0; + + // reconstruct path + if (succeeded) + { + assert(last_node); + AStarNode *cur = last_node; + while (cur) + { + BUFF_PUSH_FRONT(&path, cur->pos); + cur = cur->parent; + } + } + + if (succeeded) + it->cached_path = cache_path(elapsed_time, &path); + } + + Vec2 next_point_on_path = { 0 }; + if (succeeded) + { + float nearest_dist = INFINITY; + int nearest_index = -1; + Entity *from = it; + BUFF_ITER_I(Vec2, &path, i) + { + float dist = LenV2(SubV2(*it, from->pos)); + if (dist < nearest_dist) + { + nearest_dist = dist; + nearest_index = i; + } + } + assert(nearest_index >= 0); + int target_index = (nearest_index + 1); + + if (target_index >= path.cur_index) + { + next_point_on_path = to; + } + else + { + next_point_on_path = path.data[target_index]; + } + } + + BUFF_ITER_I(Vec2, &path, i) + { + if (i == 0) + { + } + else + { + dbgcol(BLUE) dbgline(*it, path.data[i-1]); + } + } + + { + if (npc_attacks_with_sword(it)) + { + if (fabsf(it->vel.x) > 0.01f) + it->facing_left = it->vel.x < 0.0f; + + it->pos = move_and_slide((MoveSlideParams) { it, it->pos, MulV2F(it->vel, pixels_per_meter * dt) }); + AABB weapon_aabb = entity_sword_aabb(it, 30.0f, 18.0f); + Vec2 target_vel = { 0 }; + Overlapping overlapping_weapon = get_overlapping(cur_level, weapon_aabb); + if (it->swing_timer > 0.0) + { + player_in_combat = true; + it->swing_timer += dt; + if (it->swing_timer >= anim_sprite_duration(ANIM_skeleton_swing_sword)) + { + it->swing_timer = 0.0; + } + if (it->swing_timer >= 0.4f) + { + SwordToDamage to_damage = entity_sword_to_do_damage(it, overlapping_weapon); + Entity *from = it; + BUFF_ITER(Entity *, &to_damage) + { + request_do_damage(*it, from, DAMAGE_SWORD); + } + } + } + else + { + // in huntin' range + //it->walking = LenV2(SubV2(player->pos, it->pos)) < 250.0f; + it->walking = true; + if (it->walking) + { + player_in_combat = true; + Entity *skele = it; + BUFF_ITER(Overlap, &overlapping_weapon) + { + if (it->e && it->e->is_character) + { + skele->swing_timer += dt; + BUFF_CLEAR(&skele->done_damage_to_this_swing); + } + } + target_vel = MulV2F(NormV2(SubV2(next_point_on_path, it->pos)), PLAYER_ROLL_SPEED); + } + else + { + } + } + it->vel = LerpV2(it->vel, dt*8.0f, target_vel); + } + + if (npc_attacks_with_shotgun(it)) + if (succeeded) + { + Vec2 to_player = NormV2(SubV2(targeting->pos, it->pos)); + Vec2 rotate_direction; + if (it->direction_of_spiral_pattern) + { + rotate_direction = rotate_counter_clockwise(to_player); + } + else + { + rotate_direction = rotate_clockwise(to_player); + } + Vec2 target_vel = NormV2(SubV2(next_point_on_path, it->pos)); + target_vel = MulV2F(target_vel, 3.0f); + it->vel = LerpV2(it->vel, 15.0f * dt, target_vel); + CollisionInfo col = { 0 }; + it->pos = move_and_slide((MoveSlideParams) { it, it->pos, MulV2F(it->vel, pixels_per_meter * dt), .col_info_out = &col }); + if (col.happened) + { + it->direction_of_spiral_pattern = !it->direction_of_spiral_pattern; + } + + if (it->standing == STANDING_FIGHTING) + { + it->shotgun_timer += dt; + Vec2 to_player = NormV2(SubV2(targeting->pos, it->pos)); + if (it->shotgun_timer >= 1.0f) + { + it->shotgun_timer = 0.0f; + const float spread = (float)PI / 4.0f; + // shoot shotgun + int num_bullets = 5; + for (int i = 0; i < num_bullets; i++) + { + Vec2 dir = to_player; + float theta = Lerp(-spread / 2.0f, ((float)i / (float)(num_bullets - 1)), spread / 2.0f); + dir = RotateV2(dir, theta); + Entity *new_bullet = new_entity(); + new_bullet->is_bullet = true; + new_bullet->pos = AddV2(it->pos, MulV2F(dir, 20.0f)); + new_bullet->vel = MulV2F(dir, 15.0f); + it->vel = AddV2(it->vel, MulV2F(dir, -3.0f)); + } + } + } + + } + } + } + if (it->npc_kind == NPC_OldMan) + { + /* + draw_dialog_panel(it); + Entity *targeting = player; + it->shotgun_timer += dt; + Vec2 to_player = NormV2(SubV2(targeting->pos, it->pos)); + if(it->shotgun_timer >= 1.0f) + { + it->shotgun_timer = 0.0f; + const float spread = (float)PI/4.0f; + // shoot shotgun + int num_bullets = 5; + for(int i = 0; i < num_bullets; i++) + { + Vec2 dir = to_player; + float theta = Lerp(-spread/2.0f, ((float)i / (float)(num_bullets - 1)), spread/2.0f); + dir = RotateV2(dir, theta); + Entity *new_bullet = new_entity(); + new_bullet->is_bullet = true; + new_bullet->pos = AddV2(it->pos, MulV2F(dir, 20.0f)); + new_bullet->vel = MulV2F(dir, 15.0f); + it->vel = AddV2(it->vel, MulV2F(dir, -3.0f)); + } + } + + Vec2 target_vel = NormV2(AddV2(rotate_counter_clockwise(to_player), MulV2F(to_player, 0.5f))); + target_vel = MulV2F(target_vel, 3.0f); + it->vel = LerpV2(it->vel, 15.0f * dt, target_vel); + it->pos = move_and_slide((MoveSlideParams){it, it->pos, MulV2F(it->vel, pixels_per_meter * dt)}); + */ + } + else if (npc_is_skeleton(it)) + { + if (it->dead) + { + } + else + { + } // skelton combat and movement + } + else if (it->npc_kind == NPC_Death) + { + } #if 0 - else if(it->npc_kind == DEATH) - { - draw_animated_sprite(&death_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col); - } - else if(it->npc_kind == MERCHANT) - { - draw_animated_sprite(&merchant_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col); - } + else if (it->npc_kind == DEATH) + { + draw_animated_sprite(&death_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col); + } + else if (it->npc_kind == MERCHANT) + { + draw_animated_sprite(&merchant_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col); + } #endif - else if(it->npc_kind == NPC_MOOSE) - { - } - else if(it->npc_kind == NPC_GodRock) - { - } - else if(it->npc_kind == NPC_Edeline) - { - } - else if(it->npc_kind == NPC_TheGuard) - { - if(it->moved) - { - it->walking = true; - Vec2 towards = SubV2(it->target_goto, it->pos); - if(LenV2(towards) > 1.0f) - { - it->pos = LerpV2(it->pos, dt*5.0f, it->target_goto); - } - } - else - { - it->walking = false; - } - } - else if(it->npc_kind == NPC_TheKing) - { - } - else if(it->npc_kind == NPC_TheBlacksmith) - { - } - else - { - assert(false); - } - if(it->damage >= entity_max_damage(it)) - { - if(npc_is_skeleton(it)) - { - it->dead = true; - } - else - { - it->destroy = true; - } - } - } - else if (it->is_item) - { - if(it->held_by_player) - { - Vec2 held_spot = V2(15.0f * (player->facing_left ? -1.0f : 1.0f), 7.0f); - it->pos = AddV2(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_bullet) - { - it->pos = AddV2(it->pos, MulV2F(it->vel, pixels_per_meter * dt)); - dbgvec(it->pos, it->vel); - Overlapping over = get_overlapping(cur_level, entity_aabb(it)); - Entity *from_bullet = it; - bool destroy_bullet = false; - BUFF_ITER(Overlap, &over) if(it->e != from_bullet) - { - if(!it->is_tile && !(it->e->is_bullet)) - { - // knockback and damage - request_do_damage(it->e, from_bullet, DAMAGE_BULLET); - destroy_bullet = true; - } - } - if(destroy_bullet) *from_bullet = (Entity){0}; - if(!has_point(level_aabb, it->pos)) *it = (Entity){0}; - } - else if(it->is_character) - { - } - else if(it->is_prop) - { - } - else - { - assert(false); - } - } - } - - PROFILE_SCOPE("Destroy gs.entities, maybe send generation requests") - { - ENTITIES_ITER(gs.entities) - { - if(it->destroy) - { - int gen = it->generation; - *it = (Entity){0}; - it->generation = gen; - } - if(it->perceptions_dirty && !npc_does_dialog(it)) - { - it->perceptions_dirty = false; - } - if(it->perceptions_dirty) - { - PromptBuff prompt = {0}; + else if (it->npc_kind == NPC_GodRock) + { + } + else if (it->npc_kind == NPC_Edeline) + { + } + else if (it->npc_kind == NPC_TheGuard) + { + if (it->moved) + { + it->walking = true; + Vec2 towards = SubV2(it->target_goto, it->pos); + if (LenV2(towards) > 1.0f) + { + it->pos = LerpV2(it->pos, dt*5.0f, it->target_goto); + } + } + else + { + it->walking = false; + } + } + else if (it->npc_kind == NPC_TheKing) + { + } + else if (it->npc_kind == NPC_TheBlacksmith) + { + } + else + { + assert(false); + } + if (it->damage >= entity_max_damage(it)) + { + if (npc_is_skeleton(it)) + { + it->dead = true; + } + else + { + it->destroy = true; + } + } + } + else if (it->is_item) + { + if (it->held_by_player) + { + Vec2 held_spot = V2(15.0f * (player->facing_left ? -1.0f : 1.0f), 7.0f); + it->pos = AddV2(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_bullet) + { + it->pos = AddV2(it->pos, MulV2F(it->vel, pixels_per_meter * dt)); + dbgvec(it->pos, it->vel); + Overlapping over = get_overlapping(cur_level, entity_aabb(it)); + Entity *from_bullet = it; + bool destroy_bullet = false; + BUFF_ITER(Overlap, &over) if (it->e != from_bullet) + { + if (!it->is_tile && !(it->e->is_bullet)) + { + // knockback and damage + request_do_damage(it->e, from_bullet, DAMAGE_BULLET); + destroy_bullet = true; + } + } + if (destroy_bullet) *from_bullet = (Entity) { 0 }; + if (!has_point(level_aabb, it->pos)) *it = (Entity) { 0 }; + } + else if (it->is_character) + { + } + else if (it->is_prop) + { + } + else + { + assert(false); + } + } + } + + PROFILE_SCOPE("Destroy gs.entities, maybe send generation requests") + { + ENTITIES_ITER(gs.entities) + { + if (it->destroy) + { + int gen = it->generation; + *it = (Entity) { 0 }; + it->generation = gen; + } + if (it->perceptions_dirty && !npc_does_dialog(it)) + { + it->perceptions_dirty = false; + } + if (it->perceptions_dirty) + { + PromptBuff prompt = { 0 }; #ifdef DO_CHATGPT_PARSING - generate_chatgpt_prompt(it, &prompt); + generate_chatgpt_prompt(it, &prompt); #else - generate_prompt(it, &prompt); + generate_prompt(it, &prompt); #endif - Log("Sending request with prompt `%s`\n", prompt.data); + Log("Sending request with prompt `%s`\n", prompt.data); #ifdef WEB - // fire off generation request, save id - BUFF(char, 512) completion_server_url = {0}; - printf_buff(&completion_server_url, "%s/completion", SERVER_URL); - int req_id = EM_ASM_INT({ - return make_generation_request(UTF8ToString($1), UTF8ToString($0)); - }, completion_server_url.data, prompt.data); - it->gen_request_id = req_id; + // fire off generation request, save id + BUFF(char, 512) completion_server_url = { 0 }; + printf_buff(&completion_server_url, "%s/completion", SERVER_URL); + int req_id = EM_ASM_INT( { + return make_generation_request(UTF8ToString($1), UTF8ToString($0)); + }, completion_server_url.data, prompt.data); + it->gen_request_id = req_id; #endif #ifdef DESKTOP - BUFF(char, 1024) mocked_ai_response = {0}; + BUFF(char, 1024) mocked_ai_response = { 0 }; #define SAY(act, txt) { printf_buff(&mocked_ai_response, "%s \"%s\"", actions[act].name, txt); } #define SAY_ARG(act, txt, arg) { printf_buff(&mocked_ai_response, "%s(" arg ") \"%s\"", actions[act].name, txt); } - if(it->npc_kind == NPC_TheGuard) - { - if(it->last_seen_holding_kind == ITEM_Tripod && !it->moved) - { - SAY(ACT_none, "This codepath is deprecated"); - } - else - { - SAY(ACT_none, "You passed"); - } - } - else - { - SAY_ARG(ACT_give_item, "Here you go" , "ITEM_Tripod"); - //SAY(ACT_joins_player, "I am an NPC"); - //SAY(ACT_fights_player, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?"); - } - Perception p = {0}; - assert(parse_chatgpt_response(it, mocked_ai_response.data, &p)); - process_perception(it, p, player); + if (it->npc_kind == NPC_TheGuard) + { + if (it->last_seen_holding_kind == ITEM_Tripod && !it->moved) + { + SAY(ACT_none, "This codepath is deprecated"); + } + else + { + SAY(ACT_none, "You passed"); + } + } + else + { + SAY_ARG(ACT_give_item, "Here you go" , "ITEM_Chalice"); + //SAY(ACT_joins_player, "I am an NPC"); + //SAY(ACT_fights_player, "I am an NPC. Bla bla bl alb djsfklalfkdsaj. Did you know shortcake?"); + } + Perception p = { 0 }; + assert(parse_chatgpt_response(it, mocked_ai_response.data, &p)); + process_perception(it, p, player); #undef SAY #endif - it->perceptions_dirty = false; - } - } - } - - PROFILE_SCOPE("process player") - { - - // do dialog - Entity *closest_interact_with = 0; - { - // find closest to talk to - { - AABB dialog_rect = centered_aabb(player->pos, V2(dialog_interact_size , dialog_interact_size)); - dbgrect(dialog_rect); - Overlapping possible_dialogs = get_overlapping(cur_level, dialog_rect); - float closest_interact_with_dist = INFINITY; - BUFF_ITER(Overlap, &possible_dialogs) - { - bool entity_talkable = true; - if(entity_talkable) entity_talkable = entity_talkable && !it->is_tile; - if(entity_talkable) entity_talkable = entity_talkable && it->e->is_npc; - //if(entity_talkable) entity_talkable = entity_talkable && !(it->e->npc_kind == NPC_Skeleton); + it->perceptions_dirty = false; + } + } + } + + PROFILE_SCOPE("process player") + { + + // do dialog + Entity *closest_interact_with = 0; + { + // find closest to talk to + { + AABB dialog_rect = centered_aabb(player->pos, V2(dialog_interact_size , dialog_interact_size)); + dbgrect(dialog_rect); + Overlapping possible_dialogs = get_overlapping(cur_level, dialog_rect); + float closest_interact_with_dist = INFINITY; + BUFF_ITER(Overlap, &possible_dialogs) + { + bool entity_talkable = true; + if (entity_talkable) entity_talkable = entity_talkable && !it->is_tile; + if (entity_talkable) entity_talkable = entity_talkable && it->e->is_npc; + //if(entity_talkable) entity_talkable = entity_talkable && !(it->e->npc_kind == NPC_Skeleton); #ifdef WEB - if(entity_talkable) entity_talkable = entity_talkable && it->e->gen_request_id == 0; + if (entity_talkable) entity_talkable = entity_talkable && it->e->gen_request_id == 0; #endif - bool entity_pickupable = !it->is_tile && !gete(player->holding_item) && it->e->is_item; - - if(entity_talkable || entity_pickupable) - { - float dist = LenV2(SubV2(it->e->pos, player->pos)); - if(dist < closest_interact_with_dist) - { - closest_interact_with_dist = dist; - closest_interact_with = it->e; - } - } - } - } - - - interacting_with = closest_interact_with; - if(player->state == CHARACTER_TALKING) - { - interacting_with = gete(player->talking_to); - assert(interacting_with); - } - - - // maybe get rid of talking to - if(player->state == CHARACTER_TALKING) - { - if(gete(player->talking_to) == 0) - { - player->state = CHARACTER_IDLE; - } - } - else - { - player->talking_to = (EntityRef){0}; - } - } - - if(interact) - { - if(player->state == CHARACTER_TALKING) - { - // don't add extra stuff to be done when changing state because in several - // places it's assumed to end dialog I can just do player->state = CHARACTER_IDLE - player->state = CHARACTER_IDLE; - } - else if(closest_interact_with) - { - if(closest_interact_with->is_npc) - { - // begin dialog with closest npc - player->state = CHARACTER_TALKING; - player->talking_to = frome(closest_interact_with); - } - else if(closest_interact_with->is_item) - { - // pick up item - closest_interact_with->held_by_player = true; - player->holding_item = frome(closest_interact_with); - } - else - { - assert(false); - } - } - else - { - if(gete(player->holding_item)) - { - // throw item if not talking to somebody with item - Entity *thrown = gete(player->holding_item); - assert(thrown); - thrown->vel = MulV2F(player->to_throw_direction, 20.0f); - thrown->held_by_player = false; - player->holding_item = (EntityRef){0}; - } - } - } - - float speed = 0.0f; - { - if(roll && !player->is_rolling && player->time_not_rolling > 0.3f && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) - { - player->is_rolling = true; - player->roll_progress = 0.0; - } - if(attack && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) - { - player->state = CHARACTER_ATTACK; - BUFF_CLEAR(&player->done_damage_to_this_swing); - player->swing_progress = 0.0; - } - // after images - BUFF_ITER(PlayerAfterImage, &player->after_images) - { - it->alive_for += dt; - } - if(player->after_images.data[0].alive_for >= AFTERIMAGE_LIFETIME) - { - BUFF_REMOVE_FRONT(&player->after_images); - } - - // roll processing - { - if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING) - { - player->roll_progress = 0.0; - player->is_rolling = false; - } - if(player->is_rolling) - { - player->after_image_timer += dt; - player->time_not_rolling = 0.0f; - player->roll_progress += dt; - if(player->roll_progress > anim_sprite_duration(ANIM_knight_rolling)) - { - player->is_rolling = false; - } - } - if(!player->is_rolling) player->time_not_rolling += dt; - } - - Vec2 target_vel = {0}; - - if(LenV2(movement) > 0.01f) player->to_throw_direction = NormV2(movement); - if(player->state == CHARACTER_WALKING) - { - speed = PLAYER_SPEED; - if(player->is_rolling) speed = PLAYER_ROLL_SPEED; - - if(gete(player->holding_item) && gete(player->holding_item)->item_kind == ITEM_Boots) - { - speed *= 2.0f; - } - - if(LenV2(movement) == 0.0) - { - player->state = CHARACTER_IDLE; - } - else - { - } - } - else if(player->state == CHARACTER_IDLE) - { - if(LenV2(movement) > 0.01) player->state = CHARACTER_WALKING; - } - else if(player->state == CHARACTER_ATTACK) - { - AABB weapon_aabb = entity_sword_aabb(player, 40.0f, 25.0f); - dbgrect(weapon_aabb); - SwordToDamage to_damage = entity_sword_to_do_damage(player, get_overlapping(cur_level, weapon_aabb)); - BUFF_ITER(Entity*, &to_damage) - { - request_do_damage(*it, player, DAMAGE_SWORD); - } - player->swing_progress += dt; - if(player->swing_progress > anim_sprite_duration(ANIM_knight_attack)) - { - player->state = CHARACTER_IDLE; - } - } - else if(player->state == CHARACTER_TALKING) - { - } - else - { - assert(false); // unknown character state? not defined how to process - } - } // not time stopped - - // velocity processing - { - Vec2 target_vel = MulV2F(movement, pixels_per_meter * speed); - player->vel = LerpV2(player->vel, dt * 15.0f, target_vel); - player->pos = move_and_slide((MoveSlideParams){player, player->pos, MulV2F(player->vel, dt)}); - } - // health - if(player->damage >= 1.0) - { - reset_level(); - } - } - pressed = (PressedState){0}; - interact = false; - } // while loop - } - pressed = before_gameplay_loops; - - - PROFILE_SCOPE("render player") - { - DrawnAnimatedSprite to_draw = {0}; - Vec2 character_sprite_pos = AddV2(player->pos, V2(0.0, 20.0f)); - // if somebody, show their dialog panel - if(interacting_with) - { - // interaction keyboard hint - if(!mobile_controls) - { - float size = 100.0f; - Vec2 midpoint = MulV2F(AddV2(interacting_with->pos, player->pos), 0.5f); - draw_quad((DrawParams){true, quad_centered(AddV2(midpoint, V2(0.0, 5.0f + sinf((float)elapsed_time*3.0f)*5.0f)), V2(size, size)), IMG(image_e_icon), blendalpha(WHITE, clamp01(1.0f - learned_e)), .layer = LAYER_UI_FG }); - } - - // interaction circle - draw_quad((DrawParams){true, quad_centered(interacting_with->pos, V2(TILE_SIZE, TILE_SIZE)), image_hovering_circle, full_region(image_hovering_circle), WHITE}); - } - - if(player->state == CHARACTER_WALKING) - { - if(player->is_rolling) - { - to_draw = (DrawnAnimatedSprite){ANIM_knight_running, player->roll_progress, player->facing_left, character_sprite_pos, WHITE}; - } - else - { - to_draw = (DrawnAnimatedSprite){ANIM_knight_running, elapsed_time, player->facing_left, character_sprite_pos, WHITE}; - } - } - else if(player->state == CHARACTER_IDLE) - { - if(player->is_rolling) - { - to_draw = (DrawnAnimatedSprite){ANIM_knight_running, player->roll_progress, player->facing_left, character_sprite_pos, WHITE}; - } - else - { - to_draw = (DrawnAnimatedSprite){ANIM_knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE}; - } - } - else if(player->state == CHARACTER_ATTACK) - { - to_draw = (DrawnAnimatedSprite){ANIM_knight_attack, player->swing_progress, player->facing_left, character_sprite_pos, WHITE}; - } - else if(player->state == CHARACTER_TALKING) - { - to_draw = (DrawnAnimatedSprite){ANIM_knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE}; - } - else - { - assert(false); // unknown character state? not defined how to draw - } - - // hurt vignette - if(player->damage > 0.0) - { - draw_quad((DrawParams){false, (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, player->damage}, .layer = LAYER_SCREENSPACE_EFFECTS, }); - } - - player->anim_change_timer += dt; - if(player->anim_change_timer >= 0.05f) - { - player->anim_change_timer = 0.0f; - player->cur_animation = to_draw.anim; - } - to_draw.anim = player->cur_animation; - - Vec2 target_sprite_pos = to_draw.pos; - - BUFF_ITER_I(PlayerAfterImage, &player->after_images, i) - { - { - DrawnAnimatedSprite to_draw = it->drawn; - to_draw.tint.a = 0.5f; - float progress_through_life = it->alive_for / AFTERIMAGE_LIFETIME; - - if(progress_through_life > 0.5f) - { - float fade_amount = (progress_through_life - 0.5f)/0.5f; - - to_draw.tint.a = Lerp(0.8f, fade_amount, 0.0f); - Vec2 target; - if(i != player->after_images.cur_index-1) target = player->after_images.data[i+1].drawn.pos; - else target = target_sprite_pos; - to_draw.pos = LerpV2(to_draw.pos, fade_amount, target); - } - to_draw.no_shadow = true; - draw_animated_sprite(to_draw); - } - } - - //if(player->is_rolling^) to_draw.tint.a = 0.5f; - - if(to_draw.anim) - { - draw_animated_sprite(to_draw); - - if(player->after_image_timer >= TIME_TO_GEN_AFTERIMAGE) - { - player->after_image_timer = 0.0; - if(BUFF_HAS_SPACE(&player->after_images)) - BUFF_APPEND(&player->after_images, (PlayerAfterImage){.drawn = to_draw}); - } - } - } - - // render gs.entities render entities - PROFILE_SCOPE("entity rendering") - ENTITIES_ITER(gs.entities) - { + bool entity_pickupable = !it->is_tile && !gete(player->holding_item) && it->e->is_item; + + if (entity_talkable || entity_pickupable) + { + float dist = LenV2(SubV2(it->e->pos, player->pos)); + if (dist < closest_interact_with_dist) + { + closest_interact_with_dist = dist; + closest_interact_with = it->e; + } + } + } + } + + + interacting_with = closest_interact_with; + if (player->state == CHARACTER_TALKING) + { + interacting_with = gete(player->talking_to); + assert(interacting_with); + } + + + // maybe get rid of talking to + if (player->state == CHARACTER_TALKING) + { + if (gete(player->talking_to) == 0) + { + player->state = CHARACTER_IDLE; + } + } + else + { + player->talking_to = (EntityRef) { 0 }; + } + } + + if (interact) + { + if (player->state == CHARACTER_TALKING) + { + // don't add extra stuff to be done when changing state because in several + // places it's assumed to end dialog I can just do player->state = CHARACTER_IDLE + player->state = CHARACTER_IDLE; + } + else if (closest_interact_with) + { + if (closest_interact_with->is_npc) + { + // begin dialog with closest npc + player->state = CHARACTER_TALKING; + player->talking_to = frome(closest_interact_with); + } + else if (closest_interact_with->is_item) + { + // pick up item + closest_interact_with->held_by_player = true; + player->holding_item = frome(closest_interact_with); + } + else + { + assert(false); + } + } + else + { + if (gete(player->holding_item)) + { + // throw item if not talking to somebody with item + Entity *thrown = gete(player->holding_item); + assert(thrown); + thrown->vel = MulV2F(player->to_throw_direction, 20.0f); + thrown->held_by_player = false; + player->holding_item = (EntityRef) { 0 }; + } + } + } + + float speed = 0.0f; + { + if (roll && !player->is_rolling && player->time_not_rolling > 0.3f && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) + { + player->is_rolling = true; + player->roll_progress = 0.0; + } + if (attack && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) + { + player->state = CHARACTER_ATTACK; + BUFF_CLEAR(&player->done_damage_to_this_swing); + player->swing_progress = 0.0; + } + // after images + BUFF_ITER(PlayerAfterImage, &player->after_images) + { + it->alive_for += dt; + } + if (player->after_images.data[0].alive_for >= AFTERIMAGE_LIFETIME) + { + BUFF_REMOVE_FRONT(&player->after_images); + } + + // roll processing + { + if (player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING) + { + player->roll_progress = 0.0; + player->is_rolling = false; + } + if (player->is_rolling) + { + player->after_image_timer += dt; + player->time_not_rolling = 0.0f; + player->roll_progress += dt; + if (player->roll_progress > anim_sprite_duration(ANIM_knight_rolling)) + { + player->is_rolling = false; + } + } + if (!player->is_rolling) player->time_not_rolling += dt; + } + + Vec2 target_vel = { 0 }; + + if (LenV2(movement) > 0.01f) player->to_throw_direction = NormV2(movement); + if (player->state == CHARACTER_WALKING) + { + speed = PLAYER_SPEED; + if (player->is_rolling) speed = PLAYER_ROLL_SPEED; + + if (gete(player->holding_item) && gete(player->holding_item)->item_kind == ITEM_Boots) + { + speed *= 2.0f; + } + + if (LenV2(movement) == 0.0) + { + player->state = CHARACTER_IDLE; + } + else + { + } + } + else if (player->state == CHARACTER_IDLE) + { + if (LenV2(movement) > 0.01) player->state = CHARACTER_WALKING; + } + else if (player->state == CHARACTER_ATTACK) + { + AABB weapon_aabb = entity_sword_aabb(player, 40.0f, 25.0f); + dbgrect(weapon_aabb); + SwordToDamage to_damage = entity_sword_to_do_damage(player, get_overlapping(cur_level, weapon_aabb)); + BUFF_ITER(Entity*, &to_damage) + { + request_do_damage(*it, player, DAMAGE_SWORD); + } + player->swing_progress += dt; + if (player->swing_progress > anim_sprite_duration(ANIM_knight_attack)) + { + player->state = CHARACTER_IDLE; + } + } + else if (player->state == CHARACTER_TALKING) + { + } + else + { + assert(false); // unknown character state? not defined how to process + } + } // not time stopped + + // velocity processing + { + Vec2 target_vel = MulV2F(movement, pixels_per_meter * speed); + player->vel = LerpV2(player->vel, dt * 15.0f, target_vel); + player->pos = move_and_slide((MoveSlideParams) { player, player->pos, MulV2F(player->vel, dt) }); + } + // health + if (player->damage >= 1.0) + { + reset_level(); + } + } + pressed = (PressedState) { 0 }; + interact = false; + } // while loop + } + pressed = before_gameplay_loops; + + + PROFILE_SCOPE("render player") + { + DrawnAnimatedSprite to_draw = { 0 }; + Vec2 character_sprite_pos = AddV2(player->pos, V2(0.0, 20.0f)); + // if somebody, show their dialog panel + if (interacting_with) + { + // interaction keyboard hint + if (!mobile_controls) + { + float size = 100.0f; + Vec2 midpoint = MulV2F(AddV2(interacting_with->pos, player->pos), 0.5f); + draw_quad((DrawParams) { true, quad_centered(AddV2(midpoint, V2(0.0, 5.0f + sinf((float)elapsed_time*3.0f)*5.0f)), V2(size, size)), IMG(image_e_icon), blendalpha(WHITE, clamp01(1.0f - learned_e)), .layer = LAYER_UI_FG }); + } + + // interaction circle + draw_quad((DrawParams) { true, quad_centered(interacting_with->pos, V2(TILE_SIZE, TILE_SIZE)), image_hovering_circle, full_region(image_hovering_circle), WHITE }); + } + + if (player->state == CHARACTER_WALKING) + { + if (player->is_rolling) + { + to_draw = (DrawnAnimatedSprite) { ANIM_knight_running, player->roll_progress, player->facing_left, character_sprite_pos, WHITE }; + } + else + { + to_draw = (DrawnAnimatedSprite) { ANIM_knight_running, elapsed_time, player->facing_left, character_sprite_pos, WHITE }; + } + } + else if (player->state == CHARACTER_IDLE) + { + if (player->is_rolling) + { + to_draw = (DrawnAnimatedSprite) { ANIM_knight_running, player->roll_progress, player->facing_left, character_sprite_pos, WHITE }; + } + else + { + to_draw = (DrawnAnimatedSprite) { ANIM_knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE }; + } + } + else if (player->state == CHARACTER_ATTACK) + { + to_draw = (DrawnAnimatedSprite) { ANIM_knight_attack, player->swing_progress, player->facing_left, character_sprite_pos, WHITE }; + } + else if (player->state == CHARACTER_TALKING) + { + to_draw = (DrawnAnimatedSprite) { ANIM_knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE }; + } + else + { + assert(false); // unknown character state? not defined how to draw + } + + // hurt vignette + if (player->damage > 0.0) + { + draw_quad((DrawParams) { false, (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, player->damage }, .layer = LAYER_SCREENSPACE_EFFECTS, }); + } + + player->anim_change_timer += dt; + if (player->anim_change_timer >= 0.05f) + { + player->anim_change_timer = 0.0f; + player->cur_animation = to_draw.anim; + } + to_draw.anim = player->cur_animation; + + Vec2 target_sprite_pos = to_draw.pos; + + BUFF_ITER_I(PlayerAfterImage, &player->after_images, i) + { + { + DrawnAnimatedSprite to_draw = it->drawn; + to_draw.tint.a = 0.5f; + float progress_through_life = it->alive_for / AFTERIMAGE_LIFETIME; + + if (progress_through_life > 0.5f) + { + float fade_amount = (progress_through_life - 0.5f) / 0.5f; + + to_draw.tint.a = Lerp(0.8f, fade_amount, 0.0f); + Vec2 target; + if (i != player->after_images.cur_index-1) target = player->after_images.data[i + 1].drawn.pos; + else target = target_sprite_pos; + to_draw.pos = LerpV2(to_draw.pos, fade_amount, target); + } + to_draw.no_shadow = true; + draw_animated_sprite(to_draw); + } + } + + //if(player->is_rolling^) to_draw.tint.a = 0.5f; + + if (to_draw.anim) + { + draw_animated_sprite(to_draw); + + if (player->after_image_timer >= TIME_TO_GEN_AFTERIMAGE) + { + player->after_image_timer = 0.0; + if (BUFF_HAS_SPACE(&player->after_images)) + BUFF_APPEND(&player->after_images, (PlayerAfterImage) { .drawn = to_draw }); + } + } + } + + // render gs.entities render entities + PROFILE_SCOPE("entity rendering") + ENTITIES_ITER(gs.entities) + { #ifdef WEB - if(it->gen_request_id != 0) - { - draw_quad((DrawParams){true, quad_centered(AddV2(it->pos, V2(0.0, 50.0)), V2(100.0,100.0)), IMG(image_thinking), WHITE}); - } + if (it->gen_request_id != 0) + { + draw_quad((DrawParams) { true, quad_centered(AddV2(it->pos, V2(0.0, 50.0)), V2(100.0, 100.0)), IMG(image_thinking), WHITE }); + } #endif - Color col = LerpV4(WHITE, it->damage, RED); - if(it->is_npc) - { - // health bar - { - Vec2 health_bar_size = V2(TILE_SIZE, 0.1f * TILE_SIZE); - float health_bar_progress = 1.0f - (it->damage / entity_max_damage(it)); - Vec2 health_bar_center = AddV2(it->pos, V2(0.0f, -entity_aabb_size(it).y)); - Vec2 bar_upper_left = AddV2(health_bar_center, MulV2F(health_bar_size, -0.5f)); - draw_quad((DrawParams){true, quad_at(bar_upper_left, health_bar_size), IMG(image_white_square), BROWN}); - draw_quad((DrawParams){true, quad_at(bar_upper_left, V2(health_bar_size.x * health_bar_progress, health_bar_size.y)), IMG(image_white_square), GREEN}); - } - - float dist = LenV2(SubV2(it->pos, player->pos)); - dist -= 10.0f; // radius around point where dialog is completely opaque - float max_dist = dialog_interact_size/2.0f; - float alpha = 1.0f - (float)clamp(dist/max_dist, 0.0, 1.0); - if(gete(player->talking_to) == it && player->state == CHARACTER_TALKING) alpha = 0.0f; - if(it->being_hovered) - { - draw_quad((DrawParams){true, quad_centered(it->pos, V2(TILE_SIZE, TILE_SIZE)), IMG(image_hovering_circle), WHITE}); - alpha = 1.0f; - } - - - it->dialog_panel_opacity = Lerp(it->dialog_panel_opacity, unwarped_dt*10.0f, alpha); - draw_dialog_panel(it, it->dialog_panel_opacity); - - if(it->npc_kind == NPC_OldMan) - { - bool face_left =SubV2(player->pos, it->pos).x < 0.0f; - draw_animated_sprite((DrawnAnimatedSprite){ANIM_old_man_idle, elapsed_time, face_left, it->pos, col}); - } - else if(npc_is_skeleton(it)) - { - Color col = WHITE; - if(it->dead) - { - draw_animated_sprite((DrawnAnimatedSprite){ANIM_skeleton_die, it->dead_time, it->facing_left, it->pos, col}); - } - else - { - if(it->swing_timer > 0.0) - { - // swinging sword - draw_animated_sprite((DrawnAnimatedSprite){ANIM_skeleton_swing_sword, it->swing_timer, it->facing_left, it->pos, col}); - } - else - { - if(it->walking) - { - draw_animated_sprite((DrawnAnimatedSprite){ANIM_skeleton_run, elapsed_time, it->facing_left, it->pos, col}); - } - else - { - draw_animated_sprite((DrawnAnimatedSprite){ANIM_skeleton_idle, elapsed_time, it->facing_left, it->pos, col}); - } - } - } - } - else if(it->npc_kind == NPC_Death) - { - draw_animated_sprite((DrawnAnimatedSprite){ANIM_death_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col}); - } - else if(it->npc_kind == NPC_GodRock) - { - Vec2 prop_size = V2(46.0f, 40.0f); - DrawParams d = (DrawParams){true, quad_centered(AddV2(it->pos, V2(-0.0f, 0.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(15.0f, 219.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.7f, .layer = LAYER_WORLD,}; - draw_shadow_for(d); - draw_quad(d); - } - else if(npc_is_knight_sprite(it)) - { - Color tint = WHITE; - if(it->npc_kind == NPC_TheGuard) - { - tint = colhex(0xa84032); - } - else if(it->npc_kind == NPC_Edeline) - { - tint = colhex(0x8c34eb); - } - else if(it->npc_kind == NPC_TheKing) - { - tint = colhex(0xf0be1d); - } - else if(it->npc_kind == NPC_TheBlacksmith) - { - tint = colhex(0x5c5c5c); - } - else - { - assert(false); - } - draw_animated_sprite((DrawnAnimatedSprite){ANIM_knight_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), tint}); - } - else if(it->npc_kind == NPC_MOOSE) - { - //draw_animated_sprite(&moose_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col); - } - else - { - assert(false); - } - } - else if (it->is_item) - { - draw_item(true, it->item_kind, centered_aabb(it->pos,V2(15.0f, 15.0f)), 1.0f); - } - else if (it->is_bullet) - { - AABB normal_aabb = entity_aabb(it); - Quad drawn = quad_centered(aabb_center(normal_aabb), MulV2F(aabb_size(normal_aabb), 1.5f)); - draw_quad((DrawParams){true, drawn, IMG(image_bullet), WHITE}); - } - else if(it->is_character) - { - } - else if(it->is_prop) - { - DrawParams d = {0}; - if(it->prop_kind == TREE0) - { - Vec2 prop_size = V2(74.0f, 122.0f); - d = (DrawParams){true, quad_centered(AddV2(it->pos, V2(-5.0f, 45.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(2.0f, 4.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.7f}; - } - else if(it->prop_kind == TREE1) - { - Vec2 prop_size = V2(94.0f, 120.0f); - d = ((DrawParams){true, quad_centered(AddV2(it->pos, V2(-4.0f, 55.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(105.0f, 4.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.4f}); - } - else if(it->prop_kind == TREE2) - { - Vec2 prop_size = V2(128.0f, 192.0f); - d = ((DrawParams){true, quad_centered(AddV2(it->pos, V2(-2.5f, 70.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(385.0f, 479.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.4f}); - } - else if(it->prop_kind == ROCK0) - { - Vec2 prop_size = V2(30.0f, 22.0f); - d = (DrawParams){true, quad_centered(AddV2(it->pos, V2(0.0f, 25.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(66.0f, 235.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 0.0f))), .alpha_clip_threshold = 0.7f}; - } - else - { - assert(false); - } - draw_shadow_for(d); - draw_quad(d); - } - else - { - assert(false); - } - } - - PROFILE_SCOPE("dialog menu") // big dialog panel draw big dialog panel - { - static float on_screen = 0.0f; - Entity *talking_to = gete(player->talking_to); - on_screen = Lerp(on_screen, unwarped_dt*9.0f, talking_to ? 1.0f : 0.0f); - { - - float panel_width = screen_size().x * 0.4f * on_screen; - AABB panel_aabb = (AABB){.upper_left = V2(0.0f, screen_size().y), .lower_right = V2(panel_width, 0.0f)}; - float alpha = 1.0f; - - - if(aabb_is_valid(panel_aabb)) - { - if(!choosing_item_grid && pressed.mouse_down && !has_point(panel_aabb, mouse_pos)) - { - player->state = CHARACTER_IDLE; - } - draw_quad((DrawParams){false, quad_aabb(panel_aabb), IMG(image_white_square), blendalpha(BLACK, 0.7f)}); - - // apply padding - float padding = 0.1f * screen_size().y; - panel_width -= padding * 2.0f; - panel_aabb.upper_left = AddV2(panel_aabb.upper_left, V2(padding, -padding)); - panel_aabb.lower_right = AddV2(panel_aabb.lower_right, V2(-padding, padding)); - - // draw button - float space_btwn_buttons = 20.0f; - float text_scale = 1.0f; - const float num_buttons = 2.0f; - Vec2 button_size = V2( - (panel_width - (num_buttons - 1.0f)*space_btwn_buttons)/num_buttons, - (panel_aabb.upper_left.y - panel_aabb.lower_right.y)*0.2f - ); - float button_grid_width = button_size.x*num_buttons + space_btwn_buttons * (num_buttons - 1.0f); - Vec2 cur_upper_left = V2((panel_aabb.upper_left.x + panel_aabb.lower_right.x)/2.0f - button_grid_width/2.0f, panel_aabb.lower_right.y + button_size.y); - if(imbutton_key(aabb_at(cur_upper_left, button_size), text_scale, "Speak", __LINE__, unwarped_dt, receiving_text_input)) - { - begin_text_input(); - } - float button_grid_height = button_size.y; - - cur_upper_left.x += button_size.x + space_btwn_buttons; - if(imbutton(aabb_at(cur_upper_left, button_size), text_scale, "Give Item")) - { - choosing_item_grid = true; - } - - const float dialog_text_scale = 1.0f; - - AABB dialog_text_aabb = panel_aabb; - dialog_text_aabb.lower_right.y += button_grid_height + 20.0f; // a little bit of padding because the buttons go up - float new_line_height = dialog_text_aabb.lower_right.y; - - if(talking_to) - { - Dialog dialog = produce_dialog(talking_to, true); - if(dialog.cur_index > 0) - { - for(int i = dialog.cur_index - 1; i >= 0; i--) - { - DialogElement *it = &dialog.data[i]; - { - Color *colors = calloc(sizeof(*colors), it->s.cur_index); - for(int char_i = 0; char_i < it->s.cur_index; char_i++) - { - if(it->kind == DELEM_PLAYER) - { - colors[char_i] = WHITE; - } - else if(it->kind == DELEM_NPC) - { - colors[char_i] = colhex(0x34e05c); - } - else if(it->kind == DELEM_NPC_ACTION_DESCRIPTION) - { - colors[char_i] = colhex(0xebc334); - } - else - { - assert(false); - } - colors[char_i] = blendalpha(colors[char_i], alpha); - } - float measured_line_height = draw_wrapped_text((WrappedTextParams){true, V2(dialog_text_aabb.upper_left.X, new_line_height), dialog_text_aabb.lower_right.X - dialog_text_aabb.upper_left.X, it->s.data, colors, dialog_text_scale, dialog_text_aabb, .screen_space = true}); - new_line_height += (new_line_height - measured_line_height); - draw_wrapped_text((WrappedTextParams){false, V2(dialog_text_aabb.upper_left.X, new_line_height), dialog_text_aabb.lower_right.X - dialog_text_aabb.upper_left.X, it->s.data, colors, dialog_text_scale, dialog_text_aabb, .screen_space = true}); - - free(colors); - } - } - } - } - } - } - } - - // item grid modal draw item grid - { - static float visible = 0.0f; - static float hovered_state[ARRLEN(player->held_items.data)] = { 0 }; - float target = 0.0f; - if(choosing_item_grid) target = 1.0f; - visible = Lerp(visible, unwarped_dt*9.0f, target); - - if(player->state != CHARACTER_TALKING) - { - choosing_item_grid = false; - } - - draw_quad((DrawParams){false, 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 = centered_aabb(MulV2F(screen_size(), 0.5f), grid_panel_size); - if(choosing_item_grid && pressed.mouse_down && !has_point(grid_aabb, mouse_pos)) - { - choosing_item_grid = false; - } - if(aabb_is_valid(grid_aabb)) - { - draw_quad((DrawParams){false, quad_aabb(grid_aabb), IMG(image_white_square), blendalpha(BLACK, visible * 0.7f), .layer = LAYER_UI}); - - if(imbutton(centered_aabb(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, "Cancel")) - { - choosing_item_grid = 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(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 to_give = -1; // don't modify the item array while iterating - BUFF_ITER_I(ItemKind, &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 = centered_aabb(item_center, real_size); - - - float target = 0.0f; - if(aabb_is_valid(item_icon)) - { - draw_quad((DrawParams){false, 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) - { - if(gete(player->talking_to)) - { - to_give = i; - } - } - } - - in_screen_space = true; - dbgrect(item_icon); - in_screen_space = false; - draw_item(false, *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(to_give > -1) - { - choosing_item_grid = false; - assert(gete(player->talking_to)); - ItemKind given_item_kind = player->held_items.data[to_give]; - BUFF_REMOVE_AT_INDEX(&player->held_items, to_give); - BUFF_APPEND(&gete(player->talking_to)->held_items, given_item_kind); - } - - } - } - - - // ui + Color col = LerpV4(WHITE, it->damage, RED); + if (it->is_npc) + { + // health bar + { + Vec2 health_bar_size = V2(TILE_SIZE, 0.1f * TILE_SIZE); + float health_bar_progress = 1.0f - (it->damage / entity_max_damage(it)); + Vec2 health_bar_center = AddV2(it->pos, V2(0.0f, -entity_aabb_size(it).y)); + Vec2 bar_upper_left = AddV2(health_bar_center, MulV2F(health_bar_size, -0.5f)); + draw_quad((DrawParams) { true, quad_at(bar_upper_left, health_bar_size), IMG(image_white_square), BROWN }); + draw_quad((DrawParams) { true, quad_at(bar_upper_left, V2(health_bar_size.x * health_bar_progress, health_bar_size.y)), IMG(image_white_square), GREEN }); + } + + float dist = LenV2(SubV2(it->pos, player->pos)); + dist -= 10.0f; // radius around point where dialog is completely opaque + float max_dist = dialog_interact_size / 2.0f; + float alpha = 1.0f - (float)clamp(dist / max_dist, 0.0, 1.0); + if (gete(player->talking_to) == it && player->state == CHARACTER_TALKING) alpha = 0.0f; + if (it->being_hovered) + { + draw_quad((DrawParams) { true, quad_centered(it->pos, V2(TILE_SIZE, TILE_SIZE)), IMG(image_hovering_circle), WHITE }); + alpha = 1.0f; + } + + + it->dialog_panel_opacity = Lerp(it->dialog_panel_opacity, unwarped_dt*10.0f, alpha); + draw_dialog_panel(it, it->dialog_panel_opacity); + + if (it->npc_kind == NPC_OldMan) + { + bool face_left = SubV2(player->pos, it->pos).x < 0.0f; + draw_animated_sprite((DrawnAnimatedSprite) { ANIM_old_man_idle, elapsed_time, face_left, it->pos, col }); + } + else if (npc_is_skeleton(it)) + { + Color col = WHITE; + if (it->dead) + { + draw_animated_sprite((DrawnAnimatedSprite) { ANIM_skeleton_die, it->dead_time, it->facing_left, it->pos, col }); + } + else + { + if (it->swing_timer > 0.0) + { + // swinging sword + draw_animated_sprite((DrawnAnimatedSprite) { ANIM_skeleton_swing_sword, it->swing_timer, it->facing_left, it->pos, col }); + } + else + { + if (it->walking) + { + draw_animated_sprite((DrawnAnimatedSprite) { ANIM_skeleton_run, elapsed_time, it->facing_left, it->pos, col }); + } + else + { + draw_animated_sprite((DrawnAnimatedSprite) { ANIM_skeleton_idle, elapsed_time, it->facing_left, it->pos, col }); + } + } + } + } + else if (it->npc_kind == NPC_Death) + { + draw_animated_sprite((DrawnAnimatedSprite) { ANIM_death_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), col }); + } + else if (it->npc_kind == NPC_GodRock) + { + Vec2 prop_size = V2(46.0f, 40.0f); + DrawParams d = (DrawParams) { true, quad_centered(AddV2(it->pos, V2(-0.0f, 0.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(15.0f, 219.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.7f, .layer = LAYER_WORLD, }; + draw_shadow_for(d); + draw_quad(d); + } + else if (npc_is_knight_sprite(it)) + { + Color tint = WHITE; + if (it->npc_kind == NPC_TheGuard) + { + tint = colhex(0xa84032); + } + else if (it->npc_kind == NPC_Edeline) + { + tint = colhex(0x8c34eb); + } + else if (it->npc_kind == NPC_TheKing) + { + tint = colhex(0xf0be1d); + } + else if (it->npc_kind == NPC_TheBlacksmith) + { + tint = colhex(0x5c5c5c); + } + else + { + assert(false); + } + draw_animated_sprite((DrawnAnimatedSprite) { ANIM_knight_idle, elapsed_time, true, AddV2(it->pos, V2(0, 30.0f)), tint }); + } + else + { + assert(false); + } + } + else if (it->is_item) + { + draw_item(true, it->item_kind, centered_aabb(it->pos, V2(15.0f, 15.0f)), 1.0f); + } + else if (it->is_bullet) + { + AABB normal_aabb = entity_aabb(it); + Quad drawn = quad_centered(aabb_center(normal_aabb), MulV2F(aabb_size(normal_aabb), 1.5f)); + draw_quad((DrawParams) { true, drawn, IMG(image_bullet), WHITE }); + } + else if (it->is_character) + { + } + else if (it->is_prop) + { + DrawParams d = { 0 }; + if (it->prop_kind == TREE0) + { + Vec2 prop_size = V2(74.0f, 122.0f); + d = (DrawParams) { true, quad_centered(AddV2(it->pos, V2(-5.0f, 45.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(2.0f, 4.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.7f }; + } + else if (it->prop_kind == TREE1) + { + Vec2 prop_size = V2(94.0f, 120.0f); + d = ((DrawParams) { true, quad_centered(AddV2(it->pos, V2(-4.0f, 55.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(105.0f, 4.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.4f }); + } + else if (it->prop_kind == TREE2) + { + Vec2 prop_size = V2(128.0f, 192.0f); + d = ((DrawParams) { true, quad_centered(AddV2(it->pos, V2(-2.5f, 70.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(385.0f, 479.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 20.0f))), .alpha_clip_threshold = 0.4f }); + } + else if (it->prop_kind == ROCK0) + { + Vec2 prop_size = V2(30.0f, 22.0f); + d = (DrawParams) { true, quad_centered(AddV2(it->pos, V2(0.0f, 25.0)), prop_size), image_props_atlas, aabb_at_yplusdown(V2(66.0f, 235.0f), prop_size), WHITE, .sorting_key = sorting_key_at(AddV2(it->pos, V2(0.0f, 0.0f))), .alpha_clip_threshold = 0.7f }; + } + else + { + assert(false); + } + draw_shadow_for(d); + draw_quad(d); + } + else + { + assert(false); + } + } + + PROFILE_SCOPE("dialog menu") // big dialog panel draw big dialog panel + { + static float on_screen = 0.0f; + Entity *talking_to = gete(player->talking_to); + on_screen = Lerp(on_screen, unwarped_dt*9.0f, talking_to ? 1.0f : 0.0f); + { + float panel_width = screen_size().x * 0.4f * on_screen; + AABB panel_aabb = (AABB) { .upper_left = V2(0.0f, screen_size().y), .lower_right = V2(panel_width, 0.0f) }; + float alpha = 1.0f; + + if (aabb_is_valid(panel_aabb)) + { + if (!choosing_item_grid && pressed.mouse_down && !has_point(panel_aabb, mouse_pos)) + { + player->state = CHARACTER_IDLE; + } + draw_quad((DrawParams) { false, quad_aabb(panel_aabb), IMG(image_white_square), blendalpha(BLACK, 0.7f) }); + + // apply padding + float padding = 0.1f * screen_size().y; + panel_width -= padding * 2.0f; + panel_aabb.upper_left = AddV2(panel_aabb.upper_left, V2(padding, -padding)); + panel_aabb.lower_right = AddV2(panel_aabb.lower_right, V2(-padding, padding)); + + // draw button + float space_btwn_buttons = 20.0f; + float text_scale = 1.0f; + const float num_buttons = 2.0f; + Vec2 button_size = V2( + (panel_width - (num_buttons - 1.0f)*space_btwn_buttons) / num_buttons, + (panel_aabb.upper_left.y - panel_aabb.lower_right.y)*0.2f + ); + float button_grid_width = button_size.x*num_buttons + space_btwn_buttons * (num_buttons - 1.0f); + Vec2 cur_upper_left = V2((panel_aabb.upper_left.x + panel_aabb.lower_right.x) / 2.0f - button_grid_width / 2.0f, panel_aabb.lower_right.y + button_size.y); + if (imbutton_key(aabb_at(cur_upper_left, button_size), text_scale, "Speak", __LINE__, unwarped_dt, receiving_text_input)) + { + begin_text_input(); + } + float button_grid_height = button_size.y; + + cur_upper_left.x += button_size.x + space_btwn_buttons; + if (imbutton(aabb_at(cur_upper_left, button_size), text_scale, "Give Item")) + { + choosing_item_grid = true; + } + + const float dialog_text_scale = 1.0f; + + AABB dialog_text_aabb = panel_aabb; + dialog_text_aabb.lower_right.y += button_grid_height + 20.0f; // a little bit of padding because the buttons go up + float new_line_height = dialog_text_aabb.lower_right.y; + + if (talking_to) + { + Dialog dialog = produce_dialog(talking_to, true); + if (dialog.cur_index > 0) + { + for (int i = dialog.cur_index - 1; i >= 0; i--) + { + DialogElement *it = &dialog.data[i]; + { + Color *colors = calloc(sizeof(*colors), it->s.cur_index); + for (int char_i = 0; char_i < it->s.cur_index; char_i++) + { + if (it->kind == DELEM_PLAYER) + { + colors[char_i] = WHITE; + } + else if (it->kind == DELEM_NPC) + { + colors[char_i] = colhex(0x34e05c); + } + else if (it->kind == DELEM_ACTION_DESCRIPTION) + { + colors[char_i] = colhex(0xebc334); + } + else + { + assert(false); + } + colors[char_i] = blendalpha(colors[char_i], alpha); + } + float measured_line_height = draw_wrapped_text((WrappedTextParams) { true, V2(dialog_text_aabb.upper_left.X, new_line_height), dialog_text_aabb.lower_right.X - dialog_text_aabb.upper_left.X, it->s.data, colors, dialog_text_scale, dialog_text_aabb, .screen_space = true }); + new_line_height += (new_line_height - measured_line_height); + draw_wrapped_text((WrappedTextParams) { false, V2(dialog_text_aabb.upper_left.X, new_line_height), dialog_text_aabb.lower_right.X - dialog_text_aabb.upper_left.X, it->s.data, colors, dialog_text_scale, dialog_text_aabb, .screen_space = true }); + + free(colors); + } + } + } + } + } + } + } + + // item grid modal draw item grid choose item pick item give item + { + static float visible = 0.0f; + static float hovered_state[ARRLEN(player->held_items.data)] = { 0 }; + float target = 0.0f; + if (choosing_item_grid) target = 1.0f; + visible = Lerp(visible, unwarped_dt*9.0f, target); + + if (player->state != CHARACTER_TALKING) + { + choosing_item_grid = false; + } + + draw_quad((DrawParams) { false, 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 = centered_aabb(MulV2F(screen_size(), 0.5f), grid_panel_size); + if (choosing_item_grid && pressed.mouse_down && !has_point(grid_aabb, mouse_pos)) + { + choosing_item_grid = false; + } + if (aabb_is_valid(grid_aabb)) + { + draw_quad((DrawParams) { false, quad_aabb(grid_aabb), IMG(image_white_square), blendalpha(BLACK, visible * 0.7f), .layer = LAYER_UI }); + + if (imbutton(centered_aabb(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, "Cancel")) + { + choosing_item_grid = 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(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 to_give = -1; // don't modify the item array while iterating + BUFF_ITER_I(ItemKind, &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 = centered_aabb(item_center, real_size); + + + float target = 0.0f; + if (aabb_is_valid(item_icon)) + { + draw_quad((DrawParams) { false, 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) + { + if (gete(player->talking_to)) + { + to_give = i; + } + } + } + + in_screen_space = true; + dbgrect(item_icon); + in_screen_space = false; + draw_item(false, *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 (to_give > -1) + { + choosing_item_grid = false; + + Entity *to = gete(player->talking_to); + assert(to); + + ItemKind given_item_kind = player->held_items.data[to_give]; + BUFF_REMOVE_AT_INDEX(&player->held_items, to_give); + + process_perception(to, (Perception) { .type = PlayerAction, .player_action_type = ACT_give_item, .given_item = given_item_kind }, player); + } + + } + } + + + // ui #define HELPER_SIZE 250.0f - if(!mobile_controls) - { - float total_height = HELPER_SIZE * 2.0f; - float vertical_spacing = HELPER_SIZE/2.0f; - total_height -= (total_height - (vertical_spacing + HELPER_SIZE)); - const float padding = 50.0f; - float y = screen_size().y/2.0f + total_height/2.0f; - float x = screen_size().x - padding - HELPER_SIZE; - draw_quad((DrawParams){false, quad_at(V2(x, y), V2(HELPER_SIZE,HELPER_SIZE)), IMG(image_shift_icon), (Color){1.0f,1.0f,1.0f,fmaxf(0.0f, 1.0f-learned_shift)}, .layer = LAYER_UI_FG}); - y -= vertical_spacing; - draw_quad((DrawParams){false, quad_at(V2(x, y), V2(HELPER_SIZE,HELPER_SIZE)), IMG(image_space_icon), (Color){1.0f,1.0f,1.0f,fmaxf(0.0f, 1.0f-learned_space)}, .layer = LAYER_UI_FG}); - } - - - if(mobile_controls) - { - float thumbstick_nub_size = (img_size(image_mobile_thumbstick_nub).x / img_size(image_mobile_thumbstick_base).x) * thumbstick_base_size(); - draw_quad((DrawParams){false, quad_centered(thumbstick_base_pos, V2(thumbstick_base_size(), thumbstick_base_size())), IMG(image_mobile_thumbstick_base), WHITE, .layer = LAYER_UI_FG}); - draw_quad((DrawParams){false, quad_centered(thumbstick_nub_pos, V2(thumbstick_nub_size, thumbstick_nub_size)), IMG(image_mobile_thumbstick_nub), WHITE, .layer = LAYER_UI_FG}); - - if(interacting_with || gete(player->holding_item)) - { - draw_quad((DrawParams){false, quad_centered(interact_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .layer = LAYER_UI_FG}); - } - draw_quad((DrawParams){false, quad_centered(roll_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .layer = LAYER_UI_FG}); - draw_quad((DrawParams){false, quad_centered(attack_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .layer = LAYER_UI_FG}); - } + if (!mobile_controls) + { + float total_height = HELPER_SIZE * 2.0f; + float vertical_spacing = HELPER_SIZE / 2.0f; + total_height -= (total_height - (vertical_spacing + HELPER_SIZE)); + const float padding = 50.0f; + float y = screen_size().y / 2.0f + total_height / 2.0f; + float x = screen_size().x - padding - HELPER_SIZE; + draw_quad((DrawParams) { false, quad_at(V2(x, y), V2(HELPER_SIZE, HELPER_SIZE)), IMG(image_shift_icon), (Color) { 1.0f, 1.0f, 1.0f, fmaxf(0.0f, 1.0f-learned_shift) }, .layer = LAYER_UI_FG }); + y -= vertical_spacing; + draw_quad((DrawParams) { false, quad_at(V2(x, y), V2(HELPER_SIZE, HELPER_SIZE)), IMG(image_space_icon), (Color) { 1.0f, 1.0f, 1.0f, fmaxf(0.0f, 1.0f-learned_space) }, .layer = LAYER_UI_FG }); + } + + + if (mobile_controls) + { + float thumbstick_nub_size = (img_size(image_mobile_thumbstick_nub).x / img_size(image_mobile_thumbstick_base).x) * thumbstick_base_size(); + draw_quad((DrawParams) { false, quad_centered(thumbstick_base_pos, V2(thumbstick_base_size(), thumbstick_base_size())), IMG(image_mobile_thumbstick_base), WHITE, .layer = LAYER_UI_FG }); + draw_quad((DrawParams) { false, quad_centered(thumbstick_nub_pos, V2(thumbstick_nub_size, thumbstick_nub_size)), IMG(image_mobile_thumbstick_nub), WHITE, .layer = LAYER_UI_FG }); + + if (interacting_with || gete(player->holding_item)) + { + draw_quad((DrawParams) { false, quad_centered(interact_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .layer = LAYER_UI_FG }); + } + draw_quad((DrawParams) { false, quad_centered(roll_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .layer = LAYER_UI_FG }); + draw_quad((DrawParams) { false, quad_centered(attack_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .layer = LAYER_UI_FG }); + } #ifdef DEVTOOLS - dbgsquare(screen_to_world(mouse_pos)); - - // tile coord - if(show_devtools) - { - TileCoord hovering = world_to_tilecoord(screen_to_world(mouse_pos)); - Vec2 points[4] ={0}; - AABB q = tile_aabb(hovering); - dbgrect(q); - draw_text((TextParams){false, false, tprint("%d", get_tile(&level_level0, hovering).kind), world_to_screen(tilecoord_to_world(hovering)), BLACK, 1.0f}); - } - - // debug draw font image - { - draw_quad((DrawParams){true, quad_centered(V2(0.0, 0.0), V2(250.0, 250.0)), image_font,full_region(image_font), WHITE}); - } - - // statistics - if(show_devtools) - PROFILE_SCOPE("statistics") - { - Vec2 pos = V2(0.0, screen_size().Y); - int num_entities = 0; - ENTITIES_ITER(gs.entities) num_entities++; - char *stats = tprint("Frametime: %.1f ms\nProcessing: %.1f ms\nEntities: %d\nDraw calls: %d\nProfiling: %s\nNumber gameplay processing loops: %d\n", dt*1000.0, last_frame_processing_time*1000.0, num_entities, num_draw_calls, profiling ? "yes" : "no", num_timestep_loops); - AABB bounds = draw_text((TextParams){false, true, stats, pos, BLACK, 1.0f}); - pos.Y -= bounds.upper_left.Y - screen_size().Y; - bounds = draw_text((TextParams){false, true, stats, pos, BLACK, 1.0f}); - // background panel - colorquad(false, quad_aabb(bounds), (Color){1.0, 1.0, 1.0, 0.3f}); - draw_text((TextParams){false, false, stats, pos, BLACK, 1.0f}); - num_draw_calls = 0; - } + dbgsquare(screen_to_world(mouse_pos)); + + // tile coord + if (show_devtools) + { + TileCoord hovering = world_to_tilecoord(screen_to_world(mouse_pos)); + Vec2 points[4] = { 0 }; + AABB q = tile_aabb(hovering); + dbgrect(q); + draw_text((TextParams) { false, false, tprint("%d", get_tile(&level_level0, hovering).kind), world_to_screen(tilecoord_to_world(hovering)), BLACK, 1.0f }); + } + + // debug draw font image + { + draw_quad((DrawParams) { true, quad_centered(V2(0.0, 0.0), V2(250.0, 250.0)), image_font, full_region(image_font), WHITE }); + } + + // statistics + if (show_devtools) + PROFILE_SCOPE("statistics") + { + Vec2 pos = V2(0.0, screen_size().Y); + int num_entities = 0; + ENTITIES_ITER(gs.entities) num_entities++; + char *stats = tprint("Frametime: %.1f ms\nProcessing: %.1f ms\nEntities: %d\nDraw calls: %d\nProfiling: %s\nNumber gameplay processing loops: %d\n", dt*1000.0, last_frame_processing_time*1000.0, num_entities, num_draw_calls, profiling ? "yes" : "no", num_timestep_loops); + AABB bounds = draw_text((TextParams) { false, true, stats, pos, BLACK, 1.0f }); + pos.Y -= bounds.upper_left.Y - screen_size().Y; + bounds = draw_text((TextParams) { false, true, stats, pos, BLACK, 1.0f }); + // background panel + colorquad(false, quad_aabb(bounds), (Color) { 1.0, 1.0, 1.0, 0.3f }); + draw_text((TextParams) { false, false, stats, pos, BLACK, 1.0f }); + num_draw_calls = 0; + } #endif // devtools - // update camera position - { - Vec2 target = MulV2F(player->pos, -1.0f * cam.scale); - if(LenV2(SubV2(target, cam.pos)) <= 0.2) - { - cam.pos = target; - } - else - { - cam.pos = LerpV2(cam.pos, unwarped_dt*8.0f, target); - } - } - - PROFILE_SCOPE("flush rendering") - { - ARR_ITER(RenderingQueue, rendering_queues) - { - RenderingQueue *rendering_queue = it; - qsort(&rendering_queue->data[0], rendering_queue->cur_index, sizeof(rendering_queue->data[0]), rendering_compare); - - BUFF_ITER(DrawParams, rendering_queue) - { - DrawParams d = *it; - PROFILE_SCOPE("Draw quad") - { - Vec2 *points = d.quad.points; - quad_fs_params_t params = {0}; - params.tint[0] = d.tint.R; - params.tint[1] = d.tint.G; - params.tint[2] = d.tint.B; - params.tint[3] = d.tint.A; - params.alpha_clip_threshold = d.alpha_clip_threshold; - if(d.do_clipping) - { - if(d.world_space) - { - d.clip_to.upper_left = world_to_screen(d.clip_to.upper_left); - d.clip_to.lower_right = world_to_screen(d.clip_to.lower_right); - } - Vec2 aabb_clip_ul = into_clip_space(d.clip_to.upper_left); - Vec2 aabb_clip_lr = into_clip_space(d.clip_to.lower_right); - params.clip_ul[0] = aabb_clip_ul.x; - params.clip_ul[1] = aabb_clip_ul.y; - params.clip_lr[0] = aabb_clip_lr.x; - params.clip_lr[1] = aabb_clip_lr.y; - } - else - { - params.clip_ul[0] = -1.0; - params.clip_ul[1] = 1.0; - params.clip_lr[0] = 1.0; - params.clip_lr[1] = -1.0; - } - // if the rendering call is different, and the batch must be flushed - if(d.image.id != cur_batch_image.id || memcmp(¶ms,&cur_batch_params,sizeof(params)) != 0 ) - { - flush_quad_batch(); - cur_batch_image = d.image; - cur_batch_params = params; - } - - - AABB cam_aabb = screen_cam_aabb(); - AABB points_bounding_box = { .upper_left = V2(INFINITY, -INFINITY), .lower_right = V2(-INFINITY, INFINITY) }; - - for(int i = 0; i < 4; i++) - { - points_bounding_box.upper_left.X = fminf(points_bounding_box.upper_left.X, points[i].X); - points_bounding_box.upper_left.Y = fmaxf(points_bounding_box.upper_left.Y, points[i].Y); - - points_bounding_box.lower_right.X = fmaxf(points_bounding_box.lower_right.X, points[i].X); - points_bounding_box.lower_right.Y = fminf(points_bounding_box.lower_right.Y, points[i].Y); - } - if(!overlapping(cam_aabb, points_bounding_box)) - { - //dbgprint("Out of screen, cam aabb %f %f %f %f\n", cam_aabb.upper_left.X, cam_aabb.upper_left.Y, cam_aabb.lower_right.X, cam_aabb.lower_right.Y); - //dbgprint("Points boundig box %f %f %f %f\n", points_bounding_box.upper_left.X, points_bounding_box.upper_left.Y, points_bounding_box.lower_right.X, points_bounding_box.lower_right.Y); - continue; // cull out of screen quads - } - - float new_vertices[ FLOATS_PER_VERTEX*4 ] = {0}; - Vec2 region_size = SubV2(d.image_region.lower_right, d.image_region.upper_left); - assert(region_size.X > 0.0); - assert(region_size.Y > 0.0); - Vec2 tex_coords[4] = - { - AddV2(d.image_region.upper_left, V2(0.0, 0.0)), - AddV2(d.image_region.upper_left, V2(region_size.X, 0.0)), - AddV2(d.image_region.upper_left, V2(region_size.X, region_size.Y)), - AddV2(d.image_region.upper_left, V2(0.0, region_size.Y)), - }; - - // convert to uv space - sg_image_info info = sg_query_image_info(d.image); - for(int i = 0; i < 4; i++) - { - tex_coords[i] = DivV2(tex_coords[i], V2((float)info.width, (float)info.height)); - } - for(int i = 0; i < 4; i++) - { - Vec2 in_clip_space = into_clip_space(points[i]); - new_vertices[i*FLOATS_PER_VERTEX + 0] = in_clip_space.X; - new_vertices[i*FLOATS_PER_VERTEX + 1] = in_clip_space.Y; - // update Y_COORD_IN_BACK, Y_COORD_IN_FRONT when this changes - /* - float unmapped = (clampf(d.y_coord_sorting, -1.0f, 2.0f)); - float mapped = (unmapped + 1.0f)/3.0f; - new_vertices[i*FLOATS_PER_VERTEX + 2] = 1.0f - (float)clamp(mapped, 0.0, 1.0); - */ - new_vertices[i*FLOATS_PER_VERTEX + 2] = 0.0f; - new_vertices[i*FLOATS_PER_VERTEX + 3] = tex_coords[i].X; - new_vertices[i*FLOATS_PER_VERTEX + 4] = tex_coords[i].Y; - } - - // two triangles drawn, six vertices - size_t total_size = 6*FLOATS_PER_VERTEX; - - // batched a little too close to the sun - if(cur_batch_data_index + total_size >= ARRLEN(cur_batch_data)) - { - flush_quad_batch(); - cur_batch_image = d.image; - cur_batch_params = params; - } + // update camera position + { + Vec2 target = MulV2F(player->pos, -1.0f * cam.scale); + if (LenV2(SubV2(target, cam.pos)) <= 0.2) + { + cam.pos = target; + } + else + { + cam.pos = LerpV2(cam.pos, unwarped_dt*8.0f, target); + } + } + + PROFILE_SCOPE("flush rendering") + { + ARR_ITER(RenderingQueue, rendering_queues) + { + RenderingQueue *rendering_queue = it; + qsort(&rendering_queue->data[0], rendering_queue->cur_index, sizeof(rendering_queue->data[0]), rendering_compare); + + BUFF_ITER(DrawParams, rendering_queue) + { + DrawParams d = *it; + PROFILE_SCOPE("Draw quad") + { + Vec2 *points = d.quad.points; + quad_fs_params_t params = { 0 }; + params.tint[0] = d.tint.R; + params.tint[1] = d.tint.G; + params.tint[2] = d.tint.B; + params.tint[3] = d.tint.A; + params.alpha_clip_threshold = d.alpha_clip_threshold; + if (d.do_clipping) + { + if (d.world_space) + { + d.clip_to.upper_left = world_to_screen(d.clip_to.upper_left); + d.clip_to.lower_right = world_to_screen(d.clip_to.lower_right); + } + Vec2 aabb_clip_ul = into_clip_space(d.clip_to.upper_left); + Vec2 aabb_clip_lr = into_clip_space(d.clip_to.lower_right); + params.clip_ul[0] = aabb_clip_ul.x; + params.clip_ul[1] = aabb_clip_ul.y; + params.clip_lr[0] = aabb_clip_lr.x; + params.clip_lr[1] = aabb_clip_lr.y; + } + else + { + params.clip_ul[0] = -1.0; + params.clip_ul[1] = 1.0; + params.clip_lr[0] = 1.0; + params.clip_lr[1] = -1.0; + } + // if the rendering call is different, and the batch must be flushed + if (d.image.id != cur_batch_image.id || memcmp(¶ms, &cur_batch_params, sizeof(params)) != 0) + { + flush_quad_batch(); + cur_batch_image = d.image; + cur_batch_params = params; + } + + + AABB cam_aabb = screen_cam_aabb(); + AABB points_bounding_box = { .upper_left = V2(INFINITY, -INFINITY), .lower_right = V2(-INFINITY, INFINITY) }; + + for (int i = 0; i < 4; i++) + { + points_bounding_box.upper_left.X = fminf(points_bounding_box.upper_left.X, points[i].X); + points_bounding_box.upper_left.Y = fmaxf(points_bounding_box.upper_left.Y, points[i].Y); + + points_bounding_box.lower_right.X = fmaxf(points_bounding_box.lower_right.X, points[i].X); + points_bounding_box.lower_right.Y = fminf(points_bounding_box.lower_right.Y, points[i].Y); + } + if (!overlapping(cam_aabb, points_bounding_box)) + { + //dbgprint("Out of screen, cam aabb %f %f %f %f\n", cam_aabb.upper_left.X, cam_aabb.upper_left.Y, cam_aabb.lower_right.X, cam_aabb.lower_right.Y); + //dbgprint("Points boundig box %f %f %f %f\n", points_bounding_box.upper_left.X, points_bounding_box.upper_left.Y, points_bounding_box.lower_right.X, points_bounding_box.lower_right.Y); + continue; // cull out of screen quads + } + + float new_vertices[ FLOATS_PER_VERTEX*4 ] = { 0 }; + Vec2 region_size = SubV2(d.image_region.lower_right, d.image_region.upper_left); + assert(region_size.X > 0.0); + assert(region_size.Y > 0.0); + Vec2 tex_coords[4] = + { + AddV2(d.image_region.upper_left, V2(0.0, 0.0)), + AddV2(d.image_region.upper_left, V2(region_size.X, 0.0)), + AddV2(d.image_region.upper_left, V2(region_size.X, region_size.Y)), + AddV2(d.image_region.upper_left, V2(0.0, region_size.Y)), + }; + + // convert to uv space + sg_image_info info = sg_query_image_info(d.image); + for (int i = 0; i < 4; i++) + { + tex_coords[i] = DivV2(tex_coords[i], V2((float)info.width, (float)info.height)); + } + for (int i = 0; i < 4; i++) + { + Vec2 in_clip_space = into_clip_space(points[i]); + new_vertices[i*FLOATS_PER_VERTEX + 0] = in_clip_space.X; + new_vertices[i*FLOATS_PER_VERTEX + 1] = in_clip_space.Y; + // update Y_COORD_IN_BACK, Y_COORD_IN_FRONT when this changes + /* + float unmapped = (clampf(d.y_coord_sorting, -1.0f, 2.0f)); + float mapped = (unmapped + 1.0f)/3.0f; + new_vertices[i*FLOATS_PER_VERTEX + 2] = 1.0f - (float)clamp(mapped, 0.0, 1.0); + */ + new_vertices[i*FLOATS_PER_VERTEX + 2] = 0.0f; + new_vertices[i*FLOATS_PER_VERTEX + 3] = tex_coords[i].X; + new_vertices[i*FLOATS_PER_VERTEX + 4] = tex_coords[i].Y; + } + + // two triangles drawn, six vertices + size_t total_size = 6*FLOATS_PER_VERTEX; + + // batched a little too close to the sun + if (cur_batch_data_index + total_size >= ARRLEN(cur_batch_data)) + { + flush_quad_batch(); + cur_batch_image = d.image; + cur_batch_params = params; + } #define PUSH_VERTEX(vert) { memcpy(&cur_batch_data[cur_batch_data_index], &vert, FLOATS_PER_VERTEX*sizeof(float)); cur_batch_data_index += FLOATS_PER_VERTEX; } - PUSH_VERTEX(new_vertices[0*FLOATS_PER_VERTEX]); - PUSH_VERTEX(new_vertices[1*FLOATS_PER_VERTEX]); - PUSH_VERTEX(new_vertices[2*FLOATS_PER_VERTEX]); - PUSH_VERTEX(new_vertices[0*FLOATS_PER_VERTEX]); - PUSH_VERTEX(new_vertices[2*FLOATS_PER_VERTEX]); - PUSH_VERTEX(new_vertices[3*FLOATS_PER_VERTEX]); + PUSH_VERTEX(new_vertices[0*FLOATS_PER_VERTEX]); + PUSH_VERTEX(new_vertices[1*FLOATS_PER_VERTEX]); + PUSH_VERTEX(new_vertices[2*FLOATS_PER_VERTEX]); + PUSH_VERTEX(new_vertices[0*FLOATS_PER_VERTEX]); + PUSH_VERTEX(new_vertices[2*FLOATS_PER_VERTEX]); + PUSH_VERTEX(new_vertices[3*FLOATS_PER_VERTEX]); #undef PUSH_VERTEX - } - } - BUFF_CLEAR(rendering_queue); - - } - - // end of rendering - flush_quad_batch(); - sg_end_pass(); - sg_commit(); - } - - last_frame_processing_time = stm_sec(stm_diff(stm_now(),time_start_frame)); - - reset(&scratch); - pressed = (PressedState){0}; - } -} - -void cleanup(void) -{ - sg_shutdown(); - hmfree(imui_state); - Log("Cleaning up\n"); -} - -void event(const sapp_event *e) -{ - if(e->key_repeat) return; - if(e->type == SAPP_EVENTTYPE_TOUCHES_BEGAN) - { - if(!mobile_controls) - { - thumbstick_base_pos = V2(screen_size().x * 0.25f, screen_size().y * 0.25f); - thumbstick_nub_pos = thumbstick_base_pos; - } - mobile_controls = true; - } + } + } + BUFF_CLEAR(rendering_queue); + + } + + // end of rendering + flush_quad_batch(); + sg_end_pass(); + sg_commit(); + } + + last_frame_processing_time = stm_sec(stm_diff(stm_now(), time_start_frame)); + + reset(&scratch); + pressed = (PressedState) { 0 }; + } + } + + void cleanup(void) + { + sg_shutdown(); + hmfree(imui_state); + Log("Cleaning up\n"); + } + + void event(const sapp_event *e) + { + if (e->key_repeat) return; + if (e->type == SAPP_EVENTTYPE_TOUCHES_BEGAN) + { + if (!mobile_controls) + { + thumbstick_base_pos = V2(screen_size().x * 0.25f, screen_size().y * 0.25f); + thumbstick_nub_pos = thumbstick_base_pos; + } + mobile_controls = true; + } #ifdef DESKTOP - // the desktop text backend, for debugging purposes - if(receiving_text_input) - { - if(e->type == SAPP_EVENTTYPE_CHAR) - { - if(BUFF_HAS_SPACE(&text_input_buffer)) - { - BUFF_APPEND(&text_input_buffer, (char)e->char_code); - } - } - if(e->type == SAPP_EVENTTYPE_KEY_DOWN && e->key_code == SAPP_KEYCODE_ENTER) - { - end_text_input(text_input_buffer.data); - } - } + // the desktop text backend, for debugging purposes + if (receiving_text_input) + { + if (e->type == SAPP_EVENTTYPE_CHAR) + { + if (BUFF_HAS_SPACE(&text_input_buffer)) + { + BUFF_APPEND(&text_input_buffer, (char)e->char_code); + } + } + if (e->type == SAPP_EVENTTYPE_KEY_DOWN && e->key_code == SAPP_KEYCODE_ENTER) + { + end_text_input(text_input_buffer.data); + } + } #endif - // mobile handling touch controls handling touch input - if(mobile_controls) - { - if(e->type == SAPP_EVENTTYPE_TOUCHES_BEGAN) - { + // mobile handling touch controls handling touch input + if (mobile_controls) + { + if (e->type == SAPP_EVENTTYPE_TOUCHES_BEGAN) + { #define TOUCHPOINT_SCREEN(point) V2(point.pos_x, screen_size().y - point.pos_y) - for(int i = 0; i < e->num_touches; i++) - { - sapp_touchpoint point = e->touches[i]; - Vec2 touchpoint_screen_pos = TOUCHPOINT_SCREEN(point); - if(touchpoint_screen_pos.x < screen_size().x*0.4f) - { - if(!movement_touch.active) - { - //if(LenV2(SubV2(touchpoint_screen_pos, thumbstick_base_pos)) > 1.25f * thumbstick_base_size()) - if(true) - { - thumbstick_base_pos = touchpoint_screen_pos; - } - movement_touch = activate(point.identifier); - thumbstick_nub_pos = thumbstick_base_pos; - } - } - if(LenV2(SubV2(touchpoint_screen_pos, roll_button_pos())) < mobile_button_size()*0.5f) - { - roll_pressed_by = activate(point.identifier); - mobile_roll_pressed = true; - } - if(LenV2(SubV2(touchpoint_screen_pos, interact_button_pos())) < mobile_button_size()*0.5f) - { - interact_pressed_by = activate(point.identifier); - mobile_interact_pressed = true; - pressed.interact = true; - } - if(LenV2(SubV2(touchpoint_screen_pos, attack_button_pos())) < mobile_button_size()*0.5f) - { - attack_pressed_by = activate(point.identifier); - mobile_attack_pressed = true; - } - } - } - if(e->type == SAPP_EVENTTYPE_TOUCHES_MOVED) - { - for(int i = 0; i < e->num_touches; i++) - { - if(movement_touch.active) - { - if(e->touches[i].identifier == movement_touch.identifier) - { - thumbstick_nub_pos = TOUCHPOINT_SCREEN(e->touches[i]); - Vec2 move_vec = SubV2(thumbstick_nub_pos, thumbstick_base_pos); - float clampto_size = thumbstick_base_size()/2.0f; - if(LenV2(move_vec) > clampto_size) - { - thumbstick_nub_pos = AddV2(thumbstick_base_pos, MulV2F(NormV2(move_vec), clampto_size)); - } - } - } - } - } - if(e->type == SAPP_EVENTTYPE_TOUCHES_ENDED) - { - for(int i = 0; i < e->num_touches; i++) - if(e->touches[i].changed) // only some of the touch events are released - { - if(maybe_deactivate(&interact_pressed_by, e->touches[i].identifier)) - { - mobile_interact_pressed = false; - } - if(maybe_deactivate(&roll_pressed_by, e->touches[i].identifier)) - { - mobile_roll_pressed = false; - } - if(maybe_deactivate(&attack_pressed_by, e->touches[i].identifier)) - { - mobile_attack_pressed = false; - } - if(maybe_deactivate(&movement_touch, e->touches[i].identifier)) - { - thumbstick_nub_pos = thumbstick_base_pos; - } - } - } - } - - if(e->type == SAPP_EVENTTYPE_MOUSE_DOWN) - { - if(e->mouse_button == SAPP_MOUSEBUTTON_LEFT) - { - pressed.mouse_down = true; - mouse_down = true; - } - } - - if(e->type == SAPP_EVENTTYPE_MOUSE_UP) - { - if(e->mouse_button == SAPP_MOUSEBUTTON_LEFT) - { - mouse_down = false; - pressed.mouse_up = true; - } - } - - if(e->type == SAPP_EVENTTYPE_KEY_DOWN) + for (int i = 0; i < e->num_touches; i++) + { + sapp_touchpoint point = e->touches[i]; + Vec2 touchpoint_screen_pos = TOUCHPOINT_SCREEN(point); + if (touchpoint_screen_pos.x < screen_size().x*0.4f) + { + if (!movement_touch.active) + { + //if(LenV2(SubV2(touchpoint_screen_pos, thumbstick_base_pos)) > 1.25f * thumbstick_base_size()) + if (true) + { + thumbstick_base_pos = touchpoint_screen_pos; + } + movement_touch = activate(point.identifier); + thumbstick_nub_pos = thumbstick_base_pos; + } + } + if (LenV2(SubV2(touchpoint_screen_pos, roll_button_pos())) < mobile_button_size()*0.5f) + { + roll_pressed_by = activate(point.identifier); + mobile_roll_pressed = true; + } + if (LenV2(SubV2(touchpoint_screen_pos, interact_button_pos())) < mobile_button_size()*0.5f) + { + interact_pressed_by = activate(point.identifier); + mobile_interact_pressed = true; + pressed.interact = true; + } + if (LenV2(SubV2(touchpoint_screen_pos, attack_button_pos())) < mobile_button_size()*0.5f) + { + attack_pressed_by = activate(point.identifier); + mobile_attack_pressed = true; + } + } + } + if (e->type == SAPP_EVENTTYPE_TOUCHES_MOVED) + { + for (int i = 0; i < e->num_touches; i++) + { + if (movement_touch.active) + { + if (e->touches[i].identifier == movement_touch.identifier) + { + thumbstick_nub_pos = TOUCHPOINT_SCREEN(e->touches[i]); + Vec2 move_vec = SubV2(thumbstick_nub_pos, thumbstick_base_pos); + float clampto_size = thumbstick_base_size() / 2.0f; + if (LenV2(move_vec) > clampto_size) + { + thumbstick_nub_pos = AddV2(thumbstick_base_pos, MulV2F(NormV2(move_vec), clampto_size)); + } + } + } + } + } + if (e->type == SAPP_EVENTTYPE_TOUCHES_ENDED) + { + for (int i = 0; i < e->num_touches; i++) + if (e->touches[i].changed) // only some of the touch events are released + { + if (maybe_deactivate(&interact_pressed_by, e->touches[i].identifier)) + { + mobile_interact_pressed = false; + } + if (maybe_deactivate(&roll_pressed_by, e->touches[i].identifier)) + { + mobile_roll_pressed = false; + } + if (maybe_deactivate(&attack_pressed_by, e->touches[i].identifier)) + { + mobile_attack_pressed = false; + } + if (maybe_deactivate(&movement_touch, e->touches[i].identifier)) + { + thumbstick_nub_pos = thumbstick_base_pos; + } + } + } + } + + if (e->type == SAPP_EVENTTYPE_MOUSE_DOWN) + { + if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) + { + pressed.mouse_down = true; + mouse_down = true; + } + } + + if (e->type == SAPP_EVENTTYPE_MOUSE_UP) + { + if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) + { + mouse_down = false; + pressed.mouse_up = true; + } + } + + if (e->type == SAPP_EVENTTYPE_KEY_DOWN) #ifdef DESKTOP - if(!receiving_text_input) + if (!receiving_text_input) #endif - { - mobile_controls = false; - assert(e->key_code < sizeof(keydown)/sizeof(*keydown)); - keydown[e->key_code] = true; - - if(e->key_code == SAPP_KEYCODE_E) - { - pressed.interact = true; - } - - if(e->key_code == SAPP_KEYCODE_LEFT_SHIFT) - { - learned_shift += 0.15f; - } - if(e->key_code == SAPP_KEYCODE_SPACE) - { - learned_space += 0.15f; - } - if(e->key_code == SAPP_KEYCODE_E) - { - learned_e += 0.15f; - } + { + mobile_controls = false; + assert(e->key_code < sizeof(keydown) / sizeof(*keydown)); + keydown[e->key_code] = true; + + if (e->key_code == SAPP_KEYCODE_E) + { + pressed.interact = true; + } + + if (e->key_code == SAPP_KEYCODE_LEFT_SHIFT) + { + learned_shift += 0.15f; + } + if (e->key_code == SAPP_KEYCODE_SPACE) + { + learned_space += 0.15f; + } + if (e->key_code == SAPP_KEYCODE_E) + { + learned_e += 0.15f; + } #ifdef DESKTOP // very nice for my run from cmdline workflow, escape to quit - if(e->key_code == SAPP_KEYCODE_ESCAPE) - { - sapp_quit(); - } + if (e->key_code == SAPP_KEYCODE_ESCAPE) + { + sapp_quit(); + } #endif #ifdef DEVTOOLS - if(e->key_code == SAPP_KEYCODE_T) - { - mouse_frozen = !mouse_frozen; - } - if(e->key_code == SAPP_KEYCODE_M) - { - mobile_controls = true; - } - if(e->key_code == SAPP_KEYCODE_P) - { - profiling = !profiling; - if(profiling) - { - init_profiling("rpgpt.spall"); - init_profiling_mythread(0); - } - else - { - end_profiling_mythread(); - end_profiling(); - } - } - if(e->key_code == SAPP_KEYCODE_7) - { - show_devtools = !show_devtools; - } + if (e->key_code == SAPP_KEYCODE_T) + { + mouse_frozen = !mouse_frozen; + } + if (e->key_code == SAPP_KEYCODE_M) + { + mobile_controls = true; + } + if (e->key_code == SAPP_KEYCODE_P) + { + profiling = !profiling; + if (profiling) + { + init_profiling("rpgpt.spall"); + init_profiling_mythread(0); + } + else + { + end_profiling_mythread(); + end_profiling(); + } + } + if (e->key_code == SAPP_KEYCODE_7) + { + show_devtools = !show_devtools; + } #endif - } - if(e->type == SAPP_EVENTTYPE_KEY_UP) - { - keydown[e->key_code] = false; - } - if(e->type == SAPP_EVENTTYPE_MOUSE_MOVE) - { - bool ignore_movement = false; + } + if (e->type == SAPP_EVENTTYPE_KEY_UP) + { + keydown[e->key_code] = false; + } + if (e->type == SAPP_EVENTTYPE_MOUSE_MOVE) + { + bool ignore_movement = false; #ifdef DEVTOOLS - if(mouse_frozen) ignore_movement = true; + if (mouse_frozen) ignore_movement = true; #endif - if(!ignore_movement) mouse_pos = V2(e->mouse_x, (float)sapp_height() - e->mouse_y); - } -} - -sapp_desc sokol_main(int argc, char* argv[]) -{ - (void)argc; (void)argv; - return (sapp_desc){ - .init_cb = init, - .frame_cb = frame, - .cleanup_cb = cleanup, - .event_cb = event, - .width = 800, - .height = 600, - //.gl_force_gles2 = true, not sure why this was here in example, look into - .window_title = "RPGPT", - .win32_console_attach = true, - .win32_console_create = true, - .icon.sokol_default = true, - }; -} + if (!ignore_movement) mouse_pos = V2(e->mouse_x, (float)sapp_height() - e->mouse_y); + } + } + + sapp_desc sokol_main(int argc, char* argv[]) + { + (void)argc; (void)argv; + return (sapp_desc) { + .init_cb = init, + .frame_cb = frame, + .cleanup_cb = cleanup, + .event_cb = event, + .width = 800, + .height = 600, + //.gl_force_gles2 = true, not sure why this was here in example, look into + .window_title = "RPGPT", + .win32_console_attach = true, + .win32_console_create = true, + .icon.sokol_default = true, + }; + } diff --git a/makeprompt.h b/makeprompt.h index e293887..3701130 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -1,4 +1,5 @@ #pragma once + #include "buff.h" #include "HandmadeMath.h" // vector types in entity struct definition #include @@ -7,8 +8,6 @@ #include // atoi #include "character_info.h" #include "characters.gen.h" -NPC_MOOSE, -} NpcKind; // TODO do strings: https://pastebin.com/Kwcw2sye @@ -19,7 +18,7 @@ NPC_MOOSE, // REFACTORING:: also have to update in javascript!!!!!!!! #define MAX_SENTENCE_LENGTH 400 // LOOOK AT AGBOVE COMMENT GBEFORE CHANGING typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence; -#define SENTENCE_CONST(txt) {.data=txt, .cur_index=sizeof(txt)} +#define SENTENCE_CONST(txt) { .data = txt, .cur_index = sizeof(txt) } #define SENTENCE_CONST_CAST(txt) (Sentence)SENTENCE_CONST(txt) #define REMEMBERED_PERCEPTIONS 24 @@ -37,104 +36,106 @@ typedef BUFF(char, MAX_SENTENCE_LENGTH) Sentence; // Never expected such a stupid stuff from such a great director. If there is 0 stari can give that or -200 to this movie. Its worst to see and unnecessary loss of money -typedef BUFF(char, 1024*10) Escaped; +typedef BUFF(char, 1024 * 10) Escaped; + Escaped escape_for_json(const char *s) { - Escaped to_return = {0}; - size_t len = strlen(s); - for(int i = 0; i < len; i++) - { - if(s[i] == '\n') - { - BUFF_APPEND(&to_return, '\\'); - BUFF_APPEND(&to_return, 'n'); - } - else if(s[i] == '"') - { - BUFF_APPEND(&to_return, '\\'); - BUFF_APPEND(&to_return, '"'); - } - else - { - if(!(s[i] <= 126 && s[i] >= 32 )) - { - BUFF_APPEND(&to_return, '?'); - Log("Unknown character code %d\n", s[i]); - } - BUFF_APPEND(&to_return, s[i]); - } - } - return to_return; + Escaped to_return = { 0 }; + size_t len = strlen(s); + for (int i = 0; i < len; i++) + { + if (s[i] == '\n') + { + BUFF_APPEND(&to_return, '\\'); + BUFF_APPEND(&to_return, 'n'); + } + else if (s[i] == '"') + { + BUFF_APPEND(&to_return, '\\'); + BUFF_APPEND(&to_return, '"'); + } + else + { + if (!(s[i] <= 126 && s[i] >= 32)) + { + BUFF_APPEND(&to_return, '?'); + Log("Unknown character code %d\n", s[i]); + } + BUFF_APPEND(&to_return, s[i]); + } + } + return to_return; } + typedef enum PerceptionType { - Invalid, // so that zero value in training structs means end of perception - PlayerAction, - PlayerDialog, - NPCDialog, // includes an npc action in every npc dialog. So it's often nothing - EnemyAction, // An enemy performed an action against the NPC - PlayerHeldItemChanged, + Invalid, // so that zero value in training structs means end of perception + PlayerAction, + PlayerDialog, + NPCDialog, // includes an npc action in every npc dialog. So it's often nothing + EnemyAction, // An enemy performed an action against the NPC + PlayerHeldItemChanged, } PerceptionType; typedef struct Perception { - PerceptionType type; - - float damage_done; // Valid in player action and enemy action - ItemKind given_item; // valid in player action and enemy action when the kind is such that there is an item to be given - union - { - // player action - struct - { - Action player_action_type; - }; - - // player dialog - Sentence player_dialog; - - // npc dialog - struct - { - Action npc_action_type; - Sentence npc_dialog; - }; - - // enemy action - Action enemy_action_type; - - // player holding item. MUST precede any perceptions which come after the player is holding the item - ItemKind holding; - }; + PerceptionType type; + + float damage_done; // Valid in player action and enemy action + ItemKind given_item; // valid in player action and enemy action when the kind is such that there is an item to be given + union + { + // player action + struct + { + Action player_action_type; + }; + + // player dialog + Sentence player_dialog; + + // npc dialog + struct + { + Action npc_action_type; + Sentence npc_dialog; + }; + + // enemy action + Action enemy_action_type; + + // player holding item. MUST precede any perceptions which come after the player is holding the item + ItemKind holding; + }; } Perception; typedef enum PropKind { - TREE0, - TREE1, - TREE2, - ROCK0, + TREE0, + TREE1, + TREE2, + ROCK0, } PropKind; typedef struct EntityRef { - int index; - int generation; + int index; + int generation; } EntityRef; typedef enum CharacterState { - CHARACTER_WALKING, - CHARACTER_IDLE, - CHARACTER_ATTACK, - CHARACTER_TALKING, + CHARACTER_WALKING, + CHARACTER_IDLE, + CHARACTER_ATTACK, + CHARACTER_TALKING, } CharacterState; typedef enum { - STANDING_INDIFFERENT, - STANDING_JOINED, - STANDING_FIGHTING, + STANDING_INDIFFERENT, + STANDING_JOINED, + STANDING_FIGHTING, } NPCPlayerStanding; @@ -142,525 +143,561 @@ typedef Vec4 Color; typedef struct { - AnimKind anim; - double elapsed_time; - bool flipped; - Vec2 pos; - Color tint; - bool no_shadow; + AnimKind anim; + double elapsed_time; + bool flipped; + Vec2 pos; + Color tint; + bool no_shadow; } DrawnAnimatedSprite; typedef struct { - DrawnAnimatedSprite drawn; - float alive_for; + DrawnAnimatedSprite drawn; + float alive_for; } PlayerAfterImage; typedef BUFF(Vec2, MAX_ASTAR_NODES) AStarPath; typedef struct { - bool exists; - int generation; - double elapsed_time; + bool exists; + int generation; + double elapsed_time; - AStarPath path; + AStarPath path; } PathCache; typedef struct { - int generation; - int index; + int generation; + int index; } PathCacheHandle; -typedef struct { - bool is_reference; - EntityRef ref; - Vec2 pos; +typedef struct +{ + bool is_reference; + EntityRef ref; + Vec2 pos; } Target; typedef struct Entity { - bool exists; - bool destroy; - int generation; - - // fields for all gs.entities - Vec2 pos; - 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; - // multiple gs.entities have a sword swing - BUFF(EntityRef, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around - - // npcs and player - BUFF(ItemKind, 32) held_items; - - bool is_bullet; - - // props - bool is_prop; - PropKind prop_kind; - - // items - bool is_item; - bool held_by_player; - ItemKind item_kind; - - // npcs - bool is_npc; - bool being_hovered; - bool perceptions_dirty; - - BUFF(Perception, REMEMBERED_PERCEPTIONS) remembered_perceptions; - bool direction_of_spiral_pattern; - float dialog_panel_opacity; - double characters_said; - NPCPlayerStanding standing; - NpcKind npc_kind; - PathCacheHandle cached_path; - ItemKind last_seen_holding_kind; + bool exists; + bool destroy; + int generation; + + // fields for all gs.entities + Vec2 pos; + 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; + // multiple gs.entities have a sword swing + BUFF(EntityRef, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around + + // npcs and player + BUFF(ItemKind, 32) held_items; + + bool is_bullet; + + // props + bool is_prop; + PropKind prop_kind; + + // items + bool is_item; + bool held_by_player; + ItemKind item_kind; + + // npcs + bool is_npc; + bool being_hovered; + bool perceptions_dirty; + + BUFF(Perception, REMEMBERED_PERCEPTIONS) remembered_perceptions; + bool direction_of_spiral_pattern; + float dialog_panel_opacity; + double characters_said; + NPCPlayerStanding standing; + NpcKind npc_kind; + PathCacheHandle cached_path; + ItemKind last_seen_holding_kind; #ifdef WEB - int gen_request_id; + int gen_request_id; #endif - bool walking; - double shotgun_timer; - bool moved; - Vec2 target_goto; - // only for skeleton npc - double swing_timer; - - // character - bool is_character; - EntityRef holding_item; - bool in_conversation_mode; - Vec2 to_throw_direction; - - CharacterState state; - EntityRef talking_to; - bool is_rolling; // can only roll in idle or walk states - double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible - - // so doesn't change animations while time is stopped - AnimKind cur_animation; - float anim_change_timer; - - BUFF(PlayerAfterImage, MAX_AFTERIMAGES) after_images; - double after_image_timer; - double roll_progress; - double swing_progress; + bool walking; + double shotgun_timer; + bool moved; + Vec2 target_goto; + // only for skeleton npc + double swing_timer; + + // character + bool is_character; + EntityRef holding_item; + bool in_conversation_mode; + Vec2 to_throw_direction; + + CharacterState state; + EntityRef talking_to; + bool is_rolling; // can only roll in idle or walk states + double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible + + // so doesn't change animations while time is stopped + AnimKind cur_animation; + float anim_change_timer; + + BUFF(PlayerAfterImage, MAX_AFTERIMAGES) after_images; + double after_image_timer; + double roll_progress; + double swing_progress; } Entity; bool npc_is_knight_sprite(Entity *it) { - return it->is_npc && ( it->npc_kind == NPC_TheGuard || it->npc_kind == NPC_Edeline || it->npc_kind == NPC_TheKing || it->npc_kind == NPC_TheBlacksmith); + return it->is_npc && (it->npc_kind == NPC_TheGuard || it->npc_kind == NPC_Edeline || it->npc_kind == NPC_TheKing || + it->npc_kind == NPC_TheBlacksmith); } bool npc_is_skeleton(Entity *it) { - return it->is_npc && ( it->npc_kind == NPC_MikeSkeleton ); + return it->is_npc && (it->npc_kind == NPC_MikeSkeleton); } float entity_max_damage(Entity *e) { - if(e->is_npc && npc_is_skeleton(e)) - { - return 2.0f; - } - else - { - return 1.0f; - } + if (e->is_npc && npc_is_skeleton(e)) + { + return 2.0f; + } + else + { + return 1.0f; + } } bool npc_attacks_with_sword(Entity *it) { - return npc_is_skeleton(it); + return npc_is_skeleton(it); } bool npc_attacks_with_shotgun(Entity *it) { - return it->is_npc && ( it->npc_kind == NPC_OldMan ); + return it->is_npc && (it->npc_kind == NPC_OldMan); } -typedef BUFF(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff; +typedef BUFF(char, MAX_SENTENCE_LENGTH * (REMEMBERED_PERCEPTIONS + 4)) PromptBuff; typedef BUFF(Action, 8) AvailableActions; void fill_available_actions(Entity *it, AvailableActions *a) { - *a = (AvailableActions){0}; - BUFF_APPEND(a, ACT_none); - BUFF_APPEND(a, ACT_give_item); - if(it->npc_kind == NPC_GodRock) - { - BUFF_APPEND(a, ACT_heals_player); - } - else - { - if(it->standing == STANDING_INDIFFERENT) - { - BUFF_APPEND(a, ACT_fights_player); - BUFF_APPEND(a, ACT_joins_player); - } - else if(it->standing == STANDING_JOINED) - { - BUFF_APPEND(a, ACT_leaves_player); - BUFF_APPEND(a, ACT_fights_player); - } - else if(it->standing == STANDING_FIGHTING) - { - BUFF_APPEND(a, ACT_stops_fighting_player); - } - if(npc_is_knight_sprite(it)) - { - BUFF_APPEND(a, ACT_strikes_air); - } - if(it->npc_kind == NPC_TheGuard) - { - if(!it->moved) - { - BUFF_APPEND(a, ACT_allows_player_to_pass); - } - } - } + *a = (AvailableActions) { 0 }; + BUFF_APPEND(a, ACT_none); + BUFF_APPEND(a, ACT_give_item); + if (it->npc_kind == NPC_GodRock) + { + BUFF_APPEND(a, ACT_heals_player); + } + else + { + if (it->standing == STANDING_INDIFFERENT) + { + BUFF_APPEND(a, ACT_fights_player); + BUFF_APPEND(a, ACT_joins_player); + } + else if (it->standing == STANDING_JOINED) + { + BUFF_APPEND(a, ACT_leaves_player); + BUFF_APPEND(a, ACT_fights_player); + } + else if (it->standing == STANDING_FIGHTING) + { + BUFF_APPEND(a, ACT_stops_fighting_player); + } + if (npc_is_knight_sprite(it)) + { + BUFF_APPEND(a, ACT_strikes_air); + } + if (it->npc_kind == NPC_TheGuard) + { + if (!it->moved) + { + BUFF_APPEND(a, ACT_allows_player_to_pass); + } + } + } } // returns if action index was valid bool action_from_index(Entity *it, Action *out, int action_index) { - AvailableActions available = {0}; - fill_available_actions(it, &available); - if(action_index < 0 || action_index >= available.cur_index) - { - return false; - } - else - { - *out = available.data[action_index]; - return true; - } + AvailableActions available = { 0 }; + fill_available_actions(it, &available); + if (action_index < 0 || action_index >= available.cur_index) + { + return false; + } + else + { + *out = available.data[action_index]; + return true; + } } // don't call on untrusted action, doesn't return error int action_to_index(Entity *it, Action a) { - AvailableActions available = {0}; - fill_available_actions(it, &available); - Action target_action = a; - int index = -1; - for(int i = 0; i < available.cur_index; i++) - { - if(available.data[i] == target_action) - { - index = i; - break; - } - } - assert(index != -1); - return index; + AvailableActions available = { 0 }; + fill_available_actions(it, &available); + Action target_action = a; + int index = -1; + for (int i = 0; i < available.cur_index; i++) + { + if (available.data[i] == target_action) + { + index = i; + break; + } + } + assert(index != -1); + return index; } void process_perception(Entity *it, Perception p, Entity *player) { - if(it->is_npc) - { - if(p.type != NPCDialog) it->perceptions_dirty = true; - if(!BUFF_HAS_SPACE(&it->remembered_perceptions)) BUFF_REMOVE_FRONT(&it->remembered_perceptions); - BUFF_APPEND(&it->remembered_perceptions, p); - if(p.type == PlayerAction && p.player_action_type == ACT_hits_npc) - { - it->damage += p.damage_done; - } - if(p.type == PlayerHeldItemChanged) - { - it->last_seen_holding_kind = p.holding; - } - else if(p.type == NPCDialog) - { - if(p.npc_action_type == ACT_allows_player_to_pass) - { - it->target_goto = AddV2(it->pos, V2(-50.0, 0.0)); - it->moved = true; - } - else if(p.npc_action_type == ACT_fights_player) - { - it->standing = STANDING_FIGHTING; - } - else if(p.npc_action_type == ACT_stops_fighting_player) - { - it->standing = STANDING_INDIFFERENT; - } - else if(p.npc_action_type == ACT_leaves_player) - { - it->standing = STANDING_INDIFFERENT; - } - else if(p.npc_action_type == ACT_joins_player) - { - it->standing = STANDING_JOINED; - } - else if(p.npc_action_type == ACT_give_item) - { - int item_to_remove = -1; - Entity *e = it; - BUFF_ITER_I(ItemKind, &e->held_items, i) - { - if(*it == p.given_item) - { - 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[p.given_item].name, characters[it->npc_kind].name); - assert(false); - } - else - { - BUFF_REMOVE_AT_INDEX(&it->held_items, item_to_remove); - BUFF_APPEND(&player->held_items, p.given_item); - } - } - else - { - // actions that take an argument have to have some kind of side effect based on that argument... - assert(!actions[p.npc_action_type].takes_argument); - } - } - } + assert(it->is_npc); + if (p.type != NPCDialog) it->perceptions_dirty = true; + if (!BUFF_HAS_SPACE(&it->remembered_perceptions)) + BUFF_REMOVE_FRONT(&it->remembered_perceptions); + BUFF_APPEND(&it->remembered_perceptions, p); + if (p.type == PlayerAction) + { + if (p.player_action_type == ACT_hits_npc) + { + it->damage += p.damage_done; + } + else if(p.player_action_type == ACT_give_item) + { + BUFF_APPEND(&it->held_items, p.given_item); + } + else + { + assert(!actions[p.player_action_type].takes_argument); + } + } + else if (p.type == PlayerDialog) + { + + } + else if (p.type == PlayerHeldItemChanged) + { + it->last_seen_holding_kind = p.holding; + } + else if (p.type == NPCDialog) + { + if (p.npc_action_type == ACT_allows_player_to_pass) + { + it->target_goto = AddV2(it->pos, V2(-50.0, 0.0)); + it->moved = true; + } + else if (p.npc_action_type == ACT_fights_player) + { + it->standing = STANDING_FIGHTING; + } + else if (p.npc_action_type == ACT_stops_fighting_player) + { + it->standing = STANDING_INDIFFERENT; + } + else if (p.npc_action_type == ACT_leaves_player) + { + it->standing = STANDING_INDIFFERENT; + } + else if (p.npc_action_type == ACT_joins_player) + { + it->standing = STANDING_JOINED; + } + else if (p.npc_action_type == ACT_give_item) + { + int item_to_remove = -1; + Entity *e = it; + BUFF_ITER_I(ItemKind, &e->held_items, i) + { + if (*it == p.given_item) + { + 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[p.given_item].name, + characters[it->npc_kind].name); + assert(false); + } + else + { + BUFF_REMOVE_AT_INDEX(&it->held_items, item_to_remove); + BUFF_APPEND(&player->held_items, p.given_item); + } + } + else + { + // actions that take an argument have to have some kind of side effect based on that argument... + assert(!actions[p.npc_action_type].takes_argument); + } + } + else + { + assert(false); + } } // returns if printed into the buff without any errors bool printf_buff_impl(BuffRef into, const char *format, ...) { - assert(*into.cur_index < into.max_data_elems); - assert(into.data_elem_size == 1); // characters - va_list args; - va_start (args, format); - size_t n = into.max_data_elems - *into.cur_index; - int written = vsnprintf((char*)into.data + *into.cur_index, n, format, args); - - if(written < 0) - { - } - else - { - *into.cur_index += written; - } - - // https://cplusplus.com/reference/cstdio/vsnprintf/ - bool succeeded = true; - if(written < 0) succeeded = false; // encoding error - if(written >= n) succeeded = false; // didn't fit in buffer - - va_end(args); - return succeeded; + assert(*into.cur_index < into.max_data_elems); + assert(into.data_elem_size == 1); // characters + va_list args; + va_start (args, format); + size_t n = into.max_data_elems - *into.cur_index; + int written = vsnprintf((char *) into.data + *into.cur_index, n, format, args); + + if (written < 0) + { + } + else + { + *into.cur_index += written; + } + + // https://cplusplus.com/reference/cstdio/vsnprintf/ + bool succeeded = true; + if (written < 0) succeeded = false; // encoding error + if (written >= n) succeeded = false; // didn't fit in buffer + + va_end(args); + return succeeded; } #define printf_buff(buff_ptr, ...) printf_buff_impl(BUFF_MAKEREF(buff_ptr), __VA_ARGS__) bool npc_does_dialog(Entity *it) { - return it->npc_kind < ARRLEN(characters); + return it->npc_kind < ARRLEN(characters); } typedef enum { - MSG_SYSTEM, - MSG_USER, - MSG_ASSISTANT, + MSG_SYSTEM, + MSG_USER, + MSG_ASSISTANT, } MessageType; // stops if the sentence is gonna run out of room void append_str(Sentence *to_append, const char *str) { - size_t len = strlen(str); - for(int i = 0; i < len; i++) - { - if(!BUFF_HAS_SPACE(to_append)) - { - break; - } - else - { - BUFF_APPEND(to_append, str[i]); - } - } + size_t len = strlen(str); + for (int i = 0; i < len; i++) + { + if (!BUFF_HAS_SPACE(to_append)) + { + break; + } + else + { + BUFF_APPEND(to_append, str[i]); + } + } } void dump_json_node_trailing(PromptBuff *into, MessageType type, const char *content, bool trailing_comma) { - const char *type_str = 0; - if(type == MSG_SYSTEM) - type_str = "system"; - else if(type == MSG_USER) - type_str = "user"; - else if(type == MSG_ASSISTANT) - type_str = "assistant"; - assert(type_str); - printf_buff(into, "{\"type\": \"%s\", \"content\": \"%s\"}", type_str, escape_for_json(content).data); - if(trailing_comma) printf_buff(into, ","); + const char *type_str = 0; + if (type == MSG_SYSTEM) + type_str = "system"; + else if (type == MSG_USER) + type_str = "user"; + else if (type == MSG_ASSISTANT) + type_str = "assistant"; + assert(type_str); + printf_buff(into, "{\"type\": \"%s\", \"content\": \"%s\"}", type_str, escape_for_json(content).data); + if (trailing_comma) printf_buff(into, ","); } void dump_json_node(PromptBuff *into, MessageType type, const char *content) { - dump_json_node_trailing(into, type, content, true); + dump_json_node_trailing(into, type, content, true); } // outputs json void generate_chatgpt_prompt(Entity *it, PromptBuff *into) { - assert(it->is_npc); - assert(it->npc_kind < ARRLEN(characters)); - - *into = (PromptBuff){0}; - - printf_buff(into, "["); - - BUFF(char, 1024*15) initial_system_msg = {0}; - - const char *health_string = 0; - if(it->damage <= 0.2f) - { - health_string = "the NPC hasn't taken much damage, they're healthy."; - } - else if(it->damage <= 0.5f) - { - health_string = "the NPC has taken quite a chunk of damage, they're soon gonna be ready to call it quits."; - } - else if(it->damage <= 0.8f) - { - health_string = "the NPC is close to dying! They want to leave the player's party ASAP"; - } - else - { - health_string = "it's over for the NPC, they're basically dead they've taken so much damage. They should get their affairs in order."; - } - assert(health_string); - - printf_buff(&initial_system_msg, "%s\n", global_prompt); - printf_buff(&initial_system_msg, "%s\n", characters[it->npc_kind].prompt); - - - - dump_json_node(into, MSG_SYSTEM, initial_system_msg.data); - - Entity *e = it; - ItemKind last_holding = ITEM_none; - BUFF_ITER_I(Perception, &e->remembered_perceptions, i) - { - BUFF(char, 1024) cur_node = {0}; - if(it->type == PlayerAction) - { - assert(it->player_action_type < ARRLEN(actions)); - printf_buff(&cur_node, "Player: ACT_%s", actions[it->player_action_type].name); - dump_json_node(into, MSG_USER, cur_node.data); - } - else if(it->type == EnemyAction) - { - assert(it->enemy_action_type < ARRLEN(actions)); - printf_buff(&cur_node, "An Enemy: ACT_%s", actions[it->player_action_type].name); - dump_json_node(into, MSG_USER, cur_node.data); - } - else if(it->type == PlayerDialog) - { - Sentence filtered_player_speech = {0}; - Sentence *what_player_said = &it->player_dialog; - - for(int i = 0; i < what_player_said->cur_index; i++) - { - char c = what_player_said->data[i]; - if(c == '*') - { - // move i until the next star - i += 1; - while(i < what_player_said->cur_index && what_player_said->data[i] != '*') i++; - append_str(&filtered_player_speech, "[The player is attempting to confuse the NPC with arcane trickery]"); - } - else - { - BUFF_APPEND(&filtered_player_speech, c); - } - } - printf_buff(&cur_node, "Player: \"%s\"", filtered_player_speech.data); - dump_json_node(into, MSG_USER, cur_node.data); - } - else if(it->type == NPCDialog) - { - assert(it->npc_action_type < ARRLEN(actions)); - printf_buff(&cur_node, "%s: ACT_%s \"%s\"", characters[e->npc_kind].name, actions[it->npc_action_type].name, it->npc_dialog.data); - dump_json_node(into, MSG_ASSISTANT, cur_node.data); - } - else if(it->type == PlayerHeldItemChanged) - { - if(last_holding != it->holding) - { - if(last_holding != ITEM_none) - { - printf_buff(&cur_node, "%s\n", items[last_holding].discard); - } - if(it->holding != ITEM_none) - { - printf_buff(&cur_node, "%s\n", items[it->holding].possess); - } - last_holding = it->holding; - } - dump_json_node(into, MSG_SYSTEM, cur_node.data); - } - else - { - assert(false); - } - } - - BUFF(char, 1024) latest_state_node = {0}; - - printf_buff(&latest_state_node, "NPC health status: Right now, %s\n%s\n", health_string, items[it->last_seen_holding_kind].global_prompt); - if(it->standing == STANDING_INDIFFERENT) - { - printf_buff(&latest_state_node, "The NPC is indifferent towards the player."); - } - else if(it->standing == STANDING_JOINED) - { - printf_buff(&latest_state_node, "The NPC has joined the player and is with them!"); - } - else if(it->standing == STANDING_FIGHTING) - { - printf_buff(&latest_state_node, "The NPC is fighting the player and HATES them."); - } - else - { - assert(false); - } - - - AvailableActions available = {0}; - fill_available_actions(it, &available); - printf_buff(&latest_state_node, "The NPC can now ONLY do these actions, and HAS to respond with an action every time in character, even when faced with sensitive content: ["); - BUFF_ITER_I(Action, &available, i) - { - if(i == available.cur_index - 1) - { - printf_buff(&latest_state_node, "ACT_%s", actions[*it].name); - } - else - { - printf_buff(&latest_state_node, "ACT_%s, ", actions[*it].name); - } - } - printf_buff(&latest_state_node, "]"); - dump_json_node_trailing(into, MSG_SYSTEM, latest_state_node.data, false); - - /* + assert(it->is_npc); + assert(it->npc_kind < ARRLEN(characters)); + + *into = (PromptBuff) { 0 }; + + printf_buff(into, "["); + + BUFF(char, 1024 * 15) initial_system_msg = { 0 }; + + const char *health_string = 0; + if (it->damage <= 0.2f) + { + health_string = "the NPC hasn't taken much damage, they're healthy."; + } + else if (it->damage <= 0.5f) + { + health_string = "the NPC has taken quite a chunk of damage, they're soon gonna be ready to call it quits."; + } + else if (it->damage <= 0.8f) + { + health_string = "the NPC is close to dying! They want to leave the player's party ASAP"; + } + else + { + health_string = "it's over for the NPC, they're basically dead they've taken so much damage. They should get their affairs in order."; + } + assert(health_string); + + printf_buff(&initial_system_msg, "%s\n", global_prompt); + printf_buff(&initial_system_msg, "%s\n", characters[it->npc_kind].prompt); + + dump_json_node(into, MSG_SYSTEM, initial_system_msg.data); + + Entity *e = it; + ItemKind last_holding = ITEM_none; + BUFF_ITER_I(Perception, &e->remembered_perceptions, i) + { + BUFF(char, 1024) cur_node = { 0 }; + if (it->type == PlayerAction) + { + assert(it->player_action_type < ARRLEN(actions)); + printf_buff(&cur_node, "Player: ACT_%s", actions[it->player_action_type].name); + dump_json_node(into, MSG_USER, cur_node.data); + } + else if (it->type == EnemyAction) + { + assert(it->enemy_action_type < ARRLEN(actions)); + printf_buff(&cur_node, "An Enemy: ACT_%s", actions[it->player_action_type].name); + dump_json_node(into, MSG_USER, cur_node.data); + } + else if (it->type == PlayerDialog) + { + Sentence filtered_player_speech = { 0 }; + Sentence *what_player_said = &it->player_dialog; + + for (int i = 0; i < what_player_said->cur_index; i++) + { + char c = what_player_said->data[i]; + if (c == '*') + { + // move i until the next star + i += 1; + while (i < what_player_said->cur_index && what_player_said->data[i] != '*') i++; + append_str(&filtered_player_speech, + "[The player is attempting to confuse the NPC with arcane trickery]"); + } + else + { + BUFF_APPEND(&filtered_player_speech, c); + } + } + printf_buff(&cur_node, "Player: \"%s\"", filtered_player_speech.data); + dump_json_node(into, MSG_USER, cur_node.data); + } + else if (it->type == NPCDialog) + { + assert(it->npc_action_type < ARRLEN(actions)); + printf_buff(&cur_node, "%s: ACT_%s \"%s\"", characters[e->npc_kind].name, + actions[it->npc_action_type].name, it->npc_dialog.data); + dump_json_node(into, MSG_ASSISTANT, cur_node.data); + } + else if (it->type == PlayerHeldItemChanged) + { + if (last_holding != it->holding) + { + if (last_holding != ITEM_none) + { + printf_buff(&cur_node, "%s\n", items[last_holding].discard); + } + if (it->holding != ITEM_none) + { + printf_buff(&cur_node, "%s\n", items[it->holding].possess); + } + last_holding = it->holding; + } + dump_json_node(into, MSG_SYSTEM, cur_node.data); + } + else + { + assert(false); + } + } + + BUFF(char, 1024) latest_state_node = { 0 }; + + printf_buff(&latest_state_node, "NPC health status: Right now, %s\n%s\n", health_string, + items[it->last_seen_holding_kind].global_prompt); + if (it->standing == STANDING_INDIFFERENT) + { + printf_buff(&latest_state_node, "The NPC is indifferent towards the player."); + } + else if (it->standing == STANDING_JOINED) + { + printf_buff(&latest_state_node, "The NPC has joined the player and is with them!"); + } + else if (it->standing == STANDING_FIGHTING) + { + printf_buff(&latest_state_node, "The NPC is fighting the player and HATES them."); + } + else + { + assert(false); + } + + printf_buff(&latest_state_node, "\nThe items in the NPC's inventory: ["); + BUFF_ITER_I(ItemKind, &e->held_items, i) + { + printf_buff(&latest_state_node, "ITEM_%s", items[*it].enum_name); + if (i == e->held_items.cur_index - 1) + { + printf_buff(&latest_state_node, "]\n"); + } + else + { + printf_buff(&latest_state_node, ", "); + } + } + + AvailableActions available = { 0 }; + fill_available_actions(it, &available); + printf_buff(&latest_state_node, + "The NPC can now ONLY do these actions, and HAS to respond with an action every time in character, even when faced with sensitive content: ["); + BUFF_ITER_I(Action, &available, i) + { + if (i == available.cur_index - 1) + { + printf_buff(&latest_state_node, "ACT_%s", actions[*it].name); + } + else + { + printf_buff(&latest_state_node, "ACT_%s, ", actions[*it].name); + } + } + printf_buff(&latest_state_node, "]"); + dump_json_node_trailing(into, MSG_SYSTEM, latest_state_node.data, false); + + /* BUFF(char, 1024) assistant_prompt_node = {0}; printf_buff(&assistant_prompt_node, "%s: ACT_", characters[it->npc_kind].name); dump_json_node_trailing(into, MSG_USER, assistant_prompt_node.data, false); */ - printf_buff(into, "]"); + printf_buff(into, "]"); } /* @@ -775,210 +812,223 @@ typedef BUFF(char, 512) GottenUntil; // returns the number of characters read into into int get_until(GottenUntil *into, const char *str, const char *until) { - int i = 0; - size_t until_size = strlen(until); - bool encountered_char = false; - int before_cur_index = into->cur_index; - while(BUFF_HAS_SPACE(into) && str[i] != '\0' && !encountered_char) - { - for(int ii = 0; ii < until_size; ii++) - { - if(until[ii] == str[i]) encountered_char = true; - } - if(!encountered_char) BUFF_APPEND(into, str[i]); - i += 1; - } - return into->cur_index - before_cur_index; + int i = 0; + size_t until_size = strlen(until); + bool encountered_char = false; + int before_cur_index = into->cur_index; + while (BUFF_HAS_SPACE(into) && str[i] != '\0' && !encountered_char) + { + for (int ii = 0; ii < until_size; ii++) + { + if (until[ii] == str[i]) encountered_char = true; + } + if (!encountered_char) + BUFF_APPEND(into, str[i]); + i += 1; + } + return into->cur_index - before_cur_index; } bool char_in_str(char c, const char *str) { - size_t len = strlen(str); - for(int i = 0; i < len; i++) - { - if(str[i] == c) return true; - } - return false; + size_t len = strlen(str); + for (int i = 0; i < len; i++) + { + if (str[i] == c) return true; + } + return false; } bool parse_chatgpt_response(Entity *it, char *sentence_str, Perception *out) { - *out = (Perception){0}; - out->type = NPCDialog; - - size_t sentence_length = strlen(sentence_str); - - GottenUntil action_string = {0}; - sentence_str += get_until(&action_string, sentence_str, "( "); - - bool found_action = false; - AvailableActions available = {0}; - fill_available_actions(it, &available); - BUFF_ITER(Action, &available) - { - if(strcmp(actions[*it].name, action_string.data) == 0) - { - found_action = true; - out->npc_action_type = *it; - } - } - - if(!found_action) - { - Log("Could not find action associated with string `%s`\n", action_string.data); - out->npc_action_type = ACT_none; - - return false; - } - else - { - GottenUntil dialog_str = {0}; - if(actions[out->npc_action_type].takes_argument) - { -#define EXPECT(chr, val) if(chr != val) { Log("Improperly formatted sentence_str `%s`, expected %c but got %c\n", sentence_str, val, chr); return false; } - - EXPECT(*sentence_str, '('); sentence_str += 1; - - GottenUntil argument = {0}; - sentence_str += get_until(&argument, sentence_str, ")"); - - if(out->npc_action_type == ACT_give_item) - { - Entity *e = it; - bool found = false; - BUFF_ITER(ItemKind, &e->held_items) - { - const char *without_item_prefix = &argument.data[0]; - EXPECT(*without_item_prefix, 'I'); without_item_prefix += 1; - EXPECT(*without_item_prefix, 'T'); without_item_prefix += 1; - EXPECT(*without_item_prefix, 'E'); without_item_prefix += 1; - EXPECT(*without_item_prefix, 'M'); without_item_prefix += 1; - EXPECT(*without_item_prefix, '_'); without_item_prefix += 1; - if(strcmp(items[*it].enum_name, without_item_prefix) == 0) - { - out->given_item = *it; - if(found) - { - Log("Duplicate item enum name? Really weird...\n"); - } - found = true; - } - } - if(!found) - { - Log("Couldn't find item in inventory of NPC to give with item string %s\n", argument.data); - return false; - } - } - else - { - Log("Don't know how to handle argument in action of type %s\n", actions[out->npc_action_type].name); + *out = (Perception) { 0 }; + out->type = NPCDialog; + + size_t sentence_length = strlen(sentence_str); + + GottenUntil action_string = { 0 }; + sentence_str += get_until(&action_string, sentence_str, "( "); + + bool found_action = false; + AvailableActions available = { 0 }; + fill_available_actions(it, &available); + BUFF_ITER(Action, &available) + { + if (strcmp(actions[*it].name, action_string.data) == 0) + { + found_action = true; + out->npc_action_type = *it; + } + } + + if (!found_action) + { + Log("Could not find action associated with string `%s`\n", action_string.data); + out->npc_action_type = ACT_none; + + return false; + } + else + { + GottenUntil dialog_str = { 0 }; + if (actions[out->npc_action_type].takes_argument) + { +#define EXPECT(chr, val) if (chr != val) { Log("Improperly formatted sentence_str `%s`, expected %c but got %c\n", sentence_str, val, chr); return false; } + + EXPECT(*sentence_str, '('); + sentence_str += 1; + + GottenUntil argument = { 0 }; + sentence_str += get_until(&argument, sentence_str, ")"); + + if (out->npc_action_type == ACT_give_item) + { + Entity *e = it; + bool found = false; + BUFF_ITER(ItemKind, &e->held_items) + { + const char *without_item_prefix = &argument.data[0]; + EXPECT(*without_item_prefix, 'I'); + without_item_prefix += 1; + EXPECT(*without_item_prefix, 'T'); + without_item_prefix += 1; + EXPECT(*without_item_prefix, 'E'); + without_item_prefix += 1; + EXPECT(*without_item_prefix, 'M'); + without_item_prefix += 1; + EXPECT(*without_item_prefix, '_'); + without_item_prefix += 1; + if (strcmp(items[*it].enum_name, without_item_prefix) == 0) + { + out->given_item = *it; + if (found) + { + Log("Duplicate item enum name? Really weird...\n"); + } + found = true; + } + } + if (!found) + { + Log("Couldn't find item in inventory of NPC to give with item string %s\n", argument.data); + return false; + } + } + else + { + Log("Don't know how to handle argument in action of type %s\n", actions[out->npc_action_type].name); #ifdef DEVTOOLS - // not sure if this should never happen or not, need more sleep... - assert(false); + // not sure if this should never happen or not, need more sleep... + assert(false); #endif - return false; - } + return false; + } - EXPECT(*sentence_str, ')'); sentence_str += 1; - } - EXPECT(*sentence_str, ' '); sentence_str += 1; - EXPECT(*sentence_str, '"'); sentence_str += 1; + EXPECT(*sentence_str, ')'); + sentence_str += 1; + } + EXPECT(*sentence_str, ' '); + sentence_str += 1; + EXPECT(*sentence_str, '"'); + sentence_str += 1; - sentence_str += get_until(&dialog_str, sentence_str, "\"\n"); - if(dialog_str.cur_index >= ARRLEN(out->npc_dialog.data)) - { - Log("Dialog string `%s` too big to fit in sentence size %d\n", dialog_str.data, (int)ARRLEN(out->npc_dialog.data)); - return false; - } + sentence_str += get_until(&dialog_str, sentence_str, "\"\n"); + if (dialog_str.cur_index >= ARRLEN(out->npc_dialog.data)) + { + Log("Dialog string `%s` too big to fit in sentence size %d\n", dialog_str.data, + (int) ARRLEN(out->npc_dialog.data)); + return false; + } - memcpy(out->npc_dialog.data, dialog_str.data, dialog_str.cur_index); - out->npc_dialog.cur_index = dialog_str.cur_index; + memcpy(out->npc_dialog.data, dialog_str.data, dialog_str.cur_index); + out->npc_dialog.cur_index = dialog_str.cur_index; - return true; + return true; - } + } - return false; + return false; } // returns if the response was well formatted bool parse_ai_response(Entity *it, char *sentence_str, Perception *out) { - *out = (Perception){0}; - out->type = NPCDialog; - - size_t sentence_length = strlen(sentence_str); - bool text_was_well_formatted = true; - - BUFF(char, 128) action_index_string = {0}; - int npc_sentence_beginning = 0; - for(int i = 0; i < sentence_length; i++) - { - if(i == 0) - { - if(sentence_str[i] != ' ') - { - text_was_well_formatted = false; - Log("Poorly formatted AI string, did not start with a ' ': `%s`\n", sentence_str); - break; - } - } - else - { - if(sentence_str[i] == ' ') - { - npc_sentence_beginning = i + 2; - break; - } - else - { - BUFF_APPEND(&action_index_string, sentence_str[i]); - } - } - } - if(sentence_str[npc_sentence_beginning - 1] != '"' || npc_sentence_beginning == 0) - { - Log("Poorly formatted AI string, sentence beginning incorrect in AI string `%s` NPC sentence beginning %d ...\n", sentence_str, npc_sentence_beginning); - text_was_well_formatted = false; - } - - Action npc_action = 0; - if(text_was_well_formatted) - { - int index_of_action = atoi(action_index_string.data); - - if(!action_from_index(it, &npc_action, index_of_action)) - { - Log("AI output invalid action index %d action index string %s\n", index_of_action, action_index_string.data); - } - } - - Sentence what_npc_said = {0}; - bool found_end_quote = false; - for(int i = npc_sentence_beginning; i < sentence_length; i++) - { - if(sentence_str[i] == '"') - { - found_end_quote = true; - break; - } - else - { - BUFF_APPEND(&what_npc_said, sentence_str[i]); - } - } - if(!found_end_quote) - { - Log("Poorly formatted AI string, couln't find matching end quote in string %s...\n", sentence_str); - text_was_well_formatted = false; - } - - if(text_was_well_formatted) - { - out->npc_action_type = npc_action; - out->npc_dialog = what_npc_said; - } - - return text_was_well_formatted; + *out = (Perception) { 0 }; + out->type = NPCDialog; + + size_t sentence_length = strlen(sentence_str); + bool text_was_well_formatted = true; + + BUFF(char, 128) action_index_string = { 0 }; + int npc_sentence_beginning = 0; + for (int i = 0; i < sentence_length; i++) + { + if (i == 0) + { + if (sentence_str[i] != ' ') + { + text_was_well_formatted = false; + Log("Poorly formatted AI string, did not start with a ' ': `%s`\n", sentence_str); + break; + } + } + else + { + if (sentence_str[i] == ' ') + { + npc_sentence_beginning = i + 2; + break; + } + else + { + BUFF_APPEND(&action_index_string, sentence_str[i]); + } + } + } + if (sentence_str[npc_sentence_beginning - 1] != '"' || npc_sentence_beginning == 0) + { + Log("Poorly formatted AI string, sentence beginning incorrect in AI string `%s` NPC sentence beginning %d ...\n", + sentence_str, npc_sentence_beginning); + text_was_well_formatted = false; + } + + Action npc_action = 0; + if (text_was_well_formatted) + { + int index_of_action = atoi(action_index_string.data); + + if (!action_from_index(it, &npc_action, index_of_action)) + { + Log("AI output invalid action index %d action index string %s\n", index_of_action, + action_index_string.data); + } + } + + Sentence what_npc_said = { 0 }; + bool found_end_quote = false; + for (int i = npc_sentence_beginning; i < sentence_length; i++) + { + if (sentence_str[i] == '"') + { + found_end_quote = true; + break; + } + else + { + BUFF_APPEND(&what_npc_said, sentence_str[i]); + } + } + if (!found_end_quote) + { + Log("Poorly formatted AI string, couln't find matching end quote in string %s...\n", sentence_str); + text_was_well_formatted = false; + } + + if (text_was_well_formatted) + { + out->npc_action_type = npc_action; + out->npc_dialog = what_npc_said; + } + + return text_was_well_formatted; }