Add skeleton enemies

main
parent 1bf8487ea6
commit 7d131a1931

@ -58,6 +58,10 @@
{ {
filepath: "drop_shadow.png", filepath: "drop_shadow.png",
} }
@image skeleton:
{
filepath: "copyrighted/skeleton.png",
}
@image shift_icon: @image shift_icon:
{ {
filepath: "shift_icon.png", filepath: "shift_icon.png",

@ -348,8 +348,8 @@
"rotation":0, "rotation":0,
"visible":true, "visible":true,
"width":32, "width":32,
"x":958.999999999997, "x":1528.66666666667,
"y":913.333333333337 "y":748.333333333337
}, },
{ {
"class":"", "class":"",
@ -359,8 +359,52 @@
"rotation":0, "rotation":0,
"visible":true, "visible":true,
"width":32, "width":32,
"x":1287.33333333333, "x":1258,
"y":748.666666666666 "y":732.666666666666
},
{
"class":"",
"height":32,
"id":6,
"name":"SKELETON",
"rotation":0,
"visible":true,
"width":32,
"x":1637,
"y":921.833333333333
},
{
"class":"",
"height":32,
"id":7,
"name":"SKELETON",
"rotation":0,
"visible":true,
"width":32,
"x":1721.33333333333,
"y":1098.66666666667
},
{
"class":"",
"height":32,
"id":8,
"name":"SKELETON",
"rotation":0,
"visible":true,
"width":32,
"x":1652,
"y":1224
},
{
"class":"",
"height":32,
"id":9,
"name":"SKELETON",
"rotation":0,
"visible":true,
"width":32,
"x":1716,
"y":1222.66666666667
}], }],
"opacity":1, "opacity":1,
"type":"objectgroup", "type":"objectgroup",
@ -369,7 +413,7 @@
"y":0 "y":0
}], }],
"nextlayerid":8, "nextlayerid":8,
"nextobjectid":6, "nextobjectid":10,
"orientation":"orthogonal", "orientation":"orthogonal",
"renderorder":"right-down", "renderorder":"right-down",
"tiledversion":"1.9.2", "tiledversion":"1.9.2",

@ -5,7 +5,13 @@ mkdir build_web
call run_codegen.bat || goto :error call run_codegen.bat || goto :error
emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -O0 -s ALLOW_MEMORY_GROWTH --source-map-base . -gsource-map -DDEVTOOLS -Ithirdparty -Igen main.c -o build_web\index.html --preload-file assets --shell-file web_template.html || goto :error @REM set FLAGS=-fsanitize=undefined -fsanitize=address
@REM GO FUCK YOURSELF
set FLAGS=-s TOTAL_STACK=5242880
@echo on
emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -O0 -s ALLOW_MEMORY_GROWTH %FLAGS% --source-map-base ../ -gsource-map -DDEVTOOLS -Ithirdparty -Igen main.c -o build_web\index.html --preload-file assets --shell-file web_template.html || goto :error
@echo off
goto :EOF goto :EOF

@ -5,8 +5,11 @@ mkdir build_web_release
call run_codegen.bat || goto :error call run_codegen.bat || goto :error
@REM GO FUCK YOURSELF
set FLAGS=-s TOTAL_STACK=5242880
echo Building release echo Building release
emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -DNDEBUG -O2 -s ALLOW_MEMORY_GROWTH -Ithirdparty -Igen main.c -o build_web_release\index.html --preload-file assets --shell-file web_template.html || goto :error emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -DNDEBUG -O2 -s ALLOW_MEMORY_GROWTH %FLAGS% -Ithirdparty -Igen main.c -o build_web_release\index.html --preload-file assets --shell-file web_template.html || goto :error
goto :EOF goto :EOF

267
main.c

@ -23,10 +23,10 @@
#include <math.h> #include <math.h>
#define PROFILING_IMPL
#ifdef DEVTOOLS #ifdef DEVTOOLS
#ifdef DESKTOP #ifdef DESKTOP
#define PROFILING #define PROFILING
#define PROFILING_IMPL
#endif #endif
#endif #endif
#include "profiling.h" #include "profiling.h"
@ -98,6 +98,7 @@ typedef struct AnimatedSprite
double time_per_frame; double time_per_frame;
int num_frames; int num_frames;
Vec2 start; Vec2 start;
Vec2 offset;
float horizontal_diff_btwn_frames; float horizontal_diff_btwn_frames;
Vec2 region_size; Vec2 region_size;
bool no_wrap; // does not wrap when playing bool no_wrap; // does not wrap when playing
@ -152,6 +153,7 @@ typedef enum NpcKind
{ {
OLD_MAN, OLD_MAN,
DEATH, DEATH,
SKELETON,
} NpcKind; } NpcKind;
typedef struct Entity typedef struct Entity
@ -163,6 +165,8 @@ typedef struct Entity
Vec2 vel; // only used sometimes, like in old man and bullet Vec2 vel; // only used sometimes, like in old man and bullet
float damage; // at 1.0, dead! zero initialized float damage; // at 1.0, dead! zero initialized
bool facing_left; bool facing_left;
// multiple entities have a sword swing
BUFF(struct Entity*, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around
bool is_bullet; bool is_bullet;
@ -180,12 +184,13 @@ typedef struct Entity
// only for death npc // only for death npc
bool going_to_target; bool going_to_target;
Vec2 target_goto; Vec2 target_goto;
// only for skeleton npc
double swing_timer;
// character // character
bool is_character; bool is_character;
CharacterState state; CharacterState state;
struct Entity *talking_to; // Maybe should be generational index, but I dunno. No death yet struct Entity *talking_to; // Maybe should be generational index, but I dunno. No death yet
BUFF(struct Entity*, 8) done_damage_to_this_swing; // only do damage once, but hitbox stays around
bool is_rolling; // can only roll in idle or walk states 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 double time_not_rolling; // for cooldown for roll, so you can't just hold it and be invincible
double roll_progress; double roll_progress;
@ -498,6 +503,54 @@ char *tprint(const char *format, ...)
return to_return; return to_return;
} }
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)),
};
}
}
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(Entity*, &from->done_damage_to_this_swing)
{
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, looking_for);
}
}
}
return to_return;
}
Vec2 entity_aabb_size(Entity *e) Vec2 entity_aabb_size(Entity *e)
{ {
if(e->is_character == ENTITY_PLAYER) if(e->is_character == ENTITY_PLAYER)
@ -514,6 +567,10 @@ 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 == SKELETON)
{
return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f);
}
else else
{ {
assert(false); assert(false);
@ -705,6 +762,37 @@ AnimatedSprite death_idle =
.horizontal_diff_btwn_frames = 100.0f, .horizontal_diff_btwn_frames = 100.0f,
.region_size = {100.0f, 100.0f}, .region_size = {100.0f, 100.0f},
}; };
AnimatedSprite skeleton_idle =
{
.img = &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},
};
AnimatedSprite skeleton_swing_sword =
{
.img = &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},
};
AnimatedSprite skeleton_run =
{
.img = &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},
};
sg_image image_font = {0}; sg_image image_font = {0};
float font_line_advance = 0.0f; float font_line_advance = 0.0f;
@ -1129,7 +1217,7 @@ Vec2 into_clip_space(Vec2 screen_space_point)
// The image region is in pixel space of the image // The image region is in pixel space of the image
void draw_quad(DrawParams d) void draw_quad(DrawParams d)
{ {
PROFILE_SCOPE("quad") PROFILE_SCOPE("Draw quad")
{ {
quad_fs_params_t params = {0}; quad_fs_params_t params = {0};
params.tint[0] = d.tint.R; params.tint[0] = d.tint.R;
@ -1258,6 +1346,7 @@ double anim_sprite_duration(AnimatedSprite *s)
void draw_animated_sprite(AnimatedSprite *s, double elapsed_time, bool flipped, Vec2 pos, Color tint) void draw_animated_sprite(AnimatedSprite *s, double elapsed_time, bool flipped, Vec2 pos, Color tint)
{ {
pos = AddV2(pos, s->offset);
sg_image spritesheet_img = *s->img; sg_image spritesheet_img = *s->img;
int index = (int)floor(elapsed_time/s->time_per_frame) % s->num_frames; int index = (int)floor(elapsed_time/s->time_per_frame) % s->num_frames;
if(s->no_wrap) if(s->no_wrap)
@ -1687,48 +1776,51 @@ void draw_dialog_panel(Entity *talking_to)
float new_line_height = dialog_panel.lower_right.Y; float new_line_height = dialog_panel.lower_right.Y;
int i = 0; int i = 0;
//BUFF_ITER(Sentence, &talking_to->player_dialog) //BUFF_ITER(Sentence, &talking_to->player_dialog)
BUFF_ITER_EX(Sentence, &talking_to->player_dialog, talking_to->player_dialog.cur_index-1, it >= &talking_to->player_dialog.data[0], it--) if(talking_to->player_dialog.cur_index > 0)
{ {
bool player_talking = i % 2 != 0; // iterating backwards BUFF_ITER_EX(Sentence, &talking_to->player_dialog, talking_to->player_dialog.cur_index-1, it >= &talking_to->player_dialog.data[0], it--)
Color *colors = calloc(sizeof(*colors), it->cur_index);
bool in_astrix = false;
for(int char_i = 0; char_i < it->cur_index; char_i++)
{ {
bool set_in_astrix_false = false; bool player_talking = i % 2 != 0; // iterating backwards
if(it->data[char_i] == '*') Color *colors = calloc(sizeof(*colors), it->cur_index);
bool in_astrix = false;
for(int char_i = 0; char_i < it->cur_index; char_i++)
{ {
if(in_astrix) bool set_in_astrix_false = false;
if(it->data[char_i] == '*')
{ {
set_in_astrix_false = true; if(in_astrix)
{
set_in_astrix_false = true;
}
else
{
in_astrix = true;
}
} }
else if(player_talking)
{ {
in_astrix = true; colors[char_i] = BLACK;
}
}
if(player_talking)
{
colors[char_i] = BLACK;
}
else
{
if(in_astrix)
{
colors[char_i] = colhex(0xffdf24);
} }
else else
{ {
colors[char_i] = colhex(0x345e22); if(in_astrix)
{
colors[char_i] = colhex(0xffdf24);
}
else
{
colors[char_i] = colhex(0x345e22);
}
} }
if(set_in_astrix_false) in_astrix = false;
} }
if(set_in_astrix_false) in_astrix = false; float measured_line_height = draw_wrapped_text(true, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->data, colors, 0.5f, true, dialog_panel);
} new_line_height += (new_line_height - measured_line_height);
float measured_line_height = draw_wrapped_text(true, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->data, colors, 0.5f, true, dialog_panel); draw_wrapped_text(false, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->data, colors, 0.5f, true, dialog_panel);
new_line_height += (new_line_height - measured_line_height);
draw_wrapped_text(false, V2(dialog_panel.upper_left.X, new_line_height), dialog_panel.lower_right.X - dialog_panel.upper_left.X, it->data, colors, 0.5f, true, dialog_panel);
free(colors); free(colors);
i++; i++;
}
} }
dbgrect(dialog_panel); dbgrect(dialog_panel);
@ -1953,6 +2045,8 @@ void frame(void)
} }
} }
#endif #endif
if(fabsf(it->vel.x) > 0.01f)
it->facing_left = it->vel.x < 0.0f;
if(it->is_npc) if(it->is_npc)
{ {
@ -1970,7 +2064,7 @@ void frame(void)
{ {
it->pos = LerpV2(it->pos, dt*5.0f, it->target_goto); it->pos = LerpV2(it->pos, dt*5.0f, it->target_goto);
} }
if(it->aggressive) if(it->npc_kind == OLD_MAN && it->aggressive)
{ {
draw_dialog_panel(it); draw_dialog_panel(it);
Entity *targeting = player; Entity *targeting = player;
@ -2004,7 +2098,67 @@ void frame(void)
Color col = LerpV4(WHITE, it->damage, RED); Color col = LerpV4(WHITE, it->damage, RED);
if(it->npc_kind == OLD_MAN) if(it->npc_kind == OLD_MAN)
{ {
draw_animated_sprite(&old_man_idle, elapsed_time, false, it->pos, col); bool face_left = false;
if(it->aggressive)
{
face_left = SubV2(player->pos, it->pos).x < 0.0f;
}
draw_animated_sprite(&old_man_idle, elapsed_time, face_left, it->pos, col);
}
else if(it->npc_kind == SKELETON)
{
if(fabsf(it->vel.x) > 0.01f)
it->facing_left = it->vel.x < 0.0f;
Color col = WHITE;
it->pos = move_and_slide(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)
{
// swinging sword
draw_animated_sprite(&skeleton_swing_sword, it->swing_timer, it->facing_left, it->pos, col);
it->swing_timer += dt;
if(it->swing_timer >= anim_sprite_duration(&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->pos, 0.2f);
}
}
}
else
{
// in huntin' range
if(LenV2(SubV2(player->pos, it->pos)) < 250.0f)
{
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);
}
}
draw_animated_sprite(&skeleton_run, elapsed_time, it->facing_left, it->pos, col);
target_vel = MulV2F(NormV2(SubV2(player->pos, it->pos)), 4.0f);
}
else
{
draw_animated_sprite(&skeleton_idle, elapsed_time, it->facing_left, it->pos, col);
}
}
it->vel = LerpV2(it->vel, dt*8.0f, target_vel);
} }
else if(it->npc_kind == DEATH) else if(it->npc_kind == DEATH)
{ {
@ -2069,6 +2223,7 @@ void frame(void)
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->aggressive; if(entity_talkable) entity_talkable = entity_talkable && !it->e->aggressive;
if(entity_talkable) entity_talkable = entity_talkable && !(it->e->npc_kind == 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
@ -2189,8 +2344,6 @@ draw_dialog_panel(talking_to);
} }
else else
{ {
if(fabsf(player->vel.x) > 0.01f)
player->facing_left = player->vel.x < 0.0f;
} }
} }
else if(player->state == CHARACTER_IDLE) else if(player->state == CHARACTER_IDLE)
@ -2207,44 +2360,12 @@ draw_dialog_panel(talking_to);
} }
else if(player->state == CHARACTER_ATTACK) else if(player->state == CHARACTER_ATTACK)
{ {
AABB weapon_aabb = {0}; AABB weapon_aabb = entity_sword_aabb(player, 40.0f, 25.0f);
if(player->facing_left)
{
weapon_aabb = (AABB){
.upper_left = AddV2(player->pos, V2(-40.0, 25.0)),
.lower_right = AddV2(player->pos, V2(0.0, -25.0)),
};
}
else
{
weapon_aabb = (AABB){
.upper_left = AddV2(player->pos, V2(0.0, 25.0)),
.lower_right = AddV2(player->pos, V2(40.0, -25.0)),
};
}
dbgrect(weapon_aabb); dbgrect(weapon_aabb);
Overlapping overlapping_weapon = get_overlapping(cur_level, weapon_aabb); SwordToDamage to_damage = entity_sword_to_do_damage(player, get_overlapping(cur_level, weapon_aabb));
BUFF_ITER(Overlap, &overlapping_weapon) BUFF_ITER(Entity*, &to_damage)
{ {
if(!it->is_tile && it->e != player) request_do_damage(*it, player->pos, 0.2f);
{
bool done_damage = false;
Entity *looking_for = it->e;
BUFF_ITER(Entity*, &player->done_damage_to_this_swing)
{
if(*it == looking_for) done_damage = true;
}
if(!done_damage)
{
if(!BUFF_HAS_SPACE(&player->done_damage_to_this_swing))
{
BUFF_REMOVE_FRONT(&player->done_damage_to_this_swing);
Log("Too many things to do damage to...\n");
}
BUFF_APPEND(&player->done_damage_to_this_swing, looking_for);
request_do_damage(it->e, player->pos, 0.2f);
}
}
} }
player->swing_progress += dt; player->swing_progress += dt;

@ -101,5 +101,5 @@ void init_profiling_mythread(uint32_t id) { (void)id; }
void end_profiling() {} void end_profiling() {}
void end_profiling_mythread() {} void end_profiling_mythread() {}
#define PROFILE_SCOPE(name) DeferLoop((void)0, (void)0) // so loop stuff in it doesn't break #define PROFILE_SCOPE(name) for(int _i_ = 0; _i_ == 0; _i_ += 1)
#endif #endif

@ -13,6 +13,7 @@ copy "EPIC RPG World Pack - Ancient Ruins V 1.7\EPIC RPG World Pack - Ancient Ru
copy "EPIC RPG World Pack - Ancient Ruins V 1.7\EPIC RPG World Pack - Ancient Ruins V 1.7\TiledMap Editor\Terrain - Ancient Ruins.tsx" "assets\copyrighted\ruins_ancient.tsx" || goto :error copy "EPIC RPG World Pack - Ancient Ruins V 1.7\EPIC RPG World Pack - Ancient Ruins V 1.7\TiledMap Editor\Terrain - Ancient Ruins.tsx" "assets\copyrighted\ruins_ancient.tsx" || goto :error
copy "EPIC RPG World Pack - Ancient Ruins V 1.7\EPIC RPG World Pack - Ancient Ruins V 1.7\Tilesets\Tileset-Terrain.png" "assets\copyrighted\ruins_ancient.png" || goto :error copy "EPIC RPG World Pack - Ancient Ruins V 1.7\EPIC RPG World Pack - Ancient Ruins V 1.7\Tilesets\Tileset-Terrain.png" "assets\copyrighted\ruins_ancient.png" || goto :error
copy "Undead - Pixel Art Characters\Undead - Pixel Art Characters\Sprites\Wraith_Red.png" "assets\copyrighted\wraith.png" || goto :error copy "Undead - Pixel Art Characters\Undead - Pixel Art Characters\Sprites\Wraith_Red.png" "assets\copyrighted\wraith.png" || goto :error
copy "Undead - Pixel Art Characters\Undead - Pixel Art Characters\Sprites\Skeleton_Blue.png" "assets\copyrighted\skeleton.png" || goto :error
rmdir /S /q gen rmdir /S /q gen
mkdir gen mkdir gen

Loading…
Cancel
Save