send units at the nearest tower when nothing is in sight

units stood still all battle. a unit only moved if it had a combat
target, and targets are only found inside SightRange - six thousand
units for a knight - while the enemy tower sits twenty thousand away. so
nothing ever walked, nothing was ever hit, no tower fell, crowns stayed
at nothing and the battle could only end on the clock.

a unit with no target now walks at the nearest enemy building, which is
the default target the client falls back to. once the tower comes inside
sight the ordinary search picks it up and the attack timer takes over.

the battle turn log carries the standing tower count per side so damage
is visible before it becomes a crown.
This commit is contained in:
WiseDev 2026-08-23 13:28:23 +03:00
parent 9084163f71
commit d1cc829623
3 changed files with 34 additions and 3 deletions

View file

@ -209,6 +209,12 @@ impl BattleSession {
pub fn stars(&self) -> (i32, i32) { pub fn stars(&self) -> (i32, i32) {
(self.mode.battle.stars(0), self.mode.battle.stars(1)) (self.mode.battle.stars(0), self.mode.battle.stars(1))
} }
pub fn towers_standing(&self) -> (usize, usize) {
(
self.mode.battle.leader_towers[0].len(),
self.mode.battle.leader_towers[1].len(),
)
}
pub fn owner_of(&self, account: LogicLong) -> Option<i32> { pub fn owner_of(&self, account: LogicLong) -> Option<i32> {
self.mode self.mode
.battle .battle
@ -312,6 +318,7 @@ impl BattleRegistry {
finished: session.is_finished(), finished: session.is_finished(),
stars: session.stars(), stars: session.stars(),
objects: session.object_count(), objects: session.object_count(),
towers: session.towers_standing(),
}) })
} }
pub async fn play<F>( pub async fn play<F>(
@ -348,4 +355,5 @@ pub struct BattleProgress {
pub finished: bool, pub finished: bool,
pub stars: (i32, i32), pub stars: (i32, i32),
pub objects: usize, pub objects: usize,
pub towers: (usize, usize),
} }

View file

@ -373,6 +373,7 @@ impl GameApi for GameService {
finished = progress.map(|state| state.finished), finished = progress.map(|state| state.finished),
stars = ?progress.map(|state| state.stars), stars = ?progress.map(|state| state.stars),
objects = progress.map(|state| state.objects), objects = progress.map(|state| state.objects),
towers = ?progress.map(|state| state.towers),
spawned, spawned,
commands = ?turn commands = ?turn
.commands .commands

View file

@ -187,6 +187,24 @@ impl LogicBattle {
} }
} }
} }
fn default_target(&self, index: usize) -> Option<usize> {
let owner = self.objects.objects[index].owner_index();
let from = self.objects.objects[index].position();
let mut best: Option<(i32, usize)> = None;
for (other, entry) in self.objects.objects.iter().enumerate() {
if other == index || entry.owner_index() == owner || !entry.is_alive() {
continue;
}
if !entry.stats().is_building() {
continue;
}
let distance = distance_squared(from, entry.position());
if best.map(|(closest, _)| distance < closest).unwrap_or(true) {
best = Some((distance, other));
}
}
best.map(|(_, other)| other)
}
fn move_objects(&mut self) { fn move_objects(&mut self) {
let positions: Vec<(i32, i32)> = self let positions: Vec<(i32, i32)> = self
.objects .objects
@ -199,11 +217,15 @@ impl LogicBattle {
if stats.speed < 1 || !self.objects.objects[index].is_alive() { if stats.speed < 1 || !self.objects.objects[index].is_alive() {
continue; continue;
} }
let Some(target) = self.objects.objects[index] let target = match self.objects.objects[index]
.combat_mut() .combat_mut()
.and_then(|component| component.target_index) .and_then(|component| component.target_index)
else { {
continue; Some(target) => target,
None => match self.default_target(index) {
Some(target) => target,
None => continue,
},
}; };
let Some(goal) = positions.get(target).copied() else { let Some(goal) = positions.get(target).copied() else {
continue; continue;