diff --git a/crates/logic/src/battle/logic_simulation.rs b/crates/logic/src/battle/logic_simulation.rs index 9155231..f394583 100644 --- a/crates/logic/src/battle/logic_simulation.rs +++ b/crates/logic/src/battle/logic_simulation.rs @@ -21,6 +21,13 @@ pub const COLUMN_ATTACKS_AIR: &str = "AttacksAir"; pub const COLUMN_ATTACKS_GROUND: &str = "AttacksGround"; pub const COLUMN_TARGET_ONLY_BUILDINGS: &str = "TargetOnlyBuildings"; pub const COLUMN_FLYING_HEIGHT: &str = "FlyingHeight"; +pub const COLUMN_MASS: &str = "Mass"; +pub const COLLISION_RADIUS_CAP: i32 = 500; +pub const COLLISION_OVERLAP_CAP: i32 = 300; +pub const COLLISION_PUSH_CAP: i32 = 300; +pub const MASS_MIN: i32 = 1; +pub const MASS_MAX: i32 = 20; +pub const MASS_FOR_STATIC: i32 = 20; pub const COLUMN_PROJECTILE: &str = "Projectile"; pub const DISTANCE_SATURATION: i32 = 46340; pub const WAYPOINT_REACHED: i32 = 250; @@ -37,6 +44,7 @@ pub struct LogicCharacterStats { pub attacks_ground: bool, pub target_only_buildings: bool, pub flying: bool, + pub mass: i32, } impl LogicCharacterStats { pub fn of(data: &LogicDataRef, level_index: i32) -> Self { @@ -65,6 +73,7 @@ impl LogicCharacterStats { attacks_ground: row.boolean(COLUMN_ATTACKS_GROUND), target_only_buildings: row.boolean(COLUMN_TARGET_ONLY_BUILDINGS), flying: row.int(COLUMN_FLYING_HEIGHT) > 0, + mass: row.int(COLUMN_MASS).clamp(MASS_MIN, MASS_MAX), } } pub fn none() -> Self { @@ -80,6 +89,7 @@ impl LogicCharacterStats { attacks_ground: false, target_only_buildings: false, flying: false, + mass: MASS_MIN, } } pub fn is_building(&self) -> bool { @@ -185,6 +195,7 @@ impl LogicBattle { pub fn tick(&mut self, tick: i32) { self.regenerate_mana(tick); self.retarget(); + self.resolve_collisions(); self.move_objects(); self.resolve_attacks(); self.remove_dead(); @@ -354,6 +365,76 @@ impl LogicBattle { base.position.y = from.1 + (dy * step / leg) as i32; } } + fn resolve_collisions(&mut self) { + let bodies: Vec<(bool, (i32, i32), LogicCharacterStats, i32, bool)> = self + .objects + .objects + .iter() + .map(|entry| { + ( + entry.is_alive(), + entry.position(), + entry.stats(), + entry.owner_index(), + entry.movement().is_some(), + ) + }) + .collect(); + for index in 0..self.objects.objects.len() { + let (alive, mine, stats, owner, moves) = &bodies[index]; + if !alive || !moves || stats.collision_radius < 1 { + continue; + } + let mut push = (0i64, 0i64, 0i32); + for (other, (other_alive, theirs, other_stats, _, other_moves)) in + bodies.iter().enumerate() + { + if other == index || !other_alive || other_stats.collision_radius < 1 { + continue; + } + if stats.flying != other_stats.flying { + continue; + } + let reach = if *other_moves { + stats.collision_radius + } else { + stats.collision_radius.min(COLLISION_RADIUS_CAP) + }; + let sum = reach + other_stats.collision_radius; + let (mut dx, mut dy) = (mine.0 - theirs.0, mine.1 - theirs.1); + if dx.abs() > sum || dy.abs() > sum { + continue; + } + let mut square = dx * dx + dy * dy; + if square == 0 { + dx = 0; + dy = if *owner == 0 { -1 } else { 1 }; + square = 1; + } + if square > sum.saturating_mul(sum) { + continue; + } + let distance = integer_sqrt(square as i64).max(1); + let other_mass = if other_stats.is_building() { + MASS_FOR_STATIC + } else { + other_stats.mass + }; + let overlap = (sum - distance as i32).clamp(0, COLLISION_OVERLAP_CAP); + let strength = + ((overlap * other_mass) / stats.mass.max(MASS_MIN) + 1).min(COLLISION_PUSH_CAP); + push.0 += (strength as i64 * dx as i64) / distance; + push.1 += (strength as i64 * dy as i64) / distance; + push.2 += 1; + } + if push.2 == 0 { + continue; + } + let base = self.objects.objects[index].body.base_mut(); + base.position.x += (push.0 / push.2 as i64) as i32; + base.position.y += (push.1 / push.2 as i64) as i32; + } + } fn resolve_attacks(&mut self) { let positions: Vec<(i32, i32)> = self .objects