From 6c4983cf1a012f852a3a689a237f98378372b7aa Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Thu, 13 Apr 2023 20:43:23 -0700 Subject: [PATCH] Dash afterimage improvements, custom properties in level editor --- assets/new_level.json | 34 ++----- character_info.h | 17 ++++ codegen.c | 16 +++- main.c | 217 ++++++++++++++++++++++-------------------- makeprompt.h | 45 ++++++++- 5 files changed, 194 insertions(+), 135 deletions(-) diff --git a/assets/new_level.json b/assets/new_level.json index 1fe0d86..9949960 100644 --- a/assets/new_level.json +++ b/assets/new_level.json @@ -377,12 +377,18 @@ "class":"", "height":32, "id":4, - "name":"Skeleton", + "name":"MikeSkeleton", + "properties":[ + { + "name":"standing", + "type":"string", + "value":"STANDING_FIGHTING" + }], "rotation":0, "visible":true, "width":32, - "x":1632.66666666667, - "y":479.666666666667 + "x":1038.33333333334, + "y":488 }, { "class":"ITEM", @@ -406,28 +412,6 @@ "x":948, "y":192 }, - { - "class":"", - "height":32, - "id":8, - "name":"Skeleton", - "rotation":0, - "visible":true, - "width":32, - "x":2465.33333333333, - "y":718.666666666667 - }, - { - "class":"", - "height":32, - "id":9, - "name":"Skeleton", - "rotation":0, - "visible":true, - "width":32, - "x":2537.33333333333, - "y":786.666666666667 - }, { "class":"PROP", "height":32, diff --git a/character_info.h b/character_info.h index f772567..06e63eb 100644 --- a/character_info.h +++ b/character_info.h @@ -25,6 +25,7 @@ const char *actions[] = { "strikes_air", "joins_player", "leaves_player", + "stops_fighting_player", }; typedef struct @@ -138,6 +139,22 @@ CharacterGen characters[] = { "\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.", + }, }; typedef struct diff --git a/codegen.c b/codegen.c index c78aa51..154b584 100644 --- a/codegen.c +++ b/codegen.c @@ -246,25 +246,33 @@ int main(int argc, char **argv) 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 }, }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string)); + 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 }, }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string)); + 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 }, }, ", MD_S8VArg(x_string), MD_S8VArg(y_string)); + 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 }, }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string)); + 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"); diff --git a/main.c b/main.c index 9fcf238..7c94e5c 100644 --- a/main.c +++ b/main.c @@ -366,7 +366,7 @@ Vec2 entity_aabb_size(Entity *e) { return V2(TILE_SIZE*1.10f, TILE_SIZE*1.10f); } - else if(e->npc_kind == NPC_Skeleton) + else if(npc_is_skeleton(e)) { return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f); } @@ -2341,7 +2341,7 @@ void frame(void) { speed_target = 1.0f; } - speed_factor = Lerp(speed_factor, unwarped_dt*15.0f, speed_target); + speed_factor = Lerp(speed_factor, unwarped_dt*10.0f, speed_target); if(fabsf(speed_factor - speed_target) <= 0.05f) { speed_factor = speed_target; @@ -2461,7 +2461,7 @@ void frame(void) { 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) @@ -2472,7 +2472,7 @@ void frame(void) { it->characters_said = (double)length; } - + if( (int)it->characters_said > (int)before ) { float dist = LenV2(SubV2(it->pos, player->pos)); @@ -2480,53 +2480,113 @@ void frame(void) play_audio(&sound_simple_talk, volume); } } + if(it->standing == STANDING_FIGHTING || it->standing == STANDING_JOINED) { Entity *targeting = player; - - 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(AddV2(rotate_direction, MulV2F(to_player, 0.5f))); - 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) + if(npc_attacks_with_sword(it)) { - 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++) + 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); + dbgrect(weapon_aabb); + Vec2 target_vel = {0}; + it->pos = AddV2(it->pos, MulV2F(it->vel, dt)); + 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 { - 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)); + // in huntin' range + it->walking = LenV2(SubV2(player->pos, it->pos)) < 250.0f; + 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(player->pos, it->pos)), 4.0f); + } + else + { + } } + it->vel = LerpV2(it->vel, dt*8.0f, target_vel); + } + if(npc_attacks_with_shotgun(it)) + { + 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(AddV2(rotate_direction, MulV2F(to_player, 0.5f))); + 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) @@ -2561,65 +2621,14 @@ void frame(void) it->pos = move_and_slide((MoveSlideParams){it, it->pos, MulV2F(it->vel, pixels_per_meter * dt)}); */ } - - else if(it->npc_kind == NPC_Skeleton) + else if(npc_is_skeleton(it)) { if(it->dead) { } else { - 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); - dbgrect(weapon_aabb); - Vec2 target_vel = {0}; - it->pos = AddV2(it->pos, MulV2F(it->vel, dt)); - 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; - 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(player->pos, it->pos)), 4.0f); - } - else - { - } - } - it->vel = LerpV2(it->vel, dt*8.0f, target_vel); - } + } // skelton combat and movement } else if(it->npc_kind == NPC_Death) { @@ -2665,7 +2674,7 @@ void frame(void) } if(it->damage >= 1.0) { - if(it->npc_kind == NPC_Skeleton) + if(npc_is_skeleton(it)) { it->dead = true; } @@ -2804,7 +2813,7 @@ void frame(void) 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); + //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; #endif @@ -3065,11 +3074,12 @@ void frame(void) to_draw.anim = player->cur_animation; Vec2 target_sprite_pos = to_draw.pos; - BUFF_ITER(PlayerAfterImage, &player->after_images) + + BUFF_ITER_I(PlayerAfterImage, &player->after_images, i) { { DrawnAnimatedSprite to_draw = it->drawn; - to_draw.tint.a = 1.0f; + to_draw.tint.a = 0.5f; float progress_through_life = it->alive_for / AFTERIMAGE_LIFETIME; if(progress_through_life > 0.5f) @@ -3077,14 +3087,17 @@ void frame(void) float fade_amount = (progress_through_life - 0.5f)/0.5f; to_draw.tint.a = Lerp(0.8f, fade_amount, 0.0f); - to_draw.pos = LerpV2(to_draw.pos, fade_amount, target_sprite_pos); + 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(player->is_rolling^) to_draw.tint.a = 0.5f; if(to_draw.anim) { @@ -3135,7 +3148,7 @@ void frame(void) 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(it->npc_kind == NPC_Skeleton) + else if(npc_is_skeleton(it)) { Color col = WHITE; if(it->dead) diff --git a/makeprompt.h b/makeprompt.h index 0f62847..e153154 100644 --- a/makeprompt.h +++ b/makeprompt.h @@ -7,7 +7,6 @@ #include // atoi #include "character_info.h" #include "characters.gen.h" - NPC_Skeleton, NPC_MOOSE, } NpcKind; @@ -224,6 +223,22 @@ bool npc_is_knight_sprite(Entity *it) return it->is_npc && ( it->npc_kind == NPC_TheGuard || it->npc_kind == NPC_Edeline); } +bool npc_is_skeleton(Entity *it) +{ + return it->is_npc && ( it->npc_kind == NPC_MikeSkeleton ); +} + +bool npc_attacks_with_sword(Entity *it) +{ + return npc_is_skeleton(it); +} + +bool npc_attacks_with_shotgun(Entity *it) +{ + return it->is_npc && ( it->npc_kind == NPC_OldMan ); +} + + typedef BUFF(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff; typedef BUFF(Action, 8) AvailableActions; @@ -249,7 +264,7 @@ void fill_available_actions(Entity *it, AvailableActions *a) } else if(it->standing == STANDING_FIGHTING) { - BUFF_APPEND(a, ACT_leaves_player); + BUFF_APPEND(a, ACT_stops_fighting_player); } if(npc_is_knight_sprite(it)) { @@ -326,6 +341,10 @@ void process_perception(Entity *it, Perception p) { 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; @@ -381,7 +400,7 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) printf_buff(into, "["); - BUFF(char, 1024*10) initial_system_msg = {0}; + BUFF(char, 1024*15) initial_system_msg = {0}; const char *health_string = 0; if(it->damage <= 0.2f) { @@ -401,7 +420,25 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into) } assert(health_string); - printf_buff(&initial_system_msg, "%s\n%s\nNPC health status: Right now, %s\n%s", global_prompt, characters[it->npc_kind].prompt, health_string, items[it->last_seen_holding_kind].global_prompt); + printf_buff(&initial_system_msg, "%s\n%s\nNPC health status: Right now, %s\n%s\n", global_prompt, characters[it->npc_kind].prompt, health_string, items[it->last_seen_holding_kind].global_prompt); + + if(it->standing == STANDING_INDIFFERENT) + { + printf_buff(&initial_system_msg, "The NPC is indifferent towards the player."); + } + else if(it->standing == STANDING_JOINED) + { + printf_buff(&initial_system_msg, "The NPC has joined the player and is with them!"); + } + else if(it->standing == STANDING_FIGHTING) + { + printf_buff(&initial_system_msg, "The NPC is fighting the player and HATES them."); + } + else + { + assert(false); + } + dump_json_node(into, MSG_SYSTEM, initial_system_msg.data); Entity *e = it;