push units apart when they overlap
collisions, from checkCollisions and checkCollision in the client. a pair is considered when both are on the same plane - air with air, ground with ground, decided by z. the radius is the unit's own CollisionRadius, capped at 500 when the other side has no movement component, which is what lets a unit squeeze past a building instead of being shoved by it, plus the other's radius. the axis test comes before the squared one, tangency counts as a hit, and two units standing exactly on top of each other are separated along y by the owner's facing rather than dividing by zero. the push is clamp(sum - distance, 0, 300) scaled by the other's mass over mine, plus one, capped at 300, spread along dx and dy over the distance. Mass is clamped to one through twenty and a building counts as twenty. the accumulator is drained the same tick it is filled, as it is in the client. what is still short of the client: the push is averaged over the pairs rather than run through updateMovementTowards, avoidance steering is not modelled, and LogicMath::sqrt is our exact root rather than the client's table - which differs from the true root above 2147441940 and will have to be reproduced bug for bug before checksums can agree.
This commit is contained in:
parent
a355b77514
commit
8a4a81057b
1 changed files with 81 additions and 0 deletions
|
|
@ -21,6 +21,13 @@ pub const COLUMN_ATTACKS_AIR: &str = "AttacksAir";
|
||||||
pub const COLUMN_ATTACKS_GROUND: &str = "AttacksGround";
|
pub const COLUMN_ATTACKS_GROUND: &str = "AttacksGround";
|
||||||
pub const COLUMN_TARGET_ONLY_BUILDINGS: &str = "TargetOnlyBuildings";
|
pub const COLUMN_TARGET_ONLY_BUILDINGS: &str = "TargetOnlyBuildings";
|
||||||
pub const COLUMN_FLYING_HEIGHT: &str = "FlyingHeight";
|
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 COLUMN_PROJECTILE: &str = "Projectile";
|
||||||
pub const DISTANCE_SATURATION: i32 = 46340;
|
pub const DISTANCE_SATURATION: i32 = 46340;
|
||||||
pub const WAYPOINT_REACHED: i32 = 250;
|
pub const WAYPOINT_REACHED: i32 = 250;
|
||||||
|
|
@ -37,6 +44,7 @@ pub struct LogicCharacterStats {
|
||||||
pub attacks_ground: bool,
|
pub attacks_ground: bool,
|
||||||
pub target_only_buildings: bool,
|
pub target_only_buildings: bool,
|
||||||
pub flying: bool,
|
pub flying: bool,
|
||||||
|
pub mass: i32,
|
||||||
}
|
}
|
||||||
impl LogicCharacterStats {
|
impl LogicCharacterStats {
|
||||||
pub fn of(data: &LogicDataRef, level_index: i32) -> Self {
|
pub fn of(data: &LogicDataRef, level_index: i32) -> Self {
|
||||||
|
|
@ -65,6 +73,7 @@ impl LogicCharacterStats {
|
||||||
attacks_ground: row.boolean(COLUMN_ATTACKS_GROUND),
|
attacks_ground: row.boolean(COLUMN_ATTACKS_GROUND),
|
||||||
target_only_buildings: row.boolean(COLUMN_TARGET_ONLY_BUILDINGS),
|
target_only_buildings: row.boolean(COLUMN_TARGET_ONLY_BUILDINGS),
|
||||||
flying: row.int(COLUMN_FLYING_HEIGHT) > 0,
|
flying: row.int(COLUMN_FLYING_HEIGHT) > 0,
|
||||||
|
mass: row.int(COLUMN_MASS).clamp(MASS_MIN, MASS_MAX),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn none() -> Self {
|
pub fn none() -> Self {
|
||||||
|
|
@ -80,6 +89,7 @@ impl LogicCharacterStats {
|
||||||
attacks_ground: false,
|
attacks_ground: false,
|
||||||
target_only_buildings: false,
|
target_only_buildings: false,
|
||||||
flying: false,
|
flying: false,
|
||||||
|
mass: MASS_MIN,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_building(&self) -> bool {
|
pub fn is_building(&self) -> bool {
|
||||||
|
|
@ -185,6 +195,7 @@ impl LogicBattle {
|
||||||
pub fn tick(&mut self, tick: i32) {
|
pub fn tick(&mut self, tick: i32) {
|
||||||
self.regenerate_mana(tick);
|
self.regenerate_mana(tick);
|
||||||
self.retarget();
|
self.retarget();
|
||||||
|
self.resolve_collisions();
|
||||||
self.move_objects();
|
self.move_objects();
|
||||||
self.resolve_attacks();
|
self.resolve_attacks();
|
||||||
self.remove_dead();
|
self.remove_dead();
|
||||||
|
|
@ -354,6 +365,76 @@ impl LogicBattle {
|
||||||
base.position.y = from.1 + (dy * step / leg) as i32;
|
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) {
|
fn resolve_attacks(&mut self) {
|
||||||
let positions: Vec<(i32, i32)> = self
|
let positions: Vec<(i32, i32)> = self
|
||||||
.objects
|
.objects
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue