Implement world time single-stepping
parent
4be5ee9b61
commit
276f85dee8
|
@ -1,387 +1,410 @@
|
|||
#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_SLIDER, .name = "slider", .slider = { .min = 0.0f, .max = 1.0f, .val = 0.5f } },
|
||||
{ .kind = DITEM_END },
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.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,
|
||||
}
|
||||
},
|
||||
{
|
||||
.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_ENTITY_REMOVAL, .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_SLIDER, .name = "slider", .slider = { .min = 0.0f, .max = 1.0f, .val = 0.5f } },
|
||||
{ .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,
|
||||
}
|
||||
},
|
||||
{
|
||||
.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_ENTITY_REMOVAL, .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;
|
||||
}
|
||||
|
|
|
@ -1,235 +1,274 @@
|
|||
#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) >= 10000) 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;
|
||||
|
||||
for (uint32_t i = 0; i < zpl_array_count(demo_npcs); i++) {
|
||||
entity_despawn(demo_npcs[i]);
|
||||
}
|
||||
|
||||
zpl_array_free(demo_npcs);
|
||||
demo_npcs = 0;
|
||||
}
|
||||
#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) >= 10000) 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;
|
||||
|
||||
for (uint32_t i = 0; i < zpl_array_count(demo_npcs); i++) {
|
||||
entity_despawn(demo_npcs[i]);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,93 +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 : "<unnamed>"));
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
#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 : "<unnamed>"));
|
||||
}
|
||||
|
||||
// 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)));
|
||||
}
|
||||
|
|
|
@ -147,6 +147,7 @@ int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
world.is_paused = false;
|
||||
world.seed = seed;
|
||||
world.chunk_size = chunk_size;
|
||||
world.chunk_amount = chunk_amount;
|
||||
|
@ -349,6 +350,26 @@ 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;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ typedef WORLD_PKT_READER(world_pkt_reader_proc);
|
|||
typedef WORLD_PKT_WRITER(world_pkt_writer_proc);
|
||||
|
||||
typedef struct {
|
||||
bool is_paused;
|
||||
uint8_t *data;
|
||||
uint32_t seed;
|
||||
uint32_t size;
|
||||
|
@ -63,6 +64,12 @@ 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);
|
||||
|
|
Loading…
Reference in New Issue