Dash afterimage improvements, custom properties in level editor

main
Cameron Murphy Reikes 2 years ago
parent 622380f077
commit 6c4983cf1a

@ -377,12 +377,18 @@
"class":"", "class":"",
"height":32, "height":32,
"id":4, "id":4,
"name":"Skeleton", "name":"MikeSkeleton",
"properties":[
{
"name":"standing",
"type":"string",
"value":"STANDING_FIGHTING"
}],
"rotation":0, "rotation":0,
"visible":true, "visible":true,
"width":32, "width":32,
"x":1632.66666666667, "x":1038.33333333334,
"y":479.666666666667 "y":488
}, },
{ {
"class":"ITEM", "class":"ITEM",
@ -406,28 +412,6 @@
"x":948, "x":948,
"y":192 "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", "class":"PROP",
"height":32, "height":32,

@ -25,6 +25,7 @@ const char *actions[] = {
"strikes_air", "strikes_air",
"joins_player", "joins_player",
"leaves_player", "leaves_player",
"stops_fighting_player",
}; };
typedef struct typedef struct
@ -138,6 +139,22 @@ CharacterGen characters[] = {
"\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.", "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 typedef struct

@ -246,25 +246,33 @@ int main(int argc, char **argv)
MD_String8 y_string = MD_ChildFromString(object, MD_S8Lit("y"), 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)); 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(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)); 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; MD_String8 class = MD_ChildFromString(object, MD_S8Lit("class"), 0)->first_child->string;
if(MD_S8Match(class, MD_S8Lit("PROP"), 0)) 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)) 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)) 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 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"); fprintf(output, "\n}, // entities\n");

213
main.c

@ -366,7 +366,7 @@ Vec2 entity_aabb_size(Entity *e)
{ {
return V2(TILE_SIZE*1.10f, TILE_SIZE*1.10f); 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); return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f);
} }
@ -2341,7 +2341,7 @@ void frame(void)
{ {
speed_target = 1.0f; 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) if(fabsf(speed_factor - speed_target) <= 0.05f)
{ {
speed_factor = speed_target; speed_factor = speed_target;
@ -2480,53 +2480,113 @@ void frame(void)
play_audio(&sound_simple_talk, volume); play_audio(&sound_simple_talk, volume);
} }
} }
if(it->standing == STANDING_FIGHTING || it->standing == STANDING_JOINED) if(it->standing == STANDING_FIGHTING || it->standing == STANDING_JOINED)
{ {
Entity *targeting = player; 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); if(npc_attacks_with_sword(it))
}
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; if(fabsf(it->vel.x) > 0.01f)
const float spread = (float)PI/4.0f; it->facing_left = it->vel.x < 0.0f;
// shoot shotgun
int num_bullets = 5; it->pos = move_and_slide((MoveSlideParams){it, it->pos, MulV2F(it->vel, pixels_per_meter * dt)});
for(int i = 0; i < num_bullets; i++) 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)
{ {
Vec2 dir = to_player; player_in_combat = true;
float theta = Lerp(-spread/2.0f, ((float)i / (float)(num_bullets - 1)), spread/2.0f); it->swing_timer += dt;
dir = RotateV2(dir, theta); if(it->swing_timer >= anim_sprite_duration(ANIM_skeleton_swing_sword))
Entity *new_bullet = new_entity(); {
new_bullet->is_bullet = true; it->swing_timer = 0.0;
new_bullet->pos = AddV2(it->pos, MulV2F(dir, 20.0f)); }
new_bullet->vel = MulV2F(dir, 15.0f); if(it->swing_timer >= 0.4f)
it->vel = AddV2(it->vel, MulV2F(dir, -3.0f)); {
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);
} }
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) 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)}); it->pos = move_and_slide((MoveSlideParams){it, it->pos, MulV2F(it->vel, pixels_per_meter * dt)});
*/ */
} }
else if(npc_is_skeleton(it))
else if(it->npc_kind == NPC_Skeleton)
{ {
if(it->dead) if(it->dead)
{ {
} }
else else
{ {
if(fabsf(it->vel.x) > 0.01f) } // skelton combat and movement
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);
}
} }
else if(it->npc_kind == NPC_Death) else if(it->npc_kind == NPC_Death)
{ {
@ -2665,7 +2674,7 @@ void frame(void)
} }
if(it->damage >= 1.0) if(it->damage >= 1.0)
{ {
if(it->npc_kind == NPC_Skeleton) if(npc_is_skeleton(it))
{ {
it->dead = true; it->dead = true;
} }
@ -2804,7 +2813,7 @@ void frame(void)
bool entity_talkable = true; bool entity_talkable = true;
if(entity_talkable) entity_talkable = entity_talkable && !it->is_tile; 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->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 #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 #endif
@ -3065,11 +3074,12 @@ void frame(void)
to_draw.anim = player->cur_animation; to_draw.anim = player->cur_animation;
Vec2 target_sprite_pos = to_draw.pos; 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; 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; float progress_through_life = it->alive_for / AFTERIMAGE_LIFETIME;
if(progress_through_life > 0.5f) if(progress_through_life > 0.5f)
@ -3077,14 +3087,17 @@ void frame(void)
float fade_amount = (progress_through_life - 0.5f)/0.5f; float fade_amount = (progress_through_life - 0.5f)/0.5f;
to_draw.tint.a = Lerp(0.8f, fade_amount, 0.0f); 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; to_draw.no_shadow = true;
draw_animated_sprite(to_draw); 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) if(to_draw.anim)
{ {
@ -3135,7 +3148,7 @@ void frame(void)
bool face_left =SubV2(player->pos, it->pos).x < 0.0f; 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}); 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; Color col = WHITE;
if(it->dead) if(it->dead)

@ -7,7 +7,6 @@
#include <stdlib.h> // atoi #include <stdlib.h> // atoi
#include "character_info.h" #include "character_info.h"
#include "characters.gen.h" #include "characters.gen.h"
NPC_Skeleton,
NPC_MOOSE, NPC_MOOSE,
} NpcKind; } 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); 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(char, MAX_SENTENCE_LENGTH*(REMEMBERED_PERCEPTIONS+4)) PromptBuff;
typedef BUFF(Action, 8) AvailableActions; typedef BUFF(Action, 8) AvailableActions;
@ -249,7 +264,7 @@ void fill_available_actions(Entity *it, AvailableActions *a)
} }
else if(it->standing == STANDING_FIGHTING) 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)) if(npc_is_knight_sprite(it))
{ {
@ -326,6 +341,10 @@ void process_perception(Entity *it, Perception p)
{ {
it->standing = STANDING_FIGHTING; 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) else if(p.npc_action_type == ACT_leaves_player)
{ {
it->standing = STANDING_INDIFFERENT; it->standing = STANDING_INDIFFERENT;
@ -381,7 +400,7 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
printf_buff(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; const char *health_string = 0;
if(it->damage <= 0.2f) if(it->damage <= 0.2f)
{ {
@ -401,7 +420,25 @@ void generate_chatgpt_prompt(Entity *it, PromptBuff *into)
} }
assert(health_string); 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); dump_json_node(into, MSG_SYSTEM, initial_system_msg.data);
Entity *e = it; Entity *e = it;

Loading…
Cancel
Save