Ensure physics aren't tied to framerate

isolation_bkp/dynres
Dominik Madarász 2021-10-27 00:22:54 +02:00
parent 16da0ad158
commit 6f57fb178a
10 changed files with 1261 additions and 1258 deletions

View File

@ -1,278 +1,278 @@
#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"
#include "packets/pkt_send_keystate.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();
}
}
if (game_mode != GAMEKIND_HEADLESS) {
game_world_cleanup_entities();
}
}
void game_render() {
if (game_mode != GAMEKIND_HEADLESS) {
platform_render();
}
}
void game_action_send_keystate(float x,
float y,
float mx,
float my,
uint8_t use,
uint8_t sprint,
uint8_t ctrl,
uint8_t drop,
uint8_t selected_item,
uint8_t swap,
uint8_t swap_from,
uint8_t swap_to) {
pkt_send_keystate_send(active_viewer->view_id, x, y, mx, my, use, sprint, ctrl, drop, selected_item, swap, swap_from, swap_to);
}
#define GAME_ENT_REMOVAL_TIME 10000
#define GAME_ENT_REMOVAL_TRESHOLD 500
void game_world_cleanup_entities(void) {
// TODO(zaklaus): not the best approach to do a cleanup, let memory stay for a while as it might be reused later on anyway.
#if 1
profile(PROF_ENTITY_REMOVAL) {
static uint64_t last_removal_time = 0;
if (last_removal_time > zpl_time_rel_ms()) return;
last_removal_time = zpl_time_rel_ms() + GAME_ENT_REMOVAL_TIME;
for (int i = 0; i < zpl_buffer_count(world_viewers); i += 1){
entity_view_tbl *view = &world_viewers[i].entities;
uint32_t deletions = 0;
for (int j = 0; j < zpl_array_count(view->entries); j += 1) {
if (deletions > GAME_ENT_REMOVAL_TRESHOLD) return;
entity_view *e = &view->entries[j].value;
if (e->tran_effect == ETRAN_REMOVE) {
entity_view_tbl_remove(view, e->ent_id);
deletions++;
}
}
}
}
#endif
}
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"
#include "packets/pkt_send_keystate.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();
}
}
if (game_mode != GAMEKIND_HEADLESS) {
game_world_cleanup_entities();
}
}
void game_render() {
if (game_mode != GAMEKIND_HEADLESS) {
platform_render();
}
}
void game_action_send_keystate(float x,
float y,
float mx,
float my,
uint8_t use,
uint8_t sprint,
uint8_t ctrl,
uint8_t drop,
uint8_t selected_item,
uint8_t swap,
uint8_t swap_from,
uint8_t swap_to) {
pkt_send_keystate_send(active_viewer->view_id, x, y, mx, my, use, sprint, ctrl, drop, selected_item, swap, swap_from, swap_to);
}
#define GAME_ENT_REMOVAL_TIME 10000
#define GAME_ENT_REMOVAL_TRESHOLD 500
void game_world_cleanup_entities(void) {
// TODO(zaklaus): not the best approach to do a cleanup, let memory stay for a while as it might be reused later on anyway.
#if 1
profile(PROF_ENTITY_REMOVAL) {
static uint64_t last_removal_time = 0;
if (last_removal_time > zpl_time_rel_ms()) return;
last_removal_time = zpl_time_rel_ms() + GAME_ENT_REMOVAL_TIME;
for (int i = 0; i < zpl_buffer_count(world_viewers); i += 1){
entity_view_tbl *view = &world_viewers[i].entities;
uint32_t deletions = 0;
for (int j = 0; j < zpl_array_count(view->entries); j += 1) {
if (deletions > GAME_ENT_REMOVAL_TRESHOLD) return;
entity_view *e = &view->entries[j].value;
if (e->tran_effect == ETRAN_REMOVE) {
entity_view_tbl_remove(view, e->ent_id);
deletions++;
}
}
}
}
#endif
}
void game_request_close() {
platform_request_close();
}

View File

@ -1,158 +1,158 @@
#include "platform.h"
#include "raylib.h"
#include "raymath.h"
#include "network.h"
#include "game.h"
#include "entity_view.h"
#include "prediction.h"
#include "camera.h"
#include "math.h"
#include "world/blocks.h"
#include "assets.h"
#include "profiler.h"
#include "debug_ui.h"
#include "utils/raylib_helpers.h"
static uint16_t screenWidth = 1600;
static uint16_t screenHeight = 900;
static float target_zoom = 1.5f;
static bool request_shutdown;
#define GFX_KIND 2
#include "renderer_bridge.c"
// NOTE(zaklaus): add-ins
#include "gui/inventory.c"
void platform_init() {
InitWindow(screenWidth, screenHeight, "eco2d");
SetWindowState(FLAG_WINDOW_UNDECORATED|FLAG_WINDOW_MAXIMIZED|FLAG_WINDOW_RESIZABLE|FLAG_MSAA_4X_HINT);
SetTargetFPS(60);
screenWidth = GetScreenWidth();
screenHeight = GetScreenHeight();
renderer_init();
}
void display_conn_status() {
if (game_is_networked()) {
if (network_client_is_connected()) {
DrawText("Connection: online", 5, 5, 12, GREEN);
} else {
DrawText("Connection: offline", 5, 5, 12, RED);
}
} else {
DrawText("Connection: single-player", 5, 5, 12, BLUE);
}
}
void platform_shutdown() {
renderer_shutdown();
CloseWindow();
}
uint8_t platform_is_running() {
return !WindowShouldClose();
}
void platform_input() {
float mouse_z = (GetMouseWheelMove()*0.5f);
if (mouse_z != 0.0f) {
target_zoom = zpl_clamp(target_zoom+mouse_z, 0.1f, 10.0f);
}
// NOTE(zaklaus): keystate handling
{
float x=0.0f, y=0.0f;
uint8_t use, sprint, drop, ctrl;
if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) x += 1.0f;
if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) x -= 1.0f;
if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) y += 1.0f;
if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) y -= 1.0f;
use = IsKeyPressed(KEY_SPACE);
sprint = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);
drop = IsKeyPressed(KEY_G) || inv_drop_item;
// NOTE(zaklaus): NEW! mouse movement
Vector2 mouse_pos = GetMousePosition();
mouse_pos.x /= screenWidth;
mouse_pos.y /= screenHeight;
mouse_pos.x -= 0.5f;
mouse_pos.y -= 0.5f;
mouse_pos = Vector2Normalize(mouse_pos);
if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) {
x = mouse_pos.x;
y = -mouse_pos.y;
}
game_action_send_keystate(x, y, mouse_pos.x, mouse_pos.y, use, sprint, ctrl, drop, inv_selected_item, inv_swap, inv_swap_from, inv_swap_to);
}
// NOTE(zaklaus): cycle through viewers
{
if (IsKeyPressed(KEY_Q)) {
game_world_view_cycle_active(-1);
}
else if (IsKeyPressed(KEY_E)) {
game_world_view_cycle_active(1);
}
}
// NOTE(zaklaus): switch render modes
{
if (IsKeyPressed(KEY_O)) {
renderer_switch(1-gfx_kind);
}
}
// NOTE(zaklaus): toggle debug drawing
#ifndef ECO2D_PROD
{
if (IsKeyPressed(KEY_T)) {
debug_draw_enable(!debug_draw_state());
}
}
#endif
}
void platform_render() {
profile(PROF_ENTITY_LERP) {
game_world_view_active_entity_map(lerp_entity_positions);
game_world_view_active_entity_map(do_entity_fadeinout);
}
BeginDrawing();
{
profile (PROF_RENDER) {
renderer_draw();
}
renderer_debug_draw();
{
// NOTE(zaklaus): add-ins
inventory_draw();
}
display_conn_status();
debug_draw();
}
EndDrawing();
if (request_shutdown) {
CloseWindow();
}
}
float platform_frametime() {
return GetFrameTime();
}
float platform_zoom_get(void) {
return target_zoom;
}
void platform_request_close(void) {
request_shutdown = true;
}
#include "platform.h"
#include "raylib.h"
#include "raymath.h"
#include "network.h"
#include "game.h"
#include "entity_view.h"
#include "prediction.h"
#include "camera.h"
#include "math.h"
#include "world/blocks.h"
#include "assets.h"
#include "profiler.h"
#include "debug_ui.h"
#include "utils/raylib_helpers.h"
static uint16_t screenWidth = 1600;
static uint16_t screenHeight = 900;
static float target_zoom = 1.5f;
static bool request_shutdown;
#define GFX_KIND 2
#include "renderer_bridge.c"
// NOTE(zaklaus): add-ins
#include "gui/inventory.c"
void platform_init() {
InitWindow(screenWidth, screenHeight, "eco2d");
SetWindowState(FLAG_WINDOW_UNDECORATED|FLAG_WINDOW_MAXIMIZED|FLAG_WINDOW_RESIZABLE|FLAG_MSAA_4X_HINT);
//SetTargetFPS(144);
screenWidth = GetScreenWidth();
screenHeight = GetScreenHeight();
renderer_init();
}
void display_conn_status() {
if (game_is_networked()) {
if (network_client_is_connected()) {
DrawText("Connection: online", 5, 5, 12, GREEN);
} else {
DrawText("Connection: offline", 5, 5, 12, RED);
}
} else {
DrawText("Connection: single-player", 5, 5, 12, BLUE);
}
}
void platform_shutdown() {
renderer_shutdown();
CloseWindow();
}
uint8_t platform_is_running() {
return !WindowShouldClose();
}
void platform_input() {
float mouse_z = (GetMouseWheelMove()*0.5f);
if (mouse_z != 0.0f) {
target_zoom = zpl_clamp(target_zoom+mouse_z, 0.1f, 10.0f);
}
// NOTE(zaklaus): keystate handling
{
float x=0.0f, y=0.0f;
uint8_t use, sprint, drop, ctrl;
if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) x += 1.0f;
if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) x -= 1.0f;
if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) y += 1.0f;
if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) y -= 1.0f;
use = IsKeyPressed(KEY_SPACE);
sprint = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);
drop = IsKeyPressed(KEY_G) || inv_drop_item;
// NOTE(zaklaus): NEW! mouse movement
Vector2 mouse_pos = GetMousePosition();
mouse_pos.x /= screenWidth;
mouse_pos.y /= screenHeight;
mouse_pos.x -= 0.5f;
mouse_pos.y -= 0.5f;
mouse_pos = Vector2Normalize(mouse_pos);
if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)) {
x = mouse_pos.x;
y = -mouse_pos.y;
}
game_action_send_keystate(x, y, mouse_pos.x, mouse_pos.y, use, sprint, ctrl, drop, inv_selected_item, inv_swap, inv_swap_from, inv_swap_to);
}
// NOTE(zaklaus): cycle through viewers
{
if (IsKeyPressed(KEY_Q)) {
game_world_view_cycle_active(-1);
}
else if (IsKeyPressed(KEY_E)) {
game_world_view_cycle_active(1);
}
}
// NOTE(zaklaus): switch render modes
{
if (IsKeyPressed(KEY_O)) {
renderer_switch(1-gfx_kind);
}
}
// NOTE(zaklaus): toggle debug drawing
#ifndef ECO2D_PROD
{
if (IsKeyPressed(KEY_T)) {
debug_draw_enable(!debug_draw_state());
}
}
#endif
}
void platform_render() {
profile(PROF_ENTITY_LERP) {
game_world_view_active_entity_map(lerp_entity_positions);
game_world_view_active_entity_map(do_entity_fadeinout);
}
BeginDrawing();
{
profile (PROF_RENDER) {
renderer_draw();
}
renderer_debug_draw();
{
// NOTE(zaklaus): add-ins
inventory_draw();
}
display_conn_status();
debug_draw();
}
EndDrawing();
if (request_shutdown) {
CloseWindow();
}
}
float platform_frametime() {
return GetFrameTime();
}
float platform_zoom_get(void) {
return target_zoom;
}
void platform_request_close(void) {
request_shutdown = true;
}

View File

@ -1,22 +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);
zpl_zero_item(veh);
veh->wheel_base = 50.0f;
veh->speed = 50.0f;
veh->reverse_speed = -20.0f;
veh->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);
}

View File

@ -1,488 +1,488 @@
#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 *chpos = 0;
if ((chpos = ecs_get_mut_if(world_ecs(), e, Chunk))) {
view.x = chpos->x;
view.y = chpos->y;
view.blocks_used = 1;
view.is_dirty = chpos->is_dirty;
chpos->is_dirty = false;
for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) {
view.blocks[i] = world.block_mapping[chpos->id][i];
}
for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) {
view.outer_blocks[i] = world.outer_block_mapping[chpos->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.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;
}
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 *chpos = 0;
if ((chpos = ecs_get_mut_if(world_ecs(), e, Chunk))) {
view.x = chpos->x;
view.y = chpos->y;
view.blocks_used = 1;
view.is_dirty = chpos->is_dirty;
chpos->is_dirty = false;
for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) {
view.blocks[i] = world.block_mapping[chpos->id][i];
}
for (int i = 0; i < world.chunk_size*world.chunk_size; i += 1) {
view.outer_blocks[i] = world.outer_block_mapping[chpos->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.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;
}
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);
}

View File

@ -1,94 +1,94 @@
#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 10
#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 {
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);
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 10
#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 {
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);
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);

View File

@ -8,7 +8,7 @@
#include "game.h"
#define PHY_BLOCK_COLLISION 1
#define PHY_WALK_DRAG 0.12
#define PHY_WALK_DRAG 4.23f
#define PHY_LOOKAHEAD(x) (zpl_sign(x)*16.0f)
#include "source/system_onfoot.c"
@ -29,7 +29,7 @@ void IntegratePositions(ecs_iter_t *it) {
for (int i = 0; i < it->count; i++) {
// NOTE(zaklaus): world bounds
{
double w = (double)world_dim();
float w = (float)world_dim();
p[i].x = zpl_clamp(p[i].x, 0, w-1);
p[i].y = zpl_clamp(p[i].y, 0, w-1);
}
@ -127,8 +127,8 @@ void ApplyWorldDragOnVelocity(ecs_iter_t *it) {
world_block_lookup lookup = world_block_from_realpos(p[i].x, p[i].y);
float drag = zpl_clamp(blocks_get_drag(lookup.block_id), 0.0f, 1.0f);
float friction = blocks_get_friction(lookup.block_id);
v[i].x = zpl_lerp(v[i].x, 0.0f, PHY_WALK_DRAG*drag*friction);
v[i].y = zpl_lerp(v[i].y, 0.0f, PHY_WALK_DRAG*drag*friction);
v[i].x = zpl_lerp(v[i].x, 0.0f, PHY_WALK_DRAG*drag*friction*it->delta_time);
v[i].y = zpl_lerp(v[i].y, 0.0f, PHY_WALK_DRAG*drag*friction*it->delta_time);
}
}

View File

@ -1,10 +1,11 @@
#pragma once
#include "flecs/flecs.h"
typedef struct {
// NOTE(zaklaus): Public systems are exposed here
} Systems;
#define SystemsImportHandles(handles) (void)(handles)
void SystemsImport(ecs_world_t *ecs);
#pragma once
#include "flecs/flecs.h"
typedef struct {
// NOTE(zaklaus): Public systems are exposed here
int32_t _unused;
} Systems;
#define SystemsImportHandles(handles) (void)(handles)
void SystemsImport(ecs_world_t *ecs);

View File

@ -1,12 +1,12 @@
#define DEMO_NPC_MOVE_SPEED 50
#define DEMO_NPC_STEER_SPEED 30
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 + zpl_cos(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED);
v[i].y += (v[i].y*d*DEMO_NPC_MOVE_SPEED + zpl_sin(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED);
}
}
#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*it->delta_time + zpl_cos(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED*it->delta_time);
v[i].y += (v[i].y*d*DEMO_NPC_MOVE_SPEED*it->delta_time + zpl_sin(zpl_to_radians(rand()%360))*DEMO_NPC_STEER_SPEED*it->delta_time);
}
}

View File

@ -1,4 +1,4 @@
#define PLR_MOVE_SPEED 30.0
#define PLR_MOVE_SPEED 800.0
#define PLR_MOVE_SPEED_MULT 1.5
void MovementImpulse(ecs_iter_t *it) {
@ -10,7 +10,7 @@ void MovementImpulse(ecs_iter_t *it) {
world_block_lookup lookup = world_block_from_realpos(p[i].x, p[i].y);
float drag = zpl_clamp(blocks_get_drag(lookup.block_id), 0.0f, 1.0f);
double speed = PLR_MOVE_SPEED * (in[i].sprint ? PLR_MOVE_SPEED_MULT : 1.0);
v[i].x += in[i].x*speed*drag;
v[i].y -= in[i].y*speed*drag;
v[i].x += in[i].x*speed*drag*it->delta_time;
v[i].y -= in[i].y*speed*drag*it->delta_time;
}
}

View File

@ -1,191 +1,192 @@
#include "debug_draw.h"
#define VEH_ENTER_RADIUS 45.0f
void LeaveVehicle(ecs_iter_t *it) {
Input *in = ecs_column(it, Input, 1);
IsInVehicle *vehp = ecs_column(it, IsInVehicle, 2);
Velocity *v = ecs_column(it, Velocity, 3);
for (int i = 0; i < it->count; i++) {
if (!in[i].use) continue;
Vehicle *veh = 0;
if ((veh = ecs_get_mut_if(it->world, vehp->veh, Vehicle))) {
for (int k = 0; k < 4; k++) {
if (veh->seats[k] == it->entities[i]) {
veh->seats[k] = 0;
break;
}
}
in[i].use = false;
ecs_remove(it->world, it->entities[i], IsInVehicle);
// NOTE(zaklaus): push passenger out
{
float px = zpl_cos(veh->heading)*400.0f;
float py = zpl_sin(veh->heading)*400.0f;
v->x += py;
v->y -= px;
}
} else {
ZPL_PANIC("unreachable code");
}
}
}
void EnterVehicle(ecs_iter_t *it) {
Input *in = ecs_column(it, Input, 1);
Position *p = ecs_column(it, Position, 2);
for (int i = 0; i < it->count; i++) {
if (!in[i].use) continue;
size_t ents_count;
int64_t *ents = world_chunk_query_entities(it->entities[i], &ents_count, 2);
bool has_entered_veh = false;
for (size_t j = 0; j < ents_count; j++) {
Vehicle *veh = 0;
if (has_entered_veh) break;
if ((veh = ecs_get_mut_if(it->world, ents[j], Vehicle))) {
Position const* p2 = ecs_get(it->world, ents[j], Position);
float dx = p2->x - p[i].x;
float dy = p2->y - p[i].y;
float range = zpl_sqrt(dx*dx + dy*dy);
if (range <= VEH_ENTER_RADIUS) {
for (int k = 0; k < 4; k++) {
if (veh->seats[k] != 0) continue;
// NOTE(zaklaus): We can enter the vehicle, yay!
veh->seats[k] = it->entities[i];
ecs_set(it->world, it->entities[i], IsInVehicle, {
.veh = ents[j]
});
p[i] = *p2;
in[i].use = false;
has_entered_veh = true;
break;
}
}
}
}
}
}
#define VEHICLE_FORCE 34.8f
#define VEHICLE_ACCEL 0.42f
#define VEHICLE_DECEL 0.28f
#define VEHICLE_STEER 3.89f
#define VEHICLE_BRAKE_FORCE 0.84f
void VehicleHandling(ecs_iter_t *it) {
Vehicle *veh = ecs_column(it, Vehicle, 1);
Position *p = ecs_column(it, Position, 2);
Velocity *v = ecs_column(it, Velocity, 3);
for (int i = 0; i < it->count; i++) {
Vehicle *car = &veh[i];
for (int j = 0; j < 4; j++) {
// NOTE(zaklaus): Perform seat cleanup
if (!world_entity_valid(veh[i].seats[j])) {
veh[i].seats[j] = 0;
continue;
}
ecs_entity_t pe = veh[i].seats[j];
// NOTE(zaklaus): Handle driver input
if (j == 0) {
Input const* in = ecs_get(it->world, pe, Input);
car->force += zpl_lerp(0.0f, in->y * VEHICLE_FORCE, VEHICLE_ACCEL*it->delta_time);
if (in->sprint) {
car->force = zpl_lerp(car->force, 0.0f, VEHICLE_BRAKE_FORCE*it->delta_time);
if (zpl_abs(car->force) < 5.5f)
car->force = 0.0f;
}
car->steer *= 0.97f;
car->steer += (in->x * VEHICLE_STEER)*it->delta_time;
car->steer = zpl_clamp(car->steer, -40.0f, 40.0f);
}
}
car->force = zpl_clamp(car->force, car->reverse_speed, car->speed);
// NOTE(zaklaus): Vehicle physics
float fr_x = p[i].x + (car->wheel_base/2.0f) * zpl_cos(car->heading);
float fr_y = p[i].y + (car->wheel_base/2.0f) * zpl_sin(car->heading);
float bk_x = p[i].x - (car->wheel_base/2.0f) * zpl_cos(car->heading);
float bk_y = p[i].y - (car->wheel_base/2.0f) * zpl_sin(car->heading);
world_block_lookup lookup = world_block_from_realpos(p[i].x, p[i].y);
float drag = zpl_clamp(blocks_get_drag(lookup.block_id), 0.0f, 1.0f);
bk_x += car->force * drag * zpl_cos(car->heading);
bk_y += car->force * drag * zpl_sin(car->heading);
fr_x += car->force * drag * zpl_cos(car->heading + zpl_to_radians(car->steer));
fr_y += car->force * drag * zpl_sin(car->heading + zpl_to_radians(car->steer));
v[i].x += (fr_x + bk_x) / 2.0f - p[i].x;
v[i].y += (fr_y + bk_y) / 2.0f - p[i].y;
car->heading = zpl_arctan2(fr_y - bk_y, fr_x - bk_x);
world_block_lookup lookahead = world_block_from_realpos(p[i].x+PHY_LOOKAHEAD(v[i].x), p[i].y+PHY_LOOKAHEAD(v[i].y));
uint32_t flags = blocks_get_flags(lookahead.block_id);
if (flags & BLOCK_FLAG_COLLISION) {
car->force = 0.0f;
}
for (int j = 0; j < 4; j++) {
if (!world_entity_valid(veh[i].seats[j])) continue;
ecs_entity_t pe = veh[i].seats[j];
// NOTE(zaklaus): Update passenger position
{
Position *p2 = ecs_get_mut(it->world, pe, Position, NULL);
Velocity *v2 = ecs_get_mut(it->world, pe, Velocity, NULL);
*p2 = p[i];
*v2 = v[i];
}
}
{
debug_v2 b2 = {p[i].x + zpl_cos(car->heading)*(car->wheel_base), p[i].y + zpl_sin(car->heading)*(car->wheel_base)};
debug_push_line((debug_v2){p[i].x, p[i].y}, b2, 0x0000FFFF);
// NOTE(zaklaus): force
{
float dx = zpl_cos(car->heading);
float dy = zpl_sin(car->heading);
debug_push_circle((debug_v2){p[i].x+dx*car->force, p[i].y+dy*car->force}, 5.0f, 0x00FF00FF);
}
// NOTE(zaklaus): steer
{
float dx = zpl_sin(car->heading);
float dy = -zpl_cos(car->heading);
debug_push_circle((debug_v2){p[i].x+dx*car->steer*-20, p[i].y+dy*car->steer*-20}, 5.0f, 0x00FFAAFF);
}
}
}
}
void ClearVehicle(ecs_iter_t *it) {
Vehicle *veh = ecs_column(it, Vehicle, 1);
for (int i = 0; i < it->count; i++) {
for (int k = 0; k < 4; k++) {
if (world_entity_valid(veh[i].seats[k])) {
ecs_remove(it->world, veh[i].seats[k], IsInVehicle);
}
}
}
}
#include "debug_draw.h"
#define VEH_ENTER_RADIUS 45.0f
void LeaveVehicle(ecs_iter_t *it) {
Input *in = ecs_column(it, Input, 1);
IsInVehicle *vehp = ecs_column(it, IsInVehicle, 2);
Velocity *v = ecs_column(it, Velocity, 3);
for (int i = 0; i < it->count; i++) {
if (!in[i].use) continue;
Vehicle *veh = 0;
if ((veh = ecs_get_mut_if(it->world, vehp->veh, Vehicle))) {
for (int k = 0; k < 4; k++) {
if (veh->seats[k] == it->entities[i]) {
veh->seats[k] = 0;
break;
}
}
in[i].use = false;
ecs_remove(it->world, it->entities[i], IsInVehicle);
// NOTE(zaklaus): push passenger out
{
float px = zpl_cos(veh->heading)*400.0f;
float py = zpl_sin(veh->heading)*400.0f;
v->x += py;
v->y -= px;
}
} else {
ZPL_PANIC("unreachable code");
}
}
}
void EnterVehicle(ecs_iter_t *it) {
Input *in = ecs_column(it, Input, 1);
Position *p = ecs_column(it, Position, 2);
for (int i = 0; i < it->count; i++) {
if (!in[i].use) continue;
size_t ents_count;
int64_t *ents = world_chunk_query_entities(it->entities[i], &ents_count, 2);
bool has_entered_veh = false;
for (size_t j = 0; j < ents_count; j++) {
Vehicle *veh = 0;
if (has_entered_veh) break;
if ((veh = ecs_get_mut_if(it->world, ents[j], Vehicle))) {
Position const* p2 = ecs_get(it->world, ents[j], Position);
float dx = p2->x - p[i].x;
float dy = p2->y - p[i].y;
float range = zpl_sqrt(dx*dx + dy*dy);
if (range <= VEH_ENTER_RADIUS) {
for (int k = 0; k < 4; k++) {
if (veh->seats[k] != 0) continue;
// NOTE(zaklaus): We can enter the vehicle, yay!
veh->seats[k] = it->entities[i];
ecs_set(it->world, it->entities[i], IsInVehicle, {
.veh = ents[j]
});
p[i] = *p2;
in[i].use = false;
has_entered_veh = true;
break;
}
}
}
}
}
}
#define VEHICLE_FORCE 340.8f
#define VEHICLE_ACCEL 0.12f
#define VEHICLE_DECEL 0.28f
#define VEHICLE_STEER 3.89f
#define VEHICLE_POWER 34.89f
#define VEHICLE_BRAKE_FORCE 0.84f
void VehicleHandling(ecs_iter_t *it) {
Vehicle *veh = ecs_column(it, Vehicle, 1);
Position *p = ecs_column(it, Position, 2);
Velocity *v = ecs_column(it, Velocity, 3);
for (int i = 0; i < it->count; i++) {
Vehicle *car = &veh[i];
for (int j = 0; j < 4; j++) {
// NOTE(zaklaus): Perform seat cleanup
if (!world_entity_valid(veh[i].seats[j])) {
veh[i].seats[j] = 0;
continue;
}
ecs_entity_t pe = veh[i].seats[j];
// NOTE(zaklaus): Handle driver input
if (j == 0) {
Input const* in = ecs_get(it->world, pe, Input);
car->force += zpl_lerp(0.0f, in->y * VEHICLE_FORCE, VEHICLE_ACCEL*it->delta_time);
if (in->sprint) {
car->force = zpl_lerp(car->force, 0.0f, VEHICLE_BRAKE_FORCE*it->delta_time);
if (zpl_abs(car->force) < 5.5f)
car->force = 0.0f;
}
car->steer *= 0.97f;
car->steer += (in->x * VEHICLE_STEER)*it->delta_time;
car->steer = zpl_clamp(car->steer, -40.0f, 40.0f);
}
}
car->force = zpl_clamp(car->force, car->reverse_speed, car->speed);
// NOTE(zaklaus): Vehicle physics
float fr_x = p[i].x + (car->wheel_base/2.0f) * zpl_cos(car->heading);
float fr_y = p[i].y + (car->wheel_base/2.0f) * zpl_sin(car->heading);
float bk_x = p[i].x - (car->wheel_base/2.0f) * zpl_cos(car->heading);
float bk_y = p[i].y - (car->wheel_base/2.0f) * zpl_sin(car->heading);
world_block_lookup lookup = world_block_from_realpos(p[i].x, p[i].y);
float drag = zpl_clamp(blocks_get_drag(lookup.block_id), 0.0f, 1.0f);
bk_x += car->force * drag * zpl_cos(car->heading);
bk_y += car->force * drag * zpl_sin(car->heading);
fr_x += car->force * drag * zpl_cos(car->heading + zpl_to_radians(car->steer));
fr_y += car->force * drag * zpl_sin(car->heading + zpl_to_radians(car->steer));
v[i].x += ((fr_x + bk_x) / 2.0f - p[i].x)*it->delta_time*VEHICLE_POWER;
v[i].y += ((fr_y + bk_y) / 2.0f - p[i].y)*it->delta_time*VEHICLE_POWER;
car->heading = zpl_arctan2(fr_y - bk_y, fr_x - bk_x);
world_block_lookup lookahead = world_block_from_realpos(p[i].x+PHY_LOOKAHEAD(v[i].x), p[i].y+PHY_LOOKAHEAD(v[i].y));
uint32_t flags = blocks_get_flags(lookahead.block_id);
if (flags & BLOCK_FLAG_COLLISION) {
car->force = 0.0f;
}
for (int j = 0; j < 4; j++) {
if (!world_entity_valid(veh[i].seats[j])) continue;
ecs_entity_t pe = veh[i].seats[j];
// NOTE(zaklaus): Update passenger position
{
Position *p2 = ecs_get_mut(it->world, pe, Position, NULL);
Velocity *v2 = ecs_get_mut(it->world, pe, Velocity, NULL);
*p2 = p[i];
*v2 = v[i];
}
}
{
debug_v2 b2 = {p[i].x + zpl_cos(car->heading)*(car->wheel_base), p[i].y + zpl_sin(car->heading)*(car->wheel_base)};
debug_push_line((debug_v2){p[i].x, p[i].y}, b2, 0x0000FFFF);
// NOTE(zaklaus): force
{
float dx = zpl_cos(car->heading);
float dy = zpl_sin(car->heading);
debug_push_circle((debug_v2){p[i].x+dx*car->force, p[i].y+dy*car->force}, 5.0f, 0x00FF00FF);
}
// NOTE(zaklaus): steer
{
float dx = zpl_sin(car->heading);
float dy = -zpl_cos(car->heading);
debug_push_circle((debug_v2){p[i].x+dx*car->steer*-20, p[i].y+dy*car->steer*-20}, 5.0f, 0x00FFAAFF);
}
}
}
}
void ClearVehicle(ecs_iter_t *it) {
Vehicle *veh = ecs_column(it, Vehicle, 1);
for (int i = 0; i < it->count; i++) {
for (int k = 0; k < 4; k++) {
if (world_entity_valid(veh[i].seats[k])) {
ecs_remove(it->world, veh[i].seats[k], IsInVehicle);
}
}
}
}