1938 lines
77 KiB
C
1938 lines
77 KiB
C
#include "sim_step.h"
|
|
#include "sim_ent.h"
|
|
#include "sim.h"
|
|
#include "sys.h"
|
|
#include "arena.h"
|
|
#include "util.h"
|
|
#include "sprite.h"
|
|
#include "math.h"
|
|
#include "atomic.h"
|
|
#include "app.h"
|
|
#include "log.h"
|
|
#include "phys.h"
|
|
#include "collider.h"
|
|
#include "rand.h"
|
|
#include "space.h"
|
|
#include "bitbuff.h"
|
|
#include "host.h"
|
|
|
|
/* ========================== *
|
|
* Sim accel
|
|
* ========================== */
|
|
|
|
struct sim_accel sim_accel_alloc(void)
|
|
{
|
|
struct sim_accel accel = ZI;
|
|
accel.space = space_alloc(SPACE_CELL_SIZE, SPACE_CELL_BINS_SQRT);
|
|
return accel;
|
|
}
|
|
|
|
void sim_accel_release(struct sim_accel *accel)
|
|
{
|
|
space_release(accel->space);
|
|
}
|
|
|
|
void sim_accel_reset(struct sim_snapshot *ss, struct sim_accel *accel)
|
|
{
|
|
space_reset(accel->space);
|
|
|
|
/* Reset ent space handles */
|
|
for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) {
|
|
struct sim_ent *ent = &ss->ents[sim_ent_index];
|
|
if (ent->valid) {
|
|
MEMZERO_STRUCT(&ent->space_handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Test
|
|
* ========================== */
|
|
|
|
/* TODO: Remove this */
|
|
|
|
INTERNAL struct sim_ent *test_spawn_smg(struct sim_ent *parent)
|
|
{
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/gun.ase"));
|
|
|
|
sim_ent_enable_prop(e, SEPROP_ATTACHED);
|
|
e->attach_slice = LIT("attach.wep");
|
|
e->layer = SIM_LAYER_RELATIVE_WEAPON;
|
|
|
|
sim_ent_enable_prop(e, SEPROP_WEAPON_SMG);
|
|
e->primary_fire_delay = 1.0f / 10.0f;
|
|
e->secondary_fire_delay = 1.0f / 10.0f;
|
|
|
|
return e;
|
|
}
|
|
|
|
INTERNAL struct sim_ent *test_spawn_launcher(struct sim_ent *parent)
|
|
{
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/gun.ase"));
|
|
|
|
sim_ent_enable_prop(e, SEPROP_ATTACHED);
|
|
e->attach_slice = LIT("attach.wep");
|
|
e->layer = SIM_LAYER_RELATIVE_WEAPON;
|
|
|
|
sim_ent_enable_prop(e, SEPROP_WEAPON_LAUNCHER);
|
|
e->primary_fire_delay = 1.0f / 10.0f;
|
|
e->secondary_fire_delay = 1.0f / 10.0f;
|
|
|
|
return e;
|
|
}
|
|
|
|
INTERNAL struct sim_ent *test_spawn_chucker(struct sim_ent *parent)
|
|
{
|
|
struct sim_ent *chucker = sim_ent_alloc_sync_src(parent);
|
|
chucker->sprite = sprite_tag_from_path(LIT("sprite/gun.ase"));
|
|
|
|
sim_ent_enable_prop(chucker, SEPROP_ATTACHED);
|
|
chucker->attach_slice = LIT("attach.wep");
|
|
chucker->layer = SIM_LAYER_RELATIVE_WEAPON;
|
|
|
|
sim_ent_enable_prop(chucker, SEPROP_WEAPON_CHUCKER);
|
|
chucker->primary_fire_delay = 1.0f / 10.0f;
|
|
chucker->secondary_fire_delay = 1.0f / 2.0f;
|
|
|
|
/* Chucker zone */
|
|
{
|
|
struct sim_ent *zone = sim_ent_alloc_sync_src(chucker);
|
|
|
|
sim_ent_enable_prop(zone, SEPROP_CHUCKER_ZONE);
|
|
|
|
sim_ent_enable_prop(zone, SEPROP_ATTACHED);
|
|
zone->attach_slice = LIT("out");
|
|
|
|
sim_ent_enable_prop(zone, SEPROP_SENSOR);
|
|
struct collider_shape collider = ZI;
|
|
collider.count = 2;
|
|
collider.points[1] = V2(0, -0.5);
|
|
collider.radius = 0.1f;
|
|
zone->local_collider = collider;
|
|
|
|
chucker->chucker_zone = zone->id;
|
|
}
|
|
|
|
return chucker;
|
|
}
|
|
|
|
INTERNAL struct sim_ent *test_spawn_employee(struct sim_ent *parent)
|
|
{
|
|
/* Player */
|
|
struct sim_ent *employee = sim_ent_nil();
|
|
{
|
|
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
|
|
struct v2 pos = V2(1, -1);
|
|
|
|
//struct v2 size = V2(0.5, 0.5);
|
|
//struct v2 size = V2(0.5, 0.25);
|
|
struct v2 size = V2(1.0, 1.0);
|
|
|
|
//f32 r = PI / 4;
|
|
f32 r = 0;
|
|
|
|
{
|
|
sim_ent_enable_prop(e, SEPROP_TEST);
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/tim.ase"));
|
|
e->mass_unscaled = 10;
|
|
e->inertia_unscaled = 5;
|
|
}
|
|
|
|
//e->sprite = sprite_tag_from_path(LIT("sprite/box_rounded.ase"));
|
|
//e->sprite_span_name = LIT("idle.unarmed");
|
|
//e->sprite_span_name = LIT("idle.one_handed");
|
|
e->sprite_span_name = LIT("idle.two_handed");
|
|
e->layer = SIM_LAYER_SHOULDERS;
|
|
|
|
e->local_collider.points[0] = V2(0, 0);
|
|
e->local_collider.count = 1;
|
|
e->local_collider.radius = 0.25f;
|
|
|
|
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
|
|
//xf.bx.y = -1.f;
|
|
|
|
sim_ent_set_xform(e, xf);
|
|
|
|
e->linear_ground_friction = 250;
|
|
e->angular_ground_friction = 200;
|
|
|
|
e->friction = 0;
|
|
|
|
//e->control_force = 500;
|
|
e->control_force = 1200;
|
|
e->control_force_max_speed = 7;
|
|
|
|
//e->control_torque = 5000;
|
|
e->control_torque = F32_INFINITY;
|
|
|
|
sim_ent_enable_prop(e, SEPROP_DYNAMIC);
|
|
sim_ent_enable_prop(e, SEPROP_SOLID);
|
|
|
|
employee = e;
|
|
}
|
|
|
|
/* Player weapon */
|
|
if (employee->valid) {
|
|
(UNUSED)test_spawn_smg;
|
|
(UNUSED)test_spawn_launcher;
|
|
(UNUSED)test_spawn_chucker;
|
|
|
|
struct sim_ent *e = test_spawn_chucker(employee);
|
|
employee->equipped = e->id;
|
|
}
|
|
|
|
return employee;
|
|
}
|
|
|
|
INTERNAL struct sim_ent *test_spawn_camera(struct sim_ent *parent, struct sim_ent *follow)
|
|
{
|
|
struct sim_ent *camera_ent = sim_ent_nil();
|
|
if (follow->valid) {
|
|
camera_ent = sim_ent_alloc_sync_src(parent);
|
|
sim_ent_set_xform(camera_ent, XFORM_IDENT);
|
|
|
|
sim_ent_enable_prop(camera_ent, SEPROP_CAMERA);
|
|
sim_ent_enable_prop(camera_ent, SEPROP_CAMERA_ACTIVE);
|
|
camera_ent->camera_follow = follow->id;
|
|
|
|
f32 width = (f32)DEFAULT_CAMERA_WIDTH;
|
|
f32 height = (f32)DEFAULT_CAMERA_HEIGHT;
|
|
camera_ent->camera_quad_xform = XFORM_TRS(.s = V2(width, height));
|
|
}
|
|
|
|
return camera_ent;
|
|
}
|
|
|
|
INTERNAL struct sim_ent *test_spawn_explosion(struct sim_ent *parent, struct v2 pos, f32 strength, f32 radius)
|
|
{
|
|
struct sim_ent *ent = sim_ent_alloc_sync_src(parent);
|
|
sim_ent_set_xform(ent, XFORM_POS(pos));
|
|
|
|
sim_ent_enable_prop(ent, SEPROP_EXPLOSION);
|
|
ent->explosion_strength = strength;
|
|
ent->explosion_radius = radius;
|
|
|
|
sim_ent_enable_prop(ent, SEPROP_SENSOR);
|
|
ent->local_collider.count = 1;
|
|
ent->local_collider.radius = radius;
|
|
|
|
return ent;
|
|
}
|
|
|
|
INTERNAL void test_teleport(struct sim_ent *ent, struct v2 pos)
|
|
{
|
|
//++ent->continuity_gen;
|
|
struct xform xf = sim_ent_get_xform(ent);
|
|
xf.og = pos;
|
|
sim_ent_set_xform(ent, xf);
|
|
}
|
|
|
|
INTERNAL void test_spawn_entities1(struct sim_ent *parent, struct v2 pos)
|
|
{
|
|
(UNUSED)pos;
|
|
|
|
/* Enemy */
|
|
{
|
|
struct sim_ent *e = test_spawn_employee(parent);
|
|
struct xform xf = sim_ent_get_xform(e);
|
|
xf.og = pos;
|
|
sim_ent_set_xform(e, xf);
|
|
}
|
|
}
|
|
|
|
INTERNAL void test_spawn_entities2(struct sim_ent *parent, struct v2 pos)
|
|
{
|
|
(UNUSED)pos;
|
|
|
|
/* Small Box */
|
|
#if 1
|
|
{
|
|
//struct sim_ent *e = sim_ent_alloc_local(parent);
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
|
|
f32 r = 0;
|
|
struct v2 size = V2(1, 0.5);
|
|
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
|
|
sim_ent_set_xform(e, xf);
|
|
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/box.ase"));
|
|
e->layer = SIM_LAYER_SHOULDERS;
|
|
|
|
e->sprite_tint = ALPHA32_F(COLOR_BLUE, 0.75);
|
|
|
|
sim_ent_enable_prop(e, SEPROP_SOLID);
|
|
struct quad collider_quad = quad_from_rect(RECT(-0.5, -0.5, 1, 1));
|
|
e->local_collider = collider_from_quad(collider_quad);
|
|
|
|
sim_ent_enable_prop(e, SEPROP_DYNAMIC);
|
|
e->mass_unscaled = 100;
|
|
e->inertia_unscaled = 50;
|
|
#if 0
|
|
e->linear_ground_friction = 100;
|
|
e->angular_ground_friction = 50;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* Tiny box */
|
|
#if 0
|
|
{
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
|
|
f32 r = PI / 4;
|
|
struct v2 size = V2(0.5, 0.25);
|
|
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
|
|
sim_ent_set_xform(e, xf);
|
|
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/bullet.ase"));
|
|
e->sprite_collider_slice = LIT("shape");
|
|
e->layer = SIM_LAYER_SHOULDERS;
|
|
|
|
sim_ent_enable_prop(e, SEPROP_SOLID);
|
|
|
|
sim_ent_enable_prop(e, SEPROP_DYNAMIC);
|
|
e->mass_unscaled = 0.5;
|
|
e->inertia_unscaled = 1000;
|
|
e->linear_ground_friction = 0.001;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
INTERNAL void test_spawn_entities3(struct sim_ent *parent, struct v2 pos)
|
|
{
|
|
(UNUSED)pos;
|
|
|
|
/* Heavy box */
|
|
{
|
|
//struct sim_ent *e = sim_ent_alloc_local(parent);
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
|
|
f32 r = 0;
|
|
struct v2 size = V2(1, 1);
|
|
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
|
|
sim_ent_set_xform(e, xf);
|
|
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/box.ase"));
|
|
e->layer = SIM_LAYER_SHOULDERS;
|
|
|
|
e->sprite_tint = COLOR_RED;
|
|
|
|
sim_ent_enable_prop(e, SEPROP_SOLID);
|
|
struct quad collider_quad = quad_from_rect(RECT(-0.5, -0.5, 1, 1));
|
|
e->local_collider = collider_from_quad(collider_quad);
|
|
}
|
|
}
|
|
|
|
INTERNAL void test_spawn_entities4(struct sim_ent *parent, struct v2 pos)
|
|
{
|
|
(UNUSED)pos;
|
|
|
|
/* Light box */
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
|
|
f32 r = 0;
|
|
struct v2 size = V2(2, 1);
|
|
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
|
|
sim_ent_set_xform(e, xf);
|
|
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/box.ase"));
|
|
e->layer = SIM_LAYER_SHOULDERS;
|
|
|
|
sim_ent_enable_prop(e, SEPROP_LIGHT_TEST);
|
|
|
|
e->sprite_tint = RGB32_F(1, 0, 1);
|
|
}
|
|
|
|
INTERNAL void test_spawn_tile(struct sim_snapshot *world, struct v2 world_pos)
|
|
{
|
|
#if 0
|
|
struct sim_ent *e = sim_ent_alloc_sync_src(parent);
|
|
|
|
i32 sign_x = (world_pos.x >= 0) - (world_pos.x < 0);
|
|
i32 sign_y = (world_pos.y >= 0) - (world_pos.y < 0);
|
|
struct v2i32 tile_index = V2I32(world_pos.x * SIM_TILES_PER_UNIT_SQRT, world_pos.y * SIM_TILES_PER_UNIT_SQRT);
|
|
world_pos.x -= sign_x < 0;
|
|
world_pos.y -= sign_y < 0;
|
|
|
|
struct v2 tile_size = V2(1.f / SIM_TILES_PER_UNIT_SQRT, 1.f / SIM_TILES_PER_UNIT_SQRT);
|
|
|
|
struct v2 pos = V2((f32)tile_index.x / SIM_TILES_PER_UNIT_SQRT, (f32)tile_index.y / SIM_TILES_PER_UNIT_SQRT);
|
|
pos = v2_add(pos, v2_mul(V2(tile_size.x * sign_x, tile_size.y * sign_y), 0.5));
|
|
|
|
struct xform xf = XFORM_TRS(.t = pos);
|
|
sim_ent_set_xform(e, xf);
|
|
|
|
e->layer = SIM_LAYER_WALLS;
|
|
e->sprite = sprite_tag_from_path(LIT("sprite/tile.ase"));
|
|
e->sprite_tint = COLOR_RED;
|
|
|
|
{
|
|
struct sprite_scope *scope = sprite_scope_begin();
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(scope, e->sprite);
|
|
e->sprite_local_xform = XFORM_TRS(.s = v2_div(sheet->frame_size, IMAGE_PIXELS_PER_UNIT));
|
|
sprite_scope_end(scope);
|
|
}
|
|
|
|
sim_ent_enable_prop(e, SEPROP_SOLID);
|
|
struct quad collider_quad = quad_from_rect(RECT(-tile_size.x / 2, -tile_size.y / 2, tile_size.y, tile_size.y));
|
|
e->local_collider = collider_from_quad(collider_quad);
|
|
#else
|
|
struct v2i32 tile_index = sim_world_tile_index_from_pos(world_pos);
|
|
sim_snapshot_set_tile(world, tile_index, SIM_TILE_KIND_WALL);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
INTERNAL SORT_COMPARE_FUNC_DEF(tile_chunk_sort_x, arg_a, arg_b, udata)
|
|
{
|
|
(UNUSED)udata;
|
|
struct sim_ent *a = *(struct sim_ent **)arg_a;
|
|
struct sim_ent *b = *(struct sim_ent **)arg_b;
|
|
i32 a_x = a->tile_chunk_index.x;
|
|
i32 b_x = b->tile_chunk_index.x;
|
|
|
|
i32 res = 0;
|
|
res = (a_x < b_x) - (a_x > b_x);
|
|
return res;
|
|
}
|
|
|
|
INTERNAL SORT_COMPARE_FUNC_DEF(tile_chunk_sort_y, arg_a, arg_b, udata)
|
|
{
|
|
(UNUSED)udata;
|
|
struct sim_ent *a = *(struct sim_ent **)arg_a;
|
|
struct sim_ent *b = *(struct sim_ent **)arg_b;
|
|
i32 a_y = a->tile_chunk_index.y;
|
|
i32 b_y = b->tile_chunk_index.y;
|
|
|
|
i32 res = 0;
|
|
res = (a_y < b_y) - (a_y > b_y);
|
|
return res;
|
|
}
|
|
|
|
INTERNAL void test_generate_walls(struct sim_snapshot *world)
|
|
{
|
|
__prof;
|
|
struct arena_temp scratch = scratch_begin_no_conflict();
|
|
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
|
|
|
|
/* Release existing walls and gather tile chunks.
|
|
* NOTE: We sort tile chunks before iterating so that chunk-edge tiles only
|
|
* need to check for adjacent walls to merge with in one direction */
|
|
struct sim_ent **x_sorted_tile_chunks = NULL;
|
|
struct sim_ent **y_sorted_tile_chunks = NULL;
|
|
u64 sorted_tile_chunks_count = 0;
|
|
{
|
|
x_sorted_tile_chunks = arena_push_dry(scratch.arena, struct sim_ent *);
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!ent->valid) continue;
|
|
if (sim_ent_has_prop(ent, SEPROP_TILE_CHUNK)) {
|
|
/* Append chunk to array */
|
|
*arena_push_no_zero(scratch.arena, struct sim_ent *) = ent;
|
|
++sorted_tile_chunks_count;
|
|
} else if (sim_ent_has_prop(ent, SEPROP_WALL)) {
|
|
/* Release existing wall */
|
|
sim_ent_enable_prop(ent, SEPROP_RELEASE);
|
|
}
|
|
}
|
|
y_sorted_tile_chunks = arena_push_array_no_zero(scratch.arena, struct sim_ent *, sorted_tile_chunks_count);
|
|
MEMCPY(y_sorted_tile_chunks, x_sorted_tile_chunks, sizeof(*x_sorted_tile_chunks) * sorted_tile_chunks_count);
|
|
|
|
/* NOTE: We sort x & y separately because it's possible that a wall
|
|
* should merge with another wall that was generated from a diagonal chunk. */
|
|
merge_sort(x_sorted_tile_chunks, sorted_tile_chunks_count, sizeof(*x_sorted_tile_chunks), tile_chunk_sort_x, NULL);
|
|
merge_sort(y_sorted_tile_chunks, sorted_tile_chunks_count, sizeof(*y_sorted_tile_chunks), tile_chunk_sort_y, NULL);
|
|
}
|
|
|
|
struct wall_node {
|
|
struct v2i32 start;
|
|
struct v2i32 end;
|
|
i32 wall_dir; /* = 0 up, 1 = right, 2 = down, 3 = left */
|
|
struct wall_node *next;
|
|
};
|
|
|
|
/* Dicts containing walls that end on edge of tile chunk, keyed by tile end index.
|
|
* Used to merge walls accross tile chunks. */
|
|
struct dict *horizontal_ends_dict = dict_init(scratch.arena, 1024);
|
|
struct dict *vertical_ends_dict = dict_init(scratch.arena, 1024);
|
|
|
|
struct wall_node *first_wall = NULL;
|
|
|
|
/* Generate horizontal wall nodes */
|
|
for (u64 sorted_index = 0; sorted_index < sorted_tile_chunks_count; ++sorted_index) {
|
|
struct sim_ent *chunk = x_sorted_tile_chunks[sorted_index];
|
|
struct v2i32 chunk_index = chunk->tile_chunk_index;
|
|
struct sim_ent *top_chunk = sim_tile_chunk_from_chunk_index(world, V2I32(chunk_index.x, chunk_index.y - 1));
|
|
struct sim_ent *bottom_chunk = sim_tile_chunk_from_chunk_index(world, V2I32(chunk_index.x, chunk_index.y + 1));
|
|
/* If there's no chunk below this one, then do an extra iteration (since walls are created at the top of each tile) */
|
|
i32 y_iterations = SIM_TILES_PER_CHUNK_SQRT + !bottom_chunk->valid;
|
|
i32 x_iterations = SIM_TILES_PER_CHUNK_SQRT + 1;
|
|
for (i32 tile_y = 0; tile_y < y_iterations; ++tile_y) {
|
|
i32 wall_start = -1;
|
|
i32 wall_end = -1;
|
|
i32 wall_dir = -1;
|
|
for (i32 tile_x = 0; tile_x < x_iterations; ++tile_x) {
|
|
i32 desired_wall_dir = -1;
|
|
enum sim_tile_kind tile = SIM_TILE_KIND_NONE;
|
|
if (tile_x < SIM_TILES_PER_CHUNK_SQRT && tile_y < SIM_TILES_PER_CHUNK_SQRT) {
|
|
tile = sim_get_chunk_tile(chunk, V2I32(tile_x, tile_y));
|
|
}
|
|
if (tile_x < SIM_TILES_PER_CHUNK_SQRT) {
|
|
enum sim_tile_kind top_tile = SIM_TILE_KIND_NONE;
|
|
if (tile_y == 0) {
|
|
if (top_chunk->valid) {
|
|
struct v2i32 top_tile_local_index = V2I32(tile_x, SIM_TILES_PER_CHUNK_SQRT - 1);
|
|
top_tile = sim_get_chunk_tile(top_chunk, top_tile_local_index);
|
|
}
|
|
} else {
|
|
top_tile = sim_get_chunk_tile(chunk, V2I32(tile_x, tile_y - 1));
|
|
}
|
|
if (tile == SIM_TILE_KIND_WALL) {
|
|
/* Process wall tile */
|
|
if (top_tile != SIM_TILE_KIND_WALL) {
|
|
desired_wall_dir = 0;
|
|
}
|
|
} else {
|
|
/* Process non-wall tile */
|
|
if (top_tile == SIM_TILE_KIND_WALL) {
|
|
desired_wall_dir = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Stop wall */
|
|
if (wall_dir >= 0 && desired_wall_dir != wall_dir) {
|
|
struct v2i32 start = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(wall_start, tile_y));
|
|
struct v2i32 end = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(wall_end, tile_y));
|
|
struct wall_node *node = NULL;
|
|
if (wall_start == 0) {
|
|
u64 start_hash = rand_u64_from_seed(*(u64 *)&start);
|
|
start_hash = rand_u64_from_seeds(start_hash, wall_dir);
|
|
struct dict_entry *entry = dict_get_entry(horizontal_ends_dict, start_hash);
|
|
if (entry) {
|
|
/* Existing wall exists accross chunk boundary */
|
|
node = (struct wall_node *)entry->value;
|
|
dict_remove_entry(horizontal_ends_dict, entry);
|
|
}
|
|
}
|
|
if (!node) {
|
|
node = arena_push(scratch.arena, struct wall_node);
|
|
node->start = start;
|
|
node->next = first_wall;
|
|
node->wall_dir = wall_dir;
|
|
first_wall = node;
|
|
}
|
|
node->end = end;
|
|
if (wall_end == SIM_TILES_PER_CHUNK_SQRT) {
|
|
u64 end_hash = rand_u64_from_seed(*(u64 *)&end);
|
|
end_hash = rand_u64_from_seeds(end_hash, wall_dir);
|
|
dict_set(scratch.arena, horizontal_ends_dict, end_hash, (u64)node);
|
|
}
|
|
wall_start = -1;
|
|
wall_end = -1;
|
|
wall_dir = -1;
|
|
}
|
|
|
|
/* Start / extend wall */
|
|
if (desired_wall_dir >= 0) {
|
|
if (wall_dir != desired_wall_dir) {
|
|
/* Start wall */
|
|
wall_start = tile_x;
|
|
}
|
|
/* Extend wall */
|
|
wall_end = tile_x + 1;
|
|
wall_dir = desired_wall_dir;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Generate vertical wall nodes */
|
|
for (u64 sorted_index = 0; sorted_index < sorted_tile_chunks_count; ++sorted_index) {
|
|
struct sim_ent *chunk = y_sorted_tile_chunks[sorted_index];
|
|
struct v2i32 chunk_index = chunk->tile_chunk_index;
|
|
struct sim_ent *left_chunk = sim_tile_chunk_from_chunk_index(world, V2I32(chunk_index.x - 1, chunk_index.y));
|
|
struct sim_ent *right_chunk = sim_tile_chunk_from_chunk_index(world, V2I32(chunk_index.x + 1, chunk_index.y));
|
|
/* If there's no chunk to the right of this one, then do an extra iteration (since walls are created on the left of each tile) */
|
|
i32 y_iterations = SIM_TILES_PER_CHUNK_SQRT + 1;
|
|
i32 x_iterations = SIM_TILES_PER_CHUNK_SQRT + !right_chunk->valid;
|
|
for (i32 tile_x = 0; tile_x < x_iterations; ++tile_x) {
|
|
i32 wall_start = -1;
|
|
i32 wall_end = -1;
|
|
i32 wall_dir = -1;
|
|
for (i32 tile_y = 0; tile_y < y_iterations; ++tile_y) {
|
|
i32 desired_wall_dir = -1;
|
|
enum sim_tile_kind tile = SIM_TILE_KIND_NONE;
|
|
if (tile_x < SIM_TILES_PER_CHUNK_SQRT && tile_y < SIM_TILES_PER_CHUNK_SQRT) {
|
|
tile = sim_get_chunk_tile(chunk, V2I32(tile_x, tile_y));
|
|
}
|
|
|
|
if (tile_y < SIM_TILES_PER_CHUNK_SQRT) {
|
|
enum sim_tile_kind left_tile = SIM_TILE_KIND_NONE;
|
|
if (tile_x == 0) {
|
|
if (left_chunk->valid) {
|
|
struct v2i32 left_tile_local_index = V2I32(SIM_TILES_PER_CHUNK_SQRT - 1, tile_y);
|
|
left_tile = sim_get_chunk_tile(left_chunk, left_tile_local_index);
|
|
}
|
|
} else {
|
|
left_tile = sim_get_chunk_tile(chunk, V2I32(tile_x - 1, tile_y));
|
|
}
|
|
if (tile == SIM_TILE_KIND_WALL) {
|
|
/* Process wall tile */
|
|
if (left_tile != SIM_TILE_KIND_WALL) {
|
|
desired_wall_dir = 3;
|
|
}
|
|
} else {
|
|
/* Process non-wall tile */
|
|
if (left_tile == SIM_TILE_KIND_WALL) {
|
|
desired_wall_dir = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Stop wall */
|
|
if (wall_dir >= 0 && desired_wall_dir != wall_dir) {
|
|
struct v2i32 start = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(tile_x, wall_start));
|
|
struct v2i32 end = sim_world_tile_index_from_local_tile_index(chunk_index, V2I32(tile_x, wall_end));
|
|
struct wall_node *node = NULL;
|
|
if (wall_start == 0) {
|
|
u64 start_hash = rand_u64_from_seed(*(u64 *)&start);
|
|
start_hash = rand_u64_from_seeds(start_hash, wall_dir);
|
|
struct dict_entry *entry = dict_get_entry(vertical_ends_dict, start_hash);
|
|
if (entry) {
|
|
/* Existing wall exists accross chunk boundary */
|
|
node = (struct wall_node *)entry->value;
|
|
dict_remove_entry(vertical_ends_dict, entry);
|
|
}
|
|
}
|
|
if (!node) {
|
|
node = arena_push(scratch.arena, struct wall_node);
|
|
node->start = start;
|
|
node->next = first_wall;
|
|
node->wall_dir = wall_dir;
|
|
first_wall = node;
|
|
}
|
|
node->end = end;
|
|
if (wall_end == SIM_TILES_PER_CHUNK_SQRT) {
|
|
u64 end_hash = rand_u64_from_seed(*(u64 *)&end);
|
|
end_hash = rand_u64_from_seeds(end_hash, wall_dir);
|
|
dict_set(scratch.arena, vertical_ends_dict, end_hash, (u64)node);
|
|
}
|
|
wall_start = -1;
|
|
wall_end = -1;
|
|
wall_dir = -1;
|
|
}
|
|
|
|
/* Start / extend wall */
|
|
if (desired_wall_dir >= 0) {
|
|
if (wall_dir != desired_wall_dir) {
|
|
/* Start wall */
|
|
wall_start = tile_y;
|
|
}
|
|
/* Extend wall */
|
|
wall_end = tile_y + 1;
|
|
wall_dir = desired_wall_dir;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Create wall entities */
|
|
for (struct wall_node *node = first_wall; node; node = node->next) {
|
|
struct sim_ent *wall_ent = sim_ent_alloc_sync_src(root);
|
|
sim_ent_enable_prop(wall_ent, SEPROP_WALL);
|
|
|
|
struct v2 start = sim_pos_from_world_tile_index(node->start);
|
|
struct v2 end = sim_pos_from_world_tile_index(node->end);
|
|
|
|
struct xform xf = XFORM_POS(start);
|
|
sim_ent_set_xform(wall_ent, xf);
|
|
|
|
sim_ent_enable_prop(wall_ent, SEPROP_SOLID);
|
|
wall_ent->local_collider.count = 2;
|
|
wall_ent->local_collider.points[1] = v2_sub(end, start);
|
|
|
|
struct v2 dirs[4] = { V2(0, -1), V2(1, 0), V2(0, 1), V2(-1, 0) };
|
|
ASSERT(node->wall_dir >= 0 && (u32)node->wall_dir < countof(dirs));
|
|
wall_ent->collision_dir = dirs[node->wall_dir];
|
|
|
|
sim_ent_activate(wall_ent, world->tick);
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
INTERNAL void test_clear_level(struct sim_step_ctx *ctx)
|
|
{
|
|
struct sim_snapshot *world = ctx->world;
|
|
for (u64 j = 0; j < world->num_ents_reserved; ++j) {
|
|
struct sim_ent *ent = &world->ents[j];
|
|
if (ent->valid) {
|
|
sim_ent_enable_prop(ent, SEPROP_RELEASE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Respond to physics collisions
|
|
* ========================== */
|
|
|
|
INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, data, step_ctx)
|
|
{
|
|
struct sim_snapshot *world = step_ctx->world;
|
|
struct sim_ent *e0 = sim_ent_from_id(world, data->e0);
|
|
struct sim_ent *e1 = sim_ent_from_id(world, data->e1);
|
|
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
|
|
b32 skip_solve = false;
|
|
|
|
if (sim_ent_should_simulate(e0) && sim_ent_should_simulate(e1)) {
|
|
/* Bullet impact */
|
|
if (sim_ent_has_prop(e0, SEPROP_BULLET)) {
|
|
struct v2 normal = data->normal; /* Impact normal */
|
|
struct v2 vrel = data->vrel; /* Impact velocity */
|
|
|
|
struct sim_ent *bullet = e0;
|
|
struct sim_ent *target = e1;
|
|
struct sim_ent *src = sim_ent_from_id(world, bullet->bullet_src);
|
|
|
|
/* Process collision if bullet already spent or * target share same top level parent */
|
|
if (!bullet->bullet_has_hit && !sim_ent_id_eq(src->top, target->top) && sim_ent_has_prop(target, SEPROP_SOLID)) {
|
|
struct v2 point = data->point;
|
|
|
|
/* Update tracer */
|
|
struct sim_ent *tracer = sim_ent_from_id(world, bullet->bullet_tracer);
|
|
if (sim_ent_should_simulate(tracer)) {
|
|
struct xform xf = sim_ent_get_xform(tracer);
|
|
xf.og = point;
|
|
sim_ent_set_xform(tracer, xf);
|
|
sim_ent_set_linear_velocity(tracer, V2(0, 0));
|
|
}
|
|
|
|
/* Update target */
|
|
struct v2 knockback = v2_mul(v2_norm(vrel), bullet->bullet_knockback);
|
|
sim_ent_apply_linear_impulse(target, knockback, point);
|
|
|
|
/* Create test blood */
|
|
/* TODO: Remove this */
|
|
{
|
|
struct xform xf = XFORM_TRS(.t = point, .r = rand_f64_from_state(&step_ctx->rand, 0, TAU));
|
|
struct sim_ent *decal = sim_ent_alloc_sync_src(root);
|
|
decal->sprite = sprite_tag_from_path(LIT("sprite/blood.ase"));
|
|
decal->sprite_tint = RGBA32_F(1, 1, 1, 0.25f);
|
|
decal->layer = SIM_LAYER_FLOOR_DECALS;
|
|
sim_ent_set_xform(decal, xf);
|
|
|
|
f32 perp_range = 0.5;
|
|
struct v2 linear_velocity = v2_mul(normal, 0.5);
|
|
linear_velocity = v2_add(linear_velocity, v2_mul(v2_perp(normal), rand_f64_from_state(&step_ctx->rand, -perp_range, perp_range)));
|
|
|
|
f32 angular_velocity_range = 5;
|
|
f32 angular_velocity = rand_f64_from_state(&step_ctx->rand, -angular_velocity_range, angular_velocity_range);
|
|
|
|
sim_ent_enable_prop(decal, SEPROP_KINEMATIC);
|
|
sim_ent_set_linear_velocity(decal, linear_velocity);
|
|
sim_ent_set_angular_velocity(decal, angular_velocity);
|
|
|
|
decal->linear_damping = 5.0f;
|
|
decal->angular_damping = 5.0f;
|
|
}
|
|
|
|
/* Create explosion */
|
|
if (bullet->bullet_explosion_strength > 0) {
|
|
test_spawn_explosion(root, point, bullet->bullet_explosion_strength, bullet->bullet_explosion_radius);
|
|
}
|
|
|
|
/* Update bullet */
|
|
bullet->bullet_has_hit = true;
|
|
sim_ent_enable_prop(bullet, SEPROP_RELEASE);
|
|
}
|
|
}
|
|
|
|
/* Explosion blast collision */
|
|
if (sim_ent_has_prop(e0, SEPROP_EXPLOSION)) {
|
|
struct sim_ent *exp = e0;
|
|
struct sim_ent *victim = e1;
|
|
|
|
struct xform xf = sim_ent_get_xform(exp);
|
|
|
|
struct collider_shape origin_collider = ZI;
|
|
origin_collider.count = 1;
|
|
|
|
struct xform victim_xf = sim_ent_get_xform(victim);
|
|
struct collider_closest_points_result closest_points = collider_closest_points(&origin_collider, &victim->local_collider, xf, victim_xf);
|
|
struct v2 dir = v2_sub(closest_points.p1, closest_points.p0);
|
|
struct v2 point = closest_points.p1;
|
|
f32 distance = v2_len(dir);
|
|
#if 0
|
|
if (closest_points.colliding) {
|
|
dir = v2_neg(dir);
|
|
//distance = 0;
|
|
}
|
|
#else
|
|
if (v2_dot(data->normal, dir) < 0) {
|
|
dir = v2_neg(dir);
|
|
point = xf.og;
|
|
distance = 0;
|
|
}
|
|
#endif
|
|
|
|
/* TODO: Blast obstruction */
|
|
f32 radius = exp->explosion_radius;
|
|
f32 strength_center = exp->explosion_strength;
|
|
if (distance < radius) {
|
|
const f32 falloff_curve = 3; /* Cubic falloff */
|
|
f32 strength_factor = math_pow(1 - distance/radius, falloff_curve);
|
|
struct v2 impulse = v2_with_len(dir, strength_center * strength_factor);
|
|
sim_ent_apply_linear_impulse(victim, impulse, point);
|
|
}
|
|
}
|
|
|
|
/* Chucker zone */
|
|
if (sim_ent_has_prop(e0, SEPROP_CHUCKER_ZONE)) {
|
|
if (!sim_ent_id_eq(e0->top, e1->top) && sim_ent_has_prop(e1, SEPROP_SOLID)) {
|
|
e0->chucker_zone_ent = e1->id;
|
|
e0->chucker_zone_ent_tick = world->tick;
|
|
}
|
|
}
|
|
}
|
|
|
|
return skip_solve;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update
|
|
* ========================== */
|
|
|
|
void sim_step(struct sim_step_ctx *ctx)
|
|
{
|
|
__prof;
|
|
struct arena_temp scratch = scratch_begin_no_conflict();
|
|
|
|
b32 is_master = ctx->is_master;
|
|
struct sim_snapshot *world = ctx->world;
|
|
|
|
struct sim_client_store *client_store = world->client->store;
|
|
struct sim_client *world_client = world->client;
|
|
struct sim_client *user_input_client = ctx->user_input_client;
|
|
struct sim_client *publish_client = ctx->publish_client;
|
|
struct sim_client *master_client = ctx->master_client;
|
|
|
|
i64 sim_dt_ns = ctx->sim_dt_ns;
|
|
|
|
/* ========================== *
|
|
* Begin frame
|
|
* ========================== */
|
|
|
|
world->sim_dt_ns = max_i64(0, sim_dt_ns);
|
|
world->sim_time_ns += world->sim_dt_ns;
|
|
f32 sim_dt = SECONDS_FROM_NS(world->sim_dt_ns);
|
|
|
|
struct sprite_scope *sprite_frame_scope = sprite_scope_begin();
|
|
|
|
struct sim_ent *root = sim_ent_from_id(world, SIM_ENT_ROOT_ID);
|
|
root->owner = world->client->player_id;
|
|
|
|
/* ========================== *
|
|
* Sync ents from cmd producing clients
|
|
* ========================== */
|
|
|
|
{
|
|
/* FIXME: Ensure only cmds are synced to master player */
|
|
for (u64 client_index = 0; client_index < client_store->num_clients_reserved; ++client_index) {
|
|
struct sim_client *client = &client_store->clients[client_index];
|
|
if (client->valid && client != master_client && client != world_client && client != publish_client) {
|
|
struct sim_ent *player = sim_ent_from_id(world, client->player_id);
|
|
|
|
/* Create player if necessary */
|
|
if (is_master && !player->valid) {
|
|
/* FIXME: Player never released upon disconnect */
|
|
player = sim_ent_alloc_sync_src(root);
|
|
player->player_client_handle = client->handle;
|
|
sim_ent_enable_prop(player, SEPROP_PLAYER);
|
|
player->predictor = player->id;
|
|
sim_ent_activate(player, world->tick);
|
|
client->player_id = player->id;
|
|
if (client == user_input_client) {
|
|
user_input_client->player_id = player->id;
|
|
world_client->player_id = player->id;
|
|
world->local_player = player->id;
|
|
player->owner = player->id;
|
|
sim_ent_enable_prop(player, SEPROP_PLAYER_IS_MASTER);
|
|
}
|
|
logf_info("Created player with id %F for sim client %F. is_master: %F", FMT_UID(player->id.uid), FMT_HANDLE(client->handle), FMT_UINT(sim_ent_has_prop(player, SEPROP_PLAYER_IS_MASTER)));
|
|
}
|
|
|
|
/* Update rtt */
|
|
if (is_master && player->valid) {
|
|
player->player_last_rtt_ns = client->last_rtt_ns;
|
|
player->player_average_rtt_seconds -= player->player_average_rtt_seconds / 200;
|
|
player->player_average_rtt_seconds += SECONDS_FROM_NS(client->last_rtt_ns) / 200;
|
|
}
|
|
|
|
/* Sync ents from client */
|
|
if (player->valid) {
|
|
struct sim_snapshot *src_ss = sim_snapshot_from_tick(client, world->tick);
|
|
if (src_ss->valid) {
|
|
sim_snapshot_sync_ents(world, src_ss, player->id, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Mark all incoming ents as sync dsts */
|
|
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
|
|
struct sim_ent *ent = &world->ents[i];
|
|
if (ent->valid && sim_ent_has_prop(ent, SEPROP_SYNC_SRC) && !sim_ent_id_eq(ent->owner, world_client->player_id)) {
|
|
sim_ent_disable_prop(ent, SEPROP_SYNC_SRC);
|
|
sim_ent_enable_prop(ent, SEPROP_SYNC_DST);
|
|
}
|
|
}
|
|
|
|
/* Mark incoming cmds with correct client */
|
|
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
|
|
struct sim_ent *ent = &world->ents[i];
|
|
if (ent->valid && sim_ent_has_prop(ent, SEPROP_CMD) && sim_ent_has_prop(ent, SEPROP_SYNC_DST)) {
|
|
ent->cmd_player = ent->owner;
|
|
}
|
|
}
|
|
|
|
/* Mark any locally created CMDs as sync sources */
|
|
if (!is_master) {
|
|
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
|
|
struct sim_ent *ent = &world->ents[i];
|
|
if (sim_ent_is_valid_and_active(ent) && sim_ent_has_prop(ent, SEPROP_CMD)) {
|
|
if (!sim_ent_id_is_nil(ent->cmd_player) && sim_ent_id_eq(ent->cmd_player, world->local_player)) {
|
|
sim_ent_enable_prop(ent, SEPROP_SYNC_SRC);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Release entities at beginning of frame
|
|
* ========================== */
|
|
|
|
sim_ent_release_all_with_prop(world, SEPROP_RELEASE);
|
|
sim_accel_reset(world, ctx->accel);
|
|
|
|
/* ========================== *
|
|
* Activate entities
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!ent->valid) continue;
|
|
if (sim_ent_has_prop(ent, SEPROP_SYNC_DST) && !sim_ent_is_owner(ent) && !sim_ent_should_predict(ent)) continue;
|
|
|
|
if (!sim_ent_has_prop(ent, SEPROP_ACTIVE)) {
|
|
u64 atick = ent->activation_tick;
|
|
if (atick != 0 || world->tick >= atick) {
|
|
sim_ent_activate(ent, world->tick);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process player cmds
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *cmd_ent = &world->ents[ent_index];
|
|
if (!is_master && !sim_ent_should_simulate(cmd_ent)) continue;
|
|
|
|
if (sim_ent_has_prop(cmd_ent, SEPROP_CMD)) {
|
|
struct sim_ent *player = sim_ent_from_id(world, cmd_ent->cmd_player);
|
|
if (sim_ent_should_simulate(player)) {
|
|
b32 persist_cmd = false;
|
|
if (!is_master && !sim_ent_id_eq(player->id, world->local_player)) {
|
|
/* We are not the master and the command is not our own, skip processing */
|
|
continue;
|
|
}
|
|
|
|
enum sim_cmd_kind kind = cmd_ent->cmd_kind;
|
|
switch (kind) {
|
|
case SIM_CMD_KIND_CONTROL:
|
|
{
|
|
/* Player's will send control cmds a lot, so keep it around to prevent re-creating it each time */
|
|
persist_cmd = true;
|
|
|
|
/* Process control cmd for player */
|
|
struct sim_control old_control = player->player_control;
|
|
struct sim_control *control = &player->player_control;
|
|
*control = cmd_ent->cmd_control;
|
|
{
|
|
u32 flags = control->flags;
|
|
|
|
player->player_cursor_pos = control->dbg_cursor;
|
|
player->player_hovered_ent = cmd_ent->cmd_control_hovered_ent;
|
|
player->player_dbg_drag_start = false;
|
|
player->player_dbg_drag_stop = false;
|
|
|
|
/* Cap movement vector magnitude */
|
|
if (v2_len_sq(control->move) > 1) {
|
|
control->move = v2_norm(control->move);
|
|
}
|
|
|
|
/* Debug cmds */
|
|
if (ctx->is_master) {
|
|
if (flags & SIM_CONTROL_FLAG_DRAG) {
|
|
if (!(old_control.flags & SIM_CONTROL_FLAG_DRAG)) {
|
|
player->player_dbg_drag_start = true;
|
|
}
|
|
} else {
|
|
if (old_control.flags & SIM_CONTROL_FLAG_DRAG) {
|
|
player->player_dbg_drag_stop = true;
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_DELETE) {
|
|
struct sim_ent *ent = sim_ent_from_id(world, player->player_hovered_ent);
|
|
if (ent->valid) {
|
|
sim_ent_enable_prop(ent, SEPROP_RELEASE);
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) {
|
|
test_clear_level(ctx);
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_SPAWN1_TEST) {
|
|
logf_debug("Spawn test 1");
|
|
u32 count = 1;
|
|
f32 spread = 0;
|
|
for (u32 j = 0; j < count; ++j) {
|
|
struct v2 pos = player->player_cursor_pos;
|
|
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
|
|
test_spawn_entities1(root, pos);
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_SPAWN2_TEST) {
|
|
logf_debug("Spawn test 2");
|
|
u32 count = 1;
|
|
f32 spread = 0;
|
|
for (u32 j = 0; j < count; ++j) {
|
|
struct v2 pos = player->player_cursor_pos;
|
|
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
|
|
test_spawn_entities2(root, pos);
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_SPAWN3_TEST) {
|
|
logf_debug("Spawn test 3");
|
|
u32 count = 1;
|
|
f32 spread = 0;
|
|
for (u32 j = 0; j < count; ++j) {
|
|
struct v2 pos = player->player_cursor_pos;
|
|
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
|
|
test_spawn_entities3(root, pos);
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_SPAWN4_TEST) {
|
|
logf_debug("Spawn test 4");
|
|
u32 count = 1;
|
|
f32 spread = 0;
|
|
for (u32 j = 0; j < count; ++j) {
|
|
struct v2 pos = player->player_cursor_pos;
|
|
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
|
|
test_spawn_entities4(root, pos);
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_WALLS_TEST) {
|
|
test_generate_walls(world);
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_EXPLODE_TEST) {
|
|
logf_debug("Explosion test");
|
|
test_spawn_explosion(root, player->player_cursor_pos, 100, 2);
|
|
}
|
|
}
|
|
|
|
if (flags & SIM_CONTROL_FLAG_TILE_TEST) {
|
|
test_spawn_tile(world, player->player_cursor_pos);
|
|
} else if (old_control.flags & SIM_CONTROL_FLAG_TILE_TEST) {
|
|
test_generate_walls(world);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
#if 0
|
|
case SIM_CMD_KIND_CHAT:
|
|
{
|
|
struct sim_data_key msg_key = cmd_ent->cmd_chat_msg;
|
|
struct string msg = sim_data_from_key(sim_data_store, msg_key);
|
|
if (msg.len > 0) {
|
|
struct sim_ent *chat_ent = sim_ent_alloc_sync_src(root);
|
|
sim_ent_enable_prop(chat_ent, SEPROP_CHAT);
|
|
chat_ent->chat_player = player->id;
|
|
chat_ent->chat_msg = msg_key;
|
|
}
|
|
} break;
|
|
#endif
|
|
|
|
default:
|
|
{
|
|
/* Invalid cmd kind */
|
|
ASSERT(false);
|
|
} break;
|
|
}
|
|
|
|
/* Release cmd */
|
|
if (!persist_cmd) {
|
|
sim_ent_enable_prop(cmd_ent, SEPROP_RELEASE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update entity control from player control
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
|
|
if (sim_ent_has_prop(ent, SEPROP_CONTROLLED)) {
|
|
struct sim_ent *player = sim_ent_from_id(world, ent->controlling_player);
|
|
if (player->valid) {
|
|
ent->control = player->player_control;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Create employees
|
|
* ========================== */
|
|
|
|
if (is_master) {
|
|
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
|
|
struct sim_ent *ent = &world->ents[i];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (sim_ent_has_prop(ent, SEPROP_PLAYER)) {
|
|
/* FIXME: Ents never released when client disconnects */
|
|
struct sim_ent *control_ent = sim_ent_from_id(world, ent->player_control_ent);
|
|
if (!control_ent->valid) {
|
|
control_ent = test_spawn_employee(root);
|
|
control_ent->predictor = ent->id;
|
|
sim_ent_enable_prop(control_ent, SEPROP_CONTROLLED);
|
|
ent->player_control_ent = control_ent->id;
|
|
control_ent->controlling_player = ent->id;
|
|
}
|
|
struct sim_ent *camera_ent = sim_ent_from_id(world, ent->player_camera_ent);
|
|
if (!camera_ent->valid) {
|
|
camera_ent = test_spawn_camera(root, control_ent);
|
|
camera_ent->predictor = ent->id;
|
|
ent->player_camera_ent = camera_ent->id;
|
|
}
|
|
struct sim_ent *camera_follow = sim_ent_from_id(world, camera_ent->camera_follow);
|
|
if (!camera_follow->valid) {
|
|
camera_ent->camera_follow = control_ent->id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update entities from sprite
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (sprite_tag_is_nil(ent->sprite)) continue;
|
|
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
|
|
|
|
/* Update animation */
|
|
{
|
|
struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name);
|
|
if (ent->animation_last_frame_change_time_ns == 0) {
|
|
ent->animation_last_frame_change_time_ns = SECONDS_FROM_NS(world->sim_time_ns);
|
|
}
|
|
|
|
f64 time_in_frame = SECONDS_FROM_NS(world->sim_time_ns - ent->animation_last_frame_change_time_ns);
|
|
u64 frame_index = ent->animation_frame;
|
|
if (frame_index < span.start || frame_index > span.end) {
|
|
frame_index = span.start;
|
|
}
|
|
|
|
if (span.end > span.start) {
|
|
struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, frame_index);
|
|
while (time_in_frame > frame.duration) {
|
|
time_in_frame -= frame.duration;
|
|
++frame_index;
|
|
if (frame_index > span.end) {
|
|
/* Loop animation */
|
|
frame_index = span.start;
|
|
}
|
|
frame = sprite_sheet_get_frame(sheet, frame_index);
|
|
ent->animation_last_frame_change_time_ns = world->sim_time_ns;
|
|
}
|
|
}
|
|
|
|
ent->animation_frame = frame_index;
|
|
}
|
|
|
|
#if 0
|
|
/* Update sprite local xform */
|
|
{
|
|
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("pivot"), ent->animation_frame);
|
|
struct v2 sprite_size = v2_div(sheet->frame_size, (f32)IMAGE_PIXELS_PER_UNIT);
|
|
|
|
struct v2 dir = v2_mul_v2(sprite_size, slice.dir);
|
|
f32 rot = v2_angle(dir) + PI / 2;
|
|
|
|
struct xform xf = XFORM_IDENT;
|
|
xf = xform_rotated(xf, -rot);
|
|
xf = xform_scaled(xf, sprite_size);
|
|
xf = xform_translated(xf, v2_neg(slice.center));
|
|
ent->sprite_local_xform = xf;
|
|
}
|
|
#endif
|
|
|
|
/* Update collider from sprite */
|
|
if (ent->sprite_collider_slice.len > 0) {
|
|
struct xform cxf = ent->sprite_local_xform;
|
|
|
|
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, ent->sprite_collider_slice, ent->animation_frame);
|
|
ent->local_collider = collider_from_quad(xform_mul_quad(cxf, quad_from_rect(slice.rect)));
|
|
}
|
|
|
|
/* Test collider */
|
|
#if 0
|
|
if (sim_ent_has_prop(ent, SEPROP_TEST)) {
|
|
//if ((true)) {
|
|
#if 0
|
|
ent->local_collider.points[0] = V2(0, 0);
|
|
ent->local_collider.count = 1;
|
|
ent->local_collider.radius = 0.5;
|
|
#elif 0
|
|
ent->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f);
|
|
ent->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f);
|
|
ent->local_collider.count = 2;
|
|
ent->local_collider.radius = 0.075f;
|
|
#elif 1
|
|
#if 0
|
|
/* "Bad" winding order */
|
|
ent->local_collider.points[0] = V2(-0.15, 0.15);
|
|
ent->local_collider.points[1] = V2(0.15, 0.15);
|
|
ent->local_collider.points[2] = V2(0, -0.15);
|
|
#else
|
|
ent->local_collider.points[0] = V2(0, -0.15);
|
|
ent->local_collider.points[1] = V2(0.15, 0.15);
|
|
ent->local_collider.points[2] = V2(-0.15, 0.15);
|
|
#endif
|
|
ent->local_collider.count = 3;
|
|
ent->local_collider.radius = 0.25;
|
|
//ent->local_collider.radius = math_fabs(math_sin(ctx->tick.time) / 3);
|
|
#else
|
|
//ent->local_collider.radius = 0.5;
|
|
ent->local_collider.radius = 0.25;
|
|
//ent->local_collider.radius = 0.;
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update attachments
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SEPROP_ATTACHED)) continue;
|
|
|
|
struct sim_ent *parent = sim_ent_from_id(world, ent->parent);
|
|
struct sprite_tag parent_sprite = parent->sprite;
|
|
struct sprite_sheet *parent_sheet = sprite_sheet_from_tag_await(sprite_frame_scope, parent_sprite);
|
|
|
|
struct xform parent_sprite_xf = parent->sprite_local_xform;
|
|
|
|
struct sprite_sheet_slice attach_slice = sprite_sheet_get_slice(parent_sheet, ent->attach_slice, parent->animation_frame);
|
|
struct v2 attach_pos = xform_mul_v2(parent_sprite_xf, attach_slice.center);
|
|
struct v2 attach_dir = xform_basis_mul_v2(parent_sprite_xf, attach_slice.dir);
|
|
|
|
struct xform xf = sim_ent_get_local_xform(ent);
|
|
xf.og = attach_pos;
|
|
xf = xform_basis_with_rotation_world(xf, v2_angle(attach_dir) + PI / 2);
|
|
sim_ent_set_local_xform(ent, xf);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process ent control
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
|
|
if (sim_ent_has_prop(ent, SEPROP_CONTROLLED)) {
|
|
struct sim_control *control = &ent->control;
|
|
u32 flags = control->flags;
|
|
if (flags & SIM_CONTROL_FLAG_FIRE) {
|
|
struct sim_ent *equipped = sim_ent_from_id(world, ent->equipped);
|
|
if (equipped->valid) {
|
|
++equipped->num_primary_triggers;
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_FIRE_ALT) {
|
|
struct sim_ent *equipped = sim_ent_from_id(world, ent->equipped);
|
|
if (equipped->valid) {
|
|
++equipped->num_secondary_triggers;
|
|
}
|
|
}
|
|
if (flags & SIM_CONTROL_FLAG_TELEPORT_TEST) {
|
|
test_teleport(ent, control->dbg_cursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process triggered entities
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
|
|
b32 primary_triggered = ent->num_primary_triggers > 0;
|
|
b32 secondary_triggered = ent->num_secondary_triggers > 0;
|
|
ent->num_primary_triggers = 0;
|
|
ent->num_secondary_triggers = 0;
|
|
|
|
if (primary_triggered) {
|
|
i64 world_time_ns = world->sim_time_ns;
|
|
if ((world_time_ns - ent->last_primary_fire_ns >= NS_FROM_SECONDS(ent->primary_fire_delay)) || ent->last_primary_fire_ns == 0) {
|
|
ent->last_primary_fire_ns = world_time_ns;
|
|
} else {
|
|
primary_triggered = false;
|
|
}
|
|
}
|
|
if (secondary_triggered) {
|
|
i64 world_time_ns = world->sim_time_ns;
|
|
if ((world_time_ns - ent->last_secondary_fire_ns >= NS_FROM_SECONDS(ent->secondary_fire_delay)) || ent->last_secondary_fire_ns == 0) {
|
|
ent->last_secondary_fire_ns = world_time_ns;
|
|
} else {
|
|
secondary_triggered = false;
|
|
}
|
|
}
|
|
|
|
/* Fire smg */
|
|
if (sim_ent_has_prop(ent, SEPROP_WEAPON_SMG)) {
|
|
if (primary_triggered) {
|
|
struct sprite_tag sprite = ent->sprite;
|
|
u32 animation_frame = ent->animation_frame;
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite);
|
|
struct xform sprite_local_xform = ent->sprite_local_xform;
|
|
struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, LIT("out"), animation_frame);
|
|
struct v2 rel_pos = xform_mul_v2(sprite_local_xform, out_slice.center);
|
|
struct v2 rel_dir = xform_basis_mul_v2(sprite_local_xform, out_slice.dir);
|
|
|
|
/* Spawn bullet */
|
|
struct sim_ent *bullet;
|
|
{
|
|
bullet = sim_ent_alloc_sync_src(root);
|
|
|
|
sim_ent_enable_prop(bullet, SEPROP_BULLET);
|
|
bullet->bullet_src = ent->id;
|
|
bullet->bullet_src_pos = rel_pos;
|
|
bullet->bullet_src_dir = rel_dir;
|
|
//bullet->bullet_launch_velocity = 0.75f;
|
|
bullet->bullet_launch_velocity = 50.0f;
|
|
bullet->bullet_knockback = 10;
|
|
bullet->layer = SIM_LAYER_BULLETS;
|
|
|
|
#if 1
|
|
/* Point collider */
|
|
bullet->local_collider.points[0] = V2(0, 0);
|
|
bullet->local_collider.count = 1;
|
|
#else
|
|
bullet->sprite = sprite_tag_from_path(LIT("sprite/bullet.ase"));
|
|
bullet->sprite_collider_slice = LIT("shape");
|
|
#endif
|
|
}
|
|
|
|
/* Spawn tracer */
|
|
{
|
|
struct sim_ent *tracer = sim_ent_alloc_sync_src(root);
|
|
tracer->tracer_fade_duration = 0.025f;
|
|
tracer->layer = SIM_LAYER_TRACERS;
|
|
sim_ent_enable_prop(tracer, SEPROP_TRACER);
|
|
|
|
bullet->bullet_tracer = tracer->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fire launcher */
|
|
if (sim_ent_has_prop(ent, SEPROP_WEAPON_LAUNCHER)) {
|
|
if (primary_triggered) {
|
|
struct sprite_tag sprite = ent->sprite;
|
|
u32 animation_frame = ent->animation_frame;
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite);
|
|
struct xform sprite_local_xform = ent->sprite_local_xform;
|
|
struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, LIT("out"), animation_frame);
|
|
struct v2 rel_pos = xform_mul_v2(sprite_local_xform, out_slice.center);
|
|
struct v2 rel_dir = xform_basis_mul_v2(sprite_local_xform, out_slice.dir);
|
|
|
|
/* Spawn bullet */
|
|
struct sim_ent *bullet;
|
|
{
|
|
bullet = sim_ent_alloc_sync_src(root);
|
|
|
|
sim_ent_enable_prop(bullet, SEPROP_BULLET);
|
|
bullet->bullet_src = ent->id;
|
|
bullet->bullet_src_pos = rel_pos;
|
|
bullet->bullet_src_dir = rel_dir;
|
|
//bullet->bullet_launch_velocity = 0.75f;
|
|
bullet->bullet_launch_velocity = 15;
|
|
bullet->bullet_knockback = 50;
|
|
bullet->bullet_explosion_strength = 100;
|
|
bullet->bullet_explosion_radius = 4;
|
|
bullet->layer = SIM_LAYER_BULLETS;
|
|
|
|
/* Point collider */
|
|
bullet->local_collider.points[0] = V2(0, 0);
|
|
bullet->local_collider.count = 1;
|
|
bullet->local_collider.radius = 0.05f;
|
|
|
|
}
|
|
|
|
/* Spawn tracer */
|
|
{
|
|
struct sim_ent *tracer = sim_ent_alloc_sync_src(root);
|
|
tracer->tracer_fade_duration = 0.025f;
|
|
tracer->layer = SIM_LAYER_TRACERS;
|
|
sim_ent_enable_prop(tracer, SEPROP_TRACER);
|
|
|
|
bullet->bullet_tracer = tracer->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fire chucker */
|
|
if (sim_ent_has_prop(ent, SEPROP_WEAPON_CHUCKER)) {
|
|
if (primary_triggered) {
|
|
}
|
|
if (secondary_triggered) {
|
|
struct sim_ent *zone = sim_ent_from_id(world, ent->chucker_zone);
|
|
struct sim_ent *target = sim_ent_from_id(world, zone->chucker_zone_ent);
|
|
struct sim_ent *old_joint_ent = sim_ent_from_id(world, ent->chucker_joint);
|
|
if (sim_ent_is_valid_and_active(target) && zone->chucker_zone_ent_tick == world->tick - 1) {
|
|
if (!sim_ent_id_eq(old_joint_ent->weld_joint_data.e1, target->id)) {
|
|
struct sim_ent *joint_ent = sim_ent_alloc_sync_src(root);
|
|
sim_ent_enable_prop(joint_ent, SEPROP_ACTIVE);
|
|
|
|
struct xform xf0 = sim_ent_get_xform(ent);
|
|
struct xform xf1 = sim_ent_get_xform(target);
|
|
struct xform xf0_to_xf1 = xform_mul(xform_invert(xf0), xf1);
|
|
|
|
sim_ent_enable_prop(joint_ent, SEPROP_WELD_JOINT);
|
|
struct phys_weld_joint_def def = phys_weld_joint_def_init();
|
|
def.e0 = ent->id;
|
|
def.e1 = target->id;
|
|
def.xf = xf0_to_xf1;
|
|
def.linear_spring_hz = 10;
|
|
def.linear_spring_damp = 0.3f;
|
|
def.angular_spring_hz = 10;
|
|
def.angular_spring_damp = 0.3f;
|
|
joint_ent->weld_joint_data = phys_weld_joint_from_def(def);
|
|
ent->chucker_joint = joint_ent->id;
|
|
}
|
|
}
|
|
if (old_joint_ent->valid) {
|
|
sim_ent_enable_prop(old_joint_ent, SEPROP_RELEASE);
|
|
sim_ent_disable_prop(old_joint_ent, SEPROP_ACTIVE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Create & update motor joints from control move
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
|
|
if (sim_ent_has_prop(ent, SEPROP_CONTROLLED)) {
|
|
struct sim_ent *joint_ent = sim_ent_from_id(world, ent->move_joint);
|
|
if (is_master && !sim_ent_is_valid_and_active(joint_ent)) {
|
|
joint_ent = sim_ent_alloc_sync_src(root);
|
|
joint_ent->predictor = ent->predictor;
|
|
joint_ent->mass_unscaled = F32_INFINITY;
|
|
joint_ent->inertia_unscaled = F32_INFINITY;
|
|
sim_ent_enable_prop(joint_ent, SEPROP_ACTIVE);
|
|
sim_ent_enable_prop(joint_ent, SEPROP_KINEMATIC);
|
|
ent->move_joint = joint_ent->id;
|
|
|
|
sim_ent_enable_prop(joint_ent, SEPROP_MOTOR_JOINT);
|
|
struct phys_motor_joint_def def = phys_motor_joint_def_init();
|
|
def.e0 = joint_ent->id; /* Re-using joint entity as e0 */
|
|
def.e1 = ent->id;
|
|
def.correction_rate = 0;
|
|
def.max_force = ent->control_force;
|
|
def.max_torque = 0;
|
|
joint_ent->motor_joint_data = phys_motor_joint_from_def(def);
|
|
}
|
|
|
|
if (sim_ent_should_simulate(joint_ent)) {
|
|
sim_ent_set_xform(joint_ent, XFORM_IDENT); /* Reset joint ent position */
|
|
sim_ent_set_linear_velocity(joint_ent, v2_mul(v2_clamp_len(ent->control.move, 1), ent->control_force_max_speed));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Create & update motor joints from control focus (aim)
|
|
* ========================== */
|
|
|
|
#if SIM_PLAYER_AIM
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
|
|
if (sim_ent_has_prop(ent, SEPROP_CONTROLLED)) {
|
|
struct xform xf = sim_ent_get_xform(ent);
|
|
struct xform sprite_xf = xform_mul(xf, ent->sprite_local_xform);
|
|
|
|
/* Retrieve / create aim joint */
|
|
struct sim_ent *joint_ent = sim_ent_from_id(world, ent->aim_joint);
|
|
if (is_master && !sim_ent_is_valid_and_active(joint_ent)) {
|
|
joint_ent = sim_ent_alloc_sync_src(root);
|
|
joint_ent->predictor = ent->predictor;
|
|
joint_ent->mass_unscaled = F32_INFINITY;
|
|
joint_ent->inertia_unscaled = F32_INFINITY;
|
|
sim_ent_enable_prop(joint_ent, SEPROP_KINEMATIC); /* Since we'll be setting velocity manually */
|
|
sim_ent_enable_prop(joint_ent, SEPROP_MOTOR_JOINT);
|
|
sim_ent_enable_prop(joint_ent, SEPROP_ACTIVE);
|
|
ent->aim_joint = joint_ent->id;
|
|
|
|
struct phys_motor_joint_def def = phys_motor_joint_def_init();
|
|
def.e0 = joint_ent->id; /* Re-using joint entity as e0 */
|
|
def.e1 = ent->id;
|
|
def.max_force = 0;
|
|
def.max_torque = ent->control_torque;
|
|
joint_ent->motor_joint_data = phys_motor_joint_from_def(def);
|
|
}
|
|
|
|
if (sim_ent_should_simulate(joint_ent)) {
|
|
/* Set correction rate dynamically since motor velocity is only set for one frame */
|
|
joint_ent->motor_joint_data.correction_rate = 10 * sim_dt;
|
|
|
|
|
|
/* Solve for final angle using law of sines */
|
|
f32 new_angle;
|
|
{
|
|
struct v2 ent_pos = xf.og;
|
|
struct v2 focus_pos = v2_add(ent_pos, ent->control.focus);
|
|
|
|
struct v2 sprite_hold_pos;
|
|
struct v2 sprite_hold_dir;
|
|
{
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite);
|
|
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame);
|
|
sprite_hold_pos = slice.center;
|
|
sprite_hold_dir = slice.dir;
|
|
}
|
|
|
|
struct v2 hold_dir = xform_basis_mul_v2(sprite_xf, sprite_hold_dir);
|
|
struct v2 hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos);
|
|
if (v2_eq(hold_pos, ent_pos)) {
|
|
/* If hold pos is same as origin (E.G if pivot is being used as hold pos), then move hold pos forward a tad to avoid issue */
|
|
sprite_hold_pos = v2_add(sprite_hold_pos, V2(0, -1));
|
|
hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos);
|
|
}
|
|
|
|
f32 forward_hold_angle_offset;
|
|
{
|
|
struct xform xf_unrotated = xform_basis_with_rotation_world(xf, 0);
|
|
struct v2 hold_pos_unrotated = xform_mul_v2(xf_unrotated, xform_mul_v2(ent->sprite_local_xform, sprite_hold_pos));
|
|
forward_hold_angle_offset = v2_angle_from_dirs(V2(0, -1), v2_sub(hold_pos_unrotated, xf_unrotated.og));
|
|
}
|
|
|
|
struct v2 hold_ent_dir = v2_sub(ent_pos, hold_pos);
|
|
struct v2 focus_ent_dir = v2_sub(ent_pos, focus_pos);
|
|
|
|
f32 hold_ent_len = v2_len(hold_ent_dir);
|
|
f32 focus_ent_len = v2_len(focus_ent_dir);
|
|
|
|
f32 final_hold_angle_btw_ent_and_focus = v2_angle_from_dirs(hold_ent_dir, hold_dir);
|
|
f32 final_focus_angle_btw_ent_and_hold = math_asin((math_sin(final_hold_angle_btw_ent_and_focus) * hold_ent_len) / focus_ent_len);
|
|
f32 final_ent_angle_btw_focus_and_hold = PI - (final_focus_angle_btw_ent_and_hold + final_hold_angle_btw_ent_and_focus);
|
|
|
|
new_angle = math_unwind_angle(v2_angle_from_dirs(V2(0, -1), v2_sub(focus_pos, ent_pos)) + final_ent_angle_btw_focus_and_hold - forward_hold_angle_offset);
|
|
}
|
|
|
|
f32 new_vel = 0;
|
|
if (!F32_IS_NAN(new_angle)) {
|
|
const f32 angle_error_allowed = 0.001f;
|
|
struct xform joint_xf = sim_ent_get_xform(joint_ent);
|
|
f32 diff = math_unwind_angle(new_angle - xform_get_rotation(joint_xf));
|
|
if (math_fabs(diff) > angle_error_allowed) {
|
|
/* Instantly snap joint ent to new angle */
|
|
new_vel = diff / sim_dt;
|
|
}
|
|
}
|
|
sim_ent_set_angular_velocity(joint_ent, new_vel);
|
|
}
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* ========================== *
|
|
* Create motor joints from ground friction (gravity)
|
|
* ========================== */
|
|
|
|
#if 1
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SEPROP_DYNAMIC)) continue;
|
|
|
|
struct sim_ent *joint_ent = sim_ent_from_id(world, ent->ground_friction_joint);
|
|
|
|
struct phys_motor_joint_def def = phys_motor_joint_def_init();
|
|
def.e0 = root->id;
|
|
def.e1 = ent->id;
|
|
def.correction_rate = 0;
|
|
def.max_force = ent->linear_ground_friction;
|
|
def.max_torque = ent->angular_ground_friction;
|
|
if (joint_ent->motor_joint_data.max_force != def.max_force || joint_ent->motor_joint_data.max_torque != def.max_torque) {
|
|
if (is_master && !sim_ent_is_valid_and_active(joint_ent)) {
|
|
joint_ent = sim_ent_alloc_sync_src(root);
|
|
joint_ent->predictor = ent->predictor;
|
|
sim_ent_enable_prop(joint_ent, SEPROP_MOTOR_JOINT);
|
|
sim_ent_enable_prop(joint_ent, SEPROP_ACTIVE);
|
|
joint_ent->motor_joint_data = phys_motor_joint_from_def(def);
|
|
ent->ground_friction_joint = joint_ent->id;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* ========================== *
|
|
* Create mouse joints from client debug drag
|
|
* ========================== */
|
|
|
|
if (is_master) {
|
|
for (u64 i = 0; i < world->num_ents_reserved; ++i) {
|
|
struct sim_ent *player = &world->ents[i];
|
|
if (!sim_ent_should_simulate(player)) continue;
|
|
if (!sim_ent_has_prop(player, SEPROP_PLAYER)) continue;
|
|
|
|
struct v2 cursor = player->player_cursor_pos;
|
|
b32 start_dragging = player->player_dbg_drag_start;
|
|
b32 stop_dragging = player->player_dbg_drag_stop;
|
|
|
|
struct sim_ent *joint_ent = sim_ent_from_id(world, player->player_dbg_drag_joint_ent);
|
|
struct sim_ent *target_ent = sim_ent_from_id(world, joint_ent->mouse_joint_data.target);
|
|
|
|
if (stop_dragging) {
|
|
target_ent = sim_ent_nil();
|
|
} else if (start_dragging) {
|
|
target_ent = sim_ent_from_id(world, player->player_hovered_ent);
|
|
}
|
|
|
|
if (sim_ent_should_simulate(target_ent)) {
|
|
if (!sim_ent_is_valid_and_active(joint_ent)) {
|
|
/* FIXME: Joint ent may never release */
|
|
joint_ent = sim_ent_alloc_local(root);
|
|
joint_ent->mass_unscaled = F32_INFINITY;
|
|
joint_ent->inertia_unscaled = F32_INFINITY;
|
|
player->player_dbg_drag_joint_ent = joint_ent->id;
|
|
sim_ent_enable_prop(joint_ent, SEPROP_MOUSE_JOINT);
|
|
sim_ent_enable_prop(joint_ent, SEPROP_ACTIVE);
|
|
}
|
|
struct xform xf = sim_ent_get_xform(target_ent);
|
|
|
|
struct phys_mouse_joint_def def = phys_mouse_joint_def_init();
|
|
def.target = target_ent->id;
|
|
if (sim_ent_id_eq(joint_ent->mouse_joint_data.target, target_ent->id)) {
|
|
def.point_local_start = joint_ent->mouse_joint_data.point_local_start;
|
|
} else {
|
|
def.point_local_start = xform_invert_mul_v2(xf, cursor);
|
|
}
|
|
def.point_end = cursor;
|
|
def.max_force = F32_INFINITY;
|
|
def.linear_spring_hz = 5;
|
|
def.linear_spring_damp = 0.7f;
|
|
def.angular_spring_hz = 1;
|
|
def.angular_spring_damp = 0.1f;
|
|
joint_ent->mouse_joint_data = phys_mouse_joint_from_def(def);
|
|
} else if (sim_ent_is_valid_and_active(joint_ent)) {
|
|
joint_ent->mouse_joint_data.target = target_ent->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Physics step
|
|
* ========================== */
|
|
|
|
{
|
|
struct phys_step_ctx phys = ZI;
|
|
phys.sim_step_ctx = ctx;
|
|
phys.collision_callback = on_collision;
|
|
phys_step(&phys, sim_dt);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update explosions
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SEPROP_EXPLOSION)) continue;
|
|
|
|
/* Explosion doesn't need to generate any more collisions after initial physics step */
|
|
sim_ent_disable_prop(ent, SEPROP_SENSOR);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update tracers
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SEPROP_TRACER)) continue;
|
|
|
|
struct v2 end = sim_ent_get_xform(ent).og;
|
|
|
|
struct v2 tick_velocity = v2_mul(ent->tracer_start_velocity, sim_dt);
|
|
struct v2 gradient_start = v2_add(ent->tracer_gradient_start, tick_velocity);
|
|
struct v2 gradient_end = v2_add(ent->tracer_gradient_end, tick_velocity);
|
|
|
|
if (v2_dot(tick_velocity, v2_sub(gradient_start, end)) > 0) {
|
|
/* Tracer has disappeared */
|
|
sim_ent_enable_prop(ent, SEPROP_RELEASE);
|
|
}
|
|
|
|
ent->tracer_gradient_start = gradient_start;
|
|
ent->tracer_gradient_end = gradient_end;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Initialize bullet kinematics from sources
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SEPROP_BULLET)) continue;
|
|
|
|
if (ent->activation_tick == world->tick) {
|
|
struct sim_ent *src = sim_ent_from_id(world, ent->bullet_src);
|
|
struct xform src_xf = sim_ent_get_xform(src);
|
|
|
|
/* Activate collision */
|
|
sim_ent_enable_prop(ent, SEPROP_SENSOR);
|
|
sim_ent_enable_prop(ent, SEPROP_TOI);
|
|
|
|
struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos);
|
|
struct v2 vel = xform_basis_mul_v2(src_xf, ent->bullet_src_dir);
|
|
vel = v2_with_len(vel, ent->bullet_launch_velocity);
|
|
|
|
#if 0
|
|
/* Add shooter velocity to bullet */
|
|
{
|
|
/* TODO: Add angular velocity as well? */
|
|
struct sim_ent *top = sim_ent_from_id(ss_blended, src->top);
|
|
impulse = v2_add(impulse, v2_mul(top->linear_velocity, dt));
|
|
}
|
|
#endif
|
|
|
|
struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(vel) + PI / 2);
|
|
sim_ent_set_xform(ent, xf);
|
|
|
|
sim_ent_enable_prop(ent, SEPROP_KINEMATIC);
|
|
sim_ent_set_linear_velocity(ent, vel);
|
|
|
|
/* Initialize tracer */
|
|
struct sim_ent *tracer = sim_ent_from_id(world, ent->bullet_tracer);
|
|
if (sim_ent_should_simulate(tracer)) {
|
|
sim_ent_set_xform(tracer, xf);
|
|
sim_ent_enable_prop(tracer, SEPROP_KINEMATIC);
|
|
sim_ent_set_linear_velocity(tracer, ent->linear_velocity);
|
|
tracer->tracer_start = pos;
|
|
tracer->tracer_start_velocity = ent->linear_velocity;
|
|
tracer->tracer_gradient_end = pos;
|
|
tracer->tracer_gradient_start = v2_sub(pos, v2_mul(ent->linear_velocity, tracer->tracer_fade_duration));
|
|
}
|
|
|
|
/* Spawn quake */
|
|
{
|
|
struct sim_ent *quake = sim_ent_alloc_sync_src(root);
|
|
sim_ent_set_xform(quake, XFORM_POS(pos));
|
|
quake->quake_intensity = 0.2f;
|
|
quake->quake_fade = quake->quake_intensity / 0.1f;
|
|
sim_ent_enable_prop(quake, SEPROP_QUAKE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update cameras
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SEPROP_CAMERA)) continue;
|
|
|
|
struct xform xf = sim_ent_get_xform(ent);
|
|
|
|
/* Camera follow */
|
|
{
|
|
struct sim_ent *follow = sim_ent_from_id(world, ent->camera_follow);
|
|
|
|
f32 aspect_ratio = 1.0;
|
|
{
|
|
struct xform quad_xf = xform_mul(sim_ent_get_xform(ent), ent->camera_quad_xform);
|
|
struct v2 camera_size = xform_get_scale(quad_xf);
|
|
if (!v2_is_zero(camera_size)) {
|
|
aspect_ratio = camera_size.x / camera_size.y;
|
|
}
|
|
}
|
|
f32 ratio_y = 0.33f;
|
|
f32 ratio_x = ratio_y / aspect_ratio;
|
|
struct v2 camera_focus_dir = v2_mul_v2(follow->control.focus, V2(ratio_x, ratio_y));
|
|
struct v2 camera_focus_pos = v2_add(sim_ent_get_xform(follow).og, camera_focus_dir);
|
|
ent->camera_xform_target = xf;
|
|
ent->camera_xform_target.og = camera_focus_pos;
|
|
|
|
/* Lerp camera */
|
|
if (ent->camera_applied_lerp_continuity_gen_plus_one == ent->camera_lerp_continuity_gen + 1) {
|
|
f32 t = 1 - math_pow(2.f, -20.f * (f32)sim_dt);
|
|
xf = xform_lerp(xf, ent->camera_xform_target, t);
|
|
} else {
|
|
/* Skip lerp */
|
|
xf = ent->camera_xform_target;
|
|
}
|
|
ent->camera_applied_lerp_continuity_gen_plus_one = ent->camera_lerp_continuity_gen + 1;
|
|
}
|
|
|
|
/* Camera shake */
|
|
{
|
|
/* TODO: Update based on distance to quake */
|
|
ent->shake = 0;
|
|
for (u64 quake_ent_index = 0; quake_ent_index < world->num_ents_reserved; ++quake_ent_index) {
|
|
struct sim_ent *quake = &world->ents[quake_ent_index];
|
|
if (!sim_ent_should_simulate(quake)) continue;
|
|
if (!sim_ent_has_prop(quake, SEPROP_QUAKE)) continue;
|
|
ent->shake += quake->quake_intensity;
|
|
}
|
|
}
|
|
|
|
sim_ent_set_xform(ent, xf);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update quakes
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &world->ents[ent_index];
|
|
if (!sim_ent_should_simulate(ent)) continue;
|
|
if (!sim_ent_has_prop(ent, SEPROP_QUAKE)) continue;
|
|
|
|
ent->quake_intensity = max_f32(0, ent->quake_intensity - (ent->quake_fade * sim_dt));
|
|
if (ent->quake_intensity <= 0) {
|
|
sim_ent_enable_prop(ent, SEPROP_RELEASE);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update relative layers
|
|
* ========================== */
|
|
|
|
{
|
|
struct arena_temp temp = arena_temp_begin(scratch.arena);
|
|
|
|
struct sim_ent **stack = arena_push_no_zero(temp.arena, struct sim_ent *);
|
|
u64 stack_count = 1;
|
|
*stack = root;
|
|
|
|
while (stack_count > 0) {
|
|
struct sim_ent *parent;
|
|
arena_pop(temp.arena, struct sim_ent *, &parent);
|
|
--stack_count;
|
|
|
|
i32 parent_layer = parent->final_layer;
|
|
for (struct sim_ent *child = sim_ent_from_id(world, parent->first); child->valid; child = sim_ent_from_id(world, child->next)) {
|
|
if (sim_ent_should_simulate(child)) {
|
|
child->final_layer = parent_layer + child->layer;
|
|
*arena_push_no_zero(temp.arena, struct sim_ent *) = child;
|
|
++stack_count;
|
|
}
|
|
}
|
|
}
|
|
|
|
arena_temp_end(temp);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Release entities at end of frame
|
|
* ========================== */
|
|
|
|
sim_ent_release_all_with_prop(world, SEPROP_RELEASE);
|
|
|
|
/* ========================== *
|
|
* Sync to publish client
|
|
* ========================== */
|
|
|
|
if (publish_client->valid && world->tick > publish_client->last_tick) {
|
|
struct sim_snapshot *prev_pub_world = sim_snapshot_from_tick(publish_client, publish_client->last_tick);
|
|
struct sim_snapshot *pub_world = sim_snapshot_alloc(publish_client, prev_pub_world, world->tick);
|
|
|
|
/* Sync */
|
|
sim_snapshot_sync_ents(pub_world, world, world_client->player_id, 0);
|
|
|
|
/* Mark all synced ents as both sync dsts & sync srcs */
|
|
for (u64 ent_index = 2; ent_index < pub_world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &pub_world->ents[ent_index];
|
|
if (ent->valid) {
|
|
sim_ent_enable_prop(ent, SEPROP_SYNC_DST);
|
|
sim_ent_enable_prop(ent, SEPROP_SYNC_SRC);
|
|
}
|
|
}
|
|
|
|
pub_world->sim_dt_ns = world->sim_dt_ns;
|
|
pub_world->sim_time_ns = world->sim_time_ns;
|
|
pub_world->continuity_gen = world->continuity_gen;
|
|
pub_world->phys_iteration = world->phys_iteration;
|
|
pub_world->local_player = world->local_player;
|
|
}
|
|
|
|
/* ========================== *
|
|
* End frame
|
|
* ========================== */
|
|
|
|
sprite_scope_end(sprite_frame_scope);
|
|
|
|
scratch_end(scratch);
|
|
}
|