diff --git a/code/game/src/assets.h b/code/game/src/assets.h index 2a2e1de..923671e 100644 --- a/code/game/src/assets.h +++ b/code/game/src/assets.h @@ -1,39 +1,39 @@ -#pragma once -#include "system.h" - -#define ASSET_INVALID 0xFF - -typedef enum { - // NOTE(zaklaus): Debug - ASSET_DEBUG_TILE, - - // NOTE(zaklaus): entities - ASSET_PLAYER, - ASSET_THING, - - // NOTE(zaklaus): items - ASSET_DEMO_ICEMAKER, - - MAX_ASSETS, - FORCE_ASSET_UINT16 = UINT16_MAX -} asset_id; - -typedef enum { - AKIND_TEXTURE, - AKIND_SOUND, - - FORCE_AKIND_UINT8 = UINT8_MAX -} asset_kind; - -int32_t assets_setup(void); -void assets_destroy(void); - -uint16_t assets_find(asset_id id); - -asset_kind assets_get_kind(uint16_t id); -void *assets_get_snd(uint16_t id); -void *assets_get_tex(uint16_t id); - -// NOTE(zaklaus): client only -#define ASSET_SRC_RECT() ((Rectangle){0, 0, 64, 64}) -#define ASSET_DST_RECT(x,y) ((Rectangle){x, y, 64, 64}) +#pragma once +#include "system.h" + +#define ASSET_INVALID 0xFF + +typedef enum { + // NOTE(zaklaus): Debug + ASSET_DEBUG_TILE, + + // NOTE(zaklaus): entities + ASSET_PLAYER, + ASSET_THING, + + // NOTE(zaklaus): items + ASSET_DEMO_ICEMAKER, + + MAX_ASSETS, + FORCE_ASSET_UINT16 = UINT16_MAX +} asset_id; + +typedef enum { + AKIND_TEXTURE, + AKIND_SOUND, + + FORCE_AKIND_UINT8 = UINT8_MAX +} asset_kind; + +int32_t assets_setup(void); +void assets_destroy(void); + +uint16_t assets_find(asset_id id); + +asset_kind assets_get_kind(uint16_t id); +void *assets_get_snd(uint16_t id); +void *assets_get_tex(uint16_t id); + +// NOTE(zaklaus): client only +#define ASSET_SRC_RECT() ((Rectangle){0, 0, 64, 64}) +#define ASSET_DST_RECT(x,y) ((Rectangle){x, y, 64, 64}) diff --git a/code/game/src/assets_list.c b/code/game/src/assets_list.c index 29fe5b6..46cd8ed 100644 --- a/code/game/src/assets_list.c +++ b/code/game/src/assets_list.c @@ -1,12 +1,12 @@ -#include "assets.h" - -static asset assets[] = { - { - .id = ASSET_DEBUG_TILE, - .kind = AKIND_TEXTURE, - }, - { - .id = ASSET_DEMO_ICEMAKER, - .kind = AKIND_TEXTURE, - } -}; +#include "assets.h" + +static asset assets[] = { + { + .id = ASSET_DEBUG_TILE, + .kind = AKIND_TEXTURE, + }, + { + .id = ASSET_DEMO_ICEMAKER, + .kind = AKIND_TEXTURE, + } +}; diff --git a/code/game/src/camera.c b/code/game/src/camera.c index a0a23dc..3dc4577 100644 --- a/code/game/src/camera.c +++ b/code/game/src/camera.c @@ -1,52 +1,52 @@ -#include "zpl.h" -#include "camera.h" -#include "platform.h" -#include "entity_view.h" -#include "game.h" - -#define CAMERA_LERP_FACTOR 11.2f - -static camera main_camera; - -void camera_reset(void) { - zpl_zero_item(&main_camera); - main_camera.mode = CAMERA_MODE_STATIONARY; - main_camera.first_time = true; -} - -void camera_update(void) { - switch (main_camera.mode) { - case CAMERA_MODE_FOLLOW: { - world_view *world = game_world_view_get_active(); - if (!world) break; - entity_view *view = entity_view_get(&world->entities, main_camera.ent_id); - if (!view) break; - float smooth_ms = zpl_clamp((float)platform_frametime(), 0.0f, 1.0f); - - main_camera.x = zpl_lerp(main_camera.x, view->x, CAMERA_LERP_FACTOR*smooth_ms); - main_camera.y = zpl_lerp(main_camera.y, view->y, CAMERA_LERP_FACTOR*smooth_ms); - - if (main_camera.first_time) { - main_camera.first_time = false; - main_camera.x = view->x; - main_camera.y = view->y; - } - }break; - - default: { - - }break; - } -} -void camera_set_follow(uint64_t ent_id) { - main_camera.mode = CAMERA_MODE_FOLLOW; - main_camera.ent_id = ent_id; -} -void camera_set_pos(double x, double y) { - main_camera.mode = CAMERA_MODE_STATIONARY; - main_camera.x = x; - main_camera.y = y; -} -camera camera_get(void) { - return main_camera; -} +#include "zpl.h" +#include "camera.h" +#include "platform.h" +#include "entity_view.h" +#include "game.h" + +#define CAMERA_LERP_FACTOR 11.2f + +static camera main_camera; + +void camera_reset(void) { + zpl_zero_item(&main_camera); + main_camera.mode = CAMERA_MODE_STATIONARY; + main_camera.first_time = true; +} + +void camera_update(void) { + switch (main_camera.mode) { + case CAMERA_MODE_FOLLOW: { + world_view *world = game_world_view_get_active(); + if (!world) break; + entity_view *view = entity_view_get(&world->entities, main_camera.ent_id); + if (!view) break; + float smooth_ms = zpl_clamp((float)platform_frametime(), 0.0f, 1.0f); + + main_camera.x = zpl_lerp(main_camera.x, view->x, CAMERA_LERP_FACTOR*smooth_ms); + main_camera.y = zpl_lerp(main_camera.y, view->y, CAMERA_LERP_FACTOR*smooth_ms); + + if (main_camera.first_time) { + main_camera.first_time = false; + main_camera.x = view->x; + main_camera.y = view->y; + } + }break; + + default: { + + }break; + } +} +void camera_set_follow(uint64_t ent_id) { + main_camera.mode = CAMERA_MODE_FOLLOW; + main_camera.ent_id = ent_id; +} +void camera_set_pos(double x, double y) { + main_camera.mode = CAMERA_MODE_STATIONARY; + main_camera.x = x; + main_camera.y = y; +} +camera camera_get(void) { + return main_camera; +} diff --git a/code/game/src/debug_ui.c b/code/game/src/debug_ui.c index d73f502..6f19c21 100644 --- a/code/game/src/debug_ui.c +++ b/code/game/src/debug_ui.c @@ -1,409 +1,409 @@ -#include "debug_ui.h" -#include "debug_draw.h" -#include "raylib.h" -#include "vehicle.h" -#include "camera.h" -#include "world/world.h" -#include "game.h" -#include "sfd.h" - -#include "modules/components.h" - -typedef enum { - DITEM_RAW, - DITEM_GAP, - DITEM_TEXT, - DITEM_BUTTON, - DITEM_SLIDER, - DITEM_LIST, - DITEM_COND, - DITEM_END, - - DITEM_FORCE_UINT8 = UINT8_MAX -} debug_kind; - -typedef struct { - float x, y; -} debug_draw_result; - -#define DBG_FONT_SIZE 22 -#define DBG_FONT_SPACING DBG_FONT_SIZE * 1.2f -#define DBG_START_XPOS 15 -#define DBG_START_YPOS 200 -#define DBG_LIST_XPOS_OFFSET 10 -#define DBG_SHADOW_OFFSET_XPOS 1 -#define DBG_SHADOW_OFFSET_YPOS 1 -#define DBG_CTRL_HANDLE_DIM 10 -#define DBG_GAP_HEIGHT DBG_FONT_SPACING * 0.5f - -static uint8_t is_shadow_rendered; -static uint8_t is_debug_open = 1; -static uint8_t is_handle_ctrl_held; -static float debug_xpos = DBG_START_XPOS; -static float debug_ypos = DBG_START_YPOS; - -typedef struct debug_item { - debug_kind kind; - char const *name; - float name_width; - uint8_t skip; - - union { - union { - char const *text; - uint64_t val; - }; - - struct { - struct debug_item *items; - uint8_t is_collapsed; - uint8_t is_sp_only; - } list; - - struct { - float val, min, max; - void (*on_change)(float); - } slider; - - void (*on_click)(void); - - uint8_t (*on_success)(void); - }; - - debug_draw_result (*proc)(struct debug_item*, float, float); -} debug_item; - -static void UIDrawText(const char *text, float posX, float posY, int fontSize, Color color); -static int UIMeasureText(const char *text, int fontSize); - -#include "debug_replay.c" - -#include "debug_ui_actions.c" -#include "debug_ui_widgets.c" - -static debug_item items[] = { - { - .kind = DITEM_LIST, - .name = "general", - .list = { - .items = (debug_item[]) { - { .kind = DITEM_TEXT, .name = "delta time", .proc = DrawDeltaTime }, - { .kind = DITEM_TEXT, .name = "pos", .proc = DrawCameraPos }, - { .kind = DITEM_TEXT, .name = "zoom", .proc = DrawZoom }, - { .kind = DITEM_END }, - } - } - }, - { - .kind = DITEM_LIST, - .name = "world simulation", - .list = { - .items = (debug_item[]) { - { .kind = DITEM_COND, .on_success = CondIsWorldRunning }, - { .kind = DITEM_BUTTON, .name = "pause", .on_click = ActWorldToggleSim }, - - { .kind = DITEM_COND, .on_success = CondIsWorldPaused, .skip = 6 }, - { .kind = DITEM_BUTTON, .name = "resume", .on_click = ActWorldToggleSim }, - - { .kind = DITEM_GAP }, - - { .kind = DITEM_TEXT, .name = "step size", .proc = DrawWorldStepSize }, - { .kind = DITEM_BUTTON, .name = "single-step", .on_click = ActWorldStep }, - { .kind = DITEM_BUTTON, .name = "increment step size", .on_click = ActWorldIncrementSimStepSize }, - { .kind = DITEM_BUTTON, .name = "decrement step size", .on_click = ActWorldDecrementSimStepSize }, - - { .kind = DITEM_END }, - }, - .is_sp_only = true, - } - }, - { - .kind = DITEM_LIST, - .name = "debug actions", - .list = { - .items = (debug_item[]) { - { .kind = DITEM_BUTTON, .name = "spawn car", .on_click = ActSpawnCar }, - { .kind = DITEM_BUTTON, .name = "place ice rink", .on_click = ActPlaceIceRink }, - { .kind = DITEM_BUTTON, .name = "erase world changes", .on_click = ActEraseWorldChanges }, - { .kind = DITEM_BUTTON, .name = "spawn circling driver", .on_click = ActSpawnCirclingDriver }, - { .kind = DITEM_BUTTON, .name = "spawn icemaker item", .on_click = ActSpawnIcemaker }, - { - .kind = DITEM_LIST, - .name = "demo npcs", - .list = { - .items = (debug_item[]) { - { .kind = DITEM_TEXT, .name = "npcs", .proc = DrawDemoNPCCount }, - { .kind = DITEM_BUTTON, .name = "spawn 1000 npcs", .on_click = ActSpawnDemoNPCs }, - { .kind = DITEM_BUTTON, .name = "remove all demo npcs", .on_click = ActDespawnDemoNPCs }, - { .kind = DITEM_END }, - }, - .is_collapsed = true - } - }, - { .kind = DITEM_END }, - }, - .is_sp_only = true, - } - }, - { - .kind = DITEM_LIST, - .name = "replay system", - .list = { - .items = (debug_item[]) { - { .kind = DITEM_TEXT, .name = "macro", .proc = DrawReplayFileName }, - { .kind = DITEM_TEXT, .name = "samples", .proc = DrawReplaySamples }, - - { .kind = DITEM_COND, .on_success = CondReplayDataPresentAndNotPlaying }, - { .kind = DITEM_BUTTON, .name = "replay", .on_click = ActReplayRun }, - - { .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 = CondReplayIsPlaying }, - { .kind = DITEM_BUTTON, .name = "stop", .on_click = ActReplayEnd }, - - { .kind = DITEM_COND, .on_success = CondReplayIsNotPlayingOrRecordsNotClear }, - { .kind = DITEM_BUTTON, .name = "clear", .on_click = ActReplayClear }, - - { .kind = DITEM_GAP }, - - { .kind = DITEM_COND, .on_success = CondReplayIsNotPlaying, .skip = 4 }, - { .kind = DITEM_BUTTON, .name = "new", .on_click = ActReplayNew }, - { .kind = DITEM_BUTTON, .name = "load", .on_click = ActReplayLoad }, - { .kind = DITEM_BUTTON, .name = "save", .on_click = ActReplaySave }, - { .kind = DITEM_BUTTON, .name = "save as...", .on_click = ActReplaySaveAs }, - - { .kind = DITEM_END }, - }, - .is_sp_only = true, - .is_collapsed = true, - } - }, - { - .kind = DITEM_LIST, - .name = "profilers", - .list = { - .items = (debug_item[]) { - { .kind = DITEM_RAW, .val = PROF_MAIN_LOOP, .proc = DrawProfilerDelta }, - { .kind = DITEM_TEXT, .name = "unmeasured time", .proc = DrawUnmeasuredTime }, - { .kind = DITEM_RAW, .val = PROF_WORLD_WRITE, .proc = DrawProfilerDelta }, - { .kind = DITEM_RAW, .val = PROF_RENDER, .proc = DrawProfilerDelta }, - { .kind = DITEM_RAW, .val = PROF_UPDATE_SYSTEMS, .proc = DrawProfilerDelta }, - { .kind = DITEM_RAW, .val = PROF_ENTITY_LERP, .proc = DrawProfilerDelta }, - { .kind = DITEM_RAW, .val = PROF_INTEGRATE_POS, .proc = DrawProfilerDelta }, - { .kind = DITEM_END }, - }, - .is_collapsed = 1 - } - }, - { - .kind = DITEM_BUTTON, - .name = "exit game", - .on_click = ActExitGame, - }, - {.kind = DITEM_END}, -}; - -debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool is_shadow) { - is_shadow_rendered = is_shadow; - for (debug_item *it = list; it->kind != DITEM_END; it += 1) { - switch (it->kind) { - case DITEM_GAP: { - ypos += DBG_GAP_HEIGHT; - }break; - case DITEM_COND: { - ZPL_ASSERT(it->on_success); - - if (!it->on_success()) { - it += it->skip ? it->skip : 1; - } - }break; - case DITEM_LIST: { - // NOTE(zaklaus): calculate and cache name width for future use - if (it->name_width == 0) { - it->name_width = UIMeasureText(it->name, DBG_FONT_SIZE); - } - Color color = RAYWHITE; - if (is_btn_pressed(xpos, ypos, it->name_width, DBG_FONT_SIZE, &color)) { - it->list.is_collapsed = !it->list.is_collapsed; - } - - UIDrawText(it->name, xpos, ypos, DBG_FONT_SIZE, color); - ypos += DBG_FONT_SPACING; - if (it->list.is_collapsed) break; - if (it->list.is_sp_only && game_get_kind() != GAMEKIND_SINGLE) break; - debug_draw_result res = debug_draw_list(it->list.items, xpos+DBG_LIST_XPOS_OFFSET, ypos, is_shadow); - ypos = res.y; - }break; - - case DITEM_TEXT: { - char const *text = TextFormat("%s: ", it->name); - if (it->name_width == 0) { - it->name_width = UIMeasureText(text, DBG_FONT_SIZE); - } - UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE); - ZPL_ASSERT(it->proc); - - debug_draw_result res = it->proc(it, xpos + it->name_width, ypos); - ypos = res.y; - }break; - - case DITEM_RAW: { - ZPL_ASSERT(it->proc); - - debug_draw_result res = it->proc(it, xpos, ypos); - ypos = res.y; - }break; - - case DITEM_BUTTON: { - 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) && 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; - - case DITEM_SLIDER: { - ZPL_ASSERT(it->slider.min != it->slider.max); - char const *text = TextFormat("%s: ", it->name); - if (it->name_width == 0) { - it->name_width = UIMeasureText(text, DBG_FONT_SIZE); - } - UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE); - xpos += it->name_width; - - DrawRectangleLines(xpos, ypos, 100.0f, DBG_FONT_SIZE, RAYWHITE); - - float stick_x = xpos + ((it->slider.val / it->slider.max) * 100.0f) - 5.0f; - DrawRectangle(stick_x, ypos, 10.0f, DBG_FONT_SIZE, RED); - - xpos += 100.0f + 5.0f; - DrawFloat(xpos, ypos, it->slider.val); - ypos += DBG_FONT_SPACING; - }break; - - default: { - - }break; - } - } - - return (debug_draw_result){xpos, ypos}; -} - -void debug_draw(void) { - // NOTE(zaklaus): Flush old debug samples - debug_draw_flush(); - - float xpos = debug_xpos; - float ypos = debug_ypos; - - // NOTE(zaklaus): move debug ui - { - debug_area_status area = check_mouse_area(xpos, ypos, DBG_CTRL_HANDLE_DIM, DBG_CTRL_HANDLE_DIM); - Color color = BLUE; - if (area == DAREA_HOVER) color = YELLOW; - if (area == DAREA_HELD) { - color = RED; - is_handle_ctrl_held = 1; - } - - if (is_handle_ctrl_held) { - debug_xpos = xpos = GetMouseX() - DBG_CTRL_HANDLE_DIM/2; - debug_ypos = ypos = GetMouseY() - DBG_CTRL_HANDLE_DIM/2; - - if (area == DAREA_PRESS) { - is_handle_ctrl_held = 0; - } - } - - DrawRectangle(xpos, ypos, DBG_CTRL_HANDLE_DIM, DBG_CTRL_HANDLE_DIM, color); - } - - // NOTE(zaklaus): toggle debug ui - { - Color color = BLUE; - debug_area_status area = check_mouse_area(xpos, 15+ypos, DBG_CTRL_HANDLE_DIM, DBG_CTRL_HANDLE_DIM); - if (area == DAREA_HOVER) color = YELLOW; - if (area == DAREA_HELD) { - color = RED; - } - if (area == DAREA_PRESS) { - is_debug_open = !is_debug_open; - } - DrawPoly((Vector2){xpos+DBG_CTRL_HANDLE_DIM/2, ypos+15+DBG_CTRL_HANDLE_DIM/2}, 3, 6.0f,is_debug_open ? 0.0f : 180.0f, color); - } - - if (is_debug_open) { - xpos += 15; - debug_draw_list(items, xpos+DBG_SHADOW_OFFSET_XPOS, ypos+DBG_SHADOW_OFFSET_YPOS, 1); // NOTE(zaklaus): draw shadow - debug_draw_list(items, xpos, ypos, 0); - } -} - -debug_area_status check_mouse_area(float xpos, float ypos, float w, float h) { - if (is_shadow_rendered) return DAREA_OUTSIDE; - bool is_inside = CheckCollisionPointRec(GetMousePosition(), (Rectangle){xpos, ypos, w, h}); - - if (is_inside) { - return IsMouseButtonReleased(MOUSE_LEFT_BUTTON) ? DAREA_PRESS : IsMouseButtonDown(MOUSE_LEFT_BUTTON) ? DAREA_HELD : DAREA_HOVER; - } - return DAREA_OUTSIDE; -} - -bool is_btn_pressed(float xpos, float ypos, float w, float h, Color *color) { - ZPL_ASSERT(color); - *color = RAYWHITE; - debug_area_status area = check_mouse_area(xpos, ypos, w, h); - if (area == DAREA_PRESS) { - *color = RED; - return true; - } else if (area == DAREA_HOVER) { - *color = YELLOW; - } else if (area == DAREA_HELD) { - *color = RED; - } - - return false; -} - -static inline -void UIDrawText(const char *text, float posX, float posY, int fontSize, Color color) { - // Check if default font has been loaded - if (GetFontDefault().texture.id != 0) { - Vector2 position = { (float)posX , (float)posY }; - - int defaultFontSize = 10; // Default Font chars height in pixel - int new_spacing = fontSize/defaultFontSize; - - DrawTextEx(GetFontDefault(), text, position, (float)fontSize , (float)new_spacing , is_shadow_rendered ? BLACK : color); - } -} - -static inline -int UIMeasureText(const char *text, int fontSize) { - Vector2 vec = { 0.0f, 0.0f }; - - // Check if default font has been loaded - if (GetFontDefault().texture.id != 0) { - int defaultFontSize = 10; // Default Font chars height in pixel - int new_spacing = fontSize/defaultFontSize; - - vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)new_spacing); - } - - return (int)vec.x; -} +#include "debug_ui.h" +#include "debug_draw.h" +#include "raylib.h" +#include "vehicle.h" +#include "camera.h" +#include "world/world.h" +#include "game.h" +#include "sfd.h" + +#include "modules/components.h" + +typedef enum { + DITEM_RAW, + DITEM_GAP, + DITEM_TEXT, + DITEM_BUTTON, + DITEM_SLIDER, + DITEM_LIST, + DITEM_COND, + DITEM_END, + + DITEM_FORCE_UINT8 = UINT8_MAX +} debug_kind; + +typedef struct { + float x, y; +} debug_draw_result; + +#define DBG_FONT_SIZE 22 +#define DBG_FONT_SPACING DBG_FONT_SIZE * 1.2f +#define DBG_START_XPOS 15 +#define DBG_START_YPOS 200 +#define DBG_LIST_XPOS_OFFSET 10 +#define DBG_SHADOW_OFFSET_XPOS 1 +#define DBG_SHADOW_OFFSET_YPOS 1 +#define DBG_CTRL_HANDLE_DIM 10 +#define DBG_GAP_HEIGHT DBG_FONT_SPACING * 0.5f + +static uint8_t is_shadow_rendered; +static uint8_t is_debug_open = 1; +static uint8_t is_handle_ctrl_held; +static float debug_xpos = DBG_START_XPOS; +static float debug_ypos = DBG_START_YPOS; + +typedef struct debug_item { + debug_kind kind; + char const *name; + float name_width; + uint8_t skip; + + union { + union { + char const *text; + uint64_t val; + }; + + struct { + struct debug_item *items; + uint8_t is_collapsed; + uint8_t is_sp_only; + } list; + + struct { + float val, min, max; + void (*on_change)(float); + } slider; + + void (*on_click)(void); + + uint8_t (*on_success)(void); + }; + + debug_draw_result (*proc)(struct debug_item*, float, float); +} debug_item; + +static void UIDrawText(const char *text, float posX, float posY, int fontSize, Color color); +static int UIMeasureText(const char *text, int fontSize); + +#include "debug_replay.c" + +#include "debug_ui_actions.c" +#include "debug_ui_widgets.c" + +static debug_item items[] = { + { + .kind = DITEM_LIST, + .name = "general", + .list = { + .items = (debug_item[]) { + { .kind = DITEM_TEXT, .name = "delta time", .proc = DrawDeltaTime }, + { .kind = DITEM_TEXT, .name = "pos", .proc = DrawCameraPos }, + { .kind = DITEM_TEXT, .name = "zoom", .proc = DrawZoom }, + { .kind = DITEM_END }, + } + } + }, + { + .kind = DITEM_LIST, + .name = "world simulation", + .list = { + .items = (debug_item[]) { + { .kind = DITEM_COND, .on_success = CondIsWorldRunning }, + { .kind = DITEM_BUTTON, .name = "pause", .on_click = ActWorldToggleSim }, + + { .kind = DITEM_COND, .on_success = CondIsWorldPaused, .skip = 6 }, + { .kind = DITEM_BUTTON, .name = "resume", .on_click = ActWorldToggleSim }, + + { .kind = DITEM_GAP }, + + { .kind = DITEM_TEXT, .name = "step size", .proc = DrawWorldStepSize }, + { .kind = DITEM_BUTTON, .name = "single-step", .on_click = ActWorldStep }, + { .kind = DITEM_BUTTON, .name = "increment step size", .on_click = ActWorldIncrementSimStepSize }, + { .kind = DITEM_BUTTON, .name = "decrement step size", .on_click = ActWorldDecrementSimStepSize }, + + { .kind = DITEM_END }, + }, + .is_sp_only = true, + } + }, + { + .kind = DITEM_LIST, + .name = "debug actions", + .list = { + .items = (debug_item[]) { + { .kind = DITEM_BUTTON, .name = "spawn car", .on_click = ActSpawnCar }, + { .kind = DITEM_BUTTON, .name = "place ice rink", .on_click = ActPlaceIceRink }, + { .kind = DITEM_BUTTON, .name = "erase world changes", .on_click = ActEraseWorldChanges }, + { .kind = DITEM_BUTTON, .name = "spawn circling driver", .on_click = ActSpawnCirclingDriver }, + { .kind = DITEM_BUTTON, .name = "spawn icemaker item", .on_click = ActSpawnIcemaker }, + { + .kind = DITEM_LIST, + .name = "demo npcs", + .list = { + .items = (debug_item[]) { + { .kind = DITEM_TEXT, .name = "npcs", .proc = DrawDemoNPCCount }, + { .kind = DITEM_BUTTON, .name = "spawn 1000 npcs", .on_click = ActSpawnDemoNPCs }, + { .kind = DITEM_BUTTON, .name = "remove all demo npcs", .on_click = ActDespawnDemoNPCs }, + { .kind = DITEM_END }, + }, + .is_collapsed = true + } + }, + { .kind = DITEM_END }, + }, + .is_sp_only = true, + } + }, + { + .kind = DITEM_LIST, + .name = "replay system", + .list = { + .items = (debug_item[]) { + { .kind = DITEM_TEXT, .name = "macro", .proc = DrawReplayFileName }, + { .kind = DITEM_TEXT, .name = "samples", .proc = DrawReplaySamples }, + + { .kind = DITEM_COND, .on_success = CondReplayDataPresentAndNotPlaying }, + { .kind = DITEM_BUTTON, .name = "replay", .on_click = ActReplayRun }, + + { .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 = CondReplayIsPlaying }, + { .kind = DITEM_BUTTON, .name = "stop", .on_click = ActReplayEnd }, + + { .kind = DITEM_COND, .on_success = CondReplayIsNotPlayingOrRecordsNotClear }, + { .kind = DITEM_BUTTON, .name = "clear", .on_click = ActReplayClear }, + + { .kind = DITEM_GAP }, + + { .kind = DITEM_COND, .on_success = CondReplayIsNotPlaying, .skip = 4 }, + { .kind = DITEM_BUTTON, .name = "new", .on_click = ActReplayNew }, + { .kind = DITEM_BUTTON, .name = "load", .on_click = ActReplayLoad }, + { .kind = DITEM_BUTTON, .name = "save", .on_click = ActReplaySave }, + { .kind = DITEM_BUTTON, .name = "save as...", .on_click = ActReplaySaveAs }, + + { .kind = DITEM_END }, + }, + .is_sp_only = true, + .is_collapsed = true, + } + }, + { + .kind = DITEM_LIST, + .name = "profilers", + .list = { + .items = (debug_item[]) { + { .kind = DITEM_RAW, .val = PROF_MAIN_LOOP, .proc = DrawProfilerDelta }, + { .kind = DITEM_TEXT, .name = "unmeasured time", .proc = DrawUnmeasuredTime }, + { .kind = DITEM_RAW, .val = PROF_WORLD_WRITE, .proc = DrawProfilerDelta }, + { .kind = DITEM_RAW, .val = PROF_RENDER, .proc = DrawProfilerDelta }, + { .kind = DITEM_RAW, .val = PROF_UPDATE_SYSTEMS, .proc = DrawProfilerDelta }, + { .kind = DITEM_RAW, .val = PROF_ENTITY_LERP, .proc = DrawProfilerDelta }, + { .kind = DITEM_RAW, .val = PROF_INTEGRATE_POS, .proc = DrawProfilerDelta }, + { .kind = DITEM_END }, + }, + .is_collapsed = 1 + } + }, + { + .kind = DITEM_BUTTON, + .name = "exit game", + .on_click = ActExitGame, + }, + {.kind = DITEM_END}, +}; + +debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool is_shadow) { + is_shadow_rendered = is_shadow; + for (debug_item *it = list; it->kind != DITEM_END; it += 1) { + switch (it->kind) { + case DITEM_GAP: { + ypos += DBG_GAP_HEIGHT; + }break; + case DITEM_COND: { + ZPL_ASSERT(it->on_success); + + if (!it->on_success()) { + it += it->skip ? it->skip : 1; + } + }break; + case DITEM_LIST: { + // NOTE(zaklaus): calculate and cache name width for future use + if (it->name_width == 0) { + it->name_width = UIMeasureText(it->name, DBG_FONT_SIZE); + } + Color color = RAYWHITE; + if (is_btn_pressed(xpos, ypos, it->name_width, DBG_FONT_SIZE, &color)) { + it->list.is_collapsed = !it->list.is_collapsed; + } + + UIDrawText(it->name, xpos, ypos, DBG_FONT_SIZE, color); + ypos += DBG_FONT_SPACING; + if (it->list.is_collapsed) break; + if (it->list.is_sp_only && game_get_kind() != GAMEKIND_SINGLE) break; + debug_draw_result res = debug_draw_list(it->list.items, xpos+DBG_LIST_XPOS_OFFSET, ypos, is_shadow); + ypos = res.y; + }break; + + case DITEM_TEXT: { + char const *text = TextFormat("%s: ", it->name); + if (it->name_width == 0) { + it->name_width = UIMeasureText(text, DBG_FONT_SIZE); + } + UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE); + ZPL_ASSERT(it->proc); + + debug_draw_result res = it->proc(it, xpos + it->name_width, ypos); + ypos = res.y; + }break; + + case DITEM_RAW: { + ZPL_ASSERT(it->proc); + + debug_draw_result res = it->proc(it, xpos, ypos); + ypos = res.y; + }break; + + case DITEM_BUTTON: { + 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) && 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; + + case DITEM_SLIDER: { + ZPL_ASSERT(it->slider.min != it->slider.max); + char const *text = TextFormat("%s: ", it->name); + if (it->name_width == 0) { + it->name_width = UIMeasureText(text, DBG_FONT_SIZE); + } + UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE); + xpos += it->name_width; + + DrawRectangleLines(xpos, ypos, 100.0f, DBG_FONT_SIZE, RAYWHITE); + + float stick_x = xpos + ((it->slider.val / it->slider.max) * 100.0f) - 5.0f; + DrawRectangle(stick_x, ypos, 10.0f, DBG_FONT_SIZE, RED); + + xpos += 100.0f + 5.0f; + DrawFloat(xpos, ypos, it->slider.val); + ypos += DBG_FONT_SPACING; + }break; + + default: { + + }break; + } + } + + return (debug_draw_result){xpos, ypos}; +} + +void debug_draw(void) { + // NOTE(zaklaus): Flush old debug samples + debug_draw_flush(); + + float xpos = debug_xpos; + float ypos = debug_ypos; + + // NOTE(zaklaus): move debug ui + { + debug_area_status area = check_mouse_area(xpos, ypos, DBG_CTRL_HANDLE_DIM, DBG_CTRL_HANDLE_DIM); + Color color = BLUE; + if (area == DAREA_HOVER) color = YELLOW; + if (area == DAREA_HELD) { + color = RED; + is_handle_ctrl_held = 1; + } + + if (is_handle_ctrl_held) { + debug_xpos = xpos = GetMouseX() - DBG_CTRL_HANDLE_DIM/2; + debug_ypos = ypos = GetMouseY() - DBG_CTRL_HANDLE_DIM/2; + + if (area == DAREA_PRESS) { + is_handle_ctrl_held = 0; + } + } + + DrawRectangle(xpos, ypos, DBG_CTRL_HANDLE_DIM, DBG_CTRL_HANDLE_DIM, color); + } + + // NOTE(zaklaus): toggle debug ui + { + Color color = BLUE; + debug_area_status area = check_mouse_area(xpos, 15+ypos, DBG_CTRL_HANDLE_DIM, DBG_CTRL_HANDLE_DIM); + if (area == DAREA_HOVER) color = YELLOW; + if (area == DAREA_HELD) { + color = RED; + } + if (area == DAREA_PRESS) { + is_debug_open = !is_debug_open; + } + DrawPoly((Vector2){xpos+DBG_CTRL_HANDLE_DIM/2, ypos+15+DBG_CTRL_HANDLE_DIM/2}, 3, 6.0f,is_debug_open ? 0.0f : 180.0f, color); + } + + if (is_debug_open) { + xpos += 15; + debug_draw_list(items, xpos+DBG_SHADOW_OFFSET_XPOS, ypos+DBG_SHADOW_OFFSET_YPOS, 1); // NOTE(zaklaus): draw shadow + debug_draw_list(items, xpos, ypos, 0); + } +} + +debug_area_status check_mouse_area(float xpos, float ypos, float w, float h) { + if (is_shadow_rendered) return DAREA_OUTSIDE; + bool is_inside = CheckCollisionPointRec(GetMousePosition(), (Rectangle){xpos, ypos, w, h}); + + if (is_inside) { + return IsMouseButtonReleased(MOUSE_LEFT_BUTTON) ? DAREA_PRESS : IsMouseButtonDown(MOUSE_LEFT_BUTTON) ? DAREA_HELD : DAREA_HOVER; + } + return DAREA_OUTSIDE; +} + +bool is_btn_pressed(float xpos, float ypos, float w, float h, Color *color) { + ZPL_ASSERT(color); + *color = RAYWHITE; + debug_area_status area = check_mouse_area(xpos, ypos, w, h); + if (area == DAREA_PRESS) { + *color = RED; + return true; + } else if (area == DAREA_HOVER) { + *color = YELLOW; + } else if (area == DAREA_HELD) { + *color = RED; + } + + return false; +} + +static inline +void UIDrawText(const char *text, float posX, float posY, int fontSize, Color color) { + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) { + Vector2 position = { (float)posX , (float)posY }; + + int defaultFontSize = 10; // Default Font chars height in pixel + int new_spacing = fontSize/defaultFontSize; + + DrawTextEx(GetFontDefault(), text, position, (float)fontSize , (float)new_spacing , is_shadow_rendered ? BLACK : color); + } +} + +static inline +int UIMeasureText(const char *text, int fontSize) { + Vector2 vec = { 0.0f, 0.0f }; + + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) { + int defaultFontSize = 10; // Default Font chars height in pixel + int new_spacing = fontSize/defaultFontSize; + + vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)new_spacing); + } + + return (int)vec.x; +} diff --git a/code/game/src/debug_ui_actions.c b/code/game/src/debug_ui_actions.c index 6a8c105..1f4d276 100644 --- a/code/game/src/debug_ui_actions.c +++ b/code/game/src/debug_ui_actions.c @@ -1,272 +1,272 @@ -#include "debug_ui.h" -#include "world/blocks.h" -#include "items.h" - -void -ActExitGame(void) { - game_request_close(); -} - -void -ActSpawnCar(void) { - ecs_entity_t e = vehicle_spawn(); - ecs_entity_t plr = camera_get().ent_id; - - Position const* origin = ecs_get(world_ecs(), plr, Position); - Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL); - *dest = *origin; - - debug_replay_special_action(RPKIND_SPAWN_CAR); -} - -void -ActSpawnIcemaker(void) { - ecs_entity_t e = item_spawn(IKIND_DEMO_ICEMAKER, 32); - ecs_entity_t plr = camera_get().ent_id; - - Position const* origin = ecs_get(world_ecs(), plr, Position); - Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL); - *dest = *origin; - - debug_replay_special_action(RPKIND_SPAWN_ICEMAKER_ITEM); -} - -void -ActSpawnCirclingDriver(void) { - ecs_entity_t plr = camera_get().ent_id; - ecs_entity_t ve = vehicle_spawn(); - ecs_entity_t e = entity_spawn(EKIND_DEMO_NPC); - - Position const *origin = ecs_get(world_ecs(), plr, Position); - Position *veh_dest = ecs_get_mut(world_ecs(), ve, Position, NULL); - Position *dest = ecs_get_mut(world_ecs(), e, Position, NULL); - *veh_dest = *origin; - *dest = *origin; - - Input *input = ecs_get_mut(world_ecs(), e, Input, NULL); - zpl_zero_item(input); - input->x = input->y = 1.0f; - - Vehicle *veh = ecs_get_mut(world_ecs(), ve, Vehicle, NULL); - veh->seats[0] = e; - - ecs_set(world_ecs(), e, IsInVehicle, { .veh = ve }); - - debug_replay_special_action(RPKIND_SPAWN_CIRCLING_DRIVER); -} - -void -ActPlaceIceRink(void) { - ecs_entity_t plr = camera_get().ent_id; - uint8_t watr_id = blocks_find(BLOCK_BIOME_DEV, BLOCK_KIND_WATER); - Position const *p = ecs_get(world_ecs(), plr, Position); - float const bs = WORLD_BLOCK_SIZE; - - for (int y = 0; y < 100; y++) { - for (int x = 0; x < 100; x++) { - world_block_lookup l = world_block_from_realpos((p->x - (x*bs)/2.0f), p->y - (y*bs)/2.0f); - world_chunk_replace_block(l.chunk_id, l.id, watr_id); - } - } - - debug_replay_special_action(RPKIND_PLACE_ICE_RINK); -} - -void -ActEraseWorldChanges(void) { - ecs_entity_t plr = camera_get().ent_id; - Position const *p = ecs_get(world_ecs(), plr, Position); - float const bs = WORLD_BLOCK_SIZE; - - for (int y = 0; y < 100; y++) { - for (int x = 0; x < 100; x++) { - world_block_lookup l = world_block_from_realpos((p->x - (x*bs)/2.0f), p->y - (y*bs)/2.0f); - world_chunk_place_block(l.chunk_id, l.id, 0); - } - } - - debug_replay_special_action(RPKIND_PLACE_ERASE_CHANGES); -} - -// NOTE(zaklaus): Replay system - -uint8_t -CondReplayStatusOn(void) { - return is_recording && !is_playing; -} - -uint8_t -CondReplayStatusOff(void) { - return !is_recording && !is_playing; -} - -uint8_t -CondReplayDataPresentAndNotPlaying(void) { - return records != NULL && !is_recording && !is_playing; -} - -uint8_t -CondReplayIsPlaying(void) { - return records != NULL && !is_recording && is_playing; -} - -uint8_t -CondReplayIsNotPlaying(void) { - return !is_recording && !is_playing; -} - -uint8_t -CondReplayIsNotPlayingOrRecordsNotClear(void) { - return records != NULL && !is_recording && !is_playing; -} - -void -ActReplayBegin(void) { - debug_replay_start(); -} - -void -ActReplayEnd(void) { - debug_replay_stop(); -} - -void -ActReplayRun(void) { - debug_replay_run(); -} - -void -ActReplayClear(void) { - debug_replay_clear(); -} - - -void -ActReplayNew(void) { - debug_replay_clear(); - zpl_zero_size(replay_filename, sizeof(replay_filename)); -} - -void -ActReplaySaveAs(void) { - if (!records) return; - char const *workdir = GetWorkingDirectory(); - - sfd_Options sfd = { - .title = "Save Macro", - .path = "art", - .filter_name = "eco2d Macro", - .filter = "*.dem", - }; - - char const *path = sfd_save_dialog(&sfd); - ChangeDirectory(workdir); - - if (path) { - zpl_strcpy(replay_filename, zpl_bprintf("%s.dem", path)); - debug_replay_store(); - } - -} - -void -ActReplaySave(void) { - if (!replay_filename[0]) { - ActReplaySaveAs(); - } - else debug_replay_store(); -} - -void -ActReplayLoad(void) { - char const *workdir = GetWorkingDirectory(); - - sfd_Options sfd = { - .title = "Load Macro", - .path = "art", - .filter_name = "eco2d Macro", - .filter = "*.dem", - }; - - char const *path = sfd_open_dialog(&sfd); - ChangeDirectory(workdir); - - if (path) { - zpl_zero_size(replay_filename, sizeof(replay_filename)); - zpl_strcpy(replay_filename, path); - debug_replay_clear(); - debug_replay_load(); - } -} - -// NOTE(zaklaus): Demo NPCs -static ecs_entity_t *demo_npcs = NULL; - -void -ActSpawnDemoNPCs(void) { - if (!demo_npcs) zpl_array_init(demo_npcs, zpl_heap()); - if (zpl_array_count(demo_npcs) >= 100000) return; - - for (uint32_t i = 0; i < 1000; i++) { - uint64_t e = entity_spawn(EKIND_DEMO_NPC); - ecs_add(world_ecs(), e, EcsDemoNPC); - Position *pos = ecs_get_mut(world_ecs(), e, Position, NULL); - pos->x=rand() % world_dim(); - pos->y=rand() % world_dim(); - - Velocity *v = ecs_get_mut(world_ecs(), e, Velocity, NULL); - v->x = (rand()%3-1) * 10; - v->y = (rand()%3-1) * 10; - - zpl_array_append(demo_npcs, e); - } -} - -void -ActDespawnDemoNPCs(void) { - if (!demo_npcs) return; - - entity_batch_despawn(demo_npcs, zpl_array_count(demo_npcs)); - - zpl_array_free(demo_npcs); - demo_npcs = 0; -} - -// NOTE(zaklaus): world simulation controls -#define WORLDSIM_STEPPING 0.01f -static float sim_step_size = 0.1f; - -void -ActWorldToggleSim(void) { - if (world_is_paused()) { - world_resume(); - } else { - world_pause(); - } -} - -void -ActWorldIncrementSimStepSize(void) { - sim_step_size += WORLDSIM_STEPPING; -} - -void -ActWorldDecrementSimStepSize(void) { - if (sim_step_size > WORLDSIM_STEPPING) - sim_step_size -= WORLDSIM_STEPPING; -} - -void -ActWorldStep(void) { - world_step(sim_step_size); -} - -uint8_t -CondIsWorldPaused(void) { - return world_is_paused(); -} - -uint8_t -CondIsWorldRunning(void) { - return !world_is_paused(); -} +#include "debug_ui.h" +#include "world/blocks.h" +#include "items.h" + +void +ActExitGame(void) { + game_request_close(); +} + +void +ActSpawnCar(void) { + ecs_entity_t e = vehicle_spawn(); + ecs_entity_t plr = camera_get().ent_id; + + Position const* origin = ecs_get(world_ecs(), plr, Position); + Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL); + *dest = *origin; + + debug_replay_special_action(RPKIND_SPAWN_CAR); +} + +void +ActSpawnIcemaker(void) { + ecs_entity_t e = item_spawn(IKIND_DEMO_ICEMAKER, 32); + ecs_entity_t plr = camera_get().ent_id; + + Position const* origin = ecs_get(world_ecs(), plr, Position); + Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL); + *dest = *origin; + + debug_replay_special_action(RPKIND_SPAWN_ICEMAKER_ITEM); +} + +void +ActSpawnCirclingDriver(void) { + ecs_entity_t plr = camera_get().ent_id; + ecs_entity_t ve = vehicle_spawn(); + ecs_entity_t e = entity_spawn(EKIND_DEMO_NPC); + + Position const *origin = ecs_get(world_ecs(), plr, Position); + Position *veh_dest = ecs_get_mut(world_ecs(), ve, Position, NULL); + Position *dest = ecs_get_mut(world_ecs(), e, Position, NULL); + *veh_dest = *origin; + *dest = *origin; + + Input *input = ecs_get_mut(world_ecs(), e, Input, NULL); + zpl_zero_item(input); + input->x = input->y = 1.0f; + + Vehicle *veh = ecs_get_mut(world_ecs(), ve, Vehicle, NULL); + veh->seats[0] = e; + + ecs_set(world_ecs(), e, IsInVehicle, { .veh = ve }); + + debug_replay_special_action(RPKIND_SPAWN_CIRCLING_DRIVER); +} + +void +ActPlaceIceRink(void) { + ecs_entity_t plr = camera_get().ent_id; + uint8_t watr_id = blocks_find(BLOCK_BIOME_DEV, BLOCK_KIND_WATER); + Position const *p = ecs_get(world_ecs(), plr, Position); + float const bs = WORLD_BLOCK_SIZE; + + for (int y = 0; y < 100; y++) { + for (int x = 0; x < 100; x++) { + world_block_lookup l = world_block_from_realpos((p->x - (x*bs)/2.0f), p->y - (y*bs)/2.0f); + world_chunk_replace_block(l.chunk_id, l.id, watr_id); + } + } + + debug_replay_special_action(RPKIND_PLACE_ICE_RINK); +} + +void +ActEraseWorldChanges(void) { + ecs_entity_t plr = camera_get().ent_id; + Position const *p = ecs_get(world_ecs(), plr, Position); + float const bs = WORLD_BLOCK_SIZE; + + for (int y = 0; y < 100; y++) { + for (int x = 0; x < 100; x++) { + world_block_lookup l = world_block_from_realpos((p->x - (x*bs)/2.0f), p->y - (y*bs)/2.0f); + world_chunk_place_block(l.chunk_id, l.id, 0); + } + } + + debug_replay_special_action(RPKIND_PLACE_ERASE_CHANGES); +} + +// NOTE(zaklaus): Replay system + +uint8_t +CondReplayStatusOn(void) { + return is_recording && !is_playing; +} + +uint8_t +CondReplayStatusOff(void) { + return !is_recording && !is_playing; +} + +uint8_t +CondReplayDataPresentAndNotPlaying(void) { + return records != NULL && !is_recording && !is_playing; +} + +uint8_t +CondReplayIsPlaying(void) { + return records != NULL && !is_recording && is_playing; +} + +uint8_t +CondReplayIsNotPlaying(void) { + return !is_recording && !is_playing; +} + +uint8_t +CondReplayIsNotPlayingOrRecordsNotClear(void) { + return records != NULL && !is_recording && !is_playing; +} + +void +ActReplayBegin(void) { + debug_replay_start(); +} + +void +ActReplayEnd(void) { + debug_replay_stop(); +} + +void +ActReplayRun(void) { + debug_replay_run(); +} + +void +ActReplayClear(void) { + debug_replay_clear(); +} + + +void +ActReplayNew(void) { + debug_replay_clear(); + zpl_zero_size(replay_filename, sizeof(replay_filename)); +} + +void +ActReplaySaveAs(void) { + if (!records) return; + char const *workdir = GetWorkingDirectory(); + + sfd_Options sfd = { + .title = "Save Macro", + .path = "art", + .filter_name = "eco2d Macro", + .filter = "*.dem", + }; + + char const *path = sfd_save_dialog(&sfd); + ChangeDirectory(workdir); + + if (path) { + zpl_strcpy(replay_filename, zpl_bprintf("%s.dem", path)); + debug_replay_store(); + } + +} + +void +ActReplaySave(void) { + if (!replay_filename[0]) { + ActReplaySaveAs(); + } + else debug_replay_store(); +} + +void +ActReplayLoad(void) { + char const *workdir = GetWorkingDirectory(); + + sfd_Options sfd = { + .title = "Load Macro", + .path = "art", + .filter_name = "eco2d Macro", + .filter = "*.dem", + }; + + char const *path = sfd_open_dialog(&sfd); + ChangeDirectory(workdir); + + if (path) { + zpl_zero_size(replay_filename, sizeof(replay_filename)); + zpl_strcpy(replay_filename, path); + debug_replay_clear(); + debug_replay_load(); + } +} + +// NOTE(zaklaus): Demo NPCs +static ecs_entity_t *demo_npcs = NULL; + +void +ActSpawnDemoNPCs(void) { + if (!demo_npcs) zpl_array_init(demo_npcs, zpl_heap()); + if (zpl_array_count(demo_npcs) >= 100000) return; + + for (uint32_t i = 0; i < 1000; i++) { + uint64_t e = entity_spawn(EKIND_DEMO_NPC); + ecs_add(world_ecs(), e, EcsDemoNPC); + Position *pos = ecs_get_mut(world_ecs(), e, Position, NULL); + pos->x=rand() % world_dim(); + pos->y=rand() % world_dim(); + + Velocity *v = ecs_get_mut(world_ecs(), e, Velocity, NULL); + v->x = (rand()%3-1) * 10; + v->y = (rand()%3-1) * 10; + + zpl_array_append(demo_npcs, e); + } +} + +void +ActDespawnDemoNPCs(void) { + if (!demo_npcs) return; + + entity_batch_despawn(demo_npcs, zpl_array_count(demo_npcs)); + + zpl_array_free(demo_npcs); + demo_npcs = 0; +} + +// NOTE(zaklaus): world simulation controls +#define WORLDSIM_STEPPING 0.01f +static float sim_step_size = 0.1f; + +void +ActWorldToggleSim(void) { + if (world_is_paused()) { + world_resume(); + } else { + world_pause(); + } +} + +void +ActWorldIncrementSimStepSize(void) { + sim_step_size += WORLDSIM_STEPPING; +} + +void +ActWorldDecrementSimStepSize(void) { + if (sim_step_size > WORLDSIM_STEPPING) + sim_step_size -= WORLDSIM_STEPPING; +} + +void +ActWorldStep(void) { + world_step(sim_step_size); +} + +uint8_t +CondIsWorldPaused(void) { + return world_is_paused(); +} + +uint8_t +CondIsWorldRunning(void) { + return !world_is_paused(); +} diff --git a/code/game/src/debug_ui_widgets.c b/code/game/src/debug_ui_widgets.c index c59a922..78b2b7f 100644 --- a/code/game/src/debug_ui_widgets.c +++ b/code/game/src/debug_ui_widgets.c @@ -1,101 +1,101 @@ -#include "debug_ui.h" -#include "raylib.h" -#include "platform.h" -#include "profiler.h" - -//~ NOTE(zaklaus): helpers - -static inline debug_draw_result -DrawFloat(float xpos, float ypos, float val) { - char const *text = TextFormat("%.02f\n", val); - UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE); - return (debug_draw_result){.x = xpos + UIMeasureText(text, DBG_FONT_SIZE), .y = ypos + DBG_FONT_SPACING}; -} - -static inline debug_draw_result -DrawColoredText(float xpos, float ypos, char const *text, Color color) { - ZPL_ASSERT(text); - UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, color); - return (debug_draw_result){.x = xpos + UIMeasureText(text, DBG_FONT_SIZE), .y = ypos + DBG_FONT_SPACING}; -} - -static inline debug_draw_result -DrawFormattedText(float xpos, float ypos, char const *text) { - return DrawColoredText(xpos, ypos, text, RAYWHITE); -} - -//~ NOTE(zaklaus): widgets - -static inline debug_draw_result -DrawCameraPos(debug_item *it, float xpos, float ypos) { - (void)it; - camera cam = camera_get(); - return DrawFormattedText(xpos, ypos, TextFormat("%d %d", (int)(cam.x/WORLD_BLOCK_SIZE), (int)(cam.y/WORLD_BLOCK_SIZE))); -} - -static inline debug_draw_result -DrawUnmeasuredTime(debug_item *it, float xpos, float ypos) { - (void)it; - float total_time = profiler_delta(PROF_TOTAL_TIME); - float acc_time = profiler_delta(PROF_MAIN_LOOP); - - return DrawFormattedText(xpos, ypos, TextFormat("%.02f ms", (total_time-acc_time) * 1000.0f)); -} - -static inline debug_draw_result -DrawDeltaTime(debug_item *it, float xpos, float ypos) { - (void)it; - float dt = GetFrameTime(); - return DrawFormattedText(xpos, ypos, TextFormat("%.02f (%.02f fps)", dt * 1000.0f, 1.0f/dt)); -} - -static inline debug_draw_result -DrawZoom(debug_item *it, float xpos, float ypos) { - (void)it; - - return DrawFloat(xpos, ypos, platform_zoom_get()); -} - -static inline debug_draw_result -DrawLiteral(debug_item *it, float xpos, float ypos) { - ZPL_ASSERT(it->text); - return DrawFormattedText(xpos, ypos, it->text); -} - -static inline debug_draw_result -DrawProfilerDelta(debug_item *it, float xpos, float ypos) { - float dt = profiler_delta(it->val); - return DrawFormattedText(xpos, ypos, TextFormat("%s: %.02f ms", profiler_name(it->val), dt * 1000.0f)); -} - -static inline debug_draw_result -DrawReplaySamples(debug_item *it, float xpos, float ypos) { - (void)it; - size_t cnt = 0; - if (records) { - cnt = zpl_array_count(records); - } - return DrawFormattedText(xpos, ypos, TextFormat("%d of %d", record_pos, cnt)); -} - -static inline debug_draw_result -DrawReplayFileName(debug_item *it, float xpos, float ypos) { - (void)it; - return DrawFormattedText(xpos, ypos, TextFormat("%s", replay_filename[0] ? replay_filename : "")); -} - -// NOTE(zaklaus): demo npcs - -static inline debug_draw_result -DrawDemoNPCCount(debug_item *it, float xpos, float ypos) { - (void)it; - return DrawFormattedText(xpos, ypos, TextFormat("%d", demo_npcs ? zpl_array_count(demo_npcs) : 0)); -} - - -// NOTE(zaklaus): world simulation -static inline debug_draw_result -DrawWorldStepSize(debug_item *it, float xpos, float ypos) { - (void)it; - return DrawFormattedText(xpos, ypos, TextFormat("%d ms", (int16_t)(sim_step_size*1000.f))); -} +#include "debug_ui.h" +#include "raylib.h" +#include "platform.h" +#include "profiler.h" + +//~ NOTE(zaklaus): helpers + +static inline debug_draw_result +DrawFloat(float xpos, float ypos, float val) { + char const *text = TextFormat("%.02f\n", val); + UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, RAYWHITE); + return (debug_draw_result){.x = xpos + UIMeasureText(text, DBG_FONT_SIZE), .y = ypos + DBG_FONT_SPACING}; +} + +static inline debug_draw_result +DrawColoredText(float xpos, float ypos, char const *text, Color color) { + ZPL_ASSERT(text); + UIDrawText(text, xpos, ypos, DBG_FONT_SIZE, color); + return (debug_draw_result){.x = xpos + UIMeasureText(text, DBG_FONT_SIZE), .y = ypos + DBG_FONT_SPACING}; +} + +static inline debug_draw_result +DrawFormattedText(float xpos, float ypos, char const *text) { + return DrawColoredText(xpos, ypos, text, RAYWHITE); +} + +//~ NOTE(zaklaus): widgets + +static inline debug_draw_result +DrawCameraPos(debug_item *it, float xpos, float ypos) { + (void)it; + camera cam = camera_get(); + return DrawFormattedText(xpos, ypos, TextFormat("%d %d", (int)(cam.x/WORLD_BLOCK_SIZE), (int)(cam.y/WORLD_BLOCK_SIZE))); +} + +static inline debug_draw_result +DrawUnmeasuredTime(debug_item *it, float xpos, float ypos) { + (void)it; + float total_time = profiler_delta(PROF_TOTAL_TIME); + float acc_time = profiler_delta(PROF_MAIN_LOOP); + + return DrawFormattedText(xpos, ypos, TextFormat("%.02f ms", (total_time-acc_time) * 1000.0f)); +} + +static inline debug_draw_result +DrawDeltaTime(debug_item *it, float xpos, float ypos) { + (void)it; + float dt = GetFrameTime(); + return DrawFormattedText(xpos, ypos, TextFormat("%.02f (%.02f fps)", dt * 1000.0f, 1.0f/dt)); +} + +static inline debug_draw_result +DrawZoom(debug_item *it, float xpos, float ypos) { + (void)it; + + return DrawFloat(xpos, ypos, platform_zoom_get()); +} + +static inline debug_draw_result +DrawLiteral(debug_item *it, float xpos, float ypos) { + ZPL_ASSERT(it->text); + return DrawFormattedText(xpos, ypos, it->text); +} + +static inline debug_draw_result +DrawProfilerDelta(debug_item *it, float xpos, float ypos) { + float dt = profiler_delta(it->val); + return DrawFormattedText(xpos, ypos, TextFormat("%s: %.02f ms", profiler_name(it->val), dt * 1000.0f)); +} + +static inline debug_draw_result +DrawReplaySamples(debug_item *it, float xpos, float ypos) { + (void)it; + size_t cnt = 0; + if (records) { + cnt = zpl_array_count(records); + } + return DrawFormattedText(xpos, ypos, TextFormat("%d of %d", record_pos, cnt)); +} + +static inline debug_draw_result +DrawReplayFileName(debug_item *it, float xpos, float ypos) { + (void)it; + return DrawFormattedText(xpos, ypos, TextFormat("%s", replay_filename[0] ? replay_filename : "")); +} + +// NOTE(zaklaus): demo npcs + +static inline debug_draw_result +DrawDemoNPCCount(debug_item *it, float xpos, float ypos) { + (void)it; + return DrawFormattedText(xpos, ypos, TextFormat("%d", demo_npcs ? zpl_array_count(demo_npcs) : 0)); +} + + +// NOTE(zaklaus): world simulation +static inline debug_draw_result +DrawWorldStepSize(debug_item *it, float xpos, float ypos) { + (void)it; + return DrawFormattedText(xpos, ypos, TextFormat("%d ms", (int16_t)(sim_step_size*1000.f))); +} diff --git a/code/game/src/entity.h b/code/game/src/entity.h index 290003d..5030ee1 100644 --- a/code/game/src/entity.h +++ b/code/game/src/entity.h @@ -1,6 +1,6 @@ -#pragma once -#include "system.h" - -uint64_t entity_spawn(uint16_t class_id /* 0 = no streaming */); -void entity_batch_despawn(uint64_t *ids, size_t num_ids); -void entity_despawn(uint64_t ent_id); +#pragma once +#include "system.h" + +uint64_t entity_spawn(uint16_t class_id /* 0 = no streaming */); +void entity_batch_despawn(uint64_t *ids, size_t num_ids); +void entity_despawn(uint64_t ent_id); diff --git a/code/game/src/game.c b/code/game/src/game.c index 670016d..50e4b30 100644 --- a/code/game/src/game.c +++ b/code/game/src/game.c @@ -1,233 +1,233 @@ -#include "game.h" -#include "zpl.h" -#include "platform.h" -#include "world/world.h" -#include "packet.h" -#include "signal_handling.h" -#include "network.h" -#include "entity.h" -#include "world_view.h" -#include "entity_view.h" -#include "camera.h" -#include "profiler.h" - -#include "flecs/flecs.h" -//#include "flecs/flecs_dash.h" -//#include "flecs/flecs_systems_civetweb.h" -#include "flecs/flecs_os_api_stdcpp.h" - -#include "modules/components.h" -#include "modules/systems.h" - -#include "packets/pkt_00_init.h" -#include "packets/pkt_01_welcome.h" - -static uint8_t game_mode; - -static world_view *world_viewers; -static world_view *active_viewer; - -static WORLD_PKT_READER(pkt_reader) { - pkt_header header = {0}; - uint32_t ok = pkt_header_decode(&header, data, datalen); - header.udata = udata; - - if (ok && header.ok) { - return pkt_handlers[header.id].handler(&header) >= 0; - } else { - zpl_printf("[warn] unknown packet id %d (header %d data %d)\n", header.id, ok, header.ok); - } - return -1; -} - -static WORLD_PKT_WRITER(sp_pkt_writer) { - (void)udata; - return world_read(pkt->data, pkt->datalen, (void*)game_world_view_get_active()->owner_id); -} - -static WORLD_PKT_WRITER(mp_pkt_writer) { - if (pkt->is_reliable) { - return network_msg_send(udata, pkt->data, pkt->datalen, pkt->channel_id); - } - else { - return network_msg_send_unreliable(udata, pkt->data, pkt->datalen, pkt->channel_id); - } -} - -static WORLD_PKT_WRITER(mp_cli_pkt_writer) { - (void)udata; - if (pkt->is_reliable) { - return network_msg_send(0, pkt->data, pkt->datalen, pkt->channel_id); - } - else { - return network_msg_send_unreliable(0, pkt->data, pkt->datalen, pkt->channel_id); - } -} - -void world_viewers_init(uint32_t num_viewers) { - zpl_buffer_init(world_viewers, zpl_heap(), num_viewers); - - for (uint32_t i = 0; i < num_viewers; i++) { - zpl_buffer_append(world_viewers, world_view_create(i)); - } -} - -void world_viewers_destroy() { - for (uint32_t i = 0; i < zpl_buffer_count(world_viewers); i++) { - world_view_destroy(&world_viewers[i]); - } - zpl_buffer_free(world_viewers); -} - -world_view *game_world_view_get(uint16_t idx) { - return &world_viewers[idx]; -} - -world_view *game_world_view_get_active(void) { - return active_viewer; -} - -void game_world_view_cycle_active(int8_t dir) { - uint16_t idx = (uint16_t)(active_viewer - world_viewers); - game_world_view_set_active_by_idx(zpl_max(0, (idx+dir)%zpl_buffer_count(world_viewers))); -} -void game_world_view_set_active_by_idx(uint16_t idx) { - ZPL_ASSERT(idx >= 0 && idx < zpl_buffer_count(world_viewers)); - game_world_view_set_active(&world_viewers[idx]); -} - -void game_world_view_active_entity_map(void (*map_proc)(uint64_t key, entity_view * value)) { - entity_view_map(&active_viewer->entities, map_proc); -} - -entity_view *game_world_view_active_get_entity(uint64_t ent_id) { - return entity_view_get(&active_viewer->entities, ent_id); -} - -void game_world_view_set_active(world_view *view) { - active_viewer = view; - camera_set_follow(view->owner_id); -} - -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() { - return zpl_time_rel(); -} - -void game_init(game_kind play_mode, uint32_t num_viewers, int32_t seed, uint16_t chunk_size, uint16_t chunk_amount, int8_t is_dash_enabled) { - game_mode = play_mode; - - if (game_mode != GAMEKIND_HEADLESS) { - platform_init(); - - world_viewers_init(num_viewers); - active_viewer = &world_viewers[0]; - camera_reset(); - } - - if (game_mode != GAMEKIND_SINGLE) { - network_init(); - } - - if (game_mode == GAMEKIND_CLIENT) { - world_setup_pkt_handlers(pkt_reader, mp_cli_pkt_writer); -#ifndef _DEBUG - network_client_connect("lab.zakto.pw", 27000); -#else - network_client_connect("127.0.0.1", 27000); -#endif - } else { - stdcpp_set_os_api(); - world_setup_pkt_handlers(pkt_reader, game_mode == GAMEKIND_SINGLE ? sp_pkt_writer : mp_pkt_writer); - world_init(seed, chunk_size, chunk_amount); - if (is_dash_enabled) flecs_dash_init(); - - if (game_mode == GAMEKIND_HEADLESS) { - network_server_start(0, 27000); - ecs_set_target_fps(world_ecs(), 60); - } - } - - if (game_mode == GAMEKIND_SINGLE) { - for (uint32_t i = 0; i < num_viewers; i++) { - pkt_00_init_send(i); - } - } -} - -int8_t game_is_networked() { - return game_mode != GAMEKIND_SINGLE; -} - -void game_shutdown() { - - if (game_mode == GAMEKIND_CLIENT) { - network_client_disconnect(); - } else { - world_destroy(); - - if (game_mode == GAMEKIND_HEADLESS) { - network_server_stop(); - } - } - - if (game_mode != GAMEKIND_SINGLE) { - network_destroy(); - } - - if (game_mode != GAMEKIND_HEADLESS) { - world_viewers_destroy(); - - // TODO(zaklaus): crashes on exit - //platform_shutdown(); - } -} - -uint8_t game_is_running() { - return game_mode == GAMEKIND_HEADLESS || platform_is_running(); -} - -game_kind game_get_kind(void) { - return game_mode; -} - -void game_input() { - if (game_mode != GAMEKIND_HEADLESS) { - platform_input(); - } -} - -void game_update() { - if (game_mode == GAMEKIND_CLIENT) { - network_client_tick(); - } - else { - world_update(); - - if (game_mode == GAMEKIND_HEADLESS) { - network_server_tick(); - } - } -} - -void game_render() { - if (game_mode != GAMEKIND_HEADLESS) { - platform_render(); - } -} - -void game_action_send_keystate(game_keystate_data *data) { - pkt_send_keystate_send(active_viewer->view_id, data); -} - -void game_request_close() { - platform_request_close(); +#include "game.h" +#include "zpl.h" +#include "platform.h" +#include "world/world.h" +#include "packet.h" +#include "signal_handling.h" +#include "network.h" +#include "entity.h" +#include "world_view.h" +#include "entity_view.h" +#include "camera.h" +#include "profiler.h" + +#include "flecs/flecs.h" +//#include "flecs/flecs_dash.h" +//#include "flecs/flecs_systems_civetweb.h" +#include "flecs/flecs_os_api_stdcpp.h" + +#include "modules/components.h" +#include "modules/systems.h" + +#include "packets/pkt_00_init.h" +#include "packets/pkt_01_welcome.h" + +static uint8_t game_mode; + +static world_view *world_viewers; +static world_view *active_viewer; + +static WORLD_PKT_READER(pkt_reader) { + pkt_header header = {0}; + uint32_t ok = pkt_header_decode(&header, data, datalen); + header.udata = udata; + + if (ok && header.ok) { + return pkt_handlers[header.id].handler(&header) >= 0; + } else { + zpl_printf("[warn] unknown packet id %d (header %d data %d)\n", header.id, ok, header.ok); + } + return -1; +} + +static WORLD_PKT_WRITER(sp_pkt_writer) { + (void)udata; + return world_read(pkt->data, pkt->datalen, (void*)game_world_view_get_active()->owner_id); +} + +static WORLD_PKT_WRITER(mp_pkt_writer) { + if (pkt->is_reliable) { + return network_msg_send(udata, pkt->data, pkt->datalen, pkt->channel_id); + } + else { + return network_msg_send_unreliable(udata, pkt->data, pkt->datalen, pkt->channel_id); + } +} + +static WORLD_PKT_WRITER(mp_cli_pkt_writer) { + (void)udata; + if (pkt->is_reliable) { + return network_msg_send(0, pkt->data, pkt->datalen, pkt->channel_id); + } + else { + return network_msg_send_unreliable(0, pkt->data, pkt->datalen, pkt->channel_id); + } +} + +void world_viewers_init(uint32_t num_viewers) { + zpl_buffer_init(world_viewers, zpl_heap(), num_viewers); + + for (uint32_t i = 0; i < num_viewers; i++) { + zpl_buffer_append(world_viewers, world_view_create(i)); + } +} + +void world_viewers_destroy() { + for (uint32_t i = 0; i < zpl_buffer_count(world_viewers); i++) { + world_view_destroy(&world_viewers[i]); + } + zpl_buffer_free(world_viewers); +} + +world_view *game_world_view_get(uint16_t idx) { + return &world_viewers[idx]; +} + +world_view *game_world_view_get_active(void) { + return active_viewer; +} + +void game_world_view_cycle_active(int8_t dir) { + uint16_t idx = (uint16_t)(active_viewer - world_viewers); + game_world_view_set_active_by_idx(zpl_max(0, (idx+dir)%zpl_buffer_count(world_viewers))); +} +void game_world_view_set_active_by_idx(uint16_t idx) { + ZPL_ASSERT(idx >= 0 && idx < zpl_buffer_count(world_viewers)); + game_world_view_set_active(&world_viewers[idx]); +} + +void game_world_view_active_entity_map(void (*map_proc)(uint64_t key, entity_view * value)) { + entity_view_map(&active_viewer->entities, map_proc); +} + +entity_view *game_world_view_active_get_entity(uint64_t ent_id) { + return entity_view_get(&active_viewer->entities, ent_id); +} + +void game_world_view_set_active(world_view *view) { + active_viewer = view; + camera_set_follow(view->owner_id); +} + +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() { + return zpl_time_rel(); +} + +void game_init(game_kind play_mode, uint32_t num_viewers, int32_t seed, uint16_t chunk_size, uint16_t chunk_amount, int8_t is_dash_enabled) { + game_mode = play_mode; + + if (game_mode != GAMEKIND_HEADLESS) { + platform_init(); + + world_viewers_init(num_viewers); + active_viewer = &world_viewers[0]; + camera_reset(); + } + + if (game_mode != GAMEKIND_SINGLE) { + network_init(); + } + + if (game_mode == GAMEKIND_CLIENT) { + world_setup_pkt_handlers(pkt_reader, mp_cli_pkt_writer); +#ifndef _DEBUG + network_client_connect("lab.zakto.pw", 27000); +#else + network_client_connect("127.0.0.1", 27000); +#endif + } else { + stdcpp_set_os_api(); + world_setup_pkt_handlers(pkt_reader, game_mode == GAMEKIND_SINGLE ? sp_pkt_writer : mp_pkt_writer); + world_init(seed, chunk_size, chunk_amount); + if (is_dash_enabled) flecs_dash_init(); + + if (game_mode == GAMEKIND_HEADLESS) { + network_server_start(0, 27000); + ecs_set_target_fps(world_ecs(), 60); + } + } + + if (game_mode == GAMEKIND_SINGLE) { + for (uint32_t i = 0; i < num_viewers; i++) { + pkt_00_init_send(i); + } + } +} + +int8_t game_is_networked() { + return game_mode != GAMEKIND_SINGLE; +} + +void game_shutdown() { + + if (game_mode == GAMEKIND_CLIENT) { + network_client_disconnect(); + } else { + world_destroy(); + + if (game_mode == GAMEKIND_HEADLESS) { + network_server_stop(); + } + } + + if (game_mode != GAMEKIND_SINGLE) { + network_destroy(); + } + + if (game_mode != GAMEKIND_HEADLESS) { + world_viewers_destroy(); + + // TODO(zaklaus): crashes on exit + //platform_shutdown(); + } +} + +uint8_t game_is_running() { + return game_mode == GAMEKIND_HEADLESS || platform_is_running(); +} + +game_kind game_get_kind(void) { + return game_mode; +} + +void game_input() { + if (game_mode != GAMEKIND_HEADLESS) { + platform_input(); + } +} + +void game_update() { + if (game_mode == GAMEKIND_CLIENT) { + network_client_tick(); + } + else { + world_update(); + + if (game_mode == GAMEKIND_HEADLESS) { + network_server_tick(); + } + } +} + +void game_render() { + if (game_mode != GAMEKIND_HEADLESS) { + platform_render(); + } +} + +void game_action_send_keystate(game_keystate_data *data) { + pkt_send_keystate_send(active_viewer->view_id, data); +} + +void game_request_close() { + platform_request_close(); } \ No newline at end of file diff --git a/code/game/src/game.h b/code/game/src/game.h index bd2646c..eaeb6dc 100644 --- a/code/game/src/game.h +++ b/code/game/src/game.h @@ -1,36 +1,36 @@ -#pragma once -#include "system.h" -#include "world_view.h" -#include "packets/pkt_send_keystate.h" - -typedef enum { - GAMEKIND_SINGLE, - GAMEKIND_CLIENT, - GAMEKIND_HEADLESS, - FORCE_GAMEKIND_UINT8 = UINT8_MAX -} game_kind; - -void game_init(game_kind play_mode, uint32_t num_viewers, int32_t seed, uint16_t chunk_size, uint16_t chunk_amount, int8_t is_dash_enabled); -void game_shutdown(); -void game_request_close(); -uint8_t game_is_running(); -int8_t game_is_networked(); -float game_time(); -game_kind game_get_kind(void); - -//~ NOTE(zaklaus): game events -void game_input(); -void game_update(); -void game_render(); - -//~ NOTE(zaklaus): world view management -world_view *game_world_view_get_active(void); -world_view *game_world_view_get(uint16_t idx); -void game_world_view_set_active_by_idx(uint16_t idx); -void game_world_view_set_active(world_view *view); -void game_world_view_cycle_active(int8_t dir); -void game_world_view_active_entity_map(void (*map_proc)(uint64_t key, entity_view * value)); -entity_view *game_world_view_active_get_entity(uint64_t ent_id); - -//~ NOTE(zaklaus): viewer -> host actions +#pragma once +#include "system.h" +#include "world_view.h" +#include "packets/pkt_send_keystate.h" + +typedef enum { + GAMEKIND_SINGLE, + GAMEKIND_CLIENT, + GAMEKIND_HEADLESS, + FORCE_GAMEKIND_UINT8 = UINT8_MAX +} game_kind; + +void game_init(game_kind play_mode, uint32_t num_viewers, int32_t seed, uint16_t chunk_size, uint16_t chunk_amount, int8_t is_dash_enabled); +void game_shutdown(); +void game_request_close(); +uint8_t game_is_running(); +int8_t game_is_networked(); +float game_time(); +game_kind game_get_kind(void); + +//~ NOTE(zaklaus): game events +void game_input(); +void game_update(); +void game_render(); + +//~ NOTE(zaklaus): world view management +world_view *game_world_view_get_active(void); +world_view *game_world_view_get(uint16_t idx); +void game_world_view_set_active_by_idx(uint16_t idx); +void game_world_view_set_active(world_view *view); +void game_world_view_cycle_active(int8_t dir); +void game_world_view_active_entity_map(void (*map_proc)(uint64_t key, entity_view * value)); +entity_view *game_world_view_active_get_entity(uint64_t ent_id); + +//~ NOTE(zaklaus): viewer -> host actions void game_action_send_keystate(game_keystate_data *data); \ No newline at end of file diff --git a/code/game/src/network.c b/code/game/src/network.c index f7fd86b..ab0a1b4 100644 --- a/code/game/src/network.c +++ b/code/game/src/network.c @@ -1,189 +1,189 @@ -#include "zpl.h" - -#define ENET_IMPLEMENTATION -#include "enet.h" - -#define LIBRG_IMPL -#define LIBRG_CUSTOM_ZPL -#define LIBRG_ENTITY_MAXCHUNKS 1 -#include "librg.h" - -#include "network.h" -#include "packet.h" -#include "packets/pkt_00_init.h" -#include "world/world.h" -#include "game.h" -#include "player.h" - -#define NETWORK_UPDATE_DELAY 0.100 - -static ENetHost *host = NULL; -static ENetHost *server = NULL; -static ENetPeer *peer = NULL; -static librg_world *world = NULL; - -int32_t network_init() { - return enet_initialize() != 0; -} - -int32_t network_destroy() { - enet_deinitialize(); - return 0; - -} - -//~ NOTE(zaklaus): client - -int32_t network_client_connect(const char *hostname, uint16_t port) { - ENetAddress address = {0}; address.port = port; - enet_address_set_host(&address, hostname); - - host = enet_host_create(NULL, 1, 2, 0, 0); - peer = enet_host_connect(host, &address, 2, 0); - - if (peer == NULL) { - zpl_printf("[ERROR] Cannot connect to specicied server: %s:%d\n", hostname, port); - return 1; - } - - world = librg_world_create(); - librg_world_userdata_set(world, peer); - - return 0; -} - -int32_t network_client_disconnect() { - enet_peer_disconnect_now(peer, 0); - enet_host_destroy(host); - - librg_world_destroy(world); - - peer = NULL; - host = NULL; - world = NULL; - - return 0; -} - -int32_t network_client_tick() { - ENetEvent event = {0}; - - while (enet_host_service(host, &event, 1) > 0) { - switch (event.type) { - case ENET_EVENT_TYPE_CONNECT: { - zpl_printf("[INFO] We connected to the server.\n"); - pkt_00_init_send(0); - } break; - case ENET_EVENT_TYPE_DISCONNECT: - case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { - zpl_printf("[INFO] We disconnected from server.\n"); - } break; - - case ENET_EVENT_TYPE_RECEIVE: { - if (!world_read(event.packet->data, event.packet->dataLength, event.peer)) { - zpl_printf("[INFO] Server sent us an unsupported packet.\n"); - } - - /* Clean up the packet now that we're done using it. */ - enet_packet_destroy(event.packet); - } break; - - case ENET_EVENT_TYPE_NONE: break; - } - } - - return 0; -} - -bool network_client_is_connected() { - return peer ? enet_peer_get_state(peer) == ENET_PEER_STATE_CONNECTED : false; -} - -//~ NOTE(zaklaus): server - -int32_t network_server_start(const char *host, uint16_t port) { - (void)host; - - ENetAddress address = {0}; - - address.host = ENET_HOST_ANY; - address.port = port; - - server = enet_host_create(&address, 8, 2, 0, 0); - - if (server == NULL) { - zpl_printf("[ERROR] An error occured while trying to create a server host.\n"); - return 1; - } - - return 0; -} - -int32_t network_server_stop(void) { - enet_host_destroy(server); - server = 0; - return 0; -} - -int32_t network_server_tick(void) { - ENetEvent event = {0}; - while (enet_host_service(server, &event, 1) > 0) { - switch (event.type) { - case ENET_EVENT_TYPE_CONNECT: { - zpl_printf("[INFO] A new user %d connected.\n", event.peer->incomingPeerID); - } break; - case ENET_EVENT_TYPE_DISCONNECT: - case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { - zpl_printf("[INFO] A user %d disconnected.\n", event.peer->incomingPeerID); - - if (event.peer->data) { - player_despawn((ecs_entity_t)event.peer->data); - event.peer->data = 0; - } - } break; - - case ENET_EVENT_TYPE_RECEIVE: { - if (!world_read(event.packet->data, event.packet->dataLength, event.peer)) { - zpl_printf("[INFO] User %d sent us a malformed packet.\n", event.peer->incomingPeerID); - } - - /* Clean up the packet now that we're done using it. */ - enet_packet_destroy(event.packet); - } break; - - case ENET_EVENT_TYPE_NONE: break; - } - } - - return 0; -} - -void network_server_assign_entity(void *peer_id, uint64_t ent_id) { - ENetPeer *peer = (ENetPeer *)peer_id; - peer->data = (void*)ent_id; -} - -uint64_t network_server_get_entity(void *peer_id) { - if (game_get_kind() == GAMEKIND_SINGLE) { - return (uint64_t)peer_id; - } - ENetPeer *peer = (ENetPeer *)peer_id; - ZPL_ASSERT(peer->data); - return (uint64_t)peer->data; -} - -//~ NOTE(zaklaus): messaging - -static int32_t network_msg_send_raw(ENetPeer *peer_id, void *data, size_t datalen, uint32_t flags, uint16_t channel_id) { - if (peer_id == 0) peer_id = peer; - ENetPacket *packet = enet_packet_create(data, datalen, flags); - return enet_peer_send(peer_id, channel_id, packet); -} - -int32_t network_msg_send(void *peer_id, void *data, size_t datalen, uint16_t channel_id) { - return network_msg_send_raw(peer_id, data, datalen, ENET_PACKET_FLAG_RELIABLE, channel_id); -} - -int32_t network_msg_send_unreliable(void *peer_id, void *data, size_t datalen, uint16_t channel_id) { - return network_msg_send_raw(peer_id, data, datalen, 0, channel_id); -} +#include "zpl.h" + +#define ENET_IMPLEMENTATION +#include "enet.h" + +#define LIBRG_IMPL +#define LIBRG_CUSTOM_ZPL +#define LIBRG_ENTITY_MAXCHUNKS 1 +#include "librg.h" + +#include "network.h" +#include "packet.h" +#include "packets/pkt_00_init.h" +#include "world/world.h" +#include "game.h" +#include "player.h" + +#define NETWORK_UPDATE_DELAY 0.100 + +static ENetHost *host = NULL; +static ENetHost *server = NULL; +static ENetPeer *peer = NULL; +static librg_world *world = NULL; + +int32_t network_init() { + return enet_initialize() != 0; +} + +int32_t network_destroy() { + enet_deinitialize(); + return 0; + +} + +//~ NOTE(zaklaus): client + +int32_t network_client_connect(const char *hostname, uint16_t port) { + ENetAddress address = {0}; address.port = port; + enet_address_set_host(&address, hostname); + + host = enet_host_create(NULL, 1, 2, 0, 0); + peer = enet_host_connect(host, &address, 2, 0); + + if (peer == NULL) { + zpl_printf("[ERROR] Cannot connect to specicied server: %s:%d\n", hostname, port); + return 1; + } + + world = librg_world_create(); + librg_world_userdata_set(world, peer); + + return 0; +} + +int32_t network_client_disconnect() { + enet_peer_disconnect_now(peer, 0); + enet_host_destroy(host); + + librg_world_destroy(world); + + peer = NULL; + host = NULL; + world = NULL; + + return 0; +} + +int32_t network_client_tick() { + ENetEvent event = {0}; + + while (enet_host_service(host, &event, 1) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: { + zpl_printf("[INFO] We connected to the server.\n"); + pkt_00_init_send(0); + } break; + case ENET_EVENT_TYPE_DISCONNECT: + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { + zpl_printf("[INFO] We disconnected from server.\n"); + } break; + + case ENET_EVENT_TYPE_RECEIVE: { + if (!world_read(event.packet->data, event.packet->dataLength, event.peer)) { + zpl_printf("[INFO] Server sent us an unsupported packet.\n"); + } + + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy(event.packet); + } break; + + case ENET_EVENT_TYPE_NONE: break; + } + } + + return 0; +} + +bool network_client_is_connected() { + return peer ? enet_peer_get_state(peer) == ENET_PEER_STATE_CONNECTED : false; +} + +//~ NOTE(zaklaus): server + +int32_t network_server_start(const char *host, uint16_t port) { + (void)host; + + ENetAddress address = {0}; + + address.host = ENET_HOST_ANY; + address.port = port; + + server = enet_host_create(&address, 8, 2, 0, 0); + + if (server == NULL) { + zpl_printf("[ERROR] An error occured while trying to create a server host.\n"); + return 1; + } + + return 0; +} + +int32_t network_server_stop(void) { + enet_host_destroy(server); + server = 0; + return 0; +} + +int32_t network_server_tick(void) { + ENetEvent event = {0}; + while (enet_host_service(server, &event, 1) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: { + zpl_printf("[INFO] A new user %d connected.\n", event.peer->incomingPeerID); + } break; + case ENET_EVENT_TYPE_DISCONNECT: + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { + zpl_printf("[INFO] A user %d disconnected.\n", event.peer->incomingPeerID); + + if (event.peer->data) { + player_despawn((ecs_entity_t)event.peer->data); + event.peer->data = 0; + } + } break; + + case ENET_EVENT_TYPE_RECEIVE: { + if (!world_read(event.packet->data, event.packet->dataLength, event.peer)) { + zpl_printf("[INFO] User %d sent us a malformed packet.\n", event.peer->incomingPeerID); + } + + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy(event.packet); + } break; + + case ENET_EVENT_TYPE_NONE: break; + } + } + + return 0; +} + +void network_server_assign_entity(void *peer_id, uint64_t ent_id) { + ENetPeer *peer = (ENetPeer *)peer_id; + peer->data = (void*)ent_id; +} + +uint64_t network_server_get_entity(void *peer_id) { + if (game_get_kind() == GAMEKIND_SINGLE) { + return (uint64_t)peer_id; + } + ENetPeer *peer = (ENetPeer *)peer_id; + ZPL_ASSERT(peer->data); + return (uint64_t)peer->data; +} + +//~ NOTE(zaklaus): messaging + +static int32_t network_msg_send_raw(ENetPeer *peer_id, void *data, size_t datalen, uint32_t flags, uint16_t channel_id) { + if (peer_id == 0) peer_id = peer; + ENetPacket *packet = enet_packet_create(data, datalen, flags); + return enet_peer_send(peer_id, channel_id, packet); +} + +int32_t network_msg_send(void *peer_id, void *data, size_t datalen, uint16_t channel_id) { + return network_msg_send_raw(peer_id, data, datalen, ENET_PACKET_FLAG_RELIABLE, channel_id); +} + +int32_t network_msg_send_unreliable(void *peer_id, void *data, size_t datalen, uint16_t channel_id) { + return network_msg_send_raw(peer_id, data, datalen, 0, channel_id); +} diff --git a/code/game/src/packets/pkt_send_keystate.c b/code/game/src/packets/pkt_send_keystate.c index 25cda91..e6c03f5 100644 --- a/code/game/src/packets/pkt_send_keystate.c +++ b/code/game/src/packets/pkt_send_keystate.c @@ -1,71 +1,71 @@ -#include "packet_utils.h" -#include "network.h" -#include "packets/pkt_send_keystate.h" -#include "modules/components.h" -#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) }, - { PKT_REAL(pkt_send_keystate, mx) }, - { PKT_REAL(pkt_send_keystate, my) }, - { PKT_UINT(pkt_send_keystate, use) }, - { PKT_UINT(pkt_send_keystate, sprint) }, - { PKT_UINT(pkt_send_keystate, ctrl) }, - { PKT_UINT(pkt_send_keystate, selected_item) }, - { PKT_UINT(pkt_send_keystate, drop) }, - { PKT_UINT(pkt_send_keystate, swap) }, - { PKT_UINT(pkt_send_keystate, swap_from) }, - { PKT_UINT(pkt_send_keystate, swap_to) }, - { PKT_UINT(pkt_send_keystate, placement_num) }, - { PKT_ARRAY(pkt_send_keystate, placements) }, - { PKT_END }, -}; - -size_t pkt_send_keystate_send(uint16_t view_id, - game_keystate_data *data) { - return pkt_world_write(MSG_ID_SEND_KEYSTATE, pkt_send_keystate_encode(data), 1, view_id, NULL, 1); -} - -size_t pkt_send_keystate_encode(pkt_send_keystate *table) { - cw_pack_context pc = {0}; - pkt_pack_msg(&pc, pkt_pack_desc_args(pkt_send_keystate_desc)); - pkt_pack_struct(&pc, pkt_send_keystate_desc, PKT_STRUCT_PTR(table)); - return pkt_pack_msg_size(&pc); -} - -int32_t pkt_send_keystate_handler(pkt_header *header) { - pkt_send_keystate table; - PKT_IF(pkt_msg_decode(header, pkt_send_keystate_desc, pkt_pack_desc_args(pkt_send_keystate_desc), PKT_STRUCT_PTR(&table))); - ecs_entity_t e = network_server_get_entity(header->udata); - - if (!world_entity_valid(e)) - return 1; - - Input *i = ecs_get_mut(world_ecs(), e, Input, NULL); - if (i && !i->is_blocked) { - i->x = zpl_clamp(table.x, -1.0f, 1.0f); - i->y = zpl_clamp(table.y, -1.0f, 1.0f); - i->mx = table.mx; - i->my = table.my; - i->use = table.use; - i->sprint = table.sprint; - i->ctrl = table.ctrl; - i->selected_item = zpl_clamp(table.selected_item, 0, ITEMS_INVENTORY_SIZE-1); - i->drop = table.drop; - i->swap = table.swap; - i->swap_from = zpl_clamp(table.swap_from, 0, ITEMS_INVENTORY_SIZE-1); - i->swap_to = zpl_clamp(table.swap_to, 0, ITEMS_INVENTORY_SIZE-1); - i->num_placements = zpl_clamp(table.placement_num, 0, BUILD_MAX_PLACEMENTS); - for (uint8_t j = 0; j < i->num_placements; j++) { - i->placements_x[j] = table.placements[j].x; - i->placements_y[j] = table.placements[j].y; - } - debug_replay_record_keystate(table); - } - - return 0; -} +#include "packet_utils.h" +#include "network.h" +#include "packets/pkt_send_keystate.h" +#include "modules/components.h" +#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) }, + { PKT_REAL(pkt_send_keystate, mx) }, + { PKT_REAL(pkt_send_keystate, my) }, + { PKT_UINT(pkt_send_keystate, use) }, + { PKT_UINT(pkt_send_keystate, sprint) }, + { PKT_UINT(pkt_send_keystate, ctrl) }, + { PKT_UINT(pkt_send_keystate, selected_item) }, + { PKT_UINT(pkt_send_keystate, drop) }, + { PKT_UINT(pkt_send_keystate, swap) }, + { PKT_UINT(pkt_send_keystate, swap_from) }, + { PKT_UINT(pkt_send_keystate, swap_to) }, + { PKT_UINT(pkt_send_keystate, placement_num) }, + { PKT_ARRAY(pkt_send_keystate, placements) }, + { PKT_END }, +}; + +size_t pkt_send_keystate_send(uint16_t view_id, + game_keystate_data *data) { + return pkt_world_write(MSG_ID_SEND_KEYSTATE, pkt_send_keystate_encode(data), 1, view_id, NULL, 1); +} + +size_t pkt_send_keystate_encode(pkt_send_keystate *table) { + cw_pack_context pc = {0}; + pkt_pack_msg(&pc, pkt_pack_desc_args(pkt_send_keystate_desc)); + pkt_pack_struct(&pc, pkt_send_keystate_desc, PKT_STRUCT_PTR(table)); + return pkt_pack_msg_size(&pc); +} + +int32_t pkt_send_keystate_handler(pkt_header *header) { + pkt_send_keystate table; + PKT_IF(pkt_msg_decode(header, pkt_send_keystate_desc, pkt_pack_desc_args(pkt_send_keystate_desc), PKT_STRUCT_PTR(&table))); + ecs_entity_t e = network_server_get_entity(header->udata); + + if (!world_entity_valid(e)) + return 1; + + Input *i = ecs_get_mut(world_ecs(), e, Input, NULL); + if (i && !i->is_blocked) { + i->x = zpl_clamp(table.x, -1.0f, 1.0f); + i->y = zpl_clamp(table.y, -1.0f, 1.0f); + i->mx = table.mx; + i->my = table.my; + i->use = table.use; + i->sprint = table.sprint; + i->ctrl = table.ctrl; + i->selected_item = zpl_clamp(table.selected_item, 0, ITEMS_INVENTORY_SIZE-1); + i->drop = table.drop; + i->swap = table.swap; + i->swap_from = zpl_clamp(table.swap_from, 0, ITEMS_INVENTORY_SIZE-1); + i->swap_to = zpl_clamp(table.swap_to, 0, ITEMS_INVENTORY_SIZE-1); + i->num_placements = zpl_clamp(table.placement_num, 0, BUILD_MAX_PLACEMENTS); + for (uint8_t j = 0; j < i->num_placements; j++) { + i->placements_x[j] = table.placements[j].x; + i->placements_y[j] = table.placements[j].y; + } + debug_replay_record_keystate(table); + } + + return 0; +} diff --git a/code/game/src/packets/pkt_send_keystate.h b/code/game/src/packets/pkt_send_keystate.h index 1772a63..e331c03 100644 --- a/code/game/src/packets/pkt_send_keystate.h +++ b/code/game/src/packets/pkt_send_keystate.h @@ -1,33 +1,33 @@ -#pragma once -#include "system.h" -#include "packet_utils.h" -#include "item_placement.h" - -typedef struct { - float x; - float y; - float mx; - float my; - uint8_t use; - uint8_t sprint; - uint8_t ctrl; - uint8_t selected_item; - uint8_t drop; - uint8_t swap; - uint8_t swap_from; - uint8_t swap_to; - - // TODO(zaklaus): build mode - uint8_t placement_num; - item_placement placements[BUILD_MAX_PLACEMENTS]; -} pkt_send_keystate; - -typedef pkt_send_keystate game_keystate_data; - -size_t pkt_send_keystate_send(uint16_t view_id, - game_keystate_data *data); -size_t pkt_send_keystate_encode(pkt_send_keystate *table); -extern pkt_desc pkt_send_keystate_desc[]; - -PKT_HANDLER_PROC(pkt_send_keystate_handler); - +#pragma once +#include "system.h" +#include "packet_utils.h" +#include "item_placement.h" + +typedef struct { + float x; + float y; + float mx; + float my; + uint8_t use; + uint8_t sprint; + uint8_t ctrl; + uint8_t selected_item; + uint8_t drop; + uint8_t swap; + uint8_t swap_from; + uint8_t swap_to; + + // TODO(zaklaus): build mode + uint8_t placement_num; + item_placement placements[BUILD_MAX_PLACEMENTS]; +} pkt_send_keystate; + +typedef pkt_send_keystate game_keystate_data; + +size_t pkt_send_keystate_send(uint16_t view_id, + game_keystate_data *data); +size_t pkt_send_keystate_encode(pkt_send_keystate *table); +extern pkt_desc pkt_send_keystate_desc[]; + +PKT_HANDLER_PROC(pkt_send_keystate_handler); + diff --git a/code/game/src/platform_raylib.c b/code/game/src/platform_raylib.c index dede8eb..191eceb 100644 --- a/code/game/src/platform_raylib.c +++ b/code/game/src/platform_raylib.c @@ -58,6 +58,29 @@ uint8_t platform_is_running() { return !WindowShouldClose(); } +static game_keystate_data last_input_data = {0}; + +void platform_input_update_input_frame(game_keystate_data data) { + // NOTE(zaklaus): Test if there are any changes + if (data.x != last_input_data.x) goto send_data; + if (data.y != last_input_data.y) goto send_data; + if (data.use != last_input_data.use) goto send_data; + if (data.sprint != last_input_data.sprint) goto send_data; + if (data.ctrl != last_input_data.ctrl) goto send_data; + if (data.selected_item != last_input_data.selected_item) goto send_data; + if (data.drop != last_input_data.drop) goto send_data; + if (data.swap != last_input_data.swap) goto send_data; + if (data.swap_from != last_input_data.swap_from) goto send_data; + if (data.swap_to != last_input_data.swap_to) goto send_data; + if (data.placement_num != last_input_data.placement_num) goto send_data; + if (zpl_memcompare(data.placements, last_input_data.placements, zpl_size_of(data.placements))) goto send_data; + return; + + send_data: + last_input_data = data; + game_action_send_keystate(&data); +} + void platform_input() { float mouse_z = (GetMouseWheelMove()*0.5f); @@ -92,7 +115,7 @@ void platform_input() { y = -mouse_pos.y; } - game_keystate_data data = { + game_keystate_data in_data = { .x = x, .y = y, .mx = mouse_pos.x, @@ -111,11 +134,11 @@ void platform_input() { if (build_submit_placements) { build_submit_placements = false; - data.placement_num = build_num_placements; - zpl_memcopy(data.placements, build_placements, build_num_placements*zpl_size_of(item_placement)); + in_data.placement_num = build_num_placements; + zpl_memcopy(in_data.placements, build_placements, build_num_placements*zpl_size_of(item_placement)); } - game_action_send_keystate(&data); + platform_input_update_input_frame(in_data); } // NOTE(zaklaus): cycle through viewers diff --git a/code/game/src/player.c b/code/game/src/player.c index 2d9fd1f..633d40c 100644 --- a/code/game/src/player.c +++ b/code/game/src/player.c @@ -1,36 +1,36 @@ -#include "player.h" -#include "entity.h" -#include "entity_view.h" -#include "flecs/flecs.h" -#include "flecs/flecs_meta.h" -#include "librg.h" -#include "world/world.h" - -#include "modules/components.h" -#include "modules/systems.h" -#include "zpl.h" - -#define PLAYER_MAX_HP 100.0f - -uint64_t player_spawn(char *name) { - ecs_entity_t e = entity_spawn(EKIND_PLAYER); - - if (!name) { - name = zpl_bprintf("player_%d", e); - } - - ecs_set_name(world_ecs(), e, name); - ecs_set(world_ecs(), e, ClientInfo, {0}); - ecs_set(world_ecs(), e, Input, {0}); - ecs_set(world_ecs(), e, Inventory, {0}); - ecs_set(world_ecs(), e, Health, {.hp = PLAYER_MAX_HP, .max_hp = PLAYER_MAX_HP}); - ecs_add(world_ecs(), e, Player); - - librg_entity_owner_set(world_tracker(), e, (int64_t)e); - - return (uint64_t)e; -} - -void player_despawn(uint64_t ent_id) { - entity_despawn(ent_id); -} +#include "player.h" +#include "entity.h" +#include "entity_view.h" +#include "flecs/flecs.h" +#include "flecs/flecs_meta.h" +#include "librg.h" +#include "world/world.h" + +#include "modules/components.h" +#include "modules/systems.h" +#include "zpl.h" + +#define PLAYER_MAX_HP 100.0f + +uint64_t player_spawn(char *name) { + ecs_entity_t e = entity_spawn(EKIND_PLAYER); + + if (!name) { + name = zpl_bprintf("player_%d", e); + } + + ecs_set_name(world_ecs(), e, name); + ecs_set(world_ecs(), e, ClientInfo, {0}); + ecs_set(world_ecs(), e, Input, {0}); + ecs_set(world_ecs(), e, Inventory, {0}); + ecs_set(world_ecs(), e, Health, {.hp = PLAYER_MAX_HP, .max_hp = PLAYER_MAX_HP}); + ecs_add(world_ecs(), e, Player); + + librg_entity_owner_set(world_tracker(), e, (int64_t)e); + + return (uint64_t)e; +} + +void player_despawn(uint64_t ent_id) { + entity_despawn(ent_id); +} diff --git a/code/game/src/prediction.c b/code/game/src/prediction.c index bd11d53..9c5788f 100644 --- a/code/game/src/prediction.c +++ b/code/game/src/prediction.c @@ -1,120 +1,120 @@ -#include "zpl.h" -#include "prediction.h" -#include "platform.h" -#include "world/world.h" -#include "game.h" - -#define PREDICT_SMOOTH_FACTOR_LO 7.5 -#define PREDICT_SMOOTH_FACTOR_HI 12.5 - -static inline float map_factor(float x) { - x = 1.0f - zpl_clamp01(x); - return 1.0f - x*x*x*x*x*x*x*x; -} - -static inline float base_angle(float x) { - while (x > ZPL_TAU) x -= ZPL_TAU; - while (x < 0.0f) x += ZPL_TAU; - return x; -} - -static inline float spherical_lerp(float a, float b, float t) { - a = base_angle(a); - b = base_angle(b); - float d = b - a; - - if (d < -ZPL_PI) { - b += ZPL_TAU; - } else if (d > ZPL_PI) { - b -= ZPL_TAU; - } - - return base_angle(zpl_lerp(a, b, t)); -} - -float smooth_val(float cur, float tgt, uint64_t dt) { - float factor = zpl_clamp01(map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_MP_FAST_MS, WORLD_TRACKER_UPDATE_MP_SLOW_MS))); - -#if 0 - dt = 200; - factor = map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_MP_FAST_MS, WORLD_TRACKER_UPDATE_MP_SLOW_MS)); - zpl_printf("lerp factor: %f\n", factor); - zpl_exit(0); -#endif - - return zpl_lerp(cur, tgt, zpl_clamp01(zpl_lerp(PREDICT_SMOOTH_FACTOR_LO, PREDICT_SMOOTH_FACTOR_HI, factor)*platform_frametime())); -} - -float smooth_val_spherical(float cur, float tgt, uint64_t dt) { - float factor = zpl_clamp01(map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_MP_FAST_MS, WORLD_TRACKER_UPDATE_MP_SLOW_MS))); - - return spherical_lerp(cur, tgt, zpl_clamp01(zpl_lerp(PREDICT_SMOOTH_FACTOR_LO, PREDICT_SMOOTH_FACTOR_HI, factor)*platform_frametime())); -} - -void predict_receive_update(entity_view *d, entity_view *data) { - if (d && data->flag & EFLAG_INTERP) { - // NOTE(zaklaus): store target pos but keep x,y unchanged - float tx = data->x; - float ty = data->y; - float theading = data->heading; - data->x = d->x; - data->y = d->y; - data->heading = d->heading; - data->tx = tx; - data->ty = ty; - data->theading = theading; - } - - data->tran_effect = d->tran_effect; - data->tran_time = d->tran_time; -} - -#define ENTITY_DO_LERP_SP 0 - -void lerp_entity_positions(uint64_t key, entity_view *data) { - (void)key; - world_view *view = game_world_view_get_active(); - - if (data->flag == EFLAG_INTERP) { -#if ENTITY_DO_LERP_SP==0 - if (game_get_kind() == GAMEKIND_CLIENT) -#else - if (1) -#endif - { - data->x = smooth_val(data->x, data->tx, view->delta_time[data->layer_id]); - data->y = smooth_val(data->y, data->ty, view->delta_time[data->layer_id]); - data->heading = smooth_val_spherical(data->heading, data->theading, view->delta_time[data->layer_id]); - } else { - (void)view; - data->x = data->tx; - data->y = data->ty; - data->heading = data->theading; - } - } -} - -void do_entity_fadeinout(uint64_t key, entity_view * data) { - (void)key; - switch (data->tran_effect) { - case ETRAN_FADEIN: { - data->tran_time += platform_frametime(); - - if (data->tran_time > 1.0f) { - data->tran_effect = ETRAN_NONE; - data->tran_time = 1.0f; - } - }break; - - case ETRAN_FADEOUT: { - data->tran_time -= platform_frametime(); - - if (data->tran_time < 0.0f) { - data->tran_effect = ETRAN_REMOVE; - data->tran_time = 0.0f; - } - }break; - - default: break; - } -} +#include "zpl.h" +#include "prediction.h" +#include "platform.h" +#include "world/world.h" +#include "game.h" + +#define PREDICT_SMOOTH_FACTOR_LO 7.5 +#define PREDICT_SMOOTH_FACTOR_HI 12.5 + +static inline float map_factor(float x) { + x = 1.0f - zpl_clamp01(x); + return 1.0f - x*x*x*x*x*x*x*x; +} + +static inline float base_angle(float x) { + while (x > ZPL_TAU) x -= ZPL_TAU; + while (x < 0.0f) x += ZPL_TAU; + return x; +} + +static inline float spherical_lerp(float a, float b, float t) { + a = base_angle(a); + b = base_angle(b); + float d = b - a; + + if (d < -ZPL_PI) { + b += ZPL_TAU; + } else if (d > ZPL_PI) { + b -= ZPL_TAU; + } + + return base_angle(zpl_lerp(a, b, t)); +} + +float smooth_val(float cur, float tgt, uint64_t dt) { + float factor = zpl_clamp01(map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_MP_FAST_MS, WORLD_TRACKER_UPDATE_MP_SLOW_MS))); + +#if 0 + dt = 200; + factor = map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_MP_FAST_MS, WORLD_TRACKER_UPDATE_MP_SLOW_MS)); + zpl_printf("lerp factor: %f\n", factor); + zpl_exit(0); +#endif + + return zpl_lerp(cur, tgt, zpl_clamp01(zpl_lerp(PREDICT_SMOOTH_FACTOR_LO, PREDICT_SMOOTH_FACTOR_HI, factor)*platform_frametime())); +} + +float smooth_val_spherical(float cur, float tgt, uint64_t dt) { + float factor = zpl_clamp01(map_factor(zpl_unlerp(dt, WORLD_TRACKER_UPDATE_MP_FAST_MS, WORLD_TRACKER_UPDATE_MP_SLOW_MS))); + + return spherical_lerp(cur, tgt, zpl_clamp01(zpl_lerp(PREDICT_SMOOTH_FACTOR_LO, PREDICT_SMOOTH_FACTOR_HI, factor)*platform_frametime())); +} + +void predict_receive_update(entity_view *d, entity_view *data) { + if (d && data->flag & EFLAG_INTERP) { + // NOTE(zaklaus): store target pos but keep x,y unchanged + float tx = data->x; + float ty = data->y; + float theading = data->heading; + data->x = d->x; + data->y = d->y; + data->heading = d->heading; + data->tx = tx; + data->ty = ty; + data->theading = theading; + } + + data->tran_effect = d->tran_effect; + data->tran_time = d->tran_time; +} + +#define ENTITY_DO_LERP_SP 0 + +void lerp_entity_positions(uint64_t key, entity_view *data) { + (void)key; + world_view *view = game_world_view_get_active(); + + if (data->flag == EFLAG_INTERP) { +#if ENTITY_DO_LERP_SP==0 + if (game_get_kind() == GAMEKIND_CLIENT) +#else + if (1) +#endif + { + data->x = smooth_val(data->x, data->tx, view->delta_time[data->layer_id]); + data->y = smooth_val(data->y, data->ty, view->delta_time[data->layer_id]); + data->heading = smooth_val_spherical(data->heading, data->theading, view->delta_time[data->layer_id]); + } else { + (void)view; + data->x = data->tx; + data->y = data->ty; + data->heading = data->theading; + } + } +} + +void do_entity_fadeinout(uint64_t key, entity_view * data) { + (void)key; + switch (data->tran_effect) { + case ETRAN_FADEIN: { + data->tran_time += platform_frametime(); + + if (data->tran_time > 1.0f) { + data->tran_effect = ETRAN_NONE; + data->tran_time = 1.0f; + } + }break; + + case ETRAN_FADEOUT: { + data->tran_time -= platform_frametime(); + + if (data->tran_time < 0.0f) { + data->tran_effect = ETRAN_REMOVE; + data->tran_time = 0.0f; + } + }break; + + default: break; + } +} diff --git a/code/game/src/profiler.c b/code/game/src/profiler.c index 42da93b..5c8012b 100644 --- a/code/game/src/profiler.c +++ b/code/game/src/profiler.c @@ -1,61 +1,61 @@ -#include "profiler.h" -#include "raylib.h" -#include - -#define PROF_COLLATE_WINDOW 0.5 - -// NOTE(zaklaus): KEEP ORDER IN SYNC WITH profiler_kind ENUM !!! -static profiler profilers[] = { - { .id = PROF_TOTAL_TIME, .name = "measured time" }, - { .id = PROF_MAIN_LOOP, .name = "main loop" }, - { .id = PROF_WORLD_WRITE, .name = "world write" }, - { .id = PROF_RENDER, .name = "render" }, - { .id = PROF_UPDATE_SYSTEMS, .name = "update systems" }, - { .id = PROF_ENTITY_LERP, .name = "entity lerp" }, - { .id = PROF_INTEGRATE_POS, .name = "entity movement" }, -}; - -static_assert((sizeof(profilers)/sizeof(profilers[0])) == MAX_PROF, "mismatched profilers"); - -void profiler_reset(profiler_kind id) { - profilers[id].num_invocations = 0; - profilers[id].total_time = 0.0; -} - -void profiler_start(profiler_kind id) { - profilers[id].start_time = GetTime(); -} - -void profiler_stop(profiler_kind id) { - profilers[id].num_invocations += 1; - profilers[id].total_time += GetTime() - profilers[id].start_time; - profilers[id].start_time = 0.0; -} - -void profiler_collate() { - static double frame_counter = 0.0; - static uint64_t frames = 0; - - frame_counter += GetFrameTime(); - frames++; - - if (frame_counter >= PROF_COLLATE_WINDOW) { - profilers[PROF_TOTAL_TIME].delta_time = frame_counter / (double)frames; - - for (uint32_t i = PROF_MAIN_LOOP; i < MAX_PROF; i += 1) { - profiler *p = &profilers[i]; - p->delta_time = p->num_invocations == 0 ? 0.0 : p->total_time / (double)p->num_invocations; - } - - frame_counter = 0.0; - frames = 0; - } -} - -double profiler_delta(profiler_kind id) { - return profilers[id].delta_time; -} - -char const *profiler_name(profiler_kind id) { - return profilers[id].name; -} +#include "profiler.h" +#include "raylib.h" +#include + +#define PROF_COLLATE_WINDOW 0.5 + +// NOTE(zaklaus): KEEP ORDER IN SYNC WITH profiler_kind ENUM !!! +static profiler profilers[] = { + { .id = PROF_TOTAL_TIME, .name = "measured time" }, + { .id = PROF_MAIN_LOOP, .name = "main loop" }, + { .id = PROF_WORLD_WRITE, .name = "world write" }, + { .id = PROF_RENDER, .name = "render" }, + { .id = PROF_UPDATE_SYSTEMS, .name = "update systems" }, + { .id = PROF_ENTITY_LERP, .name = "entity lerp" }, + { .id = PROF_INTEGRATE_POS, .name = "entity movement" }, +}; + +static_assert((sizeof(profilers)/sizeof(profilers[0])) == MAX_PROF, "mismatched profilers"); + +void profiler_reset(profiler_kind id) { + profilers[id].num_invocations = 0; + profilers[id].total_time = 0.0; +} + +void profiler_start(profiler_kind id) { + profilers[id].start_time = GetTime(); +} + +void profiler_stop(profiler_kind id) { + profilers[id].num_invocations += 1; + profilers[id].total_time += GetTime() - profilers[id].start_time; + profilers[id].start_time = 0.0; +} + +void profiler_collate() { + static double frame_counter = 0.0; + static uint64_t frames = 0; + + frame_counter += GetFrameTime(); + frames++; + + if (frame_counter >= PROF_COLLATE_WINDOW) { + profilers[PROF_TOTAL_TIME].delta_time = frame_counter / (double)frames; + + for (uint32_t i = PROF_MAIN_LOOP; i < MAX_PROF; i += 1) { + profiler *p = &profilers[i]; + p->delta_time = p->num_invocations == 0 ? 0.0 : p->total_time / (double)p->num_invocations; + } + + frame_counter = 0.0; + frames = 0; + } +} + +double profiler_delta(profiler_kind id) { + return profilers[id].delta_time; +} + +char const *profiler_name(profiler_kind id) { + return profilers[id].name; +} diff --git a/code/game/src/profiler.h b/code/game/src/profiler.h index 4c93d5f..ecee36c 100644 --- a/code/game/src/profiler.h +++ b/code/game/src/profiler.h @@ -1,36 +1,36 @@ -#pragma once -#include "system.h" - -typedef enum { - PROF_TOTAL_TIME, - PROF_MAIN_LOOP, - - PROF_WORLD_WRITE, - PROF_RENDER, - PROF_UPDATE_SYSTEMS, - PROF_ENTITY_LERP, - PROF_INTEGRATE_POS, - - MAX_PROF, - PROF_FORCE_UINT8 = UINT8_MAX -} profiler_kind; - -typedef struct { - profiler_kind id; - char const *name; - - uint32_t num_invocations; - double start_time; - double delta_time; - double total_time; -} profiler; - -void profiler_reset(profiler_kind id); -void profiler_start(profiler_kind id); -void profiler_stop(profiler_kind id); -void profiler_collate(void); - -double profiler_delta(profiler_kind id); -char const *profiler_name(profiler_kind id); - -#define profile(id) defer(profiler_start(id), profiler_stop(id)) +#pragma once +#include "system.h" + +typedef enum { + PROF_TOTAL_TIME, + PROF_MAIN_LOOP, + + PROF_WORLD_WRITE, + PROF_RENDER, + PROF_UPDATE_SYSTEMS, + PROF_ENTITY_LERP, + PROF_INTEGRATE_POS, + + MAX_PROF, + PROF_FORCE_UINT8 = UINT8_MAX +} profiler_kind; + +typedef struct { + profiler_kind id; + char const *name; + + uint32_t num_invocations; + double start_time; + double delta_time; + double total_time; +} profiler; + +void profiler_reset(profiler_kind id); +void profiler_start(profiler_kind id); +void profiler_stop(profiler_kind id); +void profiler_collate(void); + +double profiler_delta(profiler_kind id); +char const *profiler_name(profiler_kind id); + +#define profile(id) defer(profiler_start(id), profiler_stop(id)) diff --git a/code/game/src/renderer_v0.c b/code/game/src/renderer_v0.c index 8f5f648..4a83c17 100644 --- a/code/game/src/renderer_v0.c +++ b/code/game/src/renderer_v0.c @@ -1,209 +1,209 @@ -static Camera2D render_camera; -static float zoom_overlay_tran = 0.0f; - -#define CAM_OVERLAY_ZOOM_LEVEL 0.80f -#define ALPHA(x) ColorAlpha(x, data->tran_time) - -float zpl_lerp(float,float,float); -float zpl_to_degrees(float); - -void DEBUG_draw_ground(uint64_t key, entity_view * data) { - (void)key; - switch (data->kind) { - case EKIND_CHUNK: { - world_view *view = game_world_view_get_active(); - int32_t size = view->chunk_size * WORLD_BLOCK_SIZE; - int16_t offset = 0; - - float x = data->x * size + offset; - float y = data->y * size + offset; - - RenderTexture2D tex = GetChunkTexture(key); - float scale = (size)/(float)(tex.texture.width); - tex.texture.width *= scale; - tex.texture.height *= scale; - DrawTextureRec(tex.texture, (Rectangle){0, 0, size, -size}, (Vector2){x, y}, ColorAlpha(WHITE, data->tran_time)); - - if (zoom_overlay_tran > 0.02f) { - DrawRectangleEco(x, y, size-offset, size-offset, ColorAlpha(ColorFromHSV(data->color, 0.13f, 0.89f), data->tran_time*zoom_overlay_tran*0.75f)); - - DrawTextEco(TextFormat("%d %d", (int)data->x, (int)data->y), (int16_t)x+15, (int16_t)y+15, 200 , ColorAlpha(BLACK, data->tran_time*zoom_overlay_tran), 0.0); - - } - }break; - - default:break; - } -} - -extern bool inv_is_open; - -void DEBUG_draw_entities(uint64_t key, entity_view * data) { - uint16_t size = 16; - uint16_t font_size = (uint16_t)lerp(4.0f, 32.0f, 0.5f/(float)render_camera.zoom); - float font_spacing = 1.1f; - float title_bg_offset = 4; - float fixed_title_offset = 8; - - switch (data->kind) { - case EKIND_DEMO_NPC: { - float x = data->x; - float y = data->y; - DrawCircleEco(x, y, size, ColorAlpha(BLUE, data->tran_time)); - }break; - case EKIND_PLAYER: { - float x = data->x; - float y = data->y; - float health = (data->hp / data->max_hp); - const char *title = TextFormat("Player %d", key); - int title_w = MeasureTextEco(title, font_size, font_spacing); - DrawRectangleEco(x-title_w/2-title_bg_offset/2, y-size-font_size-fixed_title_offset, title_w+title_bg_offset, font_size, ColorAlpha(BLACK, data->tran_time)); - DrawRectangleEco(x-title_w/2-title_bg_offset/2, y-size-fixed_title_offset, title_w*health+title_bg_offset, font_size*0.2f, ColorAlpha(RED, data->tran_time)); - DrawTextEco(title, x-title_w/2, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(RAYWHITE, data->tran_time), font_spacing); - DrawCircleEco(x, y, size, ColorAlpha(YELLOW, data->tran_time)); - - if (data->has_items && !data->inside_vehicle) { - float ix = data->x; - float iy = data->y; - if (data->items[data->selected_item].quantity > 0) { - item_kind it_kind = data->items[data->selected_item].kind; - uint32_t qty = data->items[data->selected_item].quantity; - uint16_t it_id = item_find(it_kind); - DrawTexturePro(GetSpriteTexture2D(assets_find(item_get_asset(it_id))), ASSET_SRC_RECT(), ((Rectangle){ix, iy, 32, 32}), (Vector2){0.5f,0.5f}, 0.0f, ALPHA(WHITE)); - - if (!inv_is_open) - DrawTextEco(zpl_bprintf("%d", qty), ix+24, iy+24, 8, RAYWHITE, 0.0f); - } - } - }break; - case EKIND_MACRO_BOT: { - float x = data->x; - float y = data->y; - const char *title = TextFormat("Bot %d", key); - int title_w = MeasureTextEco(title, font_size, font_spacing); - DrawRectangleEco(x-title_w/2-title_bg_offset/2, y-size-font_size-fixed_title_offset, title_w+title_bg_offset, font_size, ColorAlpha(GRAY, data->tran_time)); - DrawTextEco(title, x-title_w/2, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(BLACK, data->tran_time), font_spacing); - DrawCircleEco(x, y, size, ColorAlpha(PURPLE, data->tran_time)); - }break; - case EKIND_ITEM: { - float x = data->x - 32.f; - float y = data->y - 32.f; - DrawTexturePro(GetSpriteTexture2D(assets_find(data->asset)), ASSET_SRC_RECT(), ASSET_DST_RECT(x,y), (Vector2){0.5f,0.5f}, 0.0f, ALPHA(WHITE)); - DrawTextEco(zpl_bprintf("%d", data->quantity), x, y, 10, ALPHA(RAYWHITE), 0.0f); - }break; - default:break; - } -} - -void DEBUG_draw_entities_low(uint64_t key, entity_view * data) { - (void)key; - - switch (data->kind) { - case EKIND_VEHICLE: { - float x = data->x; - float y = data->y; - float const w = 80; - float const h = 50; - DrawRectanglePro((Rectangle){x,y,w,h}, (Vector2){w/2.0f,h/2.0f}, zpl_to_degrees(data->heading), ColorAlpha(RED, data->tran_time)); - }break; - default:break; - } -} - -void renderer_draw_v0(void) { - render_camera.zoom = zpl_lerp(render_camera.zoom, target_zoom, GetFrameTime()*2.9978f); - camera_update(); - - camera game_camera = camera_get(); - render_camera.target = (Vector2){game_camera.x, game_camera.y}; - zoom_overlay_tran = zpl_lerp(zoom_overlay_tran, (target_zoom <= CAM_OVERLAY_ZOOM_LEVEL) ? 1.0f : 0.0f, GetFrameTime()*2.0f); - - - ClearBackground(GetColor(0x222034)); - BeginMode2D(render_camera); - game_world_view_active_entity_map(DEBUG_draw_ground); - game_world_view_active_entity_map(DEBUG_draw_entities_low); - game_world_view_active_entity_map(DEBUG_draw_entities); - EndMode2D(); -} - -float renderer_zoom_get_v0(void) { - return render_camera.zoom; -} - -void renderer_init_v0(void) { - render_camera.target = (Vector2){0.0f,0.0f}; - render_camera.offset = (Vector2){screenWidth >> 1, screenHeight >> 1}; - render_camera.rotation = 0.0f; - render_camera.zoom = 1.5f; - - // NOTE(zaklaus): Paint the screen before we load the game - // TODO(zaklaus): Render a cool loading screen background maybe? :wink: :wink: - - BeginDrawing(); - ClearBackground(GetColor(0x222034)); - - char const *loading_text = "zpl.eco2d is loading..."; - int text_w = MeasureText(loading_text, 120); - DrawText(loading_text, GetScreenWidth()-text_w-15, GetScreenHeight()-135, 120, RAYWHITE); - EndDrawing(); - - blocks_setup(); - assets_setup(); -} - -void renderer_shutdown_v0(void) { - blocks_destroy(); - assets_destroy(); -} - -void renderer_debug_draw_v0(void) { - BeginMode2D(render_camera); - debug_draw_queue *que = debug_draw_samples(); - - for (size_t i = 0; i < que->num_entries; i += 1) { - debug_draw_entry *e = &que->entries[i]; - Color color = GetColor(e->color); - - switch (e->kind) { - case DDRAW_LINE: { - float x = e->a.x; - float y = e->a.y; - float x2 = e->b.x; - float y2 = e->b.y; - DrawLineV((Vector2){x, y}, (Vector2){x2, y2}, color); - }break; - - case DDRAW_CIRCLE:{ - float x = e->a.x; - float y = e->a.y; - DrawCircleLinesEco(x, y, e->radius, color); - }break; - - case DDRAW_RECT:{ - float x = e->bmin.x; - float y = e->bmin.y; - float w = e->bmax.x - e->bmin.x; - float h = e->bmax.y - e->bmin.y; - DrawRectangleLinesEco(x, y, w, h, color); - }break; - - default: { - - }break; - } - } - - EndMode2D(); -} - -void renderer_draw_single_v0(float x, float y, asset_id id, Color color) { - BeginMode2D(render_camera); - - x -= 32.0f; - y -= 32.0f; - - DrawTexturePro(GetSpriteTexture2D(assets_find(id)), ASSET_SRC_RECT(), ASSET_DST_RECT(x,y), (Vector2){0.5f,0.5f}, 0.0f, color); - - EndMode2D(); -} +static Camera2D render_camera; +static float zoom_overlay_tran = 0.0f; + +#define CAM_OVERLAY_ZOOM_LEVEL 0.80f +#define ALPHA(x) ColorAlpha(x, data->tran_time) + +float zpl_lerp(float,float,float); +float zpl_to_degrees(float); + +void DEBUG_draw_ground(uint64_t key, entity_view * data) { + (void)key; + switch (data->kind) { + case EKIND_CHUNK: { + world_view *view = game_world_view_get_active(); + int32_t size = view->chunk_size * WORLD_BLOCK_SIZE; + int16_t offset = 0; + + float x = data->x * size + offset; + float y = data->y * size + offset; + + RenderTexture2D tex = GetChunkTexture(key); + float scale = (size)/(float)(tex.texture.width); + tex.texture.width *= scale; + tex.texture.height *= scale; + DrawTextureRec(tex.texture, (Rectangle){0, 0, size, -size}, (Vector2){x, y}, ColorAlpha(WHITE, data->tran_time)); + + if (zoom_overlay_tran > 0.02f) { + DrawRectangleEco(x, y, size-offset, size-offset, ColorAlpha(ColorFromHSV(data->color, 0.13f, 0.89f), data->tran_time*zoom_overlay_tran*0.75f)); + + DrawTextEco(TextFormat("%d %d", (int)data->x, (int)data->y), (int16_t)x+15, (int16_t)y+15, 200 , ColorAlpha(BLACK, data->tran_time*zoom_overlay_tran), 0.0); + + } + }break; + + default:break; + } +} + +extern bool inv_is_open; + +void DEBUG_draw_entities(uint64_t key, entity_view * data) { + uint16_t size = 16; + uint16_t font_size = (uint16_t)lerp(4.0f, 32.0f, 0.5f/(float)render_camera.zoom); + float font_spacing = 1.1f; + float title_bg_offset = 4; + float fixed_title_offset = 8; + + switch (data->kind) { + case EKIND_DEMO_NPC: { + float x = data->x; + float y = data->y; + DrawCircleEco(x, y, size, ColorAlpha(BLUE, data->tran_time)); + }break; + case EKIND_PLAYER: { + float x = data->x; + float y = data->y; + float health = (data->hp / data->max_hp); + const char *title = TextFormat("Player %d", key); + int title_w = MeasureTextEco(title, font_size, font_spacing); + DrawRectangleEco(x-title_w/2-title_bg_offset/2, y-size-font_size-fixed_title_offset, title_w+title_bg_offset, font_size, ColorAlpha(BLACK, data->tran_time)); + DrawRectangleEco(x-title_w/2-title_bg_offset/2, y-size-fixed_title_offset, title_w*health+title_bg_offset, font_size*0.2f, ColorAlpha(RED, data->tran_time)); + DrawTextEco(title, x-title_w/2, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(RAYWHITE, data->tran_time), font_spacing); + DrawCircleEco(x, y, size, ColorAlpha(YELLOW, data->tran_time)); + + if (data->has_items && !data->inside_vehicle) { + float ix = data->x; + float iy = data->y; + if (data->items[data->selected_item].quantity > 0) { + item_kind it_kind = data->items[data->selected_item].kind; + uint32_t qty = data->items[data->selected_item].quantity; + uint16_t it_id = item_find(it_kind); + DrawTexturePro(GetSpriteTexture2D(assets_find(item_get_asset(it_id))), ASSET_SRC_RECT(), ((Rectangle){ix, iy, 32, 32}), (Vector2){0.5f,0.5f}, 0.0f, ALPHA(WHITE)); + + if (!inv_is_open) + DrawTextEco(zpl_bprintf("%d", qty), ix+24, iy+24, 8, RAYWHITE, 0.0f); + } + } + }break; + case EKIND_MACRO_BOT: { + float x = data->x; + float y = data->y; + const char *title = TextFormat("Bot %d", key); + int title_w = MeasureTextEco(title, font_size, font_spacing); + DrawRectangleEco(x-title_w/2-title_bg_offset/2, y-size-font_size-fixed_title_offset, title_w+title_bg_offset, font_size, ColorAlpha(GRAY, data->tran_time)); + DrawTextEco(title, x-title_w/2, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(BLACK, data->tran_time), font_spacing); + DrawCircleEco(x, y, size, ColorAlpha(PURPLE, data->tran_time)); + }break; + case EKIND_ITEM: { + float x = data->x - 32.f; + float y = data->y - 32.f; + DrawTexturePro(GetSpriteTexture2D(assets_find(data->asset)), ASSET_SRC_RECT(), ASSET_DST_RECT(x,y), (Vector2){0.5f,0.5f}, 0.0f, ALPHA(WHITE)); + DrawTextEco(zpl_bprintf("%d", data->quantity), x, y, 10, ALPHA(RAYWHITE), 0.0f); + }break; + default:break; + } +} + +void DEBUG_draw_entities_low(uint64_t key, entity_view * data) { + (void)key; + + switch (data->kind) { + case EKIND_VEHICLE: { + float x = data->x; + float y = data->y; + float const w = 80; + float const h = 50; + DrawRectanglePro((Rectangle){x,y,w,h}, (Vector2){w/2.0f,h/2.0f}, zpl_to_degrees(data->heading), ColorAlpha(RED, data->tran_time)); + }break; + default:break; + } +} + +void renderer_draw_v0(void) { + render_camera.zoom = zpl_lerp(render_camera.zoom, target_zoom, GetFrameTime()*2.9978f); + camera_update(); + + camera game_camera = camera_get(); + render_camera.target = (Vector2){game_camera.x, game_camera.y}; + zoom_overlay_tran = zpl_lerp(zoom_overlay_tran, (target_zoom <= CAM_OVERLAY_ZOOM_LEVEL) ? 1.0f : 0.0f, GetFrameTime()*2.0f); + + + ClearBackground(GetColor(0x222034)); + BeginMode2D(render_camera); + game_world_view_active_entity_map(DEBUG_draw_ground); + game_world_view_active_entity_map(DEBUG_draw_entities_low); + game_world_view_active_entity_map(DEBUG_draw_entities); + EndMode2D(); +} + +float renderer_zoom_get_v0(void) { + return render_camera.zoom; +} + +void renderer_init_v0(void) { + render_camera.target = (Vector2){0.0f,0.0f}; + render_camera.offset = (Vector2){screenWidth >> 1, screenHeight >> 1}; + render_camera.rotation = 0.0f; + render_camera.zoom = 1.5f; + + // NOTE(zaklaus): Paint the screen before we load the game + // TODO(zaklaus): Render a cool loading screen background maybe? :wink: :wink: + + BeginDrawing(); + ClearBackground(GetColor(0x222034)); + + char const *loading_text = "zpl.eco2d is loading..."; + int text_w = MeasureText(loading_text, 120); + DrawText(loading_text, GetScreenWidth()-text_w-15, GetScreenHeight()-135, 120, RAYWHITE); + EndDrawing(); + + blocks_setup(); + assets_setup(); +} + +void renderer_shutdown_v0(void) { + blocks_destroy(); + assets_destroy(); +} + +void renderer_debug_draw_v0(void) { + BeginMode2D(render_camera); + debug_draw_queue *que = debug_draw_samples(); + + for (size_t i = 0; i < que->num_entries; i += 1) { + debug_draw_entry *e = &que->entries[i]; + Color color = GetColor(e->color); + + switch (e->kind) { + case DDRAW_LINE: { + float x = e->a.x; + float y = e->a.y; + float x2 = e->b.x; + float y2 = e->b.y; + DrawLineV((Vector2){x, y}, (Vector2){x2, y2}, color); + }break; + + case DDRAW_CIRCLE:{ + float x = e->a.x; + float y = e->a.y; + DrawCircleLinesEco(x, y, e->radius, color); + }break; + + case DDRAW_RECT:{ + float x = e->bmin.x; + float y = e->bmin.y; + float w = e->bmax.x - e->bmin.x; + float h = e->bmax.y - e->bmin.y; + DrawRectangleLinesEco(x, y, w, h, color); + }break; + + default: { + + }break; + } + } + + EndMode2D(); +} + +void renderer_draw_single_v0(float x, float y, asset_id id, Color color) { + BeginMode2D(render_camera); + + x -= 32.0f; + y -= 32.0f; + + DrawTexturePro(GetSpriteTexture2D(assets_find(id)), ASSET_SRC_RECT(), ASSET_DST_RECT(x,y), (Vector2){0.5f,0.5f}, 0.0f, color); + + EndMode2D(); +} diff --git a/code/game/src/utils/raylib_helpers.h b/code/game/src/utils/raylib_helpers.h index 759603c..97ecc2f 100644 --- a/code/game/src/utils/raylib_helpers.h +++ b/code/game/src/utils/raylib_helpers.h @@ -1,377 +1,377 @@ -#pragma once -#include "system.h" -#include "raylib.h" -#include "world/blocks.h" -#include "assets.h" - -#define RAYLIB_NEW_RLGL -#include "rlgl.h" - -static inline float lerp(float a, float b, float t) { return a * (1.0f - t) + b * t; } - -static inline -void DrawTextEco(const char *text, float posX, float posY, int fontSize, Color color, float spacing) { -#if 1 - // Check if default font has been loaded - if (GetFontDefault().texture.id != 0) { - Vector2 position = { (float)posX , (float)posY }; - - int defaultFontSize = 10; // Default Font chars height in pixel - int new_spacing = spacing == 0.0f ? fontSize/defaultFontSize : spacing; - - DrawTextEx(GetFontDefault(), text, position, (float)fontSize , (float)new_spacing , color); - } -#endif -} - -static inline -int MeasureTextEco(const char *text, int fontSize, float spacing) { -#if 1 - Vector2 vec = { 0.0f, 0.0f }; - - // Check if default font has been loaded - if (GetFontDefault().texture.id != 0) { - int defaultFontSize = 10; // Default Font chars height in pixel - int new_spacing = spacing == 0.0f ? fontSize/defaultFontSize : spacing; - - vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)new_spacing); - } - - return (int)vec.x; -#else - return 0; -#endif -} - -static inline -void DrawCircleEco(float centerX, float centerY, float radius, Color color) -{ - DrawCircleV((Vector2){ (float)centerX , (float)centerY }, radius , color); -} - -static inline -void DrawRectangleEco(float posX, float posY, float width, float height, Color color) -{ - DrawRectangleV((Vector2){ (float)posX , (float)posY }, (Vector2){ width , height }, color); -} - -static inline -Texture2D GetBlockImage(uint8_t id) { - return *(Texture2D*)blocks_get_img(id); -} - -static inline -RenderTexture2D GetChunkTexture(uint64_t id) { - RenderTexture2D *tex = (RenderTexture2D*)blocks_get_chunk_tex(id); - if (!tex) return (RenderTexture2D){0}; - return *tex; -} - -static inline -Texture2D GetSpriteTexture2D(uint16_t id) { - return *(Texture2D*)assets_get_tex(id); -} - -static inline -Sound GetSound(uint16_t id) { - return *(Sound*)assets_get_snd(id); -} - -// Draw cube -// NOTE: Cube position is the center position -static inline -void EcoDrawCube(Vector3 position, float width, float height, float length, float heading, Color color) -{ - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - - rlCheckRenderBatchLimit(36); - - rlPushMatrix(); - // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) - rlTranslatef(position.x, position.y, position.z); - rlRotatef(heading, 0, 1, 0); - //rlScalef(1.0f, 1.0f, 1.0f); // NOTE: Vertices are directly scaled on definition - - rlBegin(RL_TRIANGLES); - rlColor4ub(color.r, color.g, color.b, color.a); - - // Front face - rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left - rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right - rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left - - rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right - rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left - rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right - - // Back face - rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left - rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left - rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right - - rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right - rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right - rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left - - // Top face - rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left - rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left - rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right - - rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right - rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left - rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right - - // Bottom face - rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left - rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right - rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left - - rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Right - rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right - rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left - - // Right face - rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right - rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right - rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left - - rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left - rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right - rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left - - // Left face - rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right - rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left - rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right - - rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left - rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left - rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right - rlEnd(); - rlPopMatrix(); -} - -// Draw codepoint at specified position in 3D space -void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint) -{ -#if 0 - // Character index position in sprite font - // NOTE: In case a codepoint is not available in the font, index returned points to '?' - int index = GetGlyphIndex(font, codepoint); - float scale = fontSize/(float)font.baseSize; - - // Character destination rectangle on screen - // NOTE: We consider charsPadding on drawing - position.x += (float)(font.chars[index].offsetX - font.charsPadding)/(float)font.baseSize*scale; - position.z += (float)(font.chars[index].offsetY - font.charsPadding)/(float)font.baseSize*scale; - - // Character source rectangle from font texture atlas - // NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects - Rectangle srcRec = { font.recs[index].x - (float)font.charsPadding, font.recs[index].y - (float)font.charsPadding, - font.recs[index].width + 2.0f*font.charsPadding, font.recs[index].height + 2.0f*font.charsPadding }; - - float width = (float)(font.recs[index].width + 2.0f*font.charsPadding)/(float)font.baseSize*scale; - float height = (float)(font.recs[index].height + 2.0f*font.charsPadding)/(float)font.baseSize*scale; - - if (font.texture.id > 0) - { - const float x = 0.0f; - const float y = 0.0f; - const float z = 0.0f; - - // normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f) - const float tx = srcRec.x/font.texture.width; - const float ty = srcRec.y/font.texture.height; - const float tw = (srcRec.x+srcRec.width)/font.texture.width; - const float th = (srcRec.y+srcRec.height)/font.texture.height; - - { -#if defined(RAYLIB_NEW_RLGL) - } - rlCheckRenderBatchLimit(4 + 4*backface); - rlSetTexture(font.texture.id); -#else - if (rlCheckBufferLimit(4 + 4*backface)) rlglDraw(); - rlEnableTexture(font.texture.id); -#endif - rlPushMatrix(); - rlTranslatef(position.x, position.y, position.z); - - rlBegin(RL_QUADS); - rlColor4ub(tint.r, tint.g, tint.b, tint.a); - - // Front Face - rlNormal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up - rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Left Of The Texture and Quad - rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Left Of The Texture and Quad - rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Right Of The Texture and Quad - rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Right Of The Texture and Quad - - if (backface) - { - // Back Face - rlNormal3f(0.0f, -1.0f, 0.0f); // Normal Pointing Down - rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Right Of The Texture and Quad - rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Left Of The Texture and Quad - rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Left Of The Texture and Quad - rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Right Of The Texture and Quad - } - rlEnd(); - rlPopMatrix(); - -#if defined(RAYLIB_NEW_RLGL) - rlSetTexture(0); -#else - rlDisableTexture(); -#endif - } -#endif -} - -void DrawText3D(Font font, const char *text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint) { -#if 0 - int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop - - float textOffsetY = 0.0f; // Offset between lines (on line break '\n') - float textOffsetX = 0.0f; // Offset X to next character to draw - - float scale = fontSize/(float)font.baseSize; - - for (int i = 0; i < length;) - { - // Get next codepoint from byte string and glyph index in font - int codepointByteCount = 0; - int codepoint = GetCodepoint(&text[i], &codepointByteCount); - int index = GetGlyphIndex(font, codepoint); - - // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointByteCount = 1; - - if (codepoint == '\n') - { - // NOTE: Fixed line spacing of 1.5 line-height - // TODO: Support custom line spacing defined by user - textOffsetY += scale + lineSpacing/(float)font.baseSize*scale; - textOffsetX = 0.0f; - } - else - { - if ((codepoint != ' ') && (codepoint != '\t')) - { - DrawTextCodepoint3D(font, codepoint, (Vector3){ position.x + textOffsetX, position.y, position.z + textOffsetY }, fontSize, backface, tint); - } - - if (font.chars[index].advanceX == 0) textOffsetX += (float)(font.recs[index].width + fontSpacing)/(float)font.baseSize*scale; - else textOffsetX += (float)(font.chars[index].advanceX + fontSpacing)/(float)font.baseSize*scale; - } - - i += codepointByteCount; // Move text bytes counter to next codepoint - - } -#endif -} - -Vector3 MeasureText3D(Font font, const char* text, float fontSize, float fontSpacing, float lineSpacing) { - -#if 0 - int len = TextLength(text); - int tempLen = 0; // Used to count longer text line num chars - int lenCounter = 0; - - float tempTextWidth = 0.0f; // Used to count longer text line width - - float scale = fontSize/(float)font.baseSize; - float textHeight = scale; - float textWidth = 0.0f; - - int letter = 0; // Current character - int index = 0; // Index position in sprite font - - for (int i = 0; i < len; i++) - { - lenCounter++; - - int next = 0; - letter = GetCodepoint(&text[i], &next); - index = GetGlyphIndex(font, letter); - - // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 - if (letter == 0x3f) next = 1; - i += next - 1; - - if (letter != '\n') - { - if (font.chars[index].advanceX != 0) textWidth += (font.chars[index].advanceX+fontSpacing)/(float)font.baseSize*scale; - else textWidth += (font.recs[index].width + font.chars[index].offsetX)/(float)font.baseSize*scale; - } - else - { - if (tempTextWidth < textWidth) tempTextWidth = textWidth; - lenCounter = 0; - textWidth = 0.0f; - textHeight += scale + lineSpacing/(float)font.baseSize*scale; - } - - if (tempLen < lenCounter) tempLen = lenCounter; - } - - if (tempTextWidth < textWidth) tempTextWidth = textWidth; - - Vector3 vec = { 0 }; - vec.x = tempTextWidth + (float)((tempLen - 1)*fontSpacing/(float)font.baseSize*scale); // Adds chars spacing to measure - vec.y = 0.25f; - vec.z = textHeight; - - return vec; -#endif - -} - - -Color GenerateRandomColor(float s, float v) { - const float Phi = 0.618033988749895f; // Golden ratio conjugate - float h = GetRandomValue(0, 360); - h = fmodf((h + h*Phi), 360.0f); - return ColorFromHSV(h, s, v); -} - - -// Draw circle outline -void DrawCircleLinesEco(float centerX, float centerY, float radius, Color color) -{ - rlCheckRenderBatchLimit(2*36); - - rlBegin(RL_LINES); - rlColor4ub(color.r, color.g, color.b, color.a); - - // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) - for (int i = 0; i < 360; i += 10) - { - rlVertex2f(centerX + sinf(DEG2RAD*i)*radius, centerY + cosf(DEG2RAD*i)*radius); - rlVertex2f(centerX + sinf(DEG2RAD*(i + 10))*radius, centerY + cosf(DEG2RAD*(i + 10))*radius); - } - rlEnd(); -} - -void DrawRectangleLinesEco(float posX, float posY, float width, float height, Color color) -{ - rlBegin(RL_LINES); - rlColor4ub(color.r, color.g, color.b, color.a); - rlVertex2f(posX + 1, posY + 1); - rlVertex2f(posX + width, posY + 1); - - rlVertex2f(posX + width, posY + 1); - rlVertex2f(posX + width, posY + height); - - rlVertex2f(posX + width, posY + height); - rlVertex2f(posX + 1, posY + height); - - rlVertex2f(posX + 1, posY + height); - rlVertex2f(posX + 1, posY + 1); - rlEnd(); -} +#pragma once +#include "system.h" +#include "raylib.h" +#include "world/blocks.h" +#include "assets.h" + +#define RAYLIB_NEW_RLGL +#include "rlgl.h" + +static inline float lerp(float a, float b, float t) { return a * (1.0f - t) + b * t; } + +static inline +void DrawTextEco(const char *text, float posX, float posY, int fontSize, Color color, float spacing) { +#if 1 + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) { + Vector2 position = { (float)posX , (float)posY }; + + int defaultFontSize = 10; // Default Font chars height in pixel + int new_spacing = spacing == 0.0f ? fontSize/defaultFontSize : spacing; + + DrawTextEx(GetFontDefault(), text, position, (float)fontSize , (float)new_spacing , color); + } +#endif +} + +static inline +int MeasureTextEco(const char *text, int fontSize, float spacing) { +#if 1 + Vector2 vec = { 0.0f, 0.0f }; + + // Check if default font has been loaded + if (GetFontDefault().texture.id != 0) { + int defaultFontSize = 10; // Default Font chars height in pixel + int new_spacing = spacing == 0.0f ? fontSize/defaultFontSize : spacing; + + vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)new_spacing); + } + + return (int)vec.x; +#else + return 0; +#endif +} + +static inline +void DrawCircleEco(float centerX, float centerY, float radius, Color color) +{ + DrawCircleV((Vector2){ (float)centerX , (float)centerY }, radius , color); +} + +static inline +void DrawRectangleEco(float posX, float posY, float width, float height, Color color) +{ + DrawRectangleV((Vector2){ (float)posX , (float)posY }, (Vector2){ width , height }, color); +} + +static inline +Texture2D GetBlockImage(uint8_t id) { + return *(Texture2D*)blocks_get_img(id); +} + +static inline +RenderTexture2D GetChunkTexture(uint64_t id) { + RenderTexture2D *tex = (RenderTexture2D*)blocks_get_chunk_tex(id); + if (!tex) return (RenderTexture2D){0}; + return *tex; +} + +static inline +Texture2D GetSpriteTexture2D(uint16_t id) { + return *(Texture2D*)assets_get_tex(id); +} + +static inline +Sound GetSound(uint16_t id) { + return *(Sound*)assets_get_snd(id); +} + +// Draw cube +// NOTE: Cube position is the center position +static inline +void EcoDrawCube(Vector3 position, float width, float height, float length, float heading, Color color) +{ + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + + rlCheckRenderBatchLimit(36); + + rlPushMatrix(); + // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) + rlTranslatef(position.x, position.y, position.z); + rlRotatef(heading, 0, 1, 0); + //rlScalef(1.0f, 1.0f, 1.0f); // NOTE: Vertices are directly scaled on definition + + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // Front face + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + + // Back face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + + // Top face + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right + + // Bottom face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + + rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right + rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left + + // Right face + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left + + // Left face + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right + + rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left + rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left + rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right + rlEnd(); + rlPopMatrix(); +} + +// Draw codepoint at specified position in 3D space +void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint) +{ +#if 0 + // Character index position in sprite font + // NOTE: In case a codepoint is not available in the font, index returned points to '?' + int index = GetGlyphIndex(font, codepoint); + float scale = fontSize/(float)font.baseSize; + + // Character destination rectangle on screen + // NOTE: We consider charsPadding on drawing + position.x += (float)(font.chars[index].offsetX - font.charsPadding)/(float)font.baseSize*scale; + position.z += (float)(font.chars[index].offsetY - font.charsPadding)/(float)font.baseSize*scale; + + // Character source rectangle from font texture atlas + // NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects + Rectangle srcRec = { font.recs[index].x - (float)font.charsPadding, font.recs[index].y - (float)font.charsPadding, + font.recs[index].width + 2.0f*font.charsPadding, font.recs[index].height + 2.0f*font.charsPadding }; + + float width = (float)(font.recs[index].width + 2.0f*font.charsPadding)/(float)font.baseSize*scale; + float height = (float)(font.recs[index].height + 2.0f*font.charsPadding)/(float)font.baseSize*scale; + + if (font.texture.id > 0) + { + const float x = 0.0f; + const float y = 0.0f; + const float z = 0.0f; + + // normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f) + const float tx = srcRec.x/font.texture.width; + const float ty = srcRec.y/font.texture.height; + const float tw = (srcRec.x+srcRec.width)/font.texture.width; + const float th = (srcRec.y+srcRec.height)/font.texture.height; + + { +#if defined(RAYLIB_NEW_RLGL) + } + rlCheckRenderBatchLimit(4 + 4*backface); + rlSetTexture(font.texture.id); +#else + if (rlCheckBufferLimit(4 + 4*backface)) rlglDraw(); + rlEnableTexture(font.texture.id); +#endif + rlPushMatrix(); + rlTranslatef(position.x, position.y, position.z); + + rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + + // Front Face + rlNormal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up + rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Left Of The Texture and Quad + rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Left Of The Texture and Quad + rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Right Of The Texture and Quad + rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Right Of The Texture and Quad + + if (backface) + { + // Back Face + rlNormal3f(0.0f, -1.0f, 0.0f); // Normal Pointing Down + rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Right Of The Texture and Quad + rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Left Of The Texture and Quad + rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Left Of The Texture and Quad + rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Right Of The Texture and Quad + } + rlEnd(); + rlPopMatrix(); + +#if defined(RAYLIB_NEW_RLGL) + rlSetTexture(0); +#else + rlDisableTexture(); +#endif + } +#endif +} + +void DrawText3D(Font font, const char *text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint) { +#if 0 + int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop + + float textOffsetY = 0.0f; // Offset between lines (on line break '\n') + float textOffsetX = 0.0f; // Offset X to next character to draw + + float scale = fontSize/(float)font.baseSize; + + for (int i = 0; i < length;) + { + // Get next codepoint from byte string and glyph index in font + int codepointByteCount = 0; + int codepoint = GetCodepoint(&text[i], &codepointByteCount); + int index = GetGlyphIndex(font, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointByteCount = 1; + + if (codepoint == '\n') + { + // NOTE: Fixed line spacing of 1.5 line-height + // TODO: Support custom line spacing defined by user + textOffsetY += scale + lineSpacing/(float)font.baseSize*scale; + textOffsetX = 0.0f; + } + else + { + if ((codepoint != ' ') && (codepoint != '\t')) + { + DrawTextCodepoint3D(font, codepoint, (Vector3){ position.x + textOffsetX, position.y, position.z + textOffsetY }, fontSize, backface, tint); + } + + if (font.chars[index].advanceX == 0) textOffsetX += (float)(font.recs[index].width + fontSpacing)/(float)font.baseSize*scale; + else textOffsetX += (float)(font.chars[index].advanceX + fontSpacing)/(float)font.baseSize*scale; + } + + i += codepointByteCount; // Move text bytes counter to next codepoint + + } +#endif +} + +Vector3 MeasureText3D(Font font, const char* text, float fontSize, float fontSpacing, float lineSpacing) { + +#if 0 + int len = TextLength(text); + int tempLen = 0; // Used to count longer text line num chars + int lenCounter = 0; + + float tempTextWidth = 0.0f; // Used to count longer text line width + + float scale = fontSize/(float)font.baseSize; + float textHeight = scale; + float textWidth = 0.0f; + + int letter = 0; // Current character + int index = 0; // Index position in sprite font + + for (int i = 0; i < len; i++) + { + lenCounter++; + + int next = 0; + letter = GetCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) next = 1; + i += next - 1; + + if (letter != '\n') + { + if (font.chars[index].advanceX != 0) textWidth += (font.chars[index].advanceX+fontSpacing)/(float)font.baseSize*scale; + else textWidth += (font.recs[index].width + font.chars[index].offsetX)/(float)font.baseSize*scale; + } + else + { + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + lenCounter = 0; + textWidth = 0.0f; + textHeight += scale + lineSpacing/(float)font.baseSize*scale; + } + + if (tempLen < lenCounter) tempLen = lenCounter; + } + + if (tempTextWidth < textWidth) tempTextWidth = textWidth; + + Vector3 vec = { 0 }; + vec.x = tempTextWidth + (float)((tempLen - 1)*fontSpacing/(float)font.baseSize*scale); // Adds chars spacing to measure + vec.y = 0.25f; + vec.z = textHeight; + + return vec; +#endif + +} + + +Color GenerateRandomColor(float s, float v) { + const float Phi = 0.618033988749895f; // Golden ratio conjugate + float h = GetRandomValue(0, 360); + h = fmodf((h + h*Phi), 360.0f); + return ColorFromHSV(h, s, v); +} + + +// Draw circle outline +void DrawCircleLinesEco(float centerX, float centerY, float radius, Color color) +{ + rlCheckRenderBatchLimit(2*36); + + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + + // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) + for (int i = 0; i < 360; i += 10) + { + rlVertex2f(centerX + sinf(DEG2RAD*i)*radius, centerY + cosf(DEG2RAD*i)*radius); + rlVertex2f(centerX + sinf(DEG2RAD*(i + 10))*radius, centerY + cosf(DEG2RAD*(i + 10))*radius); + } + rlEnd(); +} + +void DrawRectangleLinesEco(float posX, float posY, float width, float height, Color color) +{ + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(posX + 1, posY + 1); + rlVertex2f(posX + width, posY + 1); + + rlVertex2f(posX + width, posY + 1); + rlVertex2f(posX + width, posY + height); + + rlVertex2f(posX + width, posY + height); + rlVertex2f(posX + 1, posY + height); + + rlVertex2f(posX + 1, posY + height); + rlVertex2f(posX + 1, posY + 1); + rlEnd(); +} diff --git a/code/game/src/vehicle.c b/code/game/src/vehicle.c index 3d6d463..ee56989 100644 --- a/code/game/src/vehicle.c +++ b/code/game/src/vehicle.c @@ -1,23 +1,23 @@ -#include "vehicle.h" -#include "entity.h" -#include "entity_view.h" -#include "world/world.h" - -#include "modules/components.h" - -uint64_t vehicle_spawn(void) { - ecs_entity_t e = entity_spawn(EKIND_VEHICLE); - - Vehicle *veh = ecs_get_mut(world_ecs(), e, Vehicle, NULL); - *veh = (Vehicle){ - .wheel_base = 50.0f, - .speed = 50.0f, - .reverse_speed = -20.0f, - .force = 0.0f, - }; - return (uint64_t)e; -} - -void vehicle_despawn(uint64_t ent_id) { - entity_despawn(ent_id); -} +#include "vehicle.h" +#include "entity.h" +#include "entity_view.h" +#include "world/world.h" + +#include "modules/components.h" + +uint64_t vehicle_spawn(void) { + ecs_entity_t e = entity_spawn(EKIND_VEHICLE); + + Vehicle *veh = ecs_get_mut(world_ecs(), e, Vehicle, NULL); + *veh = (Vehicle){ + .wheel_base = 50.0f, + .speed = 50.0f, + .reverse_speed = -20.0f, + .force = 0.0f, + }; + return (uint64_t)e; +} + +void vehicle_despawn(uint64_t ent_id) { + entity_despawn(ent_id); +} diff --git a/code/game/src/world/blocks.c b/code/game/src/world/blocks.c index 107d5e3..43d0af0 100644 --- a/code/game/src/world/blocks.c +++ b/code/game/src/world/blocks.c @@ -1,144 +1,144 @@ -#define ZPL_NANO -#include "zpl.h" -#include "world/world.h" -#include "world/blocks.h" -#include "raylib.h" -#include "gen/texgen.h" -#include "world_view.h" -#include "perlin.h" - -#define BLOCKS_COUNT (sizeof(blocks)/sizeof(block)) -#define WORLD_TEXTURE_BLOCK_SCALE 0.5f - -ZPL_TABLE(static, blocks__chunk_tbl, blocks__chunk_tbl_, RenderTexture2D); - -static blocks__chunk_tbl baked_chunks; - -static void chunks_unload_textures(uint64_t key, RenderTexture2D *value) { - (void)key; - UnloadRenderTexture(*value); -} - -typedef struct { - char *name; - uint32_t flags; - uint32_t kind; - uint32_t biome; - char symbol; - float drag; - float friction; - float bounce; - - // NOTE(zaklaus): viewer data - Texture2D img; -} block; - -#include "blocks_list.c" - -int32_t blocks_setup(void) { - for (uint32_t i=0; iimg = texgen_build_block(b->biome, b->kind); - } - - blocks__chunk_tbl_init(&baked_chunks, zpl_heap()); - return 0; -} - -void blocks_destroy(void) { - for (uint32_t i=0; ichunk_size; - RenderTexture2D canvas = LoadRenderTexture(dims, dims); - BeginTextureMode(canvas); - ClearBackground(WHITE); - for (int y = 0; y < view->chunk_size; y += 1) { - for (int x = 0; x < view->chunk_size; x += 1) { -#if 0 - Texture2D blk = blocks[chunk_blocks[(y*view->chunk_size)+x]].img; - Rectangle src = {0, 0, WORLD_BLOCK_SIZE, WORLD_BLOCK_SIZE}; - Rectangle dst = {x*blk_dims, y*blk_dims, blk_dims, blk_dims}; - DrawTexturePro(blk, src, dst, (Vector2){0.0f,0.0f}, 0.0f, WHITE); -#else - static float rots[] = { 0.0f, 90.0f, 180.f, 270.0f }; - float rot = rots[(int32_t)(perlin_fbm(view->seed, x, y, 1.2f, 3) * 4.0f) % 4]; - float half_block = blk_dims / 2.0f; - Texture2D blk = blocks[chunk_blocks[(y*view->chunk_size)+x]].img; - Rectangle src = {0, 0, WORLD_BLOCK_SIZE, WORLD_BLOCK_SIZE}; - Rectangle dst = {x*blk_dims + half_block, y*blk_dims + half_block, blk_dims, blk_dims}; - DrawTexturePro(blk, src, dst, (Vector2){half_block, half_block}, rot, WHITE); - - if (outer_chunk_blocks[(y*view->chunk_size)+x] != 0) { - Texture2D blk2 = blocks[outer_chunk_blocks[(y*view->chunk_size)+x]].img; - DrawTexturePro(blk2, src, dst, (Vector2){half_block, half_block}, rot, WHITE); - } -#endif - } - } - EndTextureMode(); - blocks__chunk_tbl_set(&baked_chunks, id, canvas); -} - -void *blocks_get_chunk_tex(uint64_t id) { - return blocks__chunk_tbl_get(&baked_chunks, id); -} - -void blocks_remove_chunk_tex(uint64_t id) { - RenderTexture2D *tex = blocks__chunk_tbl_get(&baked_chunks, id); - if (!tex) return; - UnloadRenderTexture(*tex); - blocks__chunk_tbl_remove(&baked_chunks, id); +#define ZPL_NANO +#include "zpl.h" +#include "world/world.h" +#include "world/blocks.h" +#include "raylib.h" +#include "gen/texgen.h" +#include "world_view.h" +#include "perlin.h" + +#define BLOCKS_COUNT (sizeof(blocks)/sizeof(block)) +#define WORLD_TEXTURE_BLOCK_SCALE 0.5f + +ZPL_TABLE(static, blocks__chunk_tbl, blocks__chunk_tbl_, RenderTexture2D); + +static blocks__chunk_tbl baked_chunks; + +static void chunks_unload_textures(uint64_t key, RenderTexture2D *value) { + (void)key; + UnloadRenderTexture(*value); +} + +typedef struct { + char *name; + uint32_t flags; + uint32_t kind; + uint32_t biome; + char symbol; + float drag; + float friction; + float bounce; + + // NOTE(zaklaus): viewer data + Texture2D img; +} block; + +#include "blocks_list.c" + +int32_t blocks_setup(void) { + for (uint32_t i=0; iimg = texgen_build_block(b->biome, b->kind); + } + + blocks__chunk_tbl_init(&baked_chunks, zpl_heap()); + return 0; +} + +void blocks_destroy(void) { + for (uint32_t i=0; ichunk_size; + RenderTexture2D canvas = LoadRenderTexture(dims, dims); + BeginTextureMode(canvas); + ClearBackground(WHITE); + for (int y = 0; y < view->chunk_size; y += 1) { + for (int x = 0; x < view->chunk_size; x += 1) { +#if 0 + Texture2D blk = blocks[chunk_blocks[(y*view->chunk_size)+x]].img; + Rectangle src = {0, 0, WORLD_BLOCK_SIZE, WORLD_BLOCK_SIZE}; + Rectangle dst = {x*blk_dims, y*blk_dims, blk_dims, blk_dims}; + DrawTexturePro(blk, src, dst, (Vector2){0.0f,0.0f}, 0.0f, WHITE); +#else + static float rots[] = { 0.0f, 90.0f, 180.f, 270.0f }; + float rot = rots[(int32_t)(perlin_fbm(view->seed, x, y, 1.2f, 3) * 4.0f) % 4]; + float half_block = blk_dims / 2.0f; + Texture2D blk = blocks[chunk_blocks[(y*view->chunk_size)+x]].img; + Rectangle src = {0, 0, WORLD_BLOCK_SIZE, WORLD_BLOCK_SIZE}; + Rectangle dst = {x*blk_dims + half_block, y*blk_dims + half_block, blk_dims, blk_dims}; + DrawTexturePro(blk, src, dst, (Vector2){half_block, half_block}, rot, WHITE); + + if (outer_chunk_blocks[(y*view->chunk_size)+x] != 0) { + Texture2D blk2 = blocks[outer_chunk_blocks[(y*view->chunk_size)+x]].img; + DrawTexturePro(blk2, src, dst, (Vector2){half_block, half_block}, rot, WHITE); + } +#endif + } + } + EndTextureMode(); + blocks__chunk_tbl_set(&baked_chunks, id, canvas); +} + +void *blocks_get_chunk_tex(uint64_t id) { + return blocks__chunk_tbl_get(&baked_chunks, id); +} + +void blocks_remove_chunk_tex(uint64_t id) { + RenderTexture2D *tex = blocks__chunk_tbl_get(&baked_chunks, id); + if (!tex) return; + UnloadRenderTexture(*tex); + blocks__chunk_tbl_remove(&baked_chunks, id); } \ No newline at end of file diff --git a/code/game/src/world/world.c b/code/game/src/world/world.c index 4d756b2..a019c39 100644 --- a/code/game/src/world/world.c +++ b/code/game/src/world/world.c @@ -1,509 +1,509 @@ -#include "zpl.h" -#include "librg.h" -#include "modules/components.h" -#include "modules/systems.h" -#include "world/world.h" -#include "entity_view.h" -#include "debug_replay.h" -#include "items.h" -#include "world/worldgen/worldgen.h" -#include "platform.h" -#include "profiler.h" -#include "game.h" - -#include "packets/pkt_send_librg_update.h" - -ZPL_TABLE(static, world_snapshot, world_snapshot_, entity_view); - -static world_data world = {0}; -static world_snapshot streamer_snapshot; - -entity_view world_build_entity_view(int64_t e) { - entity_view *cached_ev = world_snapshot_get(&streamer_snapshot, e); - if (cached_ev) return *cached_ev; - - entity_view view = {0}; - - const Classify *classify = ecs_get(world_ecs(), e, Classify); - ZPL_ASSERT(classify); - - view.kind = classify->id; - - const Position *pos = ecs_get(world_ecs(), e, Position); - if (pos) { - view.x = pos->x; - view.y = pos->y; - } - - const Velocity *vel = ecs_get(world_ecs(), e, Velocity); - if (vel) { - view.flag |= EFLAG_INTERP; - view.vx = vel->x; - view.vy = vel->y; - } - - const Health *health = ecs_get(world_ecs(), e, Health); - if (health) { - view.hp = health->hp; - view.max_hp = health->max_hp; - } - - if (ecs_get(world_ecs(), e, Vehicle)) { - Vehicle const* veh = ecs_get(world_ecs(), e, Vehicle); - view.heading = veh->heading; - } - - if (ecs_get(world_ecs(), e, ItemDrop)) { - ItemDrop const* dr = ecs_get(world_ecs(), e, ItemDrop); - view.asset = item_get_asset(dr->kind); - view.quantity = dr->quantity; - } - - view.inside_vehicle = ecs_get(world_ecs(), e, IsInVehicle) != 0 ? true : false; - - Inventory *inv = 0; - if ((inv = ecs_get_mut_if(world_ecs(), e, Inventory))) { - view.has_items = true; - - for (int i = 0; i < ITEMS_INVENTORY_SIZE; i += 1) { - view.items[i] = inv->items[i]; - } - - const Input *in = ecs_get(world_ecs(), e, Input); - if (in) - view.selected_item = in->selected_item; - } - - Chunk *chunk = 0; - if ((chunk = ecs_get_mut_if(world_ecs(), e, Chunk))) { - view.x = chunk->x; - view.y = chunk->y; - view.blocks_used = 1; - view.is_dirty = chunk->is_dirty; - chunk->is_dirty = false; - - for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) { - view.blocks[i] = world.block_mapping[chunk->id][i]; - } - - for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) { - view.outer_blocks[i] = world.outer_block_mapping[chunk->id][i]; - } - } - - world_snapshot_set(&streamer_snapshot, e, view); - return view; -} - -int32_t tracker_write_create(librg_world *w, librg_event *e) { - int64_t entity_id = librg_event_entity_get(w, e); -#ifdef WORLD_LAYERING - if (world.active_layer_id != WORLD_TRACKER_LAYERS-1) { - // NOTE(zaklaus): reject updates from smaller layers - return LIBRG_WRITE_REJECT; - } -#endif - size_t actual_length = librg_event_size_get(w, e); - char *buffer = librg_event_buffer_get(w, e); - - return (int32_t)entity_view_pack_struct(buffer, actual_length, world_build_entity_view(entity_id)); -} - -int32_t tracker_write_remove(librg_world *w, librg_event *e) { - (void)e; - (void)w; -#ifdef WORLD_LAYERING - if (world.active_layer_id != WORLD_TRACKER_LAYERS-1) { - // NOTE(zaklaus): reject updates from smaller layers - return LIBRG_WRITE_REJECT; - } -#endif - return 0; -} - -int32_t tracker_write_update(librg_world *w, librg_event *e) { - int64_t entity_id = librg_event_entity_get(w, e); - size_t actual_length = librg_event_size_get(w, e); - char *buffer = librg_event_buffer_get(w, e); - entity_view view = world_build_entity_view(entity_id); - - // NOTE(zaklaus): exclude chunks from updates as they never move - { - if (view.kind == EKIND_CHUNK && !view.is_dirty) { - return LIBRG_WRITE_REJECT; - } - } - - return (int32_t)entity_view_pack_struct(buffer, actual_length, view); -} - -void world_setup_pkt_handlers(world_pkt_reader_proc *reader_proc, world_pkt_writer_proc *writer_proc) { - world.reader_proc = reader_proc; - world.writer_proc = writer_proc; -} - -int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) { - if (world.data) { - return 0; - } - - world.is_paused = false; - world.seed = seed; - world.chunk_size = chunk_size; - world.chunk_amount = chunk_amount; - - world.dim = (world.chunk_size * world.chunk_amount); - world.size = world.dim * world.dim; - - if (world.tracker == NULL) { - world.tracker = librg_world_create(); - } - - if (world.tracker == NULL) { - zpl_printf("[ERROR] An error occurred while trying to create a server world.\n"); - return WORLD_ERROR_TRACKER_FAILED; - } - - /* config our world grid */ - librg_config_chunksize_set(world.tracker, WORLD_BLOCK_SIZE * world.chunk_size, WORLD_BLOCK_SIZE * world.chunk_size, 0); - librg_config_chunkamount_set(world.tracker, world.chunk_amount, world.chunk_amount, 0); - librg_config_chunkoffset_set(world.tracker, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG); - - librg_event_set(world.tracker, LIBRG_WRITE_CREATE, tracker_write_create); - librg_event_set(world.tracker, LIBRG_WRITE_REMOVE, tracker_write_remove); - librg_event_set(world.tracker, LIBRG_WRITE_UPDATE, tracker_write_update); - - world.data = zpl_malloc(sizeof(uint8_t)*world.size); - - if (!world.data) { - return WORLD_ERROR_OUTOFMEM; - } - - world.ecs = ecs_init(); - - ECS_IMPORT(world.ecs, Components); - ECS_IMPORT(world.ecs, Systems); - world.ecs_update = ecs_query_new(world.ecs, "components.ClientInfo, components.Position"); - world.chunk_mapping = zpl_malloc(sizeof(ecs_entity_t)*zpl_square(chunk_amount)); - world.block_mapping = zpl_malloc(sizeof(uint8_t*)*zpl_square(chunk_amount)); - world.outer_block_mapping = zpl_malloc(sizeof(uint8_t*)*zpl_square(chunk_amount)); - world_snapshot_init(&streamer_snapshot, zpl_heap()); - - int32_t world_build_status = worldgen_test(&world); - ZPL_ASSERT(world_build_status >= 0); - - for (int i = 0; i < world.chunk_amount * world.chunk_amount; ++i) { - ecs_entity_t e = ecs_new(world.ecs, 0); - ecs_set(world.ecs, e, Classify, {.id = EKIND_CHUNK }); - Chunk *chunk = ecs_get_mut(world.ecs, e, Chunk, NULL); - librg_entity_track(world.tracker, e); - librg_entity_chunk_set(world.tracker, e, i); - librg_chunk_to_chunkpos(world.tracker, i, &chunk->x, &chunk->y, NULL); - world.chunk_mapping[i] = e; - world.block_mapping[i] = zpl_malloc(sizeof(uint8_t)*zpl_square(chunk_size)); - world.outer_block_mapping[i] = zpl_malloc(sizeof(uint8_t)*zpl_square(chunk_size)); - chunk->id = i; - chunk->is_dirty = false; - - for (int y = 0; y < chunk_size; y += 1) { - for (int x = 0; x < chunk_size; x += 1) { - int chk_x = chunk->x * chunk_size; - int chk_y = chunk->y * chunk_size; - - uint8_t *c = &world.block_mapping[i][(y*chunk_size)+x]; - *c = world.data[(chk_y+y)*world.dim + (chk_x+x)]; - - c = &world.outer_block_mapping[i][(y*chunk_size)+x]; - *c = 0; - } - } - } - - zpl_mfree(world.data); - world.data = NULL; - - zpl_printf("[INFO] Created a new server world\n"); - - return world_build_status; -} - -int32_t world_destroy(void) { - librg_world_destroy(world.tracker); - ecs_fini(world.ecs); - zpl_mfree(world.chunk_mapping); - for (int i = 0; i < zpl_square(world.chunk_amount); i+=1) { - zpl_mfree(world.block_mapping[i]); - zpl_mfree(world.outer_block_mapping[i]); - } - zpl_mfree(world.block_mapping); - zpl_mfree(world.outer_block_mapping); - world_snapshot_destroy(&streamer_snapshot); - zpl_memset(&world, 0, sizeof(world)); - zpl_printf("[INFO] World was destroyed.\n"); - return WORLD_ERROR_NONE; -} - -#define WORLD_LIBRG_BUFSIZ 2000000 - -static void world_tracker_update(uint8_t ticker, uint32_t freq, uint8_t radius) { - if (world.tracker_update[ticker] > zpl_time_rel_ms()) return; - world.tracker_update[ticker] = zpl_time_rel_ms() + freq; - - profile(PROF_WORLD_WRITE) { - ecs_iter_t it = ecs_query_iter(world.ecs_update); - static char buffer[WORLD_LIBRG_BUFSIZ] = {0}; - world.active_layer_id = ticker; - - while (ecs_query_next(&it)) { - ClientInfo *p = ecs_column(&it, ClientInfo, 1); - - for (int i = 0; i < it.count; i++) { - size_t datalen = WORLD_LIBRG_BUFSIZ; - - // TODO(zaklaus): SUPER TEMPORARY HOT !!! simulate variable radius queries - { - librg_entity_radius_set(world_tracker(), it.entities[i], radius); - } - // TODO(zaklaus): push radius once librg patch comes in - int32_t result = librg_world_write(world_tracker(), it.entities[i], buffer, &datalen, NULL); - - if (result > 0) { - zpl_printf("[info] buffer size was not enough, please increase it by at least: %d\n", result); - } else if (result < 0) { - zpl_printf("[error] an error happened writing the world %d\n", result); - } - - pkt_send_librg_update((uint64_t)p[i].peer, p[i].view_id, ticker, buffer, datalen); - } - } - - // NOTE(zaklaus): clear out our streaming snapshot - // TODO(zaklaus): move this to zpl - { - zpl_array_clear(streamer_snapshot.hashes); - zpl_array_clear(streamer_snapshot.entries); - } - } -} - -int32_t world_update() { - profile (PROF_UPDATE_SYSTEMS) { - ecs_progress(world.ecs, 0.0f); - } - - uint32_t fast_ms = WORLD_TRACKER_UPDATE_FAST_MS; - uint32_t normal_ms = WORLD_TRACKER_UPDATE_NORMAL_MS; - uint32_t slow_ms = WORLD_TRACKER_UPDATE_SLOW_MS; - - if (game_get_kind() != GAMEKIND_SINGLE) { - fast_ms = WORLD_TRACKER_UPDATE_MP_FAST_MS; - normal_ms = WORLD_TRACKER_UPDATE_MP_NORMAL_MS; - slow_ms = WORLD_TRACKER_UPDATE_MP_SLOW_MS; - } - - world_tracker_update(0, fast_ms, 2); - world_tracker_update(1, normal_ms, 4); - world_tracker_update(2, slow_ms, 6); - - debug_replay_update(); - return 0; -} - -int32_t world_read(void* data, uint32_t datalen, void *udata) { - if (world.reader_proc) { - return world.reader_proc(data, datalen, udata); - } - return -1; -} - -int32_t world_write(pkt_header *pkt, void *udata) { - if (world.writer_proc) { - return world.writer_proc(pkt, udata); - } - return -1; -} - -uint32_t world_buf(uint8_t const **ptr, uint32_t *width) { - ZPL_ASSERT_NOT_NULL(world.data); - ZPL_ASSERT_NOT_NULL(ptr); - *ptr = world.data; - if (width) *width = world.dim; - return world.size; -} - -uint32_t world_seed(void) { - return world.seed; -} - -ecs_world_t * world_ecs() { - if (world.ecs_stage != NULL) { - return world.ecs_stage; - } - return world.ecs; -} - -void world_set_stage(ecs_world_t *ecs) { - world.ecs_stage = ecs; -} - -librg_world *world_tracker() { - return world.tracker; -} - -void world_pause(void) { - ecs_set_time_scale(world.ecs, 0.0f); - world.is_paused = true; -} - -void world_resume(void) { - ecs_set_time_scale(world.ecs, 1.0f); - world.is_paused = false; -} - -bool world_is_paused(void) { - return world.is_paused; -} - -void world_step(float step_size) { - world_resume(); - ecs_progress(world.ecs, step_size); - world_pause(); -} - -uint16_t world_chunk_size(void) { - return world.chunk_size; -} - -uint16_t world_chunk_amount(void) { - return world.chunk_amount; -} - -uint16_t world_dim(void) { - return WORLD_BLOCK_SIZE * world.chunk_size * world.chunk_amount; -} - -ecs_entity_t world_chunk_mapping(librg_chunk id) { - ZPL_ASSERT(id >= 0 && id < zpl_square(world.chunk_amount)); - return world.chunk_mapping[id]; -} - -world_block_lookup world_block_from_realpos(float x, float y) { - x = zpl_clamp(x, 0, world_dim()-1); - y = zpl_clamp(y, 0, world_dim()-1); - librg_chunk chunk_id = librg_chunk_from_realpos(world.tracker, x, y, 0); - ecs_entity_t e = world.chunk_mapping[chunk_id]; - int32_t size = world.chunk_size * WORLD_BLOCK_SIZE; - int16_t chunk_x, chunk_y; - librg_chunk_to_chunkpos(world.tracker, chunk_id, &chunk_x, &chunk_y, NULL); - - // NOTE(zaklaus): pos relative to chunk - float chx = x - chunk_x * size; - float chy = y - chunk_y * size; - - uint32_t bx = (uint32_t)chx / WORLD_BLOCK_SIZE; - uint32_t by = (uint32_t)chy / WORLD_BLOCK_SIZE; - uint32_t block_idx = (by*world.chunk_size)+bx; - uint8_t block_id = world.outer_block_mapping[chunk_id][block_idx]; - if (block_id == 0) { - block_id = world.block_mapping[chunk_id][block_idx]; - } - - // NOTE(zaklaus): pos relative to block's center - float box = chx - bx * WORLD_BLOCK_SIZE - WORLD_BLOCK_SIZE/2.0f; - float boy = chy - by * WORLD_BLOCK_SIZE - WORLD_BLOCK_SIZE/2.0f; - - world_block_lookup lookup = { - .id = block_idx, - .block_id = block_id, - .chunk_id = chunk_id, - .chunk_e = e, - .ox = box, - .oy = boy, - }; - - return lookup; -} - -world_block_lookup world_block_from_index(int64_t id, uint16_t block_idx) { - uint8_t block_id = world.outer_block_mapping[id][block_idx]; - if (block_id == 0) { - block_id = world.block_mapping[id][block_idx]; - } - - world_block_lookup lookup = { - .id = block_idx, - .block_id = block_id, - .chunk_id = id, - .chunk_e = world.chunk_mapping[id], - }; - - return lookup; -} - -int64_t world_chunk_from_realpos(float x, float y) { - librg_chunk chunk_id = librg_chunk_from_realpos(world.tracker, x, y, 0); - return world.chunk_mapping[chunk_id]; -} - -int64_t world_chunk_from_entity(ecs_entity_t id) { - return librg_entity_chunk_get(world.tracker, id); -} - -void world_chunk_replace_block(int64_t id, uint16_t block_idx, uint8_t block_id) { - ZPL_ASSERT(block_idx >= 0 && block_idx < zpl_square(world.chunk_size)); - world.block_mapping[id][block_idx] = block_id; - world_chunk_mark_dirty(world.chunk_mapping[id]); -} - -bool world_chunk_place_block(int64_t id, uint16_t block_idx, uint8_t block_id) { - ZPL_ASSERT(block_idx >= 0 && block_idx < zpl_square(world.chunk_size)); - if (world.outer_block_mapping[id][block_idx] != 0 && block_id != 0) return false; - world.outer_block_mapping[id][block_idx] = block_id; - world_chunk_mark_dirty(world.chunk_mapping[id]); - return true; -} - -uint8_t *world_chunk_get_blocks(int64_t id) { - return world.block_mapping[id]; -} - -void world_chunk_mark_dirty(ecs_entity_t e) { - bool was_added=false; - Chunk *chunk = ecs_get_mut(world_ecs(), e, Chunk, &was_added); - ZPL_ASSERT(!was_added); - if (chunk) chunk->is_dirty = true; -} - -uint8_t world_chunk_is_dirty(ecs_entity_t e) { - bool was_added=false; - Chunk *chunk = ecs_get_mut(world_ecs(), e, Chunk, &was_added); - ZPL_ASSERT(!was_added); - if (chunk) return chunk->is_dirty; - return false; -} - -int64_t *world_chunk_fetch_entities(librg_chunk chunk_id, size_t *ents_len) { - ZPL_ASSERT_NOT_NULL(ents_len); - static int64_t ents[UINT16_MAX]; - *ents_len = UINT16_MAX; - librg_world_fetch_chunk(world.tracker, chunk_id, ents, ents_len); - return ents; -} - -int64_t *world_chunk_fetch_entities_realpos(float x, float y, size_t *ents_len) { - return world_chunk_fetch_entities(librg_chunk_from_realpos(world.tracker, x, y, 0), ents_len); -} - -int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius) { - ZPL_ASSERT_NOT_NULL(ents_len); - static int64_t ents[UINT16_MAX]; - *ents_len = UINT16_MAX; - librg_entity_radius_set(world.tracker, e, radius); - librg_world_query(world.tracker, e, ents, ents_len); - return ents; -} - -uint8_t world_entity_valid(ecs_entity_t e) { - if (!e) return false; - return ecs_is_alive(world_ecs(), e); -} +#include "zpl.h" +#include "librg.h" +#include "modules/components.h" +#include "modules/systems.h" +#include "world/world.h" +#include "entity_view.h" +#include "debug_replay.h" +#include "items.h" +#include "world/worldgen/worldgen.h" +#include "platform.h" +#include "profiler.h" +#include "game.h" + +#include "packets/pkt_send_librg_update.h" + +ZPL_TABLE(static, world_snapshot, world_snapshot_, entity_view); + +static world_data world = {0}; +static world_snapshot streamer_snapshot; + +entity_view world_build_entity_view(int64_t e) { + entity_view *cached_ev = world_snapshot_get(&streamer_snapshot, e); + if (cached_ev) return *cached_ev; + + entity_view view = {0}; + + const Classify *classify = ecs_get(world_ecs(), e, Classify); + ZPL_ASSERT(classify); + + view.kind = classify->id; + + const Position *pos = ecs_get(world_ecs(), e, Position); + if (pos) { + view.x = pos->x; + view.y = pos->y; + } + + const Velocity *vel = ecs_get(world_ecs(), e, Velocity); + if (vel) { + view.flag |= EFLAG_INTERP; + view.vx = vel->x; + view.vy = vel->y; + } + + const Health *health = ecs_get(world_ecs(), e, Health); + if (health) { + view.hp = health->hp; + view.max_hp = health->max_hp; + } + + if (ecs_get(world_ecs(), e, Vehicle)) { + Vehicle const* veh = ecs_get(world_ecs(), e, Vehicle); + view.heading = veh->heading; + } + + if (ecs_get(world_ecs(), e, ItemDrop)) { + ItemDrop const* dr = ecs_get(world_ecs(), e, ItemDrop); + view.asset = item_get_asset(dr->kind); + view.quantity = dr->quantity; + } + + view.inside_vehicle = ecs_get(world_ecs(), e, IsInVehicle) != 0 ? true : false; + + Inventory *inv = 0; + if ((inv = ecs_get_mut_if(world_ecs(), e, Inventory))) { + view.has_items = true; + + for (int i = 0; i < ITEMS_INVENTORY_SIZE; i += 1) { + view.items[i] = inv->items[i]; + } + + const Input *in = ecs_get(world_ecs(), e, Input); + if (in) + view.selected_item = in->selected_item; + } + + Chunk *chunk = 0; + if ((chunk = ecs_get_mut_if(world_ecs(), e, Chunk))) { + view.x = chunk->x; + view.y = chunk->y; + view.blocks_used = 1; + view.is_dirty = chunk->is_dirty; + chunk->is_dirty = false; + + for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) { + view.blocks[i] = world.block_mapping[chunk->id][i]; + } + + for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) { + view.outer_blocks[i] = world.outer_block_mapping[chunk->id][i]; + } + } + + world_snapshot_set(&streamer_snapshot, e, view); + return view; +} + +int32_t tracker_write_create(librg_world *w, librg_event *e) { + int64_t entity_id = librg_event_entity_get(w, e); +#ifdef WORLD_LAYERING + if (world.active_layer_id != WORLD_TRACKER_LAYERS-1) { + // NOTE(zaklaus): reject updates from smaller layers + return LIBRG_WRITE_REJECT; + } +#endif + size_t actual_length = librg_event_size_get(w, e); + char *buffer = librg_event_buffer_get(w, e); + + return (int32_t)entity_view_pack_struct(buffer, actual_length, world_build_entity_view(entity_id)); +} + +int32_t tracker_write_remove(librg_world *w, librg_event *e) { + (void)e; + (void)w; +#ifdef WORLD_LAYERING + if (world.active_layer_id != WORLD_TRACKER_LAYERS-1) { + // NOTE(zaklaus): reject updates from smaller layers + return LIBRG_WRITE_REJECT; + } +#endif + return 0; +} + +int32_t tracker_write_update(librg_world *w, librg_event *e) { + int64_t entity_id = librg_event_entity_get(w, e); + size_t actual_length = librg_event_size_get(w, e); + char *buffer = librg_event_buffer_get(w, e); + entity_view view = world_build_entity_view(entity_id); + + // NOTE(zaklaus): exclude chunks from updates as they never move + { + if (view.kind == EKIND_CHUNK && !view.is_dirty) { + return LIBRG_WRITE_REJECT; + } + } + + return (int32_t)entity_view_pack_struct(buffer, actual_length, view); +} + +void world_setup_pkt_handlers(world_pkt_reader_proc *reader_proc, world_pkt_writer_proc *writer_proc) { + world.reader_proc = reader_proc; + world.writer_proc = writer_proc; +} + +int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) { + if (world.data) { + return 0; + } + + world.is_paused = false; + world.seed = seed; + world.chunk_size = chunk_size; + world.chunk_amount = chunk_amount; + + world.dim = (world.chunk_size * world.chunk_amount); + world.size = world.dim * world.dim; + + if (world.tracker == NULL) { + world.tracker = librg_world_create(); + } + + if (world.tracker == NULL) { + zpl_printf("[ERROR] An error occurred while trying to create a server world.\n"); + return WORLD_ERROR_TRACKER_FAILED; + } + + /* config our world grid */ + librg_config_chunksize_set(world.tracker, WORLD_BLOCK_SIZE * world.chunk_size, WORLD_BLOCK_SIZE * world.chunk_size, 0); + librg_config_chunkamount_set(world.tracker, world.chunk_amount, world.chunk_amount, 0); + librg_config_chunkoffset_set(world.tracker, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG); + + librg_event_set(world.tracker, LIBRG_WRITE_CREATE, tracker_write_create); + librg_event_set(world.tracker, LIBRG_WRITE_REMOVE, tracker_write_remove); + librg_event_set(world.tracker, LIBRG_WRITE_UPDATE, tracker_write_update); + + world.data = zpl_malloc(sizeof(uint8_t)*world.size); + + if (!world.data) { + return WORLD_ERROR_OUTOFMEM; + } + + world.ecs = ecs_init(); + + ECS_IMPORT(world.ecs, Components); + ECS_IMPORT(world.ecs, Systems); + world.ecs_update = ecs_query_new(world.ecs, "components.ClientInfo, components.Position"); + world.chunk_mapping = zpl_malloc(sizeof(ecs_entity_t)*zpl_square(chunk_amount)); + world.block_mapping = zpl_malloc(sizeof(uint8_t*)*zpl_square(chunk_amount)); + world.outer_block_mapping = zpl_malloc(sizeof(uint8_t*)*zpl_square(chunk_amount)); + world_snapshot_init(&streamer_snapshot, zpl_heap()); + + int32_t world_build_status = worldgen_test(&world); + ZPL_ASSERT(world_build_status >= 0); + + for (int i = 0; i < world.chunk_amount * world.chunk_amount; ++i) { + ecs_entity_t e = ecs_new(world.ecs, 0); + ecs_set(world.ecs, e, Classify, {.id = EKIND_CHUNK }); + Chunk *chunk = ecs_get_mut(world.ecs, e, Chunk, NULL); + librg_entity_track(world.tracker, e); + librg_entity_chunk_set(world.tracker, e, i); + librg_chunk_to_chunkpos(world.tracker, i, &chunk->x, &chunk->y, NULL); + world.chunk_mapping[i] = e; + world.block_mapping[i] = zpl_malloc(sizeof(uint8_t)*zpl_square(chunk_size)); + world.outer_block_mapping[i] = zpl_malloc(sizeof(uint8_t)*zpl_square(chunk_size)); + chunk->id = i; + chunk->is_dirty = false; + + for (int y = 0; y < chunk_size; y += 1) { + for (int x = 0; x < chunk_size; x += 1) { + int chk_x = chunk->x * chunk_size; + int chk_y = chunk->y * chunk_size; + + uint8_t *c = &world.block_mapping[i][(y*chunk_size)+x]; + *c = world.data[(chk_y+y)*world.dim + (chk_x+x)]; + + c = &world.outer_block_mapping[i][(y*chunk_size)+x]; + *c = 0; + } + } + } + + zpl_mfree(world.data); + world.data = NULL; + + zpl_printf("[INFO] Created a new server world\n"); + + return world_build_status; +} + +int32_t world_destroy(void) { + librg_world_destroy(world.tracker); + ecs_fini(world.ecs); + zpl_mfree(world.chunk_mapping); + for (int i = 0; i < zpl_square(world.chunk_amount); i+=1) { + zpl_mfree(world.block_mapping[i]); + zpl_mfree(world.outer_block_mapping[i]); + } + zpl_mfree(world.block_mapping); + zpl_mfree(world.outer_block_mapping); + world_snapshot_destroy(&streamer_snapshot); + zpl_memset(&world, 0, sizeof(world)); + zpl_printf("[INFO] World was destroyed.\n"); + return WORLD_ERROR_NONE; +} + +#define WORLD_LIBRG_BUFSIZ 2000000 + +static void world_tracker_update(uint8_t ticker, uint32_t freq, uint8_t radius) { + if (world.tracker_update[ticker] > zpl_time_rel_ms()) return; + world.tracker_update[ticker] = zpl_time_rel_ms() + freq; + + profile(PROF_WORLD_WRITE) { + ecs_iter_t it = ecs_query_iter(world.ecs_update); + static char buffer[WORLD_LIBRG_BUFSIZ] = {0}; + world.active_layer_id = ticker; + + while (ecs_query_next(&it)) { + ClientInfo *p = ecs_column(&it, ClientInfo, 1); + + for (int i = 0; i < it.count; i++) { + size_t datalen = WORLD_LIBRG_BUFSIZ; + + // TODO(zaklaus): SUPER TEMPORARY HOT !!! simulate variable radius queries + { + librg_entity_radius_set(world_tracker(), it.entities[i], radius); + } + // TODO(zaklaus): push radius once librg patch comes in + int32_t result = librg_world_write(world_tracker(), it.entities[i], buffer, &datalen, NULL); + + if (result > 0) { + zpl_printf("[info] buffer size was not enough, please increase it by at least: %d\n", result); + } else if (result < 0) { + zpl_printf("[error] an error happened writing the world %d\n", result); + } + + pkt_send_librg_update((uint64_t)p[i].peer, p[i].view_id, ticker, buffer, datalen); + } + } + + // NOTE(zaklaus): clear out our streaming snapshot + // TODO(zaklaus): move this to zpl + { + zpl_array_clear(streamer_snapshot.hashes); + zpl_array_clear(streamer_snapshot.entries); + } + } +} + +int32_t world_update() { + profile (PROF_UPDATE_SYSTEMS) { + ecs_progress(world.ecs, 0.0f); + } + + uint32_t fast_ms = WORLD_TRACKER_UPDATE_FAST_MS; + uint32_t normal_ms = WORLD_TRACKER_UPDATE_NORMAL_MS; + uint32_t slow_ms = WORLD_TRACKER_UPDATE_SLOW_MS; + + if (game_get_kind() != GAMEKIND_SINGLE) { + fast_ms = WORLD_TRACKER_UPDATE_MP_FAST_MS; + normal_ms = WORLD_TRACKER_UPDATE_MP_NORMAL_MS; + slow_ms = WORLD_TRACKER_UPDATE_MP_SLOW_MS; + } + + world_tracker_update(0, fast_ms, 2); + world_tracker_update(1, normal_ms, 4); + world_tracker_update(2, slow_ms, 6); + + debug_replay_update(); + return 0; +} + +int32_t world_read(void* data, uint32_t datalen, void *udata) { + if (world.reader_proc) { + return world.reader_proc(data, datalen, udata); + } + return -1; +} + +int32_t world_write(pkt_header *pkt, void *udata) { + if (world.writer_proc) { + return world.writer_proc(pkt, udata); + } + return -1; +} + +uint32_t world_buf(uint8_t const **ptr, uint32_t *width) { + ZPL_ASSERT_NOT_NULL(world.data); + ZPL_ASSERT_NOT_NULL(ptr); + *ptr = world.data; + if (width) *width = world.dim; + return world.size; +} + +uint32_t world_seed(void) { + return world.seed; +} + +ecs_world_t * world_ecs() { + if (world.ecs_stage != NULL) { + return world.ecs_stage; + } + return world.ecs; +} + +void world_set_stage(ecs_world_t *ecs) { + world.ecs_stage = ecs; +} + +librg_world *world_tracker() { + return world.tracker; +} + +void world_pause(void) { + ecs_set_time_scale(world.ecs, 0.0f); + world.is_paused = true; +} + +void world_resume(void) { + ecs_set_time_scale(world.ecs, 1.0f); + world.is_paused = false; +} + +bool world_is_paused(void) { + return world.is_paused; +} + +void world_step(float step_size) { + world_resume(); + ecs_progress(world.ecs, step_size); + world_pause(); +} + +uint16_t world_chunk_size(void) { + return world.chunk_size; +} + +uint16_t world_chunk_amount(void) { + return world.chunk_amount; +} + +uint16_t world_dim(void) { + return WORLD_BLOCK_SIZE * world.chunk_size * world.chunk_amount; +} + +ecs_entity_t world_chunk_mapping(librg_chunk id) { + ZPL_ASSERT(id >= 0 && id < zpl_square(world.chunk_amount)); + return world.chunk_mapping[id]; +} + +world_block_lookup world_block_from_realpos(float x, float y) { + x = zpl_clamp(x, 0, world_dim()-1); + y = zpl_clamp(y, 0, world_dim()-1); + librg_chunk chunk_id = librg_chunk_from_realpos(world.tracker, x, y, 0); + ecs_entity_t e = world.chunk_mapping[chunk_id]; + int32_t size = world.chunk_size * WORLD_BLOCK_SIZE; + int16_t chunk_x, chunk_y; + librg_chunk_to_chunkpos(world.tracker, chunk_id, &chunk_x, &chunk_y, NULL); + + // NOTE(zaklaus): pos relative to chunk + float chx = x - chunk_x * size; + float chy = y - chunk_y * size; + + uint32_t bx = (uint32_t)chx / WORLD_BLOCK_SIZE; + uint32_t by = (uint32_t)chy / WORLD_BLOCK_SIZE; + uint32_t block_idx = (by*world.chunk_size)+bx; + uint8_t block_id = world.outer_block_mapping[chunk_id][block_idx]; + if (block_id == 0) { + block_id = world.block_mapping[chunk_id][block_idx]; + } + + // NOTE(zaklaus): pos relative to block's center + float box = chx - bx * WORLD_BLOCK_SIZE - WORLD_BLOCK_SIZE/2.0f; + float boy = chy - by * WORLD_BLOCK_SIZE - WORLD_BLOCK_SIZE/2.0f; + + world_block_lookup lookup = { + .id = block_idx, + .block_id = block_id, + .chunk_id = chunk_id, + .chunk_e = e, + .ox = box, + .oy = boy, + }; + + return lookup; +} + +world_block_lookup world_block_from_index(int64_t id, uint16_t block_idx) { + uint8_t block_id = world.outer_block_mapping[id][block_idx]; + if (block_id == 0) { + block_id = world.block_mapping[id][block_idx]; + } + + world_block_lookup lookup = { + .id = block_idx, + .block_id = block_id, + .chunk_id = id, + .chunk_e = world.chunk_mapping[id], + }; + + return lookup; +} + +int64_t world_chunk_from_realpos(float x, float y) { + librg_chunk chunk_id = librg_chunk_from_realpos(world.tracker, x, y, 0); + return world.chunk_mapping[chunk_id]; +} + +int64_t world_chunk_from_entity(ecs_entity_t id) { + return librg_entity_chunk_get(world.tracker, id); +} + +void world_chunk_replace_block(int64_t id, uint16_t block_idx, uint8_t block_id) { + ZPL_ASSERT(block_idx >= 0 && block_idx < zpl_square(world.chunk_size)); + world.block_mapping[id][block_idx] = block_id; + world_chunk_mark_dirty(world.chunk_mapping[id]); +} + +bool world_chunk_place_block(int64_t id, uint16_t block_idx, uint8_t block_id) { + ZPL_ASSERT(block_idx >= 0 && block_idx < zpl_square(world.chunk_size)); + if (world.outer_block_mapping[id][block_idx] != 0 && block_id != 0) return false; + world.outer_block_mapping[id][block_idx] = block_id; + world_chunk_mark_dirty(world.chunk_mapping[id]); + return true; +} + +uint8_t *world_chunk_get_blocks(int64_t id) { + return world.block_mapping[id]; +} + +void world_chunk_mark_dirty(ecs_entity_t e) { + bool was_added=false; + Chunk *chunk = ecs_get_mut(world_ecs(), e, Chunk, &was_added); + ZPL_ASSERT(!was_added); + if (chunk) chunk->is_dirty = true; +} + +uint8_t world_chunk_is_dirty(ecs_entity_t e) { + bool was_added=false; + Chunk *chunk = ecs_get_mut(world_ecs(), e, Chunk, &was_added); + ZPL_ASSERT(!was_added); + if (chunk) return chunk->is_dirty; + return false; +} + +int64_t *world_chunk_fetch_entities(librg_chunk chunk_id, size_t *ents_len) { + ZPL_ASSERT_NOT_NULL(ents_len); + static int64_t ents[UINT16_MAX]; + *ents_len = UINT16_MAX; + librg_world_fetch_chunk(world.tracker, chunk_id, ents, ents_len); + return ents; +} + +int64_t *world_chunk_fetch_entities_realpos(float x, float y, size_t *ents_len) { + return world_chunk_fetch_entities(librg_chunk_from_realpos(world.tracker, x, y, 0), ents_len); +} + +int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius) { + ZPL_ASSERT_NOT_NULL(ents_len); + static int64_t ents[UINT16_MAX]; + *ents_len = UINT16_MAX; + librg_entity_radius_set(world.tracker, e, radius); + librg_world_query(world.tracker, e, ents, ents_len); + return ents; +} + +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 47e927a..78605fb 100644 --- a/code/game/src/world/world.h +++ b/code/game/src/world/world.h @@ -1,101 +1,101 @@ -#pragma once -#include "system.h" -#include "librg.h" -#include "packet.h" -#include "flecs/flecs.h" -#include "flecs/flecs_meta.h" -#include "modules/components.h" - -#define WORLD_ERROR_NONE +0x0000 -#define WORLD_ERROR_OUTOFMEM -0x0001 -#define WORLD_ERROR_INVALID_BLOCKS -0x0002 -#define WORLD_ERROR_INVALID_DIMENSIONS -0x0003 -#define WORLD_ERROR_INVALID_BUFFER -0x0004 -#define WORLD_ERROR_TRACKER_FAILED -0x0005 - -#define WORLD_LAYERING 0 -#define WORLD_TRACKER_LAYERS 3 -#define WORLD_TRACKER_UPDATE_FAST_MS 0 -#define WORLD_TRACKER_UPDATE_NORMAL_MS 50 -#define WORLD_TRACKER_UPDATE_SLOW_MS 100 -#define WORLD_TRACKER_UPDATE_MP_FAST_MS 50 -#define WORLD_TRACKER_UPDATE_MP_NORMAL_MS 150 -#define WORLD_TRACKER_UPDATE_MP_SLOW_MS 300 -#define WORLD_BLOCK_SIZE 64 - -#define WORLD_PKT_READER(name) int32_t name(void* data, uint32_t datalen, void *udata) -typedef WORLD_PKT_READER(world_pkt_reader_proc); - -#define WORLD_PKT_WRITER(name) int32_t name(pkt_header *pkt, void *udata) -typedef WORLD_PKT_WRITER(world_pkt_writer_proc); - -typedef struct { - bool is_paused; - uint8_t *data; - uint32_t seed; - uint32_t size; - uint16_t chunk_size; - uint16_t chunk_amount; - uint8_t **block_mapping; - uint8_t **outer_block_mapping; - uint16_t dim; - uint64_t tracker_update[3]; - uint8_t active_layer_id; - ecs_world_t *ecs; - ecs_world_t *ecs_stage; - ecs_query_t *ecs_update; - ecs_entity_t *chunk_mapping; - librg_world *tracker; - world_pkt_reader_proc *reader_proc; - world_pkt_writer_proc *writer_proc; -} world_data; - -void world_setup_pkt_handlers(world_pkt_reader_proc *reader_proc, world_pkt_writer_proc *writer_proc); -int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount); -int32_t world_destroy(void); -int32_t world_update(void); - -int32_t world_read(void* data, uint32_t datalen, void *udata); -int32_t world_write(pkt_header *pkt, void *udata); - -uint32_t world_buf(uint8_t const **ptr, uint32_t *width); -uint32_t world_seed(void); -ecs_world_t *world_ecs(void); -void world_set_stage(ecs_world_t *ecs); -librg_world *world_tracker(void); - -// NOTE(zaklaus): World simulation time control -void world_pause(void); -void world_resume(void); -bool world_is_paused(void); -void world_step(float step_size); - -uint16_t world_chunk_size(void); -uint16_t world_chunk_amount(void); -uint16_t world_dim(void); -ecs_entity_t world_chunk_mapping(librg_chunk id); - -typedef struct { - uint32_t id; - uint8_t block_id; - ecs_entity_t chunk_e; - int64_t chunk_id; - float ox, oy; -} world_block_lookup; - -world_block_lookup world_block_from_realpos(float x, float y); -world_block_lookup world_block_from_index(int64_t id, uint16_t block_idx); -int64_t world_chunk_from_realpos(float x, float y); -int64_t world_chunk_from_entity(ecs_entity_t id); -void world_chunk_replace_block(int64_t id, uint16_t block_idx, uint8_t block_id); -bool world_chunk_place_block(int64_t id, uint16_t block_idx, uint8_t block_id); -uint8_t *world_chunk_get_blocks(int64_t id); -void world_chunk_mark_dirty(ecs_entity_t e); -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); - +#pragma once +#include "system.h" +#include "librg.h" +#include "packet.h" +#include "flecs/flecs.h" +#include "flecs/flecs_meta.h" +#include "modules/components.h" + +#define WORLD_ERROR_NONE +0x0000 +#define WORLD_ERROR_OUTOFMEM -0x0001 +#define WORLD_ERROR_INVALID_BLOCKS -0x0002 +#define WORLD_ERROR_INVALID_DIMENSIONS -0x0003 +#define WORLD_ERROR_INVALID_BUFFER -0x0004 +#define WORLD_ERROR_TRACKER_FAILED -0x0005 + +#define WORLD_LAYERING 0 +#define WORLD_TRACKER_LAYERS 3 +#define WORLD_TRACKER_UPDATE_FAST_MS 0 +#define WORLD_TRACKER_UPDATE_NORMAL_MS 50 +#define WORLD_TRACKER_UPDATE_SLOW_MS 100 +#define WORLD_TRACKER_UPDATE_MP_FAST_MS 50 +#define WORLD_TRACKER_UPDATE_MP_NORMAL_MS 150 +#define WORLD_TRACKER_UPDATE_MP_SLOW_MS 300 +#define WORLD_BLOCK_SIZE 64 + +#define WORLD_PKT_READER(name) int32_t name(void* data, uint32_t datalen, void *udata) +typedef WORLD_PKT_READER(world_pkt_reader_proc); + +#define WORLD_PKT_WRITER(name) int32_t name(pkt_header *pkt, void *udata) +typedef WORLD_PKT_WRITER(world_pkt_writer_proc); + +typedef struct { + bool is_paused; + uint8_t *data; + uint32_t seed; + uint32_t size; + uint16_t chunk_size; + uint16_t chunk_amount; + uint8_t **block_mapping; + uint8_t **outer_block_mapping; + uint16_t dim; + uint64_t tracker_update[3]; + uint8_t active_layer_id; + ecs_world_t *ecs; + ecs_world_t *ecs_stage; + ecs_query_t *ecs_update; + ecs_entity_t *chunk_mapping; + librg_world *tracker; + world_pkt_reader_proc *reader_proc; + world_pkt_writer_proc *writer_proc; +} world_data; + +void world_setup_pkt_handlers(world_pkt_reader_proc *reader_proc, world_pkt_writer_proc *writer_proc); +int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount); +int32_t world_destroy(void); +int32_t world_update(void); + +int32_t world_read(void* data, uint32_t datalen, void *udata); +int32_t world_write(pkt_header *pkt, void *udata); + +uint32_t world_buf(uint8_t const **ptr, uint32_t *width); +uint32_t world_seed(void); +ecs_world_t *world_ecs(void); +void world_set_stage(ecs_world_t *ecs); +librg_world *world_tracker(void); + +// NOTE(zaklaus): World simulation time control +void world_pause(void); +void world_resume(void); +bool world_is_paused(void); +void world_step(float step_size); + +uint16_t world_chunk_size(void); +uint16_t world_chunk_amount(void); +uint16_t world_dim(void); +ecs_entity_t world_chunk_mapping(librg_chunk id); + +typedef struct { + uint32_t id; + uint8_t block_id; + ecs_entity_t chunk_e; + int64_t chunk_id; + float ox, oy; +} world_block_lookup; + +world_block_lookup world_block_from_realpos(float x, float y); +world_block_lookup world_block_from_index(int64_t id, uint16_t block_idx); +int64_t world_chunk_from_realpos(float x, float y); +int64_t world_chunk_from_entity(ecs_entity_t id); +void world_chunk_replace_block(int64_t id, uint16_t block_idx, uint8_t block_id); +bool world_chunk_place_block(int64_t id, uint16_t block_idx, uint8_t block_id); +uint8_t *world_chunk_get_blocks(int64_t id); +void world_chunk_mark_dirty(ecs_entity_t e); +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); + uint8_t world_entity_valid(ecs_entity_t e); \ No newline at end of file diff --git a/code/game/src/world_view.c b/code/game/src/world_view.c index 69c70fd..c611567 100644 --- a/code/game/src/world_view.c +++ b/code/game/src/world_view.c @@ -1,95 +1,95 @@ -#include "zpl.h" -#include "world_view.h" -#include "entity_view.h" -#include "prediction.h" -#include "librg.h" -#include "world/world.h" -#include "game.h" - -int32_t tracker_read_remove(librg_world *w, librg_event *e) { - int64_t entity_id = librg_event_entity_get(w, e); - world_view *view = (world_view*)librg_world_userdata_get(w); - entity_view_remove_chunk_texture(&view->entities, entity_id); - entity_view_destroy(&view->entities, entity_id); - return 0; -} - -int32_t tracker_read_update(librg_world *w, librg_event *e) { - int64_t entity_id = librg_event_entity_get(w, e); - size_t actual_length = librg_event_size_get(w, e); - char *buffer = librg_event_buffer_get(w, e); - world_view *view = (world_view*)librg_world_userdata_get(w); - - entity_view data = entity_view_unpack_struct(buffer, actual_length); - entity_view *d = entity_view_get(&view->entities, entity_id); -#if 1 - if (d && d->layer_id < view->active_layer_id) { - if (zpl_time_rel_ms() - d->last_update > WORLD_TRACKER_UPDATE_NORMAL_MS) { - d->layer_id = zpl_min(WORLD_TRACKER_LAYERS-1, d->layer_id+1); - } - // NOTE(zaklaus): reject updates from slower layers - else return 0; - } -#endif - - data.last_update = zpl_time_rel_ms(); - data.layer_id = view->active_layer_id; - predict_receive_update(d, &data); - entity_view_update_or_create(&view->entities, entity_id, data); - entity_view_remove_chunk_texture(&view->entities, entity_id); - entity_view_update_chunk_texture(&view->entities, entity_id, view); - return 0; -} - -int32_t tracker_read_create(librg_world *w, librg_event *e) { - int64_t entity_id = librg_event_entity_get(w, e); - size_t actual_length = librg_event_size_get(w, e); - char *buffer = librg_event_buffer_get(w, e); - world_view *view = (world_view*)librg_world_userdata_get(w); - - entity_view data = entity_view_unpack_struct(buffer, actual_length); - data.ent_id = entity_id; - data.layer_id = view->active_layer_id; - data.tran_time = 0.0f; - data.color = rand(); // TODO(zaklaus): feed from server - if (data.flag & EFLAG_INTERP) { - data.tx = data.x; - data.ty = data.y; - data.theading = data.heading; - } - entity_view_update_or_create(&view->entities, entity_id, data); - entity_view_mark_for_fadein(&view->entities, entity_id); - entity_view_update_chunk_texture(&view->entities, entity_id, view); - return 0; -} - -world_view world_view_create(uint16_t view_id) { - world_view view = {0}; - view.view_id = view_id; - view.tracker = librg_world_create(); - entity_view_init(&view.entities); - return view; -} - -void world_view_init(world_view *view, uint32_t seed, uint64_t ent_id, uint16_t chunk_size, uint16_t chunk_amount) { - view->seed = seed; - view->owner_id = ent_id; - view->chunk_size = chunk_size; - view->chunk_amount = chunk_amount; - view->dim = WORLD_BLOCK_SIZE * chunk_size * chunk_amount; - view->size = view->dim * view->dim; - - librg_config_chunksize_set(view->tracker, WORLD_BLOCK_SIZE * chunk_size, WORLD_BLOCK_SIZE * chunk_size, 1); - librg_config_chunkamount_set(view->tracker, chunk_amount, chunk_amount, 1); - librg_config_chunkoffset_set(view->tracker, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG, 0); - - librg_event_set(view->tracker, LIBRG_READ_CREATE, tracker_read_create); - librg_event_set(view->tracker, LIBRG_READ_REMOVE, tracker_read_remove); - librg_event_set(view->tracker, LIBRG_READ_UPDATE, tracker_read_update); - librg_world_userdata_set(view->tracker, view); -} - -void world_view_destroy(world_view *view) { - librg_world_destroy(view->tracker); - entity_view_free(&view->entities); -} +#include "zpl.h" +#include "world_view.h" +#include "entity_view.h" +#include "prediction.h" +#include "librg.h" +#include "world/world.h" +#include "game.h" + +int32_t tracker_read_remove(librg_world *w, librg_event *e) { + int64_t entity_id = librg_event_entity_get(w, e); + world_view *view = (world_view*)librg_world_userdata_get(w); + entity_view_remove_chunk_texture(&view->entities, entity_id); + entity_view_destroy(&view->entities, entity_id); + return 0; +} + +int32_t tracker_read_update(librg_world *w, librg_event *e) { + int64_t entity_id = librg_event_entity_get(w, e); + size_t actual_length = librg_event_size_get(w, e); + char *buffer = librg_event_buffer_get(w, e); + world_view *view = (world_view*)librg_world_userdata_get(w); + + entity_view data = entity_view_unpack_struct(buffer, actual_length); + entity_view *d = entity_view_get(&view->entities, entity_id); +#if 1 + if (d && d->layer_id < view->active_layer_id) { + if (zpl_time_rel_ms() - d->last_update > WORLD_TRACKER_UPDATE_NORMAL_MS) { + d->layer_id = zpl_min(WORLD_TRACKER_LAYERS-1, d->layer_id+1); + } + // NOTE(zaklaus): reject updates from slower layers + else return 0; + } +#endif + + data.last_update = zpl_time_rel_ms(); + data.layer_id = view->active_layer_id; + predict_receive_update(d, &data); + entity_view_update_or_create(&view->entities, entity_id, data); + entity_view_remove_chunk_texture(&view->entities, entity_id); + entity_view_update_chunk_texture(&view->entities, entity_id, view); + return 0; +} + +int32_t tracker_read_create(librg_world *w, librg_event *e) { + int64_t entity_id = librg_event_entity_get(w, e); + size_t actual_length = librg_event_size_get(w, e); + char *buffer = librg_event_buffer_get(w, e); + world_view *view = (world_view*)librg_world_userdata_get(w); + + entity_view data = entity_view_unpack_struct(buffer, actual_length); + data.ent_id = entity_id; + data.layer_id = view->active_layer_id; + data.tran_time = 0.0f; + data.color = rand(); // TODO(zaklaus): feed from server + if (data.flag & EFLAG_INTERP) { + data.tx = data.x; + data.ty = data.y; + data.theading = data.heading; + } + entity_view_update_or_create(&view->entities, entity_id, data); + entity_view_mark_for_fadein(&view->entities, entity_id); + entity_view_update_chunk_texture(&view->entities, entity_id, view); + return 0; +} + +world_view world_view_create(uint16_t view_id) { + world_view view = {0}; + view.view_id = view_id; + view.tracker = librg_world_create(); + entity_view_init(&view.entities); + return view; +} + +void world_view_init(world_view *view, uint32_t seed, uint64_t ent_id, uint16_t chunk_size, uint16_t chunk_amount) { + view->seed = seed; + view->owner_id = ent_id; + view->chunk_size = chunk_size; + view->chunk_amount = chunk_amount; + view->dim = WORLD_BLOCK_SIZE * chunk_size * chunk_amount; + view->size = view->dim * view->dim; + + librg_config_chunksize_set(view->tracker, WORLD_BLOCK_SIZE * chunk_size, WORLD_BLOCK_SIZE * chunk_size, 1); + librg_config_chunkamount_set(view->tracker, chunk_amount, chunk_amount, 1); + librg_config_chunkoffset_set(view->tracker, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG, 0); + + librg_event_set(view->tracker, LIBRG_READ_CREATE, tracker_read_create); + librg_event_set(view->tracker, LIBRG_READ_REMOVE, tracker_read_remove); + librg_event_set(view->tracker, LIBRG_READ_UPDATE, tracker_read_update); + librg_world_userdata_set(view->tracker, view); +} + +void world_view_destroy(world_view *view) { + librg_world_destroy(view->tracker); + entity_view_free(&view->entities); +} diff --git a/code/modules/modules/components.h b/code/modules/modules/components.h index 9fe981a..648ecec 100644 --- a/code/modules/modules/components.h +++ b/code/modules/modules/components.h @@ -1,176 +1,176 @@ -#pragma once -#include "flecs/flecs.h" -#include "flecs/flecs_meta.h" - -//NOTE(zaklaus): custom macro to define meta components outside the current scope - -#ifndef ECS_META_DEFINE -#define ECS_META_DEFINE(world, T)\ -ECS_COMPONENT_DEFINE(world, T);\ -ecs_new_meta(world, ecs_entity(T), &__##T##__); -#endif - -#ifndef ecs_get_mut_if -#define ecs_get_mut_if(world, entity, component)\ -(ecs_get(world, entity, component) ? ecs_get_mut(world, entity, component, NULL) : NULL) -#endif - -#define ITEMS_INVENTORY_SIZE 9 - -ECS_STRUCT(Vector2D, { - float x; - float y; - }); - -ECS_STRUCT(Chunk, { - uint32_t id; - int16_t x; - int16_t y; - uint8_t is_dirty; - }); - -ECS_STRUCT(Drawable, { - uint16_t id; - }); - -ECS_ALIAS(Vector2D, Position); -ECS_ALIAS(Vector2D, Velocity); - -ECS_STRUCT(Input, { - float x; - float y; - float mx; - float my; - uint8_t use; - uint8_t sprint; - uint8_t ctrl; - uint8_t is_blocked; - - // NOTE(zaklaus): inventory - uint8_t selected_item; - uint8_t drop; - uint8_t swap; - uint8_t swap_from; - uint8_t swap_to; - - // NOTE(zaklaus): build mode - uint8_t num_placements; - float placements_x[20]; - float placements_y[20]; - }); - -ECS_STRUCT(ClientInfo, { - uintptr_t peer; - uint16_t view_id; - }); - -ECS_STRUCT(Health, { - float hp; - float max_hp; - - //NOTE(zaklaus): Intentionally global, to allow for creative use of damage combos - float pain_time; - float heal_time; - }); - -ECS_STRUCT(Classify, { - uint16_t id; - }); - -ECS_STRUCT(Vehicle, { - uint64_t seats[4]; - - float force; - float heading; - float steer; - float wheel_base; - - float speed; - float reverse_speed; - }); - -typedef struct { - ecs_entity_t veh; -} IsInVehicle; - -typedef struct { - uint16_t kind; - uint32_t quantity; - float merger_time; -} ItemDrop; - -typedef struct { - ItemDrop items[ITEMS_INVENTORY_SIZE]; - float pickup_time; -} Inventory; - -ECS_COMPONENT_EXTERN(Chunk); -ECS_COMPONENT_EXTERN(Position); -ECS_COMPONENT_EXTERN(Vector2D); -ECS_COMPONENT_EXTERN(Drawable); -ECS_COMPONENT_EXTERN(Input); -ECS_COMPONENT_EXTERN(Velocity); -ECS_COMPONENT_EXTERN(ClientInfo); -ECS_COMPONENT_EXTERN(Health); -ECS_COMPONENT_EXTERN(Classify); -ECS_COMPONENT_EXTERN(Vehicle); -ECS_COMPONENT_EXTERN(IsInVehicle); -ECS_COMPONENT_EXTERN(ItemDrop); -ECS_COMPONENT_EXTERN(Inventory); -ECS_TAG_EXTERN(EcsActor); -ECS_TAG_EXTERN(EcsDemoNPC); -ECS_TYPE_EXTERN(Player); -ECS_TYPE_EXTERN(Movement); -ECS_TYPE_EXTERN(Walking); -ECS_TYPE_EXTERN(Flying); -ECS_TYPE_EXTERN(EcsClient); -// NOTE(zaklaus): @1 EXTERN - -typedef struct { - ECS_DECLARE_COMPONENT(Chunk); - ECS_DECLARE_COMPONENT(Position); - ECS_DECLARE_COMPONENT(Vector2D); - ECS_DECLARE_COMPONENT(Drawable); - ECS_DECLARE_COMPONENT(Input); - ECS_DECLARE_COMPONENT(Velocity); - ECS_DECLARE_COMPONENT(ClientInfo); - ECS_DECLARE_COMPONENT(Health); - ECS_DECLARE_COMPONENT(Classify); - ECS_DECLARE_COMPONENT(Vehicle); - ECS_DECLARE_COMPONENT(IsInVehicle); - ECS_DECLARE_COMPONENT(ItemDrop); - ECS_DECLARE_COMPONENT(Inventory); - ECS_DECLARE_ENTITY(EcsActor); - ECS_DECLARE_ENTITY(EcsDemoNPC); - ECS_DECLARE_TYPE(Player); - ECS_DECLARE_TYPE(Builder); - ECS_DECLARE_TYPE(Movement); - ECS_DECLARE_ENTITY(Walking); - ECS_DECLARE_ENTITY(Flying); - // NOTE(zaklaus): @2 DECLARE -} Components; - -#define ComponentsImportHandles(handles)\ -ECS_IMPORT_COMPONENT(handles, Chunk);\ -ECS_IMPORT_COMPONENT(handles, Vector2D);\ -ECS_IMPORT_COMPONENT(handles, Position);\ -ECS_IMPORT_COMPONENT(handles, Drawable);\ -ECS_IMPORT_COMPONENT(handles, Input);\ -ECS_IMPORT_COMPONENT(handles, Velocity);\ -ECS_IMPORT_COMPONENT(handles, ClientInfo);\ -ECS_IMPORT_COMPONENT(handles, Health);\ -ECS_IMPORT_COMPONENT(handles, Classify);\ -ECS_IMPORT_COMPONENT(handles, Vehicle);\ -ECS_IMPORT_COMPONENT(handles, IsInVehicle);\ -ECS_IMPORT_COMPONENT(handles, ItemDrop);\ -ECS_IMPORT_COMPONENT(handles, Inventory);\ -ECS_IMPORT_TYPE(handles, Player);\ -ECS_IMPORT_TYPE(handles, Builder);\ -ECS_IMPORT_TYPE(handles, Movement);\ -ECS_IMPORT_ENTITY(handles, EcsActor);\ -ECS_IMPORT_ENTITY(handles, EcsDemoNPC);\ -ECS_IMPORT_ENTITY(handles, Walking);\ -ECS_IMPORT_ENTITY(handles, Flying);\ -// NOTE(zaklaus): @3 IMPORT - -void ComponentsImport(ecs_world_t *ecs); +#pragma once +#include "flecs/flecs.h" +#include "flecs/flecs_meta.h" + +//NOTE(zaklaus): custom macro to define meta components outside the current scope + +#ifndef ECS_META_DEFINE +#define ECS_META_DEFINE(world, T)\ +ECS_COMPONENT_DEFINE(world, T);\ +ecs_new_meta(world, ecs_entity(T), &__##T##__); +#endif + +#ifndef ecs_get_mut_if +#define ecs_get_mut_if(world, entity, component)\ +(ecs_get(world, entity, component) ? ecs_get_mut(world, entity, component, NULL) : NULL) +#endif + +#define ITEMS_INVENTORY_SIZE 9 + +ECS_STRUCT(Vector2D, { + float x; + float y; + }); + +ECS_STRUCT(Chunk, { + uint32_t id; + int16_t x; + int16_t y; + uint8_t is_dirty; + }); + +ECS_STRUCT(Drawable, { + uint16_t id; + }); + +ECS_ALIAS(Vector2D, Position); +ECS_ALIAS(Vector2D, Velocity); + +ECS_STRUCT(Input, { + float x; + float y; + float mx; + float my; + uint8_t use; + uint8_t sprint; + uint8_t ctrl; + uint8_t is_blocked; + + // NOTE(zaklaus): inventory + uint8_t selected_item; + uint8_t drop; + uint8_t swap; + uint8_t swap_from; + uint8_t swap_to; + + // NOTE(zaklaus): build mode + uint8_t num_placements; + float placements_x[20]; + float placements_y[20]; + }); + +ECS_STRUCT(ClientInfo, { + uintptr_t peer; + uint16_t view_id; + }); + +ECS_STRUCT(Health, { + float hp; + float max_hp; + + //NOTE(zaklaus): Intentionally global, to allow for creative use of damage combos + float pain_time; + float heal_time; + }); + +ECS_STRUCT(Classify, { + uint16_t id; + }); + +ECS_STRUCT(Vehicle, { + uint64_t seats[4]; + + float force; + float heading; + float steer; + float wheel_base; + + float speed; + float reverse_speed; + }); + +typedef struct { + ecs_entity_t veh; +} IsInVehicle; + +typedef struct { + uint16_t kind; + uint32_t quantity; + float merger_time; +} ItemDrop; + +typedef struct { + ItemDrop items[ITEMS_INVENTORY_SIZE]; + float pickup_time; +} Inventory; + +ECS_COMPONENT_EXTERN(Chunk); +ECS_COMPONENT_EXTERN(Position); +ECS_COMPONENT_EXTERN(Vector2D); +ECS_COMPONENT_EXTERN(Drawable); +ECS_COMPONENT_EXTERN(Input); +ECS_COMPONENT_EXTERN(Velocity); +ECS_COMPONENT_EXTERN(ClientInfo); +ECS_COMPONENT_EXTERN(Health); +ECS_COMPONENT_EXTERN(Classify); +ECS_COMPONENT_EXTERN(Vehicle); +ECS_COMPONENT_EXTERN(IsInVehicle); +ECS_COMPONENT_EXTERN(ItemDrop); +ECS_COMPONENT_EXTERN(Inventory); +ECS_TAG_EXTERN(EcsActor); +ECS_TAG_EXTERN(EcsDemoNPC); +ECS_TYPE_EXTERN(Player); +ECS_TYPE_EXTERN(Movement); +ECS_TYPE_EXTERN(Walking); +ECS_TYPE_EXTERN(Flying); +ECS_TYPE_EXTERN(EcsClient); +// NOTE(zaklaus): @1 EXTERN + +typedef struct { + ECS_DECLARE_COMPONENT(Chunk); + ECS_DECLARE_COMPONENT(Position); + ECS_DECLARE_COMPONENT(Vector2D); + ECS_DECLARE_COMPONENT(Drawable); + ECS_DECLARE_COMPONENT(Input); + ECS_DECLARE_COMPONENT(Velocity); + ECS_DECLARE_COMPONENT(ClientInfo); + ECS_DECLARE_COMPONENT(Health); + ECS_DECLARE_COMPONENT(Classify); + ECS_DECLARE_COMPONENT(Vehicle); + ECS_DECLARE_COMPONENT(IsInVehicle); + ECS_DECLARE_COMPONENT(ItemDrop); + ECS_DECLARE_COMPONENT(Inventory); + ECS_DECLARE_ENTITY(EcsActor); + ECS_DECLARE_ENTITY(EcsDemoNPC); + ECS_DECLARE_TYPE(Player); + ECS_DECLARE_TYPE(Builder); + ECS_DECLARE_TYPE(Movement); + ECS_DECLARE_ENTITY(Walking); + ECS_DECLARE_ENTITY(Flying); + // NOTE(zaklaus): @2 DECLARE +} Components; + +#define ComponentsImportHandles(handles)\ +ECS_IMPORT_COMPONENT(handles, Chunk);\ +ECS_IMPORT_COMPONENT(handles, Vector2D);\ +ECS_IMPORT_COMPONENT(handles, Position);\ +ECS_IMPORT_COMPONENT(handles, Drawable);\ +ECS_IMPORT_COMPONENT(handles, Input);\ +ECS_IMPORT_COMPONENT(handles, Velocity);\ +ECS_IMPORT_COMPONENT(handles, ClientInfo);\ +ECS_IMPORT_COMPONENT(handles, Health);\ +ECS_IMPORT_COMPONENT(handles, Classify);\ +ECS_IMPORT_COMPONENT(handles, Vehicle);\ +ECS_IMPORT_COMPONENT(handles, IsInVehicle);\ +ECS_IMPORT_COMPONENT(handles, ItemDrop);\ +ECS_IMPORT_COMPONENT(handles, Inventory);\ +ECS_IMPORT_TYPE(handles, Player);\ +ECS_IMPORT_TYPE(handles, Builder);\ +ECS_IMPORT_TYPE(handles, Movement);\ +ECS_IMPORT_ENTITY(handles, EcsActor);\ +ECS_IMPORT_ENTITY(handles, EcsDemoNPC);\ +ECS_IMPORT_ENTITY(handles, Walking);\ +ECS_IMPORT_ENTITY(handles, Flying);\ +// NOTE(zaklaus): @3 IMPORT + +void ComponentsImport(ecs_world_t *ecs); diff --git a/code/modules/source/system_demo.c b/code/modules/source/system_demo.c index 3f2d313..494e6c4 100644 --- a/code/modules/source/system_demo.c +++ b/code/modules/source/system_demo.c @@ -1,12 +1,12 @@ - -#define DEMO_NPC_MOVE_SPEED 500 -#define DEMO_NPC_STEER_SPEED 300 - -void DemoNPCMoveAround(ecs_iter_t *it) { - Velocity *v = ecs_column(it, Velocity, 1); - for (int i = 0; i < it->count; i++) { - float d = zpl_quake_rsqrt(v[i].x*v[i].x + v[i].y*v[i].y); - v[i].x += (v[i].x*d*DEMO_NPC_MOVE_SPEED*safe_dt(it) + zpl_cos(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED*safe_dt(it)); - v[i].y += (v[i].y*d*DEMO_NPC_MOVE_SPEED*safe_dt(it) + zpl_sin(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED*safe_dt(it)); - } -} + +#define DEMO_NPC_MOVE_SPEED 500 +#define DEMO_NPC_STEER_SPEED 300 + +void DemoNPCMoveAround(ecs_iter_t *it) { + Velocity *v = ecs_column(it, Velocity, 1); + for (int i = 0; i < it->count; i++) { + float d = zpl_quake_rsqrt(v[i].x*v[i].x + v[i].y*v[i].y); + v[i].x += (v[i].x*d*DEMO_NPC_MOVE_SPEED*safe_dt(it) + zpl_cos(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED*safe_dt(it)); + v[i].y += (v[i].y*d*DEMO_NPC_MOVE_SPEED*safe_dt(it) + zpl_sin(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED*safe_dt(it)); + } +} diff --git a/dos2unix_conv.sh b/dos2unix_conv.sh new file mode 100644 index 0000000..66975e2 --- /dev/null +++ b/dos2unix_conv.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +script_path=$(cd -P -- "$(dirname -- "$0")" && pwd -P) +cd "$script_path/" || exit 1 + +find code/modules/ -type f -print0 | xargs -0 dos2unix -ic0 | xargs -0 dos2unix -b +find code/game/ -type f -print0 | xargs -0 dos2unix -ic0 | xargs -0 dos2unix -b