never take a leader off the board
the client crashed in LogicBattle::resetSimulatedManaTimers again, and
this time it was ours: remove_dead was dropping a king tower once it
fell, while battle.leaders still pointed at it. that function reads both
leaders straight out of the battle and calls a virtual on each without a
null check, so the next state to arrive killed the client.
the client's own rule is one line:
LogicCharacter::shouldDestruct() { if (this[209]) return 0; ... }
isLeader, never destructs. remove_dead now keeps them the same way, and
the harness watches every tick of a full match for a leader that has
gone missing - it caught this one at tick 693.
This commit is contained in:
parent
f5a8c39ab2
commit
14786cbc92
2 changed files with 54 additions and 2 deletions
|
|
@ -122,6 +122,19 @@ fn the_bot_deploys_in_front_of_its_own_towers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
session.advance_to(tick);
|
session.advance_to(tick);
|
||||||
|
for (side, leader) in session.mode().battle.leaders.iter().enumerate() {
|
||||||
|
let alive = session
|
||||||
|
.mode()
|
||||||
|
.battle
|
||||||
|
.objects
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.any(|entry| entry.global_id == *leader);
|
||||||
|
assert!(
|
||||||
|
alive,
|
||||||
|
"t={tick}: side {side}'s leader {leader:?} is no longer in the battle"
|
||||||
|
);
|
||||||
|
}
|
||||||
if session.is_finished() || session.towers_standing().0 == 0 {
|
if session.is_finished() || session.towers_standing().0 == 0 {
|
||||||
println!("battle over at t={tick} after {plays} bot plays");
|
println!("battle over at t={tick} after {plays} bot plays");
|
||||||
break;
|
break;
|
||||||
|
|
@ -205,3 +218,39 @@ fn a_bot_troop_walks_at_the_player_towers() {
|
||||||
"the bot troop walked away from the player towers to {end:?}"
|
"the bot troop walked away from the player towers to {end:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn both_leaders_resolve_to_a_summoner() {
|
||||||
|
let root = assets();
|
||||||
|
if let Ok(tables) = LogicDataTables::load_from_dir(&root) {
|
||||||
|
LogicDataTables::install(Arc::new(tables));
|
||||||
|
}
|
||||||
|
let builder = BattleBuilder::new(&root);
|
||||||
|
let mode = builder
|
||||||
|
.build(
|
||||||
|
LogicDataRef::by_name(table::LOCATIONS, "PvP_goblin"),
|
||||||
|
LogicDataRef::None,
|
||||||
|
LogicDataRef::by_name(table::ARENAS, "Arena_T"),
|
||||||
|
vec![avatar(5), avatar(0)],
|
||||||
|
[None, None],
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("battle");
|
||||||
|
println!("leaders: {:?}", mode.battle.leaders);
|
||||||
|
for (side, leader) in mode.battle.leaders.iter().enumerate() {
|
||||||
|
assert!(
|
||||||
|
!leader.is_none(),
|
||||||
|
"side {side} has no leader; LogicBattle::resetSimulatedManaTimers reads both \
|
||||||
|
without checking for null and takes the client down with it"
|
||||||
|
);
|
||||||
|
let found = mode
|
||||||
|
.battle
|
||||||
|
.objects
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.global_id == *leader);
|
||||||
|
assert!(
|
||||||
|
found.is_some(),
|
||||||
|
"side {side}'s leader {leader:?} matches no object in the battle"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -514,17 +514,20 @@ impl LogicBattle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn remove_dead(&mut self) {
|
fn remove_dead(&mut self) {
|
||||||
|
let leaders = self.leaders;
|
||||||
let dead: Vec<crate::battle::logic_game_object_ref::LogicGameObjectRef> = self
|
let dead: Vec<crate::battle::logic_game_object_ref::LogicGameObjectRef> = self
|
||||||
.objects
|
.objects
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|entry| !entry.is_alive())
|
.filter(|entry| !entry.is_alive() && !leaders.contains(&entry.global_id))
|
||||||
.map(|entry| entry.global_id)
|
.map(|entry| entry.global_id)
|
||||||
.collect();
|
.collect();
|
||||||
if dead.is_empty() {
|
if dead.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.objects.objects.retain(LogicGameObjectEntry::is_alive);
|
self.objects
|
||||||
|
.objects
|
||||||
|
.retain(|entry| entry.is_alive() || leaders.contains(&entry.global_id));
|
||||||
for towers in self.leader_towers.iter_mut() {
|
for towers in self.leader_towers.iter_mut() {
|
||||||
towers.retain(|tower| !dead.contains(tower));
|
towers.retain(|tower| !dead.contains(tower));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue