//////////////////////////////////////////////////////////// //~ 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); }