Towers shoot enemy towers and the enemy gate; build areas use the floor under the pointer
- With no unit in reach, towers fire at opposing towers (both sides) and the player's towers at the gate of the order / hero gate (line of sight needed, walls block) - Rooms, bridges, sell, flag and towers pick the floor tile under the pointer; only digging and the hand still aim at walls - Test for tower duels and gate fire Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
09e22b009e
commit
bd8f53e8a1
2 files changed
+162
-6
No files matched your search
+143
-1
@@ -3195,6 +3195,111 @@ impl Game {
|
||||
best
|
||||
}
|
||||
|
||||
// an enemy tower or the gate of the order within reach: (x, y, height, tower id or None = gate)
|
||||
fn tower_structure_target(&self, t: &Tower) -> Option<(f32, f32, f32, Option<u32>)> {
|
||||
let (px, py) = t.pos();
|
||||
let r = t.range();
|
||||
let minr = tower_stats(t.kind).min_range;
|
||||
let in_reach = |x: f32, y: f32| {
|
||||
let d = (x - px).powi(2) + (y - py).powi(2);
|
||||
d <= r * r && d >= minr * minr
|
||||
};
|
||||
let mut best: Option<(f32, (f32, f32, f32, Option<u32>))> = None;
|
||||
for o in self.towers.iter().filter(|o| o.hero != t.hero) {
|
||||
let (ox, oy) = o.pos();
|
||||
if in_reach(ox, oy) && self.los_to_tile(px, py, o.tile) {
|
||||
let d = (ox - px).powi(2) + (oy - py).powi(2);
|
||||
if best.map_or(true, |b| d < b.0) {
|
||||
best = Some((d, (ox, oy, o.top() * 0.6, Some(o.id))));
|
||||
}
|
||||
}
|
||||
}
|
||||
if best.is_none() && !t.hero && self.has_gate() && self.gate_hp > 0.0 {
|
||||
// the nearest tile of the gate counts
|
||||
let (gx, gy) = self.map.gate;
|
||||
let (nx, ny) = ((px - 0.5).clamp(gx as f32, gx as f32 + 2.0) + 0.5, (py - 0.5).clamp(gy as f32, gy as f32 + 2.0) + 0.5);
|
||||
if in_reach(nx, ny) && self.los_to_tile(px, py, idx(nx as i32, ny as i32)) {
|
||||
best = Some((0.0, (nx, ny, 1.0, None)));
|
||||
}
|
||||
}
|
||||
best.map(|b| b.1)
|
||||
}
|
||||
|
||||
// line of sight from a tower to a target tile: walls and other towers block,
|
||||
// the tower's own tile and the target tile do not
|
||||
fn los_to_tile(&self, x0: f32, y0: f32, target: usize) -> bool {
|
||||
let start = idx(x0 as i32, y0 as i32);
|
||||
let (tx, ty) = xy(target);
|
||||
let (x1, y1) = (tx as f32 + 0.5, ty as f32 + 0.5);
|
||||
let n = (((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt() * 3.0).ceil().max(1.0) as i32;
|
||||
for k in 1..n {
|
||||
let f = k as f32 / n as f32;
|
||||
let i = idx((x0 + (x1 - x0) * f) as i32, (y0 + (y1 - y0) * f) as i32);
|
||||
if i == start || i == target {
|
||||
continue;
|
||||
}
|
||||
let tl = &self.map.t[i];
|
||||
if tl.solid() || tl.kind == Kind::Tower {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn tower_fire_at_structure(&mut self, k: usize, (ex, ey, eh, target): (f32, f32, f32, Option<u32>)) {
|
||||
let t = self.towers[k].clone();
|
||||
let s = tower_stats(t.kind);
|
||||
if s.mana > 0.0 && !t.hero && self.mana < s.mana {
|
||||
self.towers[k].cd = 1.0;
|
||||
return;
|
||||
}
|
||||
if !t.hero {
|
||||
self.mana -= s.mana;
|
||||
}
|
||||
let (px, py) = t.pos();
|
||||
let aim = (ey - py).atan2(ex - px);
|
||||
let tw = &mut self.towers[k];
|
||||
tw.cd = s.interval;
|
||||
tw.flash = 0.15;
|
||||
tw.aim = aim;
|
||||
let (mx, my, mh) = match t.kind {
|
||||
TowerKind::Arrow => (px, py, t.top() + 0.25),
|
||||
TowerKind::Cannon => (px + aim.cos() * 0.55, py + aim.sin() * 0.55, t.top() + 0.2),
|
||||
TowerKind::Magic => (px, py, t.top() + 0.45),
|
||||
};
|
||||
let col = match t.kind {
|
||||
TowerKind::Arrow => 0xffd0e0f0,
|
||||
TowerKind::Cannon => 0xff303030,
|
||||
TowerKind::Magic => 0xffff60d0,
|
||||
};
|
||||
self.shot(mx, my, mh, ex, ey, eh, 0.3, col);
|
||||
self.fx_at(2, ex, ey, eh, 0.3, 0xffffffff);
|
||||
self.sfx(match t.kind {
|
||||
TowerKind::Arrow => Sfx::Shoot,
|
||||
TowerKind::Cannon => Sfx::Cannon,
|
||||
TowerKind::Magic => Sfx::Magic,
|
||||
}, px, py);
|
||||
let dmg = t.dmg();
|
||||
match target {
|
||||
Some(id) => {
|
||||
if let Some(o) = self.towers.iter().position(|o| o.id == id) {
|
||||
self.towers[o].hp -= dmg;
|
||||
if self.towers[o].hp <= 0.0 {
|
||||
self.destroy_tower(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.gate_hp -= dmg;
|
||||
self.gate_hit_t = self.time;
|
||||
self.sfx(Sfx::GateHit, ex, ey);
|
||||
if self.mission.as_ref().map_or(true, |m| m.gate_defenders) {
|
||||
self.check_gate_stage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_towers(&mut self, dt: f32) {
|
||||
for k in 0..self.towers.len() {
|
||||
if k >= self.towers.len() {
|
||||
@@ -3207,7 +3312,13 @@ impl Game {
|
||||
continue;
|
||||
}
|
||||
let t = self.towers[k].clone();
|
||||
let Some(j) = self.tower_target(&t) else { continue };
|
||||
let Some(j) = self.tower_target(&t) else {
|
||||
// no unit in reach: enemy towers and the enemy gate are shot at
|
||||
if let Some(target) = self.tower_structure_target(&t) {
|
||||
self.tower_fire_at_structure(k, target);
|
||||
}
|
||||
continue;
|
||||
};
|
||||
let s = tower_stats(t.kind);
|
||||
if s.mana > 0.0 && !t.hero && self.mana < s.mana {
|
||||
self.hint(17, "hint.tower_mana");
|
||||
@@ -3910,6 +4021,37 @@ mod tests {
|
||||
let _ = f;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn towers_fight_towers_and_the_gate() {
|
||||
// a tower of the player in the courtyard of a fortress, no units around
|
||||
let (mut g, _) = fort_game(|x, y| crate::fortress::FortSpec { towers: vec![TowerKind::Arrow], ..spec(x, y) });
|
||||
g.ents.clear();
|
||||
let (kx, ky) = xy(g.towers[0].tile);
|
||||
let (px, py) = (kx + 3, ky + 2);
|
||||
g.map.t[idx(px, py)].owner = Owner::Player;
|
||||
g.gold = 5000.0;
|
||||
g.build_tower(TowerKind::Arrow, px, py);
|
||||
assert_eq!(g.towers.len(), 2);
|
||||
let hp: Vec<f32> = g.towers.iter().map(|t| t.hp).collect();
|
||||
for _ in 0..100 {
|
||||
g.update(0.05);
|
||||
}
|
||||
assert!(g.towers.iter().zip(&hp).all(|(t, h)| t.hp < *h), "both towers are hit");
|
||||
// free game: a tower in reach of the hero gate shoots at the gate
|
||||
let mut g = Game::new(5, 64, 64);
|
||||
g.ents.clear();
|
||||
let (gx, gy) = g.map.gate;
|
||||
let (tx, ty) = (gx - 2, gy + 1);
|
||||
g.map.t[idx(tx, ty)].kind = Kind::Floor;
|
||||
g.map.t[idx(tx, ty)].owner = Owner::Player;
|
||||
g.gold = 5000.0;
|
||||
g.build_tower(TowerKind::Arrow, tx, ty);
|
||||
for _ in 0..60 {
|
||||
g.update(0.05);
|
||||
}
|
||||
assert!(g.gate_hp < GATE_HP, "the gate is shot at");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vaults_are_claimed_and_looted() {
|
||||
let (mut g, _) = fort_game(|x, y| crate::fortress::FortSpec { vaults: 2, vault_value: 1800, ..spec(x, y) });
|
||||
|
||||
Reference in new issue
Block a user