From d6664b9b5a9a3aab500b547e4d7448a8bc4ad416 Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Sun, 23 Aug 2026 01:07:07 -0700 Subject: allow entity damage --- src/content/entities/entity_cube.rs | 2 +- src/content/entities/entity_grenade.rs | 9 ++--- src/sim/cell/cell.rs | 17 ++++++---- src/sim/entity/mod.rs | 56 ++++++++++++++++--------------- src/sim/sim_manager/mod.rs | 61 +++++++++++++++++++++++----------- 5 files changed, 84 insertions(+), 61 deletions(-) (limited to 'src') diff --git a/src/content/entities/entity_cube.rs b/src/content/entities/entity_cube.rs index ab93e5e..fb5bbd0 100644 --- a/src/content/entities/entity_cube.rs +++ b/src/content/entities/entity_cube.rs @@ -17,7 +17,7 @@ pub fn entity_cube_def(position: Vec2, material: MaterialId) -> EntityDef { for y in 0..h { let cell_idx = x + y * w; raw_cells[cell_idx as usize] = Cell::from_material(material); - raw_cells[cell_idx as usize].set_entity(true); + raw_cells[cell_idx as usize].set_entity_integrated(true); } } diff --git a/src/content/entities/entity_grenade.rs b/src/content/entities/entity_grenade.rs index c13c239..f6c82e8 100644 --- a/src/content/entities/entity_grenade.rs +++ b/src/content/entities/entity_grenade.rs @@ -15,12 +15,7 @@ struct GrenadeEntityBehaviour { } impl EntityBehaviour for GrenadeEntityBehaviour { - fn update( - &mut self, - update_ctx: &mut EntityUpdateCtx, - ctx: &mut SimCtx, - delta_time: f32, - ) { + fn update(&mut self, update_ctx: &mut EntityUpdateCtx, ctx: &mut SimCtx, delta_time: f32) { self.fuse -= delta_time; if self.fuse <= 0.0 { apply_explosion( @@ -44,7 +39,7 @@ pub fn entity_grenade_def(position: Vec2, fuse: f32) -> EntityDef { for y in 0..h { let cell_idx = x + y * w; raw_cells[cell_idx as usize] = Cell::from_material(MaterialId::Steel); - raw_cells[cell_idx as usize].set_entity(true); + raw_cells[cell_idx as usize].set_entity_integrated(true); } } diff --git a/src/sim/cell/cell.rs b/src/sim/cell/cell.rs index be2cfad..5a9fc1f 100644 --- a/src/sim/cell/cell.rs +++ b/src/sim/cell/cell.rs @@ -8,8 +8,11 @@ pub struct Cell { } impl Cell { + // only update the cell if this matches the parity of the seqno const FLAG_PARITY: u8 = 0b0000_0001; - const FLAG_ENTITY: u8 = 0b0000_0010; + // is this cell owned by an entity? + const FLAG_ENTITY_INTEGRATED: u8 = 0b0000_0010; + // how close is the cell to settling? const MASK_SETTLED: u8 = 0b0001_1100; #[inline] @@ -31,15 +34,15 @@ impl Cell { } #[inline] - pub fn entity(self) -> bool { - (self.flags & Self::FLAG_ENTITY) == Self::FLAG_ENTITY + pub fn entity_integrated(self) -> bool { + (self.flags & Self::FLAG_ENTITY_INTEGRATED) == Self::FLAG_ENTITY_INTEGRATED } #[inline] - pub fn set_entity(&mut self, rb: bool) { - if rb { - self.flags |= Self::FLAG_ENTITY + pub fn set_entity_integrated(&mut self, entity_integrated: bool) { + if entity_integrated { + self.flags |= Self::FLAG_ENTITY_INTEGRATED } else { - self.flags &= !Self::FLAG_ENTITY + self.flags &= !Self::FLAG_ENTITY_INTEGRATED } } diff --git a/src/sim/entity/mod.rs b/src/sim/entity/mod.rs index d8a03e0..3a3fb1d 100644 --- a/src/sim/entity/mod.rs +++ b/src/sim/entity/mod.rs @@ -13,6 +13,13 @@ use crate::{ }, }; +fn mass_from_cells(cells: &[Cell]) -> f32 { + cells + .iter() + .fold(0, |acc, cur| acc + cur.material.def().density) as f32 + * MASS_SCALING +} + pub struct EntityCells { pub size: IVec2, pub cells: Vec, @@ -43,13 +50,7 @@ impl Marchable for EntityCells { } pub trait EntityBehaviour { - fn update( - &mut self, - _update_ctx: &mut EntityUpdateCtx, - _ctx: &mut SimCtx, - _delta_time: f32, - ) { - } + fn update(&mut self, _update_ctx: &mut EntityUpdateCtx, _ctx: &mut SimCtx, _delta_time: f32) {} fn physics_update( &mut self, _update_ctx: &mut EntityUpdateCtx, @@ -76,14 +77,8 @@ impl EntityDef { .translation(position / CELLS_TO_METRES) .build(); - let mass = cells - .cells - .iter() - .fold(0, |acc, cur| acc + cur.material.def().density) as f32 - * MASS_SCALING; - let collider = RbManager::convex_hull_collider_from_marchable(&cells, None) - .mass(mass) + .mass(mass_from_cells(&cells.cells)) .build(); EntityDef { @@ -130,19 +125,18 @@ impl<'a> EntityUpdateCtx<'a> { impl EntityData { pub fn transform(&self, ctx: &SimCtx) -> Option<(Vec2, (f32, f32))> { - self.rb_h - .and_then(|rb_h| { - ctx.rb_manager - .physics_manager - .rigid_body_set - .get(rb_h) - .map(|rb| { - ( - rb.translation() * CELLS_TO_METRES, - (rb.rotation().cos(), rb.rotation().sin()), - ) - }) - }) + self.rb_h.and_then(|rb_h| { + ctx.rb_manager + .physics_manager + .rigid_body_set + .get(rb_h) + .map(|rb| { + ( + rb.translation() * CELLS_TO_METRES, + (rb.rotation().cos(), rb.rotation().sin()), + ) + }) + }) } } @@ -174,6 +168,14 @@ impl Entity { None } + pub fn compute_collider(&self) -> Option { + self.data.cells.as_ref().map(|cells| { + RbManager::convex_hull_collider_from_marchable(cells, None) + .mass(mass_from_cells(&cells.cells)) + .build() + }) + } + pub fn new( id: u32, rb_h: Option, diff --git a/src/sim/sim_manager/mod.rs b/src/sim/sim_manager/mod.rs index 805d152..1a16cfd 100644 --- a/src/sim/sim_manager/mod.rs +++ b/src/sim/sim_manager/mod.rs @@ -6,7 +6,6 @@ use glam::IVec2; use crate::{ Config, config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS}, - content::materials::MaterialId, sim::{ cell::cell::Cell, cell_manager::manager::CellManager, @@ -130,28 +129,51 @@ impl SimManager { // TODO optimize for (entity_id, cells_written) in cells_written_by_entity { let entity = self.entities.get_mut(&entity_id).unwrap(); + let entity_cells = entity.data.cells.as_mut().unwrap(); + let mut should_update_entity = false; + for (lx, ly, x, y) in cells_written { // update the entity - // TODO we should skip cells that weren't changed? // TODO optimize let new_local_cell = self.cell_manager.get_cell_from_game_position(x, y).unwrap(); - if new_local_cell.material != MaterialId::Void && !new_local_cell.entity() { - panic!( - "Someone swapped into this entity's cell! ({x}, {y}, {m:#?}, {f})", - m = new_local_cell.material, - f = new_local_cell.flags - ); - } - if let Some(cells) = &mut entity.data.cells { - cells.set_cell_at_local_position( - IVec2::new(lx as i32, ly as i32), - new_local_cell, - ); + if !new_local_cell.entity_integrated() { + // this means that the entity changed in some way, so we should recompute its shape + // TODO wait N frames to debounce this + should_update_entity = true; } + // we do this unconditionally because it's cheaper than checking if it actually needs to be updated + // and because we don't have a good way to track changes to cell state + entity_cells + .set_cell_at_local_position(IVec2::new(lx as i32, ly as i32), new_local_cell); + // update the world + // TODO optimize self.cell_manager .set_cell_from_game_position(x, y, Cell::void(), false); } + + if should_update_entity { + if let Some(new_collider) = entity.compute_collider() { + self.rb_manager.physics_manager.collider_set.remove( + entity.data.collider_h.unwrap(), + &mut self.rb_manager.physics_manager.island_manager, + &mut self.rb_manager.physics_manager.rigid_body_set, + true, + ); + + let new_handle = self + .rb_manager + .physics_manager + .collider_set + .insert_with_parent( + new_collider, + entity.data.rb_h.unwrap(), + &mut self.rb_manager.physics_manager.rigid_body_set, + ); + + entity.data.collider_h = Some(new_handle); + } + } } } @@ -165,15 +187,16 @@ impl SimManager { particle_manager: &mut self.particle_manager, }; if let Some(entity) = entity - && let Some(result) = entity.physics_update(&mut ctx, delta_time) { - for d in result.deferred_destructions { - self.destroy_entity(d); - } + && let Some(result) = entity.physics_update(&mut ctx, delta_time) + { + for d in result.deferred_destructions { + self.destroy_entity(d); } + } } // before we move the rigidbodies, upsert the current terrain state - // TODO use the chunk sleeping, and make this range dynamic + // TODO make this range dynamic for cx in -5..5 { for cy in -5..5 { if let Some(chunk) = self -- cgit v1.3.1