power_play/src/pp/pp_step.c

2071 lines
75 KiB
C

////////////////////////////////////////////////////////////
//~ Sim accel
PP_Accel PP_AcquireAccel(void)
{
PP_Accel accel = ZI;
accel.space = PP_AcquireSpace(SPACE_CELL_SIZE, SPACE_CELL_BINS_SQRT);
return accel;
}
void PP_ReleaseAccel(PP_Accel *accel)
{
PP_ReleaseSpace(accel->space);
}
void PP_ResetAccel(PP_Snapshot *ss, PP_Accel *accel)
{
PP_ResetSpace(accel->space);
/* Reset ent space handles */
for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index)
{
PP_Ent *ent = &ss->ents[sim_ent_index];
if (ent->valid)
{
ZeroStruct(&ent->space_key);
}
}
}
////////////////////////////////////////////////////////////
//~ Spawn test operations
/* TODO: Remove this */
PP_Ent *PP_SpawnTestSmg(PP_Ent *parent)
{
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
e->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/gun.ase"));
PP_EnableProp(e, PP_Prop_Attached);
e->attach_slice_key = SPR_SliceKeyFromName(Lit("attach.wep"));
e->layer = PP_Layer_RelativeWeapon;
PP_EnableProp(e, PP_Prop_Smg);
e->primary_fire_delay = 1.0f / 10.0f;
e->secondary_fire_delay = 1.0f / 10.0f;
return e;
}
PP_Ent *PP_SpawnTestLauncher(PP_Ent *parent)
{
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
e->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/gun.ase"));
PP_EnableProp(e, PP_Prop_Attached);
e->attach_slice_key = SPR_SliceKeyFromName(Lit("attach.wep"));
e->layer = PP_Layer_RelativeWeapon;
PP_EnableProp(e, PP_Prop_Launcher);
e->primary_fire_delay = 1.0f / 10.0f;
e->secondary_fire_delay = 1.0f / 10.0f;
return e;
}
PP_Ent *PP_SpawnTestChucker(PP_Ent *parent)
{
PP_Ent *chucker = PP_AcquireSyncSrcEnt(parent);
chucker->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/gun.ase"));
PP_EnableProp(chucker, PP_Prop_Attached);
chucker->attach_slice_key = SPR_SliceKeyFromName(Lit("attach.wep"));
chucker->layer = PP_Layer_RelativeWeapon;
PP_EnableProp(chucker, PP_Prop_Chucker);
chucker->primary_fire_delay = 1.0f / 10.0f;
chucker->secondary_fire_delay = 1.0f / 2.0f;
/* Chucker zone */
{
PP_Ent *zone = PP_AcquireSyncSrcEnt(chucker);
PP_EnableProp(zone, PP_Prop_ChuckerZone);
PP_EnableProp(zone, PP_Prop_Attached);
zone->attach_slice_key = SPR_SliceKeyFromName(Lit("out"));
PP_EnableProp(zone, PP_Prop_Sensor);
CLD_Shape collider = ZI;
collider.count = 2;
collider.points[1] = VEC2(0, -0.5);
collider.radius = 0.1f;
zone->local_collider = collider;
chucker->chucker_zone = zone->key;
}
return chucker;
}
PP_Ent *PP_SpawnTestEmployee(PP_Ent *parent)
{
/* Player */
PP_Ent *employee = PP_NilEnt();
{
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
Vec2 pos = VEC2(1, -1);
//Vec2 size = VEC2(0.5, 0.5);
//Vec2 size = VEC2(0.5, 0.25);
Vec2 size = VEC2(1.0, 1.0);
//f32 r = Pi / 4;
f32 r = 0;
{
PP_EnableProp(e, PP_Prop_Test);
e->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/tim.ase"));
e->mass_unscaled = 10;
e->inertia_unscaled = 5;
}
//e->sprite = ResourceKeyFromStore(PP_Resources, Lit("sprite/box_rounded.ase"));
//e->sprite_span_key = SPR_SpanKeyFromName(Lit("idle.unarmed"));
//e->sprite_span_key = SPR_SpanKeyFromName(Lit("idle.one_handed"));
e->sprite_span_key = SPR_SpanKeyFromName(Lit("idle.two_handed"));
e->layer = PP_Layer_Shoulders;
e->local_collider.points[0] = VEC2(0, 0);
e->local_collider.count = 1;
e->local_collider.radius = 0.25f;
Xform xf = XformFromTrs(TRS(.t = pos, .r = r, .s = size));
//xf.bx.y = -1.f;
PP_SetXform(e, xf);
e->linear_ground_friction = 250;
e->angular_ground_friction = 200;
e->friction = 0;
PP_EnableProp(e, PP_Prop_LightTest);
e->sprite_emittance = VEC3(1, 1, 1);
//e->control_force = 500;
e->control_force = 1200;
e->control_force_max_speed = 7;
//e->control_torque = 5000;
e->control_torque = F32Infinity;
PP_EnableProp(e, PP_Prop_Dynamic);
PP_EnableProp(e, PP_Prop_Solid);
employee = e;
}
/* Player weapon */
if (employee->valid)
{
LAX PP_SpawnTestSmg;
LAX PP_SpawnTestLauncher;
LAX PP_SpawnTestChucker;
PP_Ent *e = PP_SpawnTestChucker(employee);
employee->equipped = e->key;
PP_EnableProp(e, PP_Prop_LightTest);
e->sprite_emittance = VEC3(1, 1, 1);
}
return employee;
}
PP_Ent *PP_SpawnTestCamera(PP_Ent *parent, PP_Ent *follow)
{
PP_Ent *camera_ent = PP_NilEnt();
if (follow->valid)
{
camera_ent = PP_AcquireSyncSrcEnt(parent);
PP_SetXform(camera_ent, XformIdentity);
PP_EnableProp(camera_ent, PP_Prop_Camera);
PP_EnableProp(camera_ent, PP_Prop_ActiveCamera);
camera_ent->camera_follow = follow->key;
f32 width = (f32)DEFAULT_CAMERA_WIDTH;
f32 height = (f32)DEFAULT_CAMERA_HEIGHT;
camera_ent->camera_quad_xform = XformFromTrs(TRS(.s = VEC2(width, height)));
}
return camera_ent;
}
PP_Ent *PP_SpawnTestExplosion(PP_Ent *parent, Vec2 pos, f32 strength, f32 radius)
{
PP_Ent *ent = PP_AcquireSyncSrcEnt(parent);
PP_SetXform(ent, XformFromPos(pos));
PP_EnableProp(ent, PP_Prop_Explosion);
ent->explosion_strength = strength;
ent->explosion_radius = radius;
PP_EnableProp(ent, PP_Prop_Sensor);
ent->local_collider.count = 1;
ent->local_collider.radius = radius;
return ent;
}
void PP_TeleportTest(PP_Ent *ent, Vec2 pos)
{
//++ent->continuity_gen;
Xform xf = PP_XformFromEnt(ent);
xf.og = pos;
PP_SetXform(ent, xf);
}
void PP_SpawnTestEnts1(PP_Ent *parent, Vec2 pos)
{
LAX pos;
/* Enemy */
{
PP_Ent *e = PP_SpawnTestEmployee(parent);
Xform xf = PP_XformFromEnt(e);
xf.og = pos;
PP_SetXform(e, xf);
}
}
void PP_SpawnTestEnts2(PP_Ent *parent, Vec2 pos)
{
LAX pos;
/* Small Box */
#if 1
{
//PP_Ent *e = PP_AcquireLocalEnt(parent);
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
f32 rot = 0;
Vec2 size = VEC2(0.125, 0.125);
Xform xf = XformFromTrs(TRS(.t = pos, .r = rot, .s = size));
PP_SetXform(e, xf);
e->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/tile.ase"));
e->layer = PP_Layer_Shoulders;
//e->sprite_tint = Alpha32F(Color_Blue, 0.75);
//e->sprite_tint = Alpha32F(Color_White, 1);
PP_EnableProp(e, PP_Prop_Solid);
Quad collider_quad = QuadFromRect(RectFromScalar(-0.5, -0.5, 1, 1));
e->local_collider = CLD_ShapeFromQuad(collider_quad);
PP_EnableProp(e, PP_Prop_LightTest);
/* FIXME: Remove this */
{
static RandState rand = ZI;
f32 r = RandF64FromState(&rand, 1, 5);
f32 g = RandF64FromState(&rand, 1, 5);
f32 b = RandF64FromState(&rand, 1, 5);
e->sprite_emittance = VEC3(r, g, b);
e->sprite_tint = Rgba32F(r / 5, g / 5, b / 5, 1);
}
PP_EnableProp(e, PP_Prop_Dynamic);
e->mass_unscaled = 50;
e->inertia_unscaled = 2;
#if 0
e->linear_ground_friction = 100;
e->angular_ground_friction = 50;
#endif
}
#endif
/* Tiny box */
#if 0
{
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
f32 r = Pi / 4;
Vec2 size = VEC2(0.5, 0.25);
Xform xf = XformFromTrs(.t = pos, .r = r, .s = size);
PP_SetXform(e, xf);
e->sprite = ResourceKeyFromStore(PP_Resources, Lit("sprite/bullet.ase"));
e->sprite_collider_slice = Lit("shape");
e->layer = PP_Layer_Shoulders;
PP_EnableProp(e, PP_Prop_Solid);
PP_EnableProp(e, PP_Prop_Dynamic);
e->mass_unscaled = 0.5;
e->inertia_unscaled = 1000;
e->linear_ground_friction = 0.001;
}
#endif
}
void PP_SpawnTestEnts3(PP_Ent *parent, Vec2 pos)
{
LAX pos;
/* Heavy box */
{
//PP_Ent *e = PP_AcquireLocalEnt(parent);
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
f32 r = 0;
Vec2 size = VEC2(1, 1);
Xform xf = XformFromTrs(TRS(.t = pos, .r = r, .s = size));
PP_SetXform(e, xf);
e->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/box.ase"));
e->layer = PP_Layer_Shoulders;
e->sprite_tint = Color_Red;
PP_EnableProp(e, PP_Prop_Solid);
Quad collider_quad = QuadFromRect(RectFromScalar(-0.5, -0.5, 1, 1));
e->local_collider = CLD_ShapeFromQuad(collider_quad);
}
}
void PP_SpawnTestEnts4(PP_Ent *parent, Vec2 pos)
{
LAX pos;
/* Light box */
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
f32 r = 0;
Vec2 size = VEC2(2, 1);
Xform xf = XformFromTrs(TRS(.t = pos, .r = r, .s = size));
PP_SetXform(e, xf);
//e->sprite = ResourceKeyFromStore(PP_Resources, Lit("sprite/box.ase"));
e->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/tile.ase"));
e->layer = PP_Layer_Shoulders;
PP_EnableProp(e, PP_Prop_LightTest);
e->sprite_emittance = VEC3(2, 2, 2);
e->sprite_tint = Rgb32F(1, 1, 1);
}
void PP_SpawnTestTile(PP_Snapshot *world, Vec2 world_pos)
{
#if 0
PP_Ent *e = PP_AcquireSyncSrcEnt(parent);
i32 sign_x = (world_pos.x >= 0) - (world_pos.x < 0);
i32 sign_y = (world_pos.y >= 0) - (world_pos.y < 0);
Vec2I32 tile_index = VEC2I32(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;
Vec2 tile_size = VEC2(1.f / SIM_TILES_PER_UNIT_SQRT, 1.f / SIM_TILES_PER_UNIT_SQRT);
Vec2 pos = VEC2((f32)tile_index.x / SIM_TILES_PER_UNIT_SQRT, (f32)tile_index.y / SIM_TILES_PER_UNIT_SQRT);
pos = AddVec2(pos, MulVec2(VEC2(tile_size.x * sign_x, tile_size.y * sign_y), 0.5));
Xform xf = XformFromTrs(.t = pos);
PP_SetXform(e, xf);
e->layer = PP_Layer_Walls;
e->sprite = ResourceKeyFromStore(PP_Resources, Lit("sprite/tile.ase"));
e->sprite_tint = Color_Red;
SPR_Sheet *sheet = SPR_SheetFromResource(e->sprite);
e->sprite_local_xform = XformFromTrs(.s = DivVec2(sheet->frame_size, PIXELSPR_PER_UNIT));
PP_EnableProp(e, PP_Prop_Solid);
Quad collider_quad = QuadFromRect(RectFromScalar(-tile_size.x / 2, -tile_size.y / 2, tile_size.y, tile_size.y));
e->local_collider = CLD_ShapeFromQuad(collider_quad);
#else
Vec2I32 tile_index = PP_WorldTileIndexFromPos(world_pos);
PP_SetSnapshotTile(world, tile_index, PP_TileKind_Wall);
#endif
}
void PP_ClearLevelTest(PP_SimStepCtx *ctx)
{
PP_Snapshot *world = ctx->world;
for (u64 j = 0; j < world->num_ents_reserved; ++j)
{
PP_Ent *ent = &world->ents[j];
if (ent->valid)
{
PP_EnableProp(ent, PP_Prop_Release);
}
}
}
////////////////////////////////////////////////////////////
//~ Tile test operations
MergesortCompareFuncDef(PP_SortTileXCmp, arg_a, arg_b, _)
{
PP_Ent *a = *(PP_Ent **)arg_a;
PP_Ent *b = *(PP_Ent **)arg_b;
i32 a_x = a->tile_chunk_index.x;
i32 b_x = b->tile_chunk_index.x;
i32 result = 0;
result = (a_x < b_x) - (a_x > b_x);
return result;
}
MergesortCompareFuncDef(PP_SortTileYCmp, arg_a, arg_b, _)
{
PP_Ent *a = *(PP_Ent **)arg_a;
PP_Ent *b = *(PP_Ent **)arg_b;
i32 a_y = a->tile_chunk_index.y;
i32 b_y = b->tile_chunk_index.y;
i32 result = 0;
result = (a_y < b_y) - (a_y > b_y);
return result;
}
void PP_GenerateTestWalls(PP_Snapshot *world)
{
__prof;
TempArena scratch = BeginScratchNoConflict();
PP_Ent *root = PP_EntFromKey(world, PP_RootEntKey);
/* 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 */
PP_Ent **x_sorted_tile_chunks = 0;
PP_Ent **y_sorted_tile_chunks = 0;
u64 sorted_tile_chunks_count = 0;
{
x_sorted_tile_chunks = PushDry(scratch.arena, PP_Ent *);
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!ent->valid) continue;
if (PP_HasProp(ent, PP_Prop_TileChunk))
{
/* Append chunk to array */
*PushStructNoZero(scratch.arena, PP_Ent *) = ent;
++sorted_tile_chunks_count;
}
else if (PP_HasProp(ent, PP_Prop_Wall))
{
/* Release existing wall */
PP_EnableProp(ent, PP_Prop_Release);
}
}
y_sorted_tile_chunks = PushStructsNoZero(scratch.arena, PP_Ent *, sorted_tile_chunks_count);
CopyBytes(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. */
Mergesort(x_sorted_tile_chunks, sorted_tile_chunks_count, sizeof(*x_sorted_tile_chunks), PP_SortTileXCmp, 0);
Mergesort(y_sorted_tile_chunks, sorted_tile_chunks_count, sizeof(*y_sorted_tile_chunks), PP_SortTileYCmp, 0);
}
struct wall_node
{
Vec2I32 start;
Vec2I32 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. */
Dict *horizontal_ends_dict = InitDict(scratch.arena, 1024);
Dict *vertical_ends_dict = InitDict(scratch.arena, 1024);
struct wall_node *first_wall = 0;
/* Generate horizontal wall nodes */
for (u64 sorted_index = 0; sorted_index < sorted_tile_chunks_count; ++sorted_index)
{
PP_Ent *chunk = x_sorted_tile_chunks[sorted_index];
Vec2I32 chunk_index = chunk->tile_chunk_index;
PP_Ent *top_chunk = PP_TileChunkFromChunkIndex(world, VEC2I32(chunk_index.x, chunk_index.y - 1));
PP_Ent *bottom_chunk = PP_TileChunkFromChunkIndex(world, VEC2I32(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;
PP_TileKind tile = PP_TileKind_None;
if (tile_x < SIM_TILES_PER_CHUNK_SQRT && tile_y < SIM_TILES_PER_CHUNK_SQRT)
{
tile = PP_TileKindFromChunk(chunk, VEC2I32(tile_x, tile_y));
}
if (tile_x < SIM_TILES_PER_CHUNK_SQRT)
{
PP_TileKind top_tile = PP_TileKind_None;
if (tile_y == 0)
{
if (top_chunk->valid)
{
Vec2I32 top_tile_local_index = VEC2I32(tile_x, SIM_TILES_PER_CHUNK_SQRT - 1);
top_tile = PP_TileKindFromChunk(top_chunk, top_tile_local_index);
}
}
else
{
top_tile = PP_TileKindFromChunk(chunk, VEC2I32(tile_x, tile_y - 1));
}
if (tile == PP_TileKind_Wall)
{
/* Process wall tile */
if (top_tile != PP_TileKind_Wall)
{
desired_wall_dir = 0;
}
}
else
{
/* Process non-wall tile */
if (top_tile == PP_TileKind_Wall)
{
desired_wall_dir = 2;
}
}
}
/* Stop wall */
if (wall_dir >= 0 && desired_wall_dir != wall_dir)
{
Vec2I32 start = PP_WorldTileIndexFromLocalTileIndex(chunk_index, VEC2I32(wall_start, tile_y));
Vec2I32 end = PP_WorldTileIndexFromLocalTileIndex(chunk_index, VEC2I32(wall_end, tile_y));
struct wall_node *node = 0;
if (wall_start == 0)
{
u64 start_hash = RandU64FromSeed(*(u64 *)&start);
start_hash = RandU64FromSeeds(start_hash, wall_dir);
DictEntry *entry = DictEntryFromHash(horizontal_ends_dict, start_hash);
if (entry)
{
/* Existing wall exists accross chunk boundary */
node = (struct wall_node *)entry->value;
RemoveDictEntry(horizontal_ends_dict, entry);
}
}
if (!node)
{
node = PushStruct(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 = RandU64FromSeed(*(u64 *)&end);
end_hash = RandU64FromSeeds(end_hash, wall_dir);
SetDictValue(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)
{
PP_Ent *chunk = y_sorted_tile_chunks[sorted_index];
Vec2I32 chunk_index = chunk->tile_chunk_index;
PP_Ent *left_chunk = PP_TileChunkFromChunkIndex(world, VEC2I32(chunk_index.x - 1, chunk_index.y));
PP_Ent *right_chunk = PP_TileChunkFromChunkIndex(world, VEC2I32(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;
PP_TileKind tile = PP_TileKind_None;
if (tile_x < SIM_TILES_PER_CHUNK_SQRT && tile_y < SIM_TILES_PER_CHUNK_SQRT)
{
tile = PP_TileKindFromChunk(chunk, VEC2I32(tile_x, tile_y));
}
if (tile_y < SIM_TILES_PER_CHUNK_SQRT)
{
PP_TileKind left_tile = PP_TileKind_None;
if (tile_x == 0)
{
if (left_chunk->valid)
{
Vec2I32 left_tile_local_index = VEC2I32(SIM_TILES_PER_CHUNK_SQRT - 1, tile_y);
left_tile = PP_TileKindFromChunk(left_chunk, left_tile_local_index);
}
}
else
{
left_tile = PP_TileKindFromChunk(chunk, VEC2I32(tile_x - 1, tile_y));
}
if (tile == PP_TileKind_Wall)
{
/* Process wall tile */
if (left_tile != PP_TileKind_Wall)
{
desired_wall_dir = 3;
}
}
else
{
/* Process non-wall tile */
if (left_tile == PP_TileKind_Wall)
{
desired_wall_dir = 1;
}
}
}
/* Stop wall */
if (wall_dir >= 0 && desired_wall_dir != wall_dir)
{
Vec2I32 start = PP_WorldTileIndexFromLocalTileIndex(chunk_index, VEC2I32(tile_x, wall_start));
Vec2I32 end = PP_WorldTileIndexFromLocalTileIndex(chunk_index, VEC2I32(tile_x, wall_end));
struct wall_node *node = 0;
if (wall_start == 0)
{
u64 start_hash = RandU64FromSeed(*(u64 *)&start);
start_hash = RandU64FromSeeds(start_hash, wall_dir);
DictEntry *entry = DictEntryFromHash(vertical_ends_dict, start_hash);
if (entry)
{
/* Existing wall exists accross chunk boundary */
node = (struct wall_node *)entry->value;
RemoveDictEntry(vertical_ends_dict, entry);
}
}
if (!node)
{
node = PushStruct(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 = RandU64FromSeed(*(u64 *)&end);
end_hash = RandU64FromSeeds(end_hash, wall_dir);
SetDictValue(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)
{
PP_Ent *wall_ent = PP_AcquireSyncSrcEnt(root);
PP_EnableProp(wall_ent, PP_Prop_Wall);
Vec2 start = PP_PosFromWorldTileIndex(node->start);
Vec2 end = PP_PosFromWorldTileIndex(node->end);
Xform xf = XformFromPos(start);
PP_SetXform(wall_ent, xf);
PP_EnableProp(wall_ent, PP_Prop_Solid);
wall_ent->local_collider.count = 2;
wall_ent->local_collider.points[1] = SubVec2(end, start);
Vec2 dirs[4] = { VEC2(0, -1), VEC2(1, 0), VEC2(0, 1), VEC2(-1, 0) };
Assert(node->wall_dir >= 0 && (u32)node->wall_dir < countof(dirs));
wall_ent->collision_dir = dirs[node->wall_dir];
PP_ActivateEnt(wall_ent, world->tick);
}
EndScratch(scratch);
}
////////////////////////////////////////////////////////////
//~ On collision
PP_CollisionCallbackFuncDef(PP_OnEntCollision, data, step_ctx)
{
PP_Snapshot *world = step_ctx->world;
PP_Ent *e0 = PP_EntFromKey(world, data->e0);
PP_Ent *e1 = PP_EntFromKey(world, data->e1);
PP_Ent *root = PP_EntFromKey(world, PP_RootEntKey);
b32 skip_solve = 0;
if (PP_ShouldSimulate(e0) && PP_ShouldSimulate(e1))
{
/* Bullet impact */
if (PP_HasProp(e0, PP_Prop_Bullet))
{
Vec2 normal = data->normal; /* Impact normal */
Vec2 vrel = data->vrel; /* Impact velocity */
PP_Ent *bullet = e0;
PP_Ent *target = e1;
PP_Ent *src = PP_EntFromKey(world, bullet->bullet_src);
/* Process collision if bullet already spent or * target share same top level parent */
if (!bullet->bullet_has_hit && !PP_EqEntKey(src->top, target->top) && PP_HasProp(target, PP_Prop_Solid))
{
Vec2 point = data->point;
/* Update tracer */
PP_Ent *tracer = PP_EntFromKey(world, bullet->bullet_tracer);
if (PP_ShouldSimulate(tracer))
{
Xform xf = PP_XformFromEnt(tracer);
xf.og = point;
PP_SetXform(tracer, xf);
PP_SetLinearVelocity(tracer, VEC2(0, 0));
}
/* Update target */
Vec2 knockback = MulVec2(NormVec2(vrel), bullet->bullet_knockback);
PP_ApplyLinearImpulse(target, knockback, point);
/* Create test blood */
/* TODO: Remove this */
{
Xform xf = XformFromTrs(TRS(.t = point, .r = RandF64FromState(&step_ctx->rand, 0, Tau)));
PP_Ent *decal = PP_AcquireSyncSrcEnt(root);
decal->sprite = ResourceKeyFromStore(&PP_Resources, Lit("sprite/blood.ase"));
decal->sprite_tint = Rgba32F(1, 1, 1, 0.25f);
decal->layer = PP_Layer_FloorDecals;
PP_SetXform(decal, xf);
f32 perp_range = 0.5;
Vec2 linear_velocity = MulVec2(normal, 0.5);
linear_velocity = AddVec2(linear_velocity, MulVec2(PerpVec2(normal), RandF64FromState(&step_ctx->rand, -perp_range, perp_range)));
f32 angular_velocity_range = 5;
f32 angular_velocity = RandF64FromState(&step_ctx->rand, -angular_velocity_range, angular_velocity_range);
PP_EnableProp(decal, PP_Prop_Kinematic);
PP_SetLinearVelocity(decal, linear_velocity);
PP_SetAngularVelocity(decal, angular_velocity);
decal->linear_damping = 5.0f;
decal->angular_damping = 5.0f;
}
/* Create explosion */
if (bullet->bullet_explosion_strength > 0)
{
PP_SpawnTestExplosion(root, point, bullet->bullet_explosion_strength, bullet->bullet_explosion_radius);
}
/* Update bullet */
bullet->bullet_has_hit = 1;
PP_EnableProp(bullet, PP_Prop_Release);
}
}
/* Explosion blast collision */
if (PP_HasProp(e0, PP_Prop_Explosion))
{
PP_Ent *exp = e0;
PP_Ent *victim = e1;
Xform xf = PP_XformFromEnt(exp);
CLD_Shape origin_collider = ZI;
origin_collider.count = 1;
Xform victim_xf = PP_XformFromEnt(victim);
CLD_ClosestPointData closest_points = CLD_ClosestPointDataFromShapes(&origin_collider, &victim->local_collider, xf, victim_xf);
Vec2 dir = SubVec2(closest_points.p1, closest_points.p0);
Vec2 point = closest_points.p1;
f32 distance = Vec2Len(dir);
#if 0
if (closest_points.colliding)
{
dir = NegVec2(dir);
//distance = 0;
}
#else
if (DotVec2(data->normal, dir) < 0)
{
dir = NegVec2(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 = PowF32(1 - distance/radius, falloff_curve);
Vec2 impulse = Vec2WithLen(dir, strength_center * strength_factor);
PP_ApplyLinearImpulse(victim, impulse, point);
}
}
/* Chucker zone */
if (PP_HasProp(e0, PP_Prop_ChuckerZone))
{
if (!PP_EqEntKey(e0->top, e1->top) && PP_HasProp(e1, PP_Prop_Solid))
{
e0->chucker_zone_ent = e1->key;
e0->chucker_zone_ent_tick = world->tick;
}
}
}
return skip_solve;
}
////////////////////////////////////////////////////////////
//~ Step
void PP_StepSim(PP_SimStepCtx *ctx)
{
__prof;
TempArena scratch = BeginScratchNoConflict();
b32 is_master = ctx->is_master;
PP_Snapshot *world = ctx->world;
PP_ClientStore *client_store = world->client->store;
PP_Client *world_client = world->client;
PP_Client *user_input_client = ctx->user_input_client;
PP_Client *publish_client = ctx->publish_client;
PP_Client *master_client = ctx->master_client;
i64 sim_dt_ns = ctx->sim_dt_ns;
//- Begin frame
world->sim_dt_ns = MaxI64(0, sim_dt_ns);
world->sim_time_ns += world->sim_dt_ns;
f32 sim_dt = SecondsFromNs(world->sim_dt_ns);
PP_Ent *root = PP_EntFromKey(world, PP_RootEntKey);
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)
{
PP_Client *client = &client_store->clients[client_index];
if (client->valid && client != master_client && client != world_client && client != publish_client)
{
PP_Ent *player = PP_EntFromKey(world, client->player_id);
/* Create player if necessary */
if (is_master && !player->valid)
{
/* FIXME: Player never released upon disconnect */
player = PP_AcquireSyncSrcEnt(root);
player->player_client_key = client->key;
PP_EnableProp(player, PP_Prop_Player);
player->predictor = player->key;
PP_ActivateEnt(player, world->tick);
client->player_id = player->key;
if (client == user_input_client)
{
user_input_client->player_id = player->key;
world_client->player_id = player->key;
world->local_player = player->key;
player->owner = player->key;
PP_EnableProp(player, PP_Prop_IsMaster);
}
LogInfoF("Created player with key %F for sim client %F. is_master: %F", FmtUid(player->key.uid), FmtHandle(client->key), FmtUint(PP_HasProp(player, PP_Prop_IsMaster)));
}
/* 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 += SecondsFromNs(client->last_rtt_ns) / 200;
}
/* Sync ents from client */
if (player->valid)
{
PP_Snapshot *src_ss = PP_SnapshotFromTick(client, world->tick);
if (src_ss->valid)
{
PP_SyncSnapshotEnts(world, src_ss, player->key, 0);
}
}
}
}
/* Mark all incoming ents as sync dsts */
for (u64 i = 0; i < world->num_ents_reserved; ++i)
{
PP_Ent *ent = &world->ents[i];
if (ent->valid && PP_HasProp(ent, PP_Prop_SyncSrc) && !PP_EqEntKey(ent->owner, world_client->player_id))
{
PP_DisableProp(ent, PP_Prop_SyncSrc);
PP_EnableProp(ent, PP_Prop_SyncDst);
}
}
/* Mark incoming cmds with correct client */
for (u64 i = 0; i < world->num_ents_reserved; ++i)
{
PP_Ent *ent = &world->ents[i];
if (ent->valid && PP_HasProp(ent, PP_Prop_Cmd) && PP_HasProp(ent, PP_Prop_SyncDst))
{
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)
{
PP_Ent *ent = &world->ents[i];
if (PP_IsValidAndActive(ent) && PP_HasProp(ent, PP_Prop_Cmd))
{
if (!PP_IsNilEntKey(ent->cmd_player) && PP_EqEntKey(ent->cmd_player, world->local_player))
{
PP_EnableProp(ent, PP_Prop_SyncSrc);
}
}
}
}
}
//- Release entities at beginning of frame
PP_ReleaseAllWithProp(world, PP_Prop_Release);
PP_ResetAccel(world, ctx->accel);
//- Activate entities
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!ent->valid) continue;
if (PP_HasProp(ent, PP_Prop_SyncDst) && !PP_IsOwner(ent) && !PP_ShouldPredict(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Active))
{
u64 atick = ent->activation_tick;
if (atick != 0 || world->tick >= atick)
{
PP_ActivateEnt(ent, world->tick);
}
}
}
//- Process player cmds
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *cmd_ent = &world->ents[ent_index];
if (!is_master && !PP_ShouldSimulate(cmd_ent)) continue;
if (PP_HasProp(cmd_ent, PP_Prop_Cmd))
{
PP_Ent *player = PP_EntFromKey(world, cmd_ent->cmd_player);
if (PP_ShouldSimulate(player))
{
b32 persist_cmd = 0;
if (!is_master && !PP_EqEntKey(player->key, world->local_player))
{
/* We are not the master and the command is not our own, skip processing */
continue;
}
PP_CmdKind kind = cmd_ent->cmd_kind;
switch (kind)
{
default:
{
/* Invalid cmd kind */
Assert(0);
} break;
case PP_CmdKind_Control:
{
/* Player's will send control cmds a lot, so keep it around to prevent re-creating it each time */
persist_cmd = 1;
/* Process control cmd for player */
PP_ControlData old_control = player->player_control;
PP_ControlData *control = &player->player_control;
*control = cmd_ent->cmd_control;
{
PP_ControlFlag 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 = 0;
player->player_dbg_drag_stop = 0;
/* Cap movement vector magnitude */
if (Vec2LenSq(control->move) > 1)
{
control->move = NormVec2(control->move);
}
/* Debug cmds */
if (ctx->is_master)
{
if (flags & PP_ControlFlag_Drag)
{
if (!(old_control.flags & PP_ControlFlag_Drag))
{
player->player_dbg_drag_start = 1;
}
}
else
{
if (old_control.flags & PP_ControlFlag_Drag)
{
player->player_dbg_drag_stop = 1;
}
}
if (flags & PP_ControlFlag_Delete)
{
PP_Ent *ent = PP_EntFromKey(world, player->player_hovered_ent);
if (ent->valid)
{
PP_EnableProp(ent, PP_Prop_Release);
}
}
if (flags & PP_ControlFlag_ClearAll)
{
PP_ClearLevelTest(ctx);
}
if (flags & PP_ControlFlag_SpawnTest1)
{
LogDebugF("Spawn test 1");
u32 count = 1;
f32 spread = 0;
for (u32 j = 0; j < count; ++j)
{
Vec2 pos = player->player_cursor_pos;
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
PP_SpawnTestEnts1(root, pos);
}
}
if (flags & PP_ControlFlag_SpawnTest2)
{
LogDebugF("Spawn test 2");
u32 count = 1;
f32 spread = 0;
for (u32 j = 0; j < count; ++j)
{
Vec2 pos = player->player_cursor_pos;
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
PP_SpawnTestEnts2(root, pos);
}
}
if (flags & PP_ControlFlag_SpawnTest3)
{
LogDebugF("Spawn test 3");
u32 count = 1;
f32 spread = 0;
for (u32 j = 0; j < count; ++j)
{
Vec2 pos = player->player_cursor_pos;
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
PP_SpawnTestEnts3(root, pos);
}
}
if (flags & PP_ControlFlag_SpawnTest4)
{
LogDebugF("Spawn test 4");
u32 count = 1;
f32 spread = 0;
for (u32 j = 0; j < count; ++j)
{
Vec2 pos = player->player_cursor_pos;
pos.y += (((f32)j / (f32)count) - 0.5) * spread;
PP_SpawnTestEnts4(root, pos);
}
}
if (flags & PP_ControlFlag_TestWalls)
{
PP_GenerateTestWalls(world);
}
if (flags & PP_ControlFlag_TestExplode)
{
LogSuccessF("Explosion test");
PP_SpawnTestExplosion(root, player->player_cursor_pos, 100, 2);
}
}
if (flags & PP_ControlFlag_TestTiles)
{
PP_SpawnTestTile(world, player->player_cursor_pos);
}
else if (old_control.flags & PP_ControlFlag_TestTiles)
{
PP_GenerateTestWalls(world);
}
}
} break;
#if 0
case PP_CmdKind_Chat:
{
struct sim_data_key msg_key = cmd_ent->cmd_chat_msg;
String msg = sim_data_from_key(sim_data_store, msg_key);
if (msg.len > 0)
{
PP_Ent *chat_ent = PP_AcquireSyncSrcEnt(root);
PP_EnableProp(chat_ent, PP_Prop_CHAT);
chat_ent->chat_player = player->key;
chat_ent->chat_msg = msg_key;
}
} break;
#endif
}
/* Release cmd */
if (!persist_cmd)
{
PP_EnableProp(cmd_ent, PP_Prop_Release);
}
}
}
}
//- Update entity control from player control
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (PP_HasProp(ent, PP_Prop_Controlled))
{
PP_Ent *player = PP_EntFromKey(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)
{
PP_Ent *ent = &world->ents[i];
if (!PP_ShouldSimulate(ent)) continue;
if (PP_HasProp(ent, PP_Prop_Player))
{
/* FIXME: Ents never released when client disconnects */
PP_Ent *control_ent = PP_EntFromKey(world, ent->player_control_ent);
if (!control_ent->valid)
{
control_ent = PP_SpawnTestEmployee(root);
control_ent->predictor = ent->key;
PP_EnableProp(control_ent, PP_Prop_Controlled);
ent->player_control_ent = control_ent->key;
control_ent->controlling_player = ent->key;
}
PP_Ent *camera_ent = PP_EntFromKey(world, ent->player_camera_ent);
if (!camera_ent->valid)
{
camera_ent = PP_SpawnTestCamera(root, control_ent);
camera_ent->predictor = ent->key;
ent->player_camera_ent = camera_ent->key;
}
PP_Ent *camera_follow = PP_EntFromKey(world, camera_ent->camera_follow);
if (!camera_follow->valid)
{
camera_ent->camera_follow = control_ent->key;
}
}
}
}
//- Update entities from sprite
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (IsResourceNil(ent->sprite)) continue;
SPR_Sheet *sheet = SPR_SheetFromResource(ent->sprite);
/* Update animation */
{
SPR_Span span = SPR_SpanFromKey(sheet, ent->sprite_span_key);
if (ent->animation_last_frame_change_time_ns == 0)
{
ent->animation_last_frame_change_time_ns = SecondsFromNs(world->sim_time_ns);
}
f64 time_in_frame = SecondsFromNs(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)
{
SPR_Frame frame = SPR_FrameFromIndex(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 = SPR_FrameFromIndex(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 */
{
SPR_Slice pivot_slice = SPR_SliceFromKey(sheet, Lit("pivot"), ent->animation_frame);
Vec2 sprite_size = DivVec2(sheet->frame_size, (f32)PIXELSPR_PER_UNIT);
Vec2 dir = MulVec2Vec2(sprite_size, pivot_slice.dir);
f32 rot = AngleFromVec2(dir) + Pi / 2;
Xform xf = XformIdentity;
xf = RotateXform(xf, -rot);
xf = ScaleXform(xf, sprite_size);
xf = TranslateXform(xf, NegVec2(pivot_slice.center));
ent->sprite_local_xform = xf;
}
#endif
/* Update collider from sprite */
if (ent->sprite_collider_slice_key.hash != 0)
{
Xform cxf = ent->sprite_local_xform;
SPR_Slice slice = SPR_SliceFromKey(sheet, ent->sprite_collider_slice_key, ent->animation_frame);
ent->local_collider = CLD_ShapeFromQuad(MulXformQuad(cxf, QuadFromRect(slice.rect)));
}
/* Test collider */
#if 0
if (PP_HasProp(ent, PP_Prop_Test))
{
//if ((1)) {
#if 0
ent->local_collider.points[0] = VEC2(0, 0);
ent->local_collider.count = 1;
ent->local_collider.radius = 0.5;
#elif 0
ent->local_collider.points[0] = Vec2WithLen(VEC2(0.08f, 0.17f), 0.15f);
ent->local_collider.points[1] = Vec2WithLen(VEC2(-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] = VEC2(-0.15, 0.15);
ent->local_collider.points[1] = VEC2(0.15, 0.15);
ent->local_collider.points[2] = VEC2(0, -0.15);
#else
ent->local_collider.points[0] = VEC2(0, -0.15);
ent->local_collider.points[1] = VEC2(0.15, 0.15);
ent->local_collider.points[2] = VEC2(-0.15, 0.15);
#endif
ent->local_collider.count = 3;
ent->local_collider.radius = 0.25;
//ent->local_collider.radius = AbsF32(SinF32(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)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Attached)) continue;
PP_Ent *parent = PP_EntFromKey(world, ent->parent);
ResourceKey parent_sprite = parent->sprite;
SPR_Sheet *parent_sheet = SPR_SheetFromResource(parent_sprite);
Xform parent_sprite_xf = parent->sprite_local_xform;
SPR_Slice attach_slice = SPR_SliceFromKey(parent_sheet, ent->attach_slice_key, parent->animation_frame);
Vec2 attach_pos = MulXformV2(parent_sprite_xf, attach_slice.center);
Vec2 attach_dir = MulXformBasisV2(parent_sprite_xf, attach_slice.dir);
Xform xf = PP_LocalXformFromEnt(ent);
xf.og = attach_pos;
xf = XformWithWorldRotation(xf, AngleFromVec2(attach_dir) + Pi / 2);
PP_SetLocalXform(ent, xf);
}
//- Process ent control
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (PP_HasProp(ent, PP_Prop_Controlled))
{
PP_ControlData *control = &ent->control;
PP_ControlFlag flags = control->flags;
if (flags & PP_ControlFlag_Fire)
{
PP_Ent *equipped = PP_EntFromKey(world, ent->equipped);
if (equipped->valid)
{
++equipped->num_primary_triggers;
}
}
if (flags & PP_ControlFlag_AltFire)
{
PP_Ent *equipped = PP_EntFromKey(world, ent->equipped);
if (equipped->valid)
{
++equipped->num_secondary_triggers;
}
}
if (flags & PP_ControlFlag_TestTeleport)
{
PP_TeleportTest(ent, control->dbg_cursor);
}
}
}
//- Process triggered entities
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(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 >= NsFromSeconds(ent->primary_fire_delay)) || ent->last_primary_fire_ns == 0)
{
ent->last_primary_fire_ns = world_time_ns;
}
else
{
primary_triggered = 0;
}
}
if (secondary_triggered)
{
i64 world_time_ns = world->sim_time_ns;
if ((world_time_ns - ent->last_secondary_fire_ns >= NsFromSeconds(ent->secondary_fire_delay)) || ent->last_secondary_fire_ns == 0)
{
ent->last_secondary_fire_ns = world_time_ns;
}
else
{
secondary_triggered = 0;
}
}
/* Fire smg */
if (PP_HasProp(ent, PP_Prop_Smg))
{
if (primary_triggered)
{
ResourceKey sprite = ent->sprite;
u32 animation_frame = ent->animation_frame;
SPR_Sheet *sheet = SPR_SheetFromResource(sprite);
Xform sprite_local_xform = ent->sprite_local_xform;
SPR_Slice out_slice = SPR_SliceFromKey(sheet, SPR_SliceKeyFromName(Lit("out")), animation_frame);
Vec2 rel_pos = MulXformV2(sprite_local_xform, out_slice.center);
Vec2 rel_dir = MulXformBasisV2(sprite_local_xform, out_slice.dir);
/* Spawn bullet */
PP_Ent *bullet;
{
bullet = PP_AcquireSyncSrcEnt(root);
PP_EnableProp(bullet, PP_Prop_Bullet);
bullet->bullet_src = ent->key;
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 = PP_Layer_Bullets;
#if 1
/* Point collider */
bullet->local_collider.points[0] = VEC2(0, 0);
bullet->local_collider.count = 1;
#else
bullet->sprite = ResourceKeyFromStore(PP_Resources, Lit("sprite/bullet.ase"));
bullet->sprite_collider_slice = Lit("shape");
#endif
}
/* Spawn tracer */
{
PP_Ent *tracer = PP_AcquireSyncSrcEnt(root);
tracer->tracer_fade_duration = 0.025f;
tracer->layer = PP_Layer_Tracers;
PP_EnableProp(tracer, PP_Prop_Tracer);
bullet->bullet_tracer = tracer->key;
}
}
}
/* Fire launcher */
if (PP_HasProp(ent, PP_Prop_Launcher))
{
if (primary_triggered)
{
ResourceKey sprite = ent->sprite;
u32 animation_frame = ent->animation_frame;
SPR_Sheet *sheet = SPR_SheetFromResource(sprite);
Xform sprite_local_xform = ent->sprite_local_xform;
SPR_Slice out_slice = SPR_SliceFromKey(sheet, SPR_SliceKeyFromName(Lit("out")), animation_frame);
Vec2 rel_pos = MulXformV2(sprite_local_xform, out_slice.center);
Vec2 rel_dir = MulXformBasisV2(sprite_local_xform, out_slice.dir);
/* Spawn bullet */
PP_Ent *bullet;
{
bullet = PP_AcquireSyncSrcEnt(root);
PP_EnableProp(bullet, PP_Prop_Bullet);
bullet->bullet_src = ent->key;
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 = PP_Layer_Bullets;
/* Point collider */
bullet->local_collider.points[0] = VEC2(0, 0);
bullet->local_collider.count = 1;
bullet->local_collider.radius = 0.05f;
}
/* Spawn tracer */
{
PP_Ent *tracer = PP_AcquireSyncSrcEnt(root);
tracer->tracer_fade_duration = 0.025f;
tracer->layer = PP_Layer_Tracers;
PP_EnableProp(tracer, PP_Prop_Tracer);
bullet->bullet_tracer = tracer->key;
}
}
}
/* Fire chucker */
if (PP_HasProp(ent, PP_Prop_Chucker))
{
if (primary_triggered)
{
}
if (secondary_triggered)
{
PP_Ent *zone = PP_EntFromKey(world, ent->chucker_zone);
PP_Ent *target = PP_EntFromKey(world, zone->chucker_zone_ent);
PP_Ent *old_joint_ent = PP_EntFromKey(world, ent->chucker_joint);
if (PP_IsValidAndActive(target) && zone->chucker_zone_ent_tick == world->tick - 1)
{
if (!PP_EqEntKey(old_joint_ent->weld_joint_data.e1, target->key))
{
PP_Ent *joint_ent = PP_AcquireSyncSrcEnt(root);
PP_EnableProp(joint_ent, PP_Prop_Active);
Xform xf0 = PP_XformFromEnt(ent);
Xform xf1 = PP_XformFromEnt(target);
Xform xf0_to_xf1 = MulXform(InvertXform(xf0), xf1);
PP_EnableProp(joint_ent, PP_Prop_WeldJoint);
PP_WeldJointDesc def = PP_CreateWeldJointDef();
def.e0 = ent->key;
def.e1 = target->key;
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 = PP_WeldJointFromDef(def);
ent->chucker_joint = joint_ent->key;
}
}
if (old_joint_ent->valid)
{
PP_EnableProp(old_joint_ent, PP_Prop_Release);
PP_DisableProp(old_joint_ent, PP_Prop_Active);
}
}
}
}
//- Create & update motor joints from control move
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (PP_HasProp(ent, PP_Prop_Controlled))
{
PP_Ent *joint_ent = PP_EntFromKey(world, ent->move_joint);
if (is_master && !PP_IsValidAndActive(joint_ent))
{
joint_ent = PP_AcquireSyncSrcEnt(root);
joint_ent->predictor = ent->predictor;
joint_ent->mass_unscaled = F32Infinity;
joint_ent->inertia_unscaled = F32Infinity;
PP_EnableProp(joint_ent, PP_Prop_Active);
PP_EnableProp(joint_ent, PP_Prop_Kinematic);
ent->move_joint = joint_ent->key;
PP_EnableProp(joint_ent, PP_Prop_MotorJoint);
PP_MotorJointDesc def = PP_CreateMotorJointDef();
def.e0 = joint_ent->key; /* Re-using joint entity as e0 */
def.e1 = ent->key;
def.correction_rate = 0;
def.max_force = ent->control_force;
def.max_torque = 0;
joint_ent->motor_joint_data = PP_MotorJointFromDef(def);
}
if (PP_ShouldSimulate(joint_ent))
{
PP_SetXform(joint_ent, XformIdentity); /* Reset joint ent position */
PP_SetLinearVelocity(joint_ent, MulVec2(ClampVec2Len(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)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (PP_HasProp(ent, PP_Prop_Controlled))
{
Xform xf = PP_XformFromEnt(ent);
Xform sprite_xf = MulXform(xf, ent->sprite_local_xform);
/* Retrieve / create aim joint */
PP_Ent *joint_ent = PP_EntFromKey(world, ent->aim_joint);
if (is_master && !PP_IsValidAndActive(joint_ent))
{
joint_ent = PP_AcquireSyncSrcEnt(root);
joint_ent->predictor = ent->predictor;
joint_ent->mass_unscaled = F32Infinity;
joint_ent->inertia_unscaled = F32Infinity;
PP_EnableProp(joint_ent, PP_Prop_Kinematic); /* Since we'll be setting velocity manually */
PP_EnableProp(joint_ent, PP_Prop_MotorJoint);
PP_EnableProp(joint_ent, PP_Prop_Active);
ent->aim_joint = joint_ent->key;
PP_MotorJointDesc def = PP_CreateMotorJointDef();
def.e0 = joint_ent->key; /* Re-using joint entity as e0 */
def.e1 = ent->key;
def.max_force = 0;
def.max_torque = ent->control_torque;
joint_ent->motor_joint_data = PP_MotorJointFromDef(def);
}
if (PP_ShouldSimulate(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;
{
Vec2 ent_pos = xf.og;
Vec2 focus_pos = AddVec2(ent_pos, ent->control.focus);
Vec2 sprite_hold_pos;
Vec2 sprite_hold_dir;
{
SPR_Sheet *sheet = SPR_SheetFromResource(ent->sprite);
SPR_Slice slice = SPR_SliceFromKey(sheet, SPR_SliceKeyFromName(Lit("attach.wep")), ent->animation_frame);
sprite_hold_pos = slice.center;
sprite_hold_dir = slice.dir;
}
Vec2 hold_dir = MulXformBasisV2(sprite_xf, sprite_hold_dir);
Vec2 hold_pos = MulXformV2(sprite_xf, sprite_hold_pos);
if (EqVec2(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 = AddVec2(sprite_hold_pos, VEC2(0, -1));
hold_pos = MulXformV2(sprite_xf, sprite_hold_pos);
}
f32 forward_hold_angle_offset;
{
Xform xf_unrotated = XformWithWorldRotation(xf, 0);
Vec2 hold_pos_unrotated = MulXformV2(xf_unrotated, MulXformV2(ent->sprite_local_xform, sprite_hold_pos));
forward_hold_angle_offset = AngleFromVec2Dirs(VEC2(0, -1), SubVec2(hold_pos_unrotated, xf_unrotated.og));
}
Vec2 hold_ent_dir = SubVec2(ent_pos, hold_pos);
Vec2 focus_ent_dir = SubVec2(ent_pos, focus_pos);
f32 hold_ent_len = Vec2Len(hold_ent_dir);
f32 focus_ent_len = Vec2Len(focus_ent_dir);
f32 final_hold_angle_btw_ent_and_focus = AngleFromVec2Dirs(hold_ent_dir, hold_dir);
f32 final_focus_angle_btw_ent_and_hold = ArcSinF32((SinF32(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 = UnwindAngleF32(AngleFromVec2Dirs(VEC2(0, -1), SubVec2(focus_pos, ent_pos)) + final_ent_angle_btw_focus_and_hold - forward_hold_angle_offset);
}
f32 new_vel = 0;
if (!IsF32Nan(new_angle))
{
const f32 angle_error_allowed = 0.001f;
Xform joint_xf = PP_XformFromEnt(joint_ent);
f32 diff = UnwindAngleF32(new_angle - RotationFromXform(joint_xf));
if (AbsF32(diff) > angle_error_allowed)
{
/* Instantly snap joint ent to new angle */
new_vel = diff / sim_dt;
}
}
PP_SetAngularVelocity(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)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Dynamic)) continue;
PP_Ent *joint_ent = PP_EntFromKey(world, ent->ground_friction_joint);
PP_MotorJointDesc def = PP_CreateMotorJointDef();
def.e0 = root->key;
def.e1 = ent->key;
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 && !PP_IsValidAndActive(joint_ent))
{
joint_ent = PP_AcquireSyncSrcEnt(root);
joint_ent->predictor = ent->predictor;
PP_EnableProp(joint_ent, PP_Prop_MotorJoint);
PP_EnableProp(joint_ent, PP_Prop_Active);
joint_ent->motor_joint_data = PP_MotorJointFromDef(def);
ent->ground_friction_joint = joint_ent->key;
}
}
}
#endif
//- Create mouse joints from client debug drag
if (is_master)
{
for (u64 i = 0; i < world->num_ents_reserved; ++i)
{
PP_Ent *player = &world->ents[i];
if (!PP_ShouldSimulate(player)) continue;
if (!PP_HasProp(player, PP_Prop_Player)) continue;
Vec2 cursor = player->player_cursor_pos;
b32 start_dragging = player->player_dbg_drag_start;
b32 stop_dragging = player->player_dbg_drag_stop;
PP_Ent *joint_ent = PP_EntFromKey(world, player->player_dbg_drag_joint_ent);
PP_Ent *target_ent = PP_EntFromKey(world, joint_ent->mouse_joint_data.target);
if (stop_dragging)
{
target_ent = PP_NilEnt();
}
else if (start_dragging)
{
target_ent = PP_EntFromKey(world, player->player_hovered_ent);
}
if (PP_ShouldSimulate(target_ent))
{
if (!PP_IsValidAndActive(joint_ent))
{
/* FIXME: Joint ent may never release */
joint_ent = PP_AcquireLocalEnt(root);
joint_ent->mass_unscaled = F32Infinity;
joint_ent->inertia_unscaled = F32Infinity;
player->player_dbg_drag_joint_ent = joint_ent->key;
PP_EnableProp(joint_ent, PP_Prop_MouseJoint);
PP_EnableProp(joint_ent, PP_Prop_Active);
}
Xform xf = PP_XformFromEnt(target_ent);
PP_MouseJointDesc def = PP_CreateMouseJointDef();
def.target = target_ent->key;
if (PP_EqEntKey(joint_ent->mouse_joint_data.target, target_ent->key))
{
def.point_local_start = joint_ent->mouse_joint_data.point_local_start;
}
else
{
def.point_local_start = InvertXformMulV2(xf, cursor);
}
def.point_end = cursor;
def.max_force = F32Infinity;
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 = PP_MouseJointFromDef(def);
}
else if (PP_IsValidAndActive(joint_ent))
{
joint_ent->mouse_joint_data.target = target_ent->key;
}
}
}
//- Physics step
{
PP_PhysStepCtx phys = ZI;
phys.sim_step_ctx = ctx;
phys.collision_callback = PP_OnEntCollision;
PP_StepPhys(&phys, sim_dt);
}
//- Update explosions
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Explosion)) continue;
/* Explosion doesn't need to generate any more collisions after initial physics step */
PP_DisableProp(ent, PP_Prop_Sensor);
}
//- Update tracers
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Tracer)) continue;
Vec2 end = PP_XformFromEnt(ent).og;
Vec2 tick_velocity = MulVec2(ent->tracer_start_velocity, sim_dt);
Vec2 gradient_start = AddVec2(ent->tracer_gradient_start, tick_velocity);
Vec2 gradient_end = AddVec2(ent->tracer_gradient_end, tick_velocity);
if (DotVec2(tick_velocity, SubVec2(gradient_start, end)) > 0)
{
/* Tracer has disappeared */
PP_EnableProp(ent, PP_Prop_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)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Bullet)) continue;
if (ent->activation_tick == world->tick)
{
PP_Ent *src = PP_EntFromKey(world, ent->bullet_src);
Xform src_xf = PP_XformFromEnt(src);
/* Activate collision */
PP_EnableProp(ent, PP_Prop_Sensor);
PP_EnableProp(ent, PP_Prop_Toi);
Vec2 pos = MulXformV2(src_xf, ent->bullet_src_pos);
Vec2 vel = MulXformBasisV2(src_xf, ent->bullet_src_dir);
vel = Vec2WithLen(vel, ent->bullet_launch_velocity);
#if 0
/* Add shooter velocity to bullet */
{
/* TODO: Add angular velocity as well? */
PP_Ent *top = PP_EntFromKey(ss_blended, src->top);
impulse = AddVec2(impulse, MulVec2(top->linear_velocity, dt));
}
#endif
Xform xf = XformFromTrs(TRS(.t = pos, .r = AngleFromVec2(vel) + Pi / 2));
PP_SetXform(ent, xf);
PP_EnableProp(ent, PP_Prop_Kinematic);
PP_SetLinearVelocity(ent, vel);
/* Initialize tracer */
PP_Ent *tracer = PP_EntFromKey(world, ent->bullet_tracer);
if (PP_ShouldSimulate(tracer))
{
PP_SetXform(tracer, xf);
PP_EnableProp(tracer, PP_Prop_Kinematic);
PP_SetLinearVelocity(tracer, ent->linear_velocity);
tracer->tracer_start = pos;
tracer->tracer_start_velocity = ent->linear_velocity;
tracer->tracer_gradient_end = pos;
tracer->tracer_gradient_start = SubVec2(pos, MulVec2(ent->linear_velocity, tracer->tracer_fade_duration));
}
/* Spawn quake */
{
PP_Ent *quake = PP_AcquireSyncSrcEnt(root);
PP_SetXform(quake, XformFromPos(pos));
quake->quake_intensity = 0.2f;
quake->quake_fade = quake->quake_intensity / 0.1f;
PP_EnableProp(quake, PP_Prop_Quake);
}
}
}
//- Update cameras
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Camera)) continue;
Xform xf = PP_XformFromEnt(ent);
/* Camera follow */
{
PP_Ent *follow = PP_EntFromKey(world, ent->camera_follow);
f32 aspect_ratio = 1.0;
{
Xform quad_xf = MulXform(PP_XformFromEnt(ent), ent->camera_quad_xform);
Vec2 camera_size = ScaleFromXform(quad_xf);
if (!IsVec2Zero(camera_size))
{
aspect_ratio = camera_size.x / camera_size.y;
}
}
f32 ratio_y = 0.33f;
f32 ratio_x = ratio_y / aspect_ratio;
Vec2 camera_focus_dir = MulVec2Vec2(follow->control.focus, VEC2(ratio_x, ratio_y));
Vec2 camera_focus_pos = AddVec2(PP_XformFromEnt(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 - PowF32(2.f, -20.f * (f32)sim_dt);
xf = LerpXform(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)
{
PP_Ent *quake = &world->ents[quake_ent_index];
if (!PP_ShouldSimulate(quake)) continue;
if (!PP_HasProp(quake, PP_Prop_Quake)) continue;
ent->shake += quake->quake_intensity;
}
}
PP_SetXform(ent, xf);
}
//- Update quakes
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index)
{
PP_Ent *ent = &world->ents[ent_index];
if (!PP_ShouldSimulate(ent)) continue;
if (!PP_HasProp(ent, PP_Prop_Quake)) continue;
ent->quake_intensity = MaxF32(0, ent->quake_intensity - (ent->quake_fade * sim_dt));
if (ent->quake_intensity <= 0)
{
PP_EnableProp(ent, PP_Prop_Release);
}
}
//- Update relative layers
{
TempArena temp = BeginTempArena(scratch.arena);
PP_Ent **stack = PushStructNoZero(temp.arena, PP_Ent *);
u64 stack_count = 1;
*stack = root;
while (stack_count > 0)
{
PP_Ent *parent;
PopStruct(temp.arena, PP_Ent *, &parent);
--stack_count;
i32 parent_layer = parent->final_layer;
for (PP_Ent *child = PP_EntFromKey(world, parent->first); child->valid; child = PP_EntFromKey(world, child->next))
{
if (PP_ShouldSimulate(child))
{
child->final_layer = parent_layer + child->layer;
*PushStructNoZero(temp.arena, PP_Ent *) = child;
++stack_count;
}
}
}
EndTempArena(temp);
}
//- Release entities at end of frame
PP_ReleaseAllWithProp(world, PP_Prop_Release);
//- Sync to publish client
if (publish_client->valid && world->tick > publish_client->last_tick)
{
PP_Snapshot *prev_pub_world = PP_SnapshotFromTick(publish_client, publish_client->last_tick);
PP_Snapshot *pub_world = PP_AcquireSnapshot(publish_client, prev_pub_world, world->tick);
/* Sync */
PP_SyncSnapshotEnts(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)
{
PP_Ent *ent = &pub_world->ents[ent_index];
if (ent->valid)
{
PP_EnableProp(ent, PP_Prop_SyncDst);
PP_EnableProp(ent, PP_Prop_SyncSrc);
}
}
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;
}
EndScratch(scratch);
}