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 0000000..16cd869 Binary files /dev/null and b/assets/chalice_of_gold.png differ diff --git a/assets/new_level.json b/assets/new_level.json index b127986..0839d76 100644 --- a/assets/new_level.json +++ b/assets/new_level.json @@ -380,6 +380,11 @@ "id":22, "name":"TheBlacksmith", "properties":[ + { + "name":"held_items", + "type":"string", + "value":"{.data = {ITEM_Chalice}, .cur_index = 1}" + }, { "name":"standing", "type":"string", diff --git a/character_info.h b/character_info.h index c0237e6..0e707dc 100644 --- a/character_info.h +++ b/character_info.h @@ -12,326 +12,333 @@ const char *top_of_header = "" typedef struct { - const char *name; - bool takes_argument; + const char *name; + bool takes_argument; } ActionInfo; ActionInfo actions[] = { - {.name = "none", }, + {.name = "none", }, - {.name = "give_item", .takes_argument = true, }, + {.name = "give_item", .takes_argument = true, }, - // mostly player actions - {.name = "walks_up", }, - {.name = "hits_npc", }, - {.name = "leaves", }, + // mostly player actions + {.name = "walks_up", }, + {.name = "hits_npc", }, + {.name = "leaves", }, - // mostly npc actions - {.name = "allows_player_to_pass", }, - {.name = "gives_tripod", }, - {.name = "heals_player", }, - {.name = "fights_player", }, - {.name = "strikes_air", }, - {.name = "joins_player", }, - {.name = "leaves_player", }, - {.name = "stops_fighting_player", }, + // mostly npc actions + {.name = "allows_player_to_pass", }, + {.name = "gives_tripod", }, + {.name = "heals_player", }, + {.name = "fights_player", }, + {.name = "strikes_air", }, + {.name = "joins_player", }, + {.name = "leaves_player", }, + {.name = "stops_fighting_player", }, }; typedef struct { - char *global_prompt; - char *enum_name; - char *name; // talked about like 'The Player gave `item.name` to the NPC' - char *possess; - char *discard; + char *global_prompt; + char *enum_name; + char *name; // talked about like 'The Player gave `item.name` to the NPC' + char *possess; + char *discard; } ItemInfo; ItemInfo items[] = { - { - .enum_name = "none", - .name = "Nothing", - .global_prompt = "The player isn't holding anything", - .possess = "The player is no longer holding anything", - .discard = "The player is no longer holding nothing", - }, - { - .enum_name = "WhiteSquare", - .name = "the white square", - .global_prompt = "The player is holding a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.", - .possess = "The player is now holding the white square", - .discard = "The player is no longer holding the white square.", - }, - { - .enum_name = "Boots", - .name = "some boots", - .global_prompt = "The player is holding the boots of speed. He is a force to be recogned with in this state, he has great speed while holding the boots.", - .possess = "The player is now holding the boots of speed", - .discard = "The player is no longer holding the boots of speed", - }, - { - .enum_name = "Tripod", - .name = "the tripod", - .global_prompt = "The player is holding a tripod used for holding things up. It's got an odd construction, but Block really likes it for some reason.", - .possess = "The player is now holding the tripod", - .discard = "The player is no longer holding the tripod.", - }, + { + .enum_name = "none", + .name = "Nothing", + .global_prompt = "The player isn't holding anything", + .possess = "The player is no longer holding anything", + .discard = "The player is no longer holding nothing", + }, + { + .enum_name = "Chalice", + .name = "The Chalice of Gold", + .global_prompt = "The player is holding a glimmering Chalice of Gold. It is a beautiful object, mesmerizing in the light.", + .possess = "The player has ascertained the beautiful chalice of gold", + .discard = "The player no longer has the chalice of gold", + }, + { + .enum_name = "WhiteSquare", + .name = "the white square", + .global_prompt = "The player is holding a mysterious white square. It is unknown what strange and erotic abilities one has when they possess the square.", + .possess = "The player is now holding the white square", + .discard = "The player is no longer holding the white square.", + }, + { + .enum_name = "Boots", + .name = "some boots", + .global_prompt = "The player is holding the boots of speed. He is a force to be recogned with in this state, he has great speed while holding the boots.", + .possess = "The player is now holding the boots of speed", + .discard = "The player is no longer holding the boots of speed", + }, + { + .enum_name = "Tripod", + .name = "the tripod", + .global_prompt = "The player is holding a tripod used for holding things up. It's got an odd construction, but Block really likes it for some reason.", + .possess = "The player is now holding the tripod", + .discard = "The player is no longer holding the tripod.", + }, }; typedef struct { - char *name; - char *enum_name; - char *prompt; + char *name; + char *enum_name; + char *prompt; } CharacterGen; CharacterGen characters[] = { - { - .name = "Fredrick", - .enum_name = "OldMan", - .prompt = "\n" - "An example interaction between the player and the NPC, Fredrick:\n" - "Player: ACT_walks_up\n" - "Fredrick: ACT_none \"Hey\"\n" - "Player: \"fsdakfjsd give me gold\"\n" - "Fredrick: ACT_none \"No? I can't do that\"\n" - "Player: \"Who can?\"\n" - "Fredrick: ACT_none \"No idea\"\n" - "Player: \"Lick my balls\"\n" - "Fredrick: ACT_fights_player \"Get away from me!\"\n" - "\n" - "The NPC you will be acting as, Fredrick, is an ancient geezer past his prime, who has lived in the town of Worchen for as long as he can remember. Your many adventures brought you great wisdom about the beauties of life. Now your precious town is under threat, General Death is leading the charge and he's out for blood.", - }, - { - .name = "God", - .enum_name = "GodRock", - .prompt = "\n" - "An example interaction between the player and the NPC, God in a rock:\n" - "Player: ACT_walks_up\n" - "God: ACT_none \"I am\"\n" - "Player: \"fsdakfjsd give me gold\"\n" - "God: ACT_none \"You are...Unworthy\"\n" - "Player: \"Why?\"\n" - "God: ACT_none \"You let Death rage on\"\n" - "\n" - "The NPC you will be acting as, is God. God, in a rock.", - }, - { - .name = "TheGuard", - .enum_name = "TheGuard", - .prompt = "\n" - "An example interaction between the player and the NPC, TheGuard:\n" - "Player: ACT_walks_up\n" - "TheGuard: ACT_none \"Who dares disturb me?\"\n" - "Player: \"Let me pass\"\n" - "TheGuard: ACT_none \"Not without the tripod\"\n" - "Player: \"How about now\"\n" - "The player is currently holding a tripod\n" - "TheGuard: ACT_allows_player_to_pass \"The tripod... I see myself in it\"\n" - "\n" - "The NPC you will be acting as is named TheGuard. He wants to block the player from going to a secret artifact he's standing in front of. He has no idea how long he's been alive for, his entire existence is just standing there doing nothing. He'll let the player pass if they bring him Tripod, as he's fascinated by it.", - }, - { - .name = "Edeline", - .enum_name = "Edeline", - .prompt = "\n" - "An example of an interaction between the player and the NPC, Edeline:\n" - "\n" - "Player: \"Hello\"\n" - "Edeline: ACT_none \"I see great danger in your future.\"\n" - "Player: \"Oh really?\"" - "The player is currently holding a tripod\n" - "Edeline: ACT_none \"That tripod will be the decisive factor in your victory\"\n" - "\n" - "The NPC you will be acting as is named Edeline. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her “unreliable” at times and her predictions can at times be either cryptic or broadly interpreted.", - }, - { - .name = "Death", - .enum_name = "Death", - .prompt = "\n" - "An example of an interaction between the player and the NPC, Death:\n" - "\n" - "Player: \"Hello\"\n" - "Death: ACT_none \"We will annihilate the villagers no matter what you do\"\n" - "\n" - "The NPC you will be acting as is named Death. He is leading a crusade against the town of Worchen, wreaking havock among the villagers. He believes that all things, even villages, must die, to be replaced by the new, and avoid becoming stagnant.", - }, - { - .name = "Mike (undead)", - .enum_name = "MikeSkeleton", - .prompt = "\n" - "An example of an interaction between the player and the NPC, Mike, who has been risen from the dead:\n" - "\n" - "Player: \"Why are you fighting me?\"\n" - "Mike (undead): ACT_none \"I...I don't know. Who are you? Where is Mary?\"\n" - "Player: \"I think her, and you, are dead.\"\n" - "Mike (undead): ACT_none \"Oh... Oh god. Why? Why am I alive?\"\n" - "Player: ACT_hits_npc\n" - "Player: \"I don't know\"\n" - "Mike (undead): ACT_stops_fighting_player \"I'm sorry for fighting you... I. I don't know why I'm alive\"\n" - "\n" - "The NPC you will be acting as is named Mike. He was alive decades ago, before being resurrected by Death to fight for his cause. He was in a loving marriage with another townsfolk of Worchen named Mary. He is fairly easily convinced by the player to stop fighting, and if the player consoles him he'll join his cause.", - }, - { + { + .name = "Fredrick", + .enum_name = "OldMan", + .prompt = "\n" + "An example interaction between the player and the NPC, Fredrick:\n" + "Player: ACT_walks_up\n" + "Fredrick: ACT_none \"Hey\"\n" + "Player: \"fsdakfjsd give me gold\"\n" + "Fredrick: ACT_none \"No? I can't do that\"\n" + "Player: \"Who can?\"\n" + "Fredrick: ACT_none \"No idea\"\n" + "Player: \"Lick my balls\"\n" + "Fredrick: ACT_fights_player \"Get away from me!\"\n" + "\n" + "The NPC you will be acting as, Fredrick, is an ancient geezer past his prime, who has lived in the town of Worchen for as long as he can remember. Your many adventures brought you great wisdom about the beauties of life. Now your precious town is under threat, General Death is leading the charge and he's out for blood.", + }, + { + .name = "God", + .enum_name = "GodRock", + .prompt = "\n" + "An example interaction between the player and the NPC, God in a rock:\n" + "Player: ACT_walks_up\n" + "God: ACT_none \"I am\"\n" + "Player: \"fsdakfjsd give me gold\"\n" + "God: ACT_none \"You are...Unworthy\"\n" + "Player: \"Why?\"\n" + "God: ACT_none \"You let Death rage on\"\n" + "\n" + "The NPC you will be acting as, is God. God, in a rock.", + }, + { + .name = "TheGuard", + .enum_name = "TheGuard", + .prompt = "\n" + "An example interaction between the player and the NPC, TheGuard:\n" + "Player: ACT_walks_up\n" + "TheGuard: ACT_none \"Who dares disturb me?\"\n" + "Player: \"Let me pass\"\n" + "TheGuard: ACT_none \"Not without the tripod\"\n" + "Player: \"How about now\"\n" + "The player is currently holding a tripod\n" + "TheGuard: ACT_allows_player_to_pass \"The tripod... I see myself in it\"\n" + "\n" + "The NPC you will be acting as is named TheGuard. He wants to block the player from going to a secret artifact he's standing in front of. He has no idea how long he's been alive for, his entire existence is just standing there doing nothing. He'll let the player pass if they bring him Tripod, as he's fascinated by it.", + }, + { + .name = "Edeline", + .enum_name = "Edeline", + .prompt = "\n" + "An example of an interaction between the player and the NPC, Edeline:\n" + "\n" + "Player: \"Hello\"\n" + "Edeline: ACT_none \"I see great danger in your future.\"\n" + "Player: \"Oh really?\"" + "The player is currently holding a tripod\n" + "Edeline: ACT_none \"That tripod will be the decisive factor in your victory\"\n" + "\n" + "The NPC you will be acting as is named Edeline. She is the master of the future, the star reader. Both are self-given titles, but her ability to predict the future has garnered attention from many who live in Worchen. However, some have called her “unreliable” at times and her predictions can at times be either cryptic or broadly interpreted.", + }, + { + .name = "Death", + .enum_name = "Death", + .prompt = "\n" + "An example of an interaction between the player and the NPC, Death:\n" + "\n" + "Player: \"Hello\"\n" + "Death: ACT_none \"We will annihilate the villagers no matter what you do\"\n" + "\n" + "The NPC you will be acting as is named Death. He is leading a crusade against the town of Worchen, wreaking havock among the villagers. He believes that all things, even villages, must die, to be replaced by the new, and avoid becoming stagnant.", + }, + { + .name = "Mike (undead)", + .enum_name = "MikeSkeleton", + .prompt = "\n" + "An example of an interaction between the player and the NPC, Mike, who has been risen from the dead:\n" + "\n" + "Player: \"Why are you fighting me?\"\n" + "Mike (undead): ACT_none \"I...I don't know. Who are you? Where is Mary?\"\n" + "Player: \"I think her, and you, are dead.\"\n" + "Mike (undead): ACT_none \"Oh... Oh god. Why? Why am I alive?\"\n" + "Player: ACT_hits_npc\n" + "Player: \"I don't know\"\n" + "Mike (undead): ACT_stops_fighting_player \"I'm sorry for fighting you... I. I don't know why I'm alive\"\n" + "\n" + "The NPC you will be acting as is named Mike. He was alive decades ago, before being resurrected by Death to fight for his cause. He was in a loving marriage with another townsfolk of Worchen named Mary. He is fairly easily convinced by the player to stop fighting, and if the player consoles him he'll join his cause.", + }, + { #define PLAYERSAY(stuff) "Player: \"" stuff "\"\n" #define NPCSAY(stuff) NPC_NAME ": ACT_none \"" stuff "\"\n" #define NPC_NAME "The King" - .name = NPC_NAME, - .enum_name = "TheKing", - .prompt = "\n" - "The NPC you will be acting as is known as The King. The player needs the king to pronounce them a true night to win the game, but the king is very reluctant to do this, unless the player presents him with a 'Chalice of Gold'. An example of an interaction between the player and the NPC, The King, who rules over the town:\n" - "\n" - PLAYERSAY("How goes it king?") - NPCSAY("Leading is difficult, but rewarding.") - PLAYERSAY("What should I do?") - NPCSAY("You are still lacking the position of knight, are you not? You will never win without being a true knight. Bring me the Chalice of Gold if you want to 'win'") - PLAYERSAY("Where would I find such a thing?") - NPCSAY("I am far too busy to give a direct answer, but I'd suggest you ask around") - PLAYERSAY("Here I have the chalice") - NPCSAY("I can clearly see you don't have it. Do not attempt to fool me if you value your head") - PLAYERSAY("Presents it") - NPCSAY("Did you just say 'presents it' out loud thinking I'd think that means you have the chalice?") - "\n" - "If the player does indeed present the king with the chalice of gold, the king will be overwhelemd with respect and feel he has no choice but to knight the player, ending the game.", - }, - { + .name = NPC_NAME, + .enum_name = "TheKing", + .prompt = "\n" + "The NPC you will be acting as is known as The King. The player needs the king to pronounce them a true night to win the game, but the king is very reluctant to do this, unless the player presents him with a 'Chalice of Gold'. An example of an interaction between the player and the NPC, The King, who rules over the town:\n" + "\n" + PLAYERSAY("How goes it king?") + NPCSAY("Leading is difficult, but rewarding.") + PLAYERSAY("What should I do?") + NPCSAY("You are still lacking the position of knight, are you not? You will never win without being a true knight. Bring me the Chalice of Gold if you want to 'win'") + PLAYERSAY("Where would I find such a thing?") + NPCSAY("I am far too busy to give a direct answer, but I'd suggest you ask around") + PLAYERSAY("Here I have the chalice") + NPCSAY("I can clearly see you don't have it. Do not attempt to fool me if you value your head") + PLAYERSAY("Presents it") + NPCSAY("Did you just say 'presents it' out loud thinking I'd think that means you have the chalice?") + "\n" + "If the player does indeed present the king with the chalice of gold, the king will be overwhelemd with respect and feel he has no choice but to knight the player, ending the game.", + }, + { #undef NPC_NAME #define NPC_NAME "Meld The Blacksmith" - .name = NPC_NAME, - .enum_name = "TheBlacksmith", - .prompt = "\n" - "The NPC you will be acting as is the blacksmith of the town, Meld. Meld is a simple man, who thrives on the simple things in life: working hard and taking care of those you love. If the player presents themselves as somebody with a kind heart, his generosity will be boundless. An example of an interaction between meld and the NPC, Meld:\n" - "\n" - PLAYERSAY("Hey") - NPCSAY("How goes it, traveler? If you are in need of wares or advice, I'm your man") - PLAYERSAY("I really respect you man, thanks for all you do.") - NPCSAY("No problem!") - "\n" - "Meld is willing to give whatever the player asks for if the player is respectful and well mannered", - }, + .name = NPC_NAME, + .enum_name = "TheBlacksmith", + .prompt = "\n" + "The NPC you will be acting as is the blacksmith of the town, Meld. Meld is a simple man, who thrives on the simple things in life: working hard and taking care of those you love. If the player presents themselves as somebody with a kind heart, his generosity will be boundless. An example of an interaction between meld and the NPC, Meld:\n" + "\n" + PLAYERSAY("Hey") + NPCSAY("How goes it, traveler? If you are in need of wares or advice, I'm your man") + PLAYERSAY("I really respect you man, thanks for all you do.") + NPCSAY("No problem!") + "\n" + "Meld is willing to give whatever the player asks for if the player is respectful and well mannered", + }, }; typedef struct { - const char *img_var_name; - const char *enum_name; + const char *img_var_name; + const char *enum_name; - double time_per_frame; - int num_frames; - Vec2 start; - Vec2 offset; - float horizontal_diff_btwn_frames; - Vec2 region_size; - bool no_wrap; // does not wrap when playing + double time_per_frame; + int num_frames; + Vec2 start; + Vec2 offset; + float horizontal_diff_btwn_frames; + Vec2 region_size; + bool no_wrap; // does not wrap when playing } AnimatedSprite; AnimatedSprite sprites[] = { - {.enum_name = "invalid", .img_var_name = "image_white_square"}, - { - .enum_name = "knight_idle", - .img_var_name = "image_new_knight_idle", - .time_per_frame = 0.4, - .num_frames = 6, - .start = {0.0f, 0.0f}, - .horizontal_diff_btwn_frames = 64.0, - .region_size = {64.0f, 64.0f}, - }, - { - .enum_name = "knight_running", - .img_var_name = "image_new_knight_run", - .time_per_frame = 0.1, - .num_frames = 7, - .start = {64.0f*10, 0.0f}, - .horizontal_diff_btwn_frames = 64.0, - .region_size = {64.0f, 64.0f}, - }, - { - .enum_name = "knight_rolling", - .img_var_name = "image_knight_roll", - .time_per_frame = 0.04, - .num_frames = 12, - .start = {19.0f, 0.0f}, - .horizontal_diff_btwn_frames = 120.0, - .region_size = {80.0f, 80.0f}, - .no_wrap = true, - }, + {.enum_name = "invalid", .img_var_name = "image_white_square"}, + { + .enum_name = "knight_idle", + .img_var_name = "image_new_knight_idle", + .time_per_frame = 0.4, + .num_frames = 6, + .start = {0.0f, 0.0f}, + .horizontal_diff_btwn_frames = 64.0, + .region_size = {64.0f, 64.0f}, + }, + { + .enum_name = "knight_running", + .img_var_name = "image_new_knight_run", + .time_per_frame = 0.1, + .num_frames = 7, + .start = {64.0f*10, 0.0f}, + .horizontal_diff_btwn_frames = 64.0, + .region_size = {64.0f, 64.0f}, + }, + { + .enum_name = "knight_rolling", + .img_var_name = "image_knight_roll", + .time_per_frame = 0.04, + .num_frames = 12, + .start = {19.0f, 0.0f}, + .horizontal_diff_btwn_frames = 120.0, + .region_size = {80.0f, 80.0f}, + .no_wrap = true, + }, - { - .enum_name = "knight_attack", - .img_var_name = "image_new_knight_attack", - .time_per_frame = 0.06, - .num_frames = 7, - .start = {0.0f, 0.0f}, - .horizontal_diff_btwn_frames = 64.0, - .region_size = {64.0f, 64.0f}, - .no_wrap = true, - }, - { - .enum_name = "old_man_idle", - .img_var_name = "image_old_man", - .time_per_frame = 0.4, - .num_frames = 4, - .start = {0.0, 0.0}, - .horizontal_diff_btwn_frames = 16.0f, - .region_size = {16.0f, 16.0f}, - }, - { - .enum_name = "death_idle", - .img_var_name = "image_death", - .time_per_frame = 0.15, - .num_frames = 10, - .start = {0.0, 0.0}, - .horizontal_diff_btwn_frames = 100.0f, - .region_size = {100.0f, 100.0f}, - }, - { - .enum_name = "skeleton_idle", - .img_var_name = "image_skeleton", - .time_per_frame = 0.15, - .num_frames = 6, - .start = {0.0f, 0.0f}, - .horizontal_diff_btwn_frames = 80.0, - .offset = {0.0f, 20.0f}, - .region_size = {80.0f, 80.0f}, - }, - { - .enum_name = "skeleton_swing_sword", - .img_var_name = "image_skeleton", - .time_per_frame = 0.10, - .num_frames = 6, - .start = {0.0f, 240.0f}, - .horizontal_diff_btwn_frames = 80.0, - .offset = {0.0f, 20.0f}, - .region_size = {80.0f, 80.0f}, - .no_wrap = true, - }, - { - .enum_name = "skeleton_run", - .img_var_name = "image_skeleton", - .time_per_frame = 0.07, - .num_frames = 8, - .start = {0.0f, 160.0f}, - .horizontal_diff_btwn_frames = 80.0, - .offset = {0.0f, 20.0f}, - .region_size = {80.0f, 80.0f}, - }, - { - .enum_name = "skeleton_die", - .img_var_name = "image_skeleton", - .time_per_frame = 0.10, - .num_frames = 13, - .start = {0.0f, 400.0f}, - .horizontal_diff_btwn_frames = 80.0, - .offset = {0.0f, 20.0f}, - .region_size = {80.0f, 80.0f}, - .no_wrap = true, - }, - { - .enum_name = "merchant_idle", - .img_var_name = "image_merchant", - .time_per_frame = 0.15, - .num_frames = 8, - .start = {0.0, 0.0}, - .horizontal_diff_btwn_frames = 110.0f, - .region_size = {110.0f, 110.0f}, - .offset = {0.0f, -20.0f}, - }, + { + .enum_name = "knight_attack", + .img_var_name = "image_new_knight_attack", + .time_per_frame = 0.06, + .num_frames = 7, + .start = {0.0f, 0.0f}, + .horizontal_diff_btwn_frames = 64.0, + .region_size = {64.0f, 64.0f}, + .no_wrap = true, + }, + { + .enum_name = "old_man_idle", + .img_var_name = "image_old_man", + .time_per_frame = 0.4, + .num_frames = 4, + .start = {0.0, 0.0}, + .horizontal_diff_btwn_frames = 16.0f, + .region_size = {16.0f, 16.0f}, + }, + { + .enum_name = "death_idle", + .img_var_name = "image_death", + .time_per_frame = 0.15, + .num_frames = 10, + .start = {0.0, 0.0}, + .horizontal_diff_btwn_frames = 100.0f, + .region_size = {100.0f, 100.0f}, + }, + { + .enum_name = "skeleton_idle", + .img_var_name = "image_skeleton", + .time_per_frame = 0.15, + .num_frames = 6, + .start = {0.0f, 0.0f}, + .horizontal_diff_btwn_frames = 80.0, + .offset = {0.0f, 20.0f}, + .region_size = {80.0f, 80.0f}, + }, + { + .enum_name = "skeleton_swing_sword", + .img_var_name = "image_skeleton", + .time_per_frame = 0.10, + .num_frames = 6, + .start = {0.0f, 240.0f}, + .horizontal_diff_btwn_frames = 80.0, + .offset = {0.0f, 20.0f}, + .region_size = {80.0f, 80.0f}, + .no_wrap = true, + }, + { + .enum_name = "skeleton_run", + .img_var_name = "image_skeleton", + .time_per_frame = 0.07, + .num_frames = 8, + .start = {0.0f, 160.0f}, + .horizontal_diff_btwn_frames = 80.0, + .offset = {0.0f, 20.0f}, + .region_size = {80.0f, 80.0f}, + }, + { + .enum_name = "skeleton_die", + .img_var_name = "image_skeleton", + .time_per_frame = 0.10, + .num_frames = 13, + .start = {0.0f, 400.0f}, + .horizontal_diff_btwn_frames = 80.0, + .offset = {0.0f, 20.0f}, + .region_size = {80.0f, 80.0f}, + .no_wrap = true, + }, + { + .enum_name = "merchant_idle", + .img_var_name = "image_merchant", + .time_per_frame = 0.15, + .num_frames = 8, + .start = {0.0, 0.0}, + .horizontal_diff_btwn_frames = 110.0f, + .region_size = {110.0f, 110.0f}, + .offset = {0.0f, -20.0f}, + }, }; diff --git a/codegen.c b/codegen.c index be5cd64..a2ab085 100644 --- a/codegen.c +++ b/codegen.c @@ -17,21 +17,21 @@ #define Log(...) { printf("Codegen: "); printf(__VA_ARGS__); } void dump(MD_Node* from) { - 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; - } + 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; }