diff --git a/code/game/src/debug_replay.c b/code/game/src/debug_replay.c new file mode 100644 index 0000000..2bd9774 --- /dev/null +++ b/code/game/src/debug_replay.c @@ -0,0 +1,94 @@ +#include "debug_replay.h" +#include "camera.h" +#include "entity.h" + +typedef struct { + pkt_send_keystate pkt; + uint64_t delay; +} replay_record; + +static uint8_t is_recording = false; +static replay_record *records = NULL; +static uint64_t last_record_time = 0.0f; + +static uint8_t is_playing = false; +static int record_pos = 0; +static uint64_t playback_time = 0; +static ecs_entity_t mime = 0; +static ecs_entity_t plr = 0; + +void debug_replay_start(void) { + is_recording = true; + + if (records) zpl_array_free(records); + zpl_array_init(records, zpl_heap()); + + last_record_time = zpl_time_rel_ms(); +} + +void debug_replay_clear(void) { + if (!records || is_playing || is_recording) return; + zpl_array_free(records); + records = NULL; +} + +void debug_replay_stop(void) { + is_recording = false; + // TODO(zaklaus): +} + +void debug_replay_run(void) { + if (mime) return; + is_playing = true; + record_pos = 0; + playback_time = zpl_time_rel_ms(); + + plr = camera_get().ent_id; + Position const *p1 = ecs_get(world_ecs(), plr, Position); + + mime = entity_spawn(EKIND_DEMO_NPC); + Position *pos = ecs_get_mut(world_ecs(), mime, Position, NULL); + *pos = *p1; + + ecs_set(world_ecs(), mime, Input, {0}); + + camera_set_follow(mime); +} + +void debug_replay_update(void) { + if (!is_playing) return; + if (playback_time >= zpl_time_rel_ms()) return; + + replay_record *r = &records[record_pos]; + playback_time = zpl_time_rel() + r->delay; + + Input *i = ecs_get_mut(world_ecs(), mime, Input, NULL); + i->x = r->pkt.x; + i->y = r->pkt.y; + i->use = r->pkt.use; + i->sprint = r->pkt.sprint; + + record_pos += 1; + + // NOTE(zaklaus): remove our dummy art exhibist + if (mime && record_pos == zpl_array_count(records)) { + entity_despawn(mime); + mime = 0; + + is_playing = false; + camera_set_follow(plr); + } +} + +void debug_replay_record_keystate(pkt_send_keystate state) { + if (!is_recording) return; + float record_time = zpl_time_rel_ms(); + + replay_record rec = { + .pkt = state, + .delay = (record_time - last_record_time), + }; + + zpl_array_append(records, rec); + last_record_time = zpl_time_rel_ms(); +} diff --git a/code/game/src/debug_replay.h b/code/game/src/debug_replay.h new file mode 100644 index 0000000..28b0ce5 --- /dev/null +++ b/code/game/src/debug_replay.h @@ -0,0 +1,6 @@ +#pragma once +#include "system.h" +#include "packets/pkt_send_keystate.h" + +void debug_replay_record_keystate(pkt_send_keystate state); +void debug_replay_update(void); \ No newline at end of file diff --git a/code/game/src/debug_ui.c b/code/game/src/debug_ui.c index 423aade..4975b0d 100644 --- a/code/game/src/debug_ui.c +++ b/code/game/src/debug_ui.c @@ -7,6 +7,7 @@ typedef enum { DITEM_BUTTON, DITEM_SLIDER, DITEM_LIST, + DITEM_COND, DITEM_END, DITEM_FORCE_UINT8 = UINT8_MAX @@ -52,6 +53,8 @@ typedef struct debug_item { } slider; void (*on_click)(void); + + uint8_t (*on_success)(void); }; debug_draw_result (*proc)(struct debug_item*, float, float); @@ -99,6 +102,29 @@ static debug_item items[] = { } } }, + { + .kind = DITEM_LIST, + .name = "replay system", + .list = { + .items = (debug_item[]) { + { .kind = DITEM_TEXT, .name = "macro", .text = "", .proc = DrawLiteral }, + { .kind = DITEM_BUTTON, .name = "load", .on_click = NULL }, + { .kind = DITEM_BUTTON, .name = "save", .on_click = NULL }, + + { .kind = DITEM_COND, .on_success = CondReplayStatusOff }, + { .kind = DITEM_BUTTON, .name = "record", .on_click = ActReplayBegin }, + + { .kind = DITEM_COND, .on_success = CondReplayStatusOn }, + { .kind = DITEM_BUTTON, .name = "stop", .on_click = ActReplayEnd }, + + { .kind = DITEM_COND, .on_success = CondReplayDataPresent }, + { .kind = DITEM_BUTTON, .name = "replay", .on_click = ActReplayRun }, + + { .kind = DITEM_BUTTON, .name = "clear", .on_click = ActReplayClear }, + { .kind = DITEM_END }, + } + } + }, { .kind = DITEM_LIST, .name = "profilers", @@ -129,6 +155,13 @@ debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool is_shadow_rendered = is_shadow; for (debug_item *it = list; it->kind != DITEM_END; it += 1) { switch (it->kind) { + case DITEM_COND: { + assert(it->on_success); + + if (!it->on_success()) { + it += 1; + } + }break; case DITEM_LIST: { // NOTE(zaklaus): calculate and cache name width for future use if (it->name_width == 0) { @@ -166,16 +199,19 @@ debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool }break; case DITEM_BUTTON: { - assert(it->on_click); char const *text = TextFormat("> %s", it->name); if (it->name_width == 0) { it->name_width = UIMeasureText(text, DBG_FONT_SIZE); } Color color = RAYWHITE; - if (is_btn_pressed(xpos, ypos, it->name_width, DBG_FONT_SIZE, &color)) { + if (is_btn_pressed(xpos, ypos, it->name_width, DBG_FONT_SIZE, &color) && it->on_click) { it->on_click(); } + if (!it->on_click) { + color = GRAY; + } + debug_draw_result res = DrawColoredText(xpos, ypos, text, color); ypos = res.y; }break; diff --git a/code/game/src/debug_ui_actions.c b/code/game/src/debug_ui_actions.c index f6cf75d..217e6b0 100644 --- a/code/game/src/debug_ui_actions.c +++ b/code/game/src/debug_ui_actions.c @@ -7,6 +7,8 @@ #include "modules/components.h" +#include "debug_replay.c" + static inline void ActExitGame(void) { game_request_close(); @@ -21,3 +23,41 @@ ActSpawnCar(void) { Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL); *dest = *origin; } + +// NOTE(zaklaus): Replay system + +static inline uint8_t +CondReplayStatusOn(void) { + return is_recording; +} + +static inline uint8_t +CondReplayStatusOff(void) { + return !is_recording; +} + +static inline uint8_t +CondReplayDataPresent(void) { + return records != NULL && !is_recording; +} + +static inline void +ActReplayBegin(void) { + debug_replay_start(); +} + +static inline void +ActReplayEnd(void) { + debug_replay_stop(); +} + +static inline void +ActReplayRun(void) { + debug_replay_run(); +} + +static inline void +ActReplayClear(void) { + debug_replay_clear(); +} + diff --git a/code/game/src/entity.c b/code/game/src/entity.c index 3d9a647..9e40922 100644 --- a/code/game/src/entity.c +++ b/code/game/src/entity.c @@ -27,6 +27,7 @@ uint64_t entity_spawn(uint16_t class_id) { if (class_id != EKIND_SERVER) { librg_entity_track(world_tracker(), e); librg_entity_chunk_set(world_tracker(), e, librg_chunk_from_realpos(world_tracker(), pos->x, pos->y, 0)); + librg_entity_owner_set(world_tracker(), e, (int64_t)e); } return (uint64_t)e; diff --git a/code/game/src/game.c b/code/game/src/game.c index ea0fd4a..5360d17 100644 --- a/code/game/src/game.c +++ b/code/game/src/game.c @@ -12,8 +12,8 @@ #include "profiler.h" #include "flecs/flecs.h" -#include "flecs/flecs_dash.h" -#include "flecs/flecs_systems_civetweb.h" +//#include "flecs/flecs_dash.h" +//#include "flecs/flecs_systems_civetweb.h" #include "flecs/flecs_os_api_stdcpp.h" #include "modules/components.h" @@ -98,10 +98,12 @@ void game_world_view_set_active(world_view *view) { } void flecs_dash_init() { +#if 0 ECS_IMPORT(world_ecs(), FlecsDash); ECS_IMPORT(world_ecs(), FlecsSystemsCivetweb); ecs_set(world_ecs(), 0, EcsDashServer, {.port = 27001}); +#endif } float game_time() { diff --git a/code/game/src/main.c b/code/game/src/main.c index bb2337f..e1cafc4 100644 --- a/code/game/src/main.c +++ b/code/game/src/main.c @@ -49,7 +49,7 @@ int main(int argc, char** argv) { uint16_t num_viewers = zpl_opts_integer(&opts, "viewer-count", 1); uint16_t chunk_size = DEFAULT_CHUNK_SIZE; //zpl_opts_integer(&opts, "chunk-size", DEFAULT_CHUNK_SIZE); uint16_t world_size = zpl_opts_integer(&opts, "world-size", DEFAULT_WORLD_SIZE); - uint32_t npc_count = zpl_opts_integer(&opts, "npc-count", 10000); + uint32_t npc_count = zpl_opts_integer(&opts, "npc-count", 1000); if (zpl_opts_has_arg(&opts, "random-seed")) { zpl_random rnd={0}; @@ -67,8 +67,8 @@ int main(int argc, char** argv) { game_init(is_viewer_only, num_viewers, seed, chunk_size, world_size, is_dash_enabled); // TODO(zaklaus): VERY TEMPORARY -- SPAWN SOME NPCS THAT RANDOMLY MOVE +#if 1 { - ECS_IMPORT(world_ecs(), Components); for (uint32_t i = 0; i < npc_count; i++) { uint64_t e = entity_spawn(EKIND_DEMO_NPC); ecs_add(world_ecs(), e, EcsDemoNPC); @@ -81,6 +81,7 @@ int main(int argc, char** argv) { v->y = (rand()%3-1) * 100; } } +#endif while (game_is_running()) { profile (PROF_MAIN_LOOP) { diff --git a/code/game/src/packets/pkt_send_keystate.c b/code/game/src/packets/pkt_send_keystate.c index 4922063..9b3ee22 100644 --- a/code/game/src/packets/pkt_send_keystate.c +++ b/code/game/src/packets/pkt_send_keystate.c @@ -4,6 +4,8 @@ #include "modules/systems.h" #include "world/world.h" +#include "debug_replay.h" + pkt_desc pkt_send_keystate_desc[] = { { PKT_REAL(pkt_send_keystate, x) }, { PKT_REAL(pkt_send_keystate, y) }, @@ -39,6 +41,7 @@ int32_t pkt_send_keystate_handler(pkt_header *header) { i->y = table.y; i->use = table.use; i->sprint = table.sprint; + debug_replay_record_keystate(table); } return 0; diff --git a/code/game/src/player.c b/code/game/src/player.c index 3ad9ad7..3257f64 100644 --- a/code/game/src/player.c +++ b/code/game/src/player.c @@ -34,6 +34,3 @@ uint64_t player_spawn(char *name) { void player_despawn(uint64_t ent_id) { entity_despawn(ent_id); } - -void player_freeze(uint64_t id, uint8_t state, uint8_t clear) { -} \ No newline at end of file diff --git a/code/game/src/player.h b/code/game/src/player.h index dd6ce50..cd6b611 100644 --- a/code/game/src/player.h +++ b/code/game/src/player.h @@ -3,5 +3,3 @@ uint64_t player_spawn(char *name); void player_despawn(uint64_t ent_id); - -void player_freeze(uint64_t id, uint8_t state, uint8_t clear); \ No newline at end of file diff --git a/code/game/src/world/world.c b/code/game/src/world/world.c index b91ee07..e533b54 100644 --- a/code/game/src/world/world.c +++ b/code/game/src/world/world.c @@ -4,6 +4,7 @@ #include "modules/systems.h" #include "world/world.h" #include "entity_view.h" +#include "debug_replay.h" #include "world/worldgen/worldgen.h" #include "platform.h" #include "profiler.h" @@ -151,7 +152,6 @@ int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) { } world.ecs = ecs_init(); - ecs_set_entity_range(world.ecs, 0, UINT32_MAX); ECS_IMPORT(world.ecs, Components); ECS_IMPORT(world.ecs, Systems); @@ -257,6 +257,8 @@ int32_t world_update() { world_tracker_update(0, WORLD_TRACKER_UPDATE_FAST_MS, 2); world_tracker_update(1, WORLD_TRACKER_UPDATE_NORMAL_MS, 4); world_tracker_update(2, WORLD_TRACKER_UPDATE_SLOW_MS, 6); + + debug_replay_update(); return 0; } @@ -407,4 +409,9 @@ int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius) librg_entity_radius_set(world.tracker, e, radius); librg_world_query(world.tracker, e, ents, ents_len); return ents; -} \ No newline at end of file +} + +uint8_t world_entity_valid(ecs_entity_t e) { + if (!e) return false; + return ecs_is_alive(world.ecs, e); +} diff --git a/code/game/src/world/world.h b/code/game/src/world/world.h index 0ed9cdf..acbb1c6 100644 --- a/code/game/src/world/world.h +++ b/code/game/src/world/world.h @@ -81,4 +81,6 @@ uint8_t world_chunk_is_dirty(ecs_entity_t e); // NOTE(zaklaus): Uses locally persistent buffer !! int64_t *world_chunk_fetch_entities(librg_chunk chunk_id, size_t *ents_len); int64_t *world_chunk_fetch_entities_realpos(float x, float y, size_t *ents_len); -int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius); \ No newline at end of file +int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius); + +uint8_t world_entity_valid(ecs_entity_t e); \ No newline at end of file