162 lines
6.1 KiB
Rust
162 lines
6.1 KiB
Rust
// Orders from the hand: "attack" sends the fighting creatures nearby at an enemy unit or tower,
|
|
// "guard" posts a few creatures in a room. Right click with an order tool cancels the orders.
|
|
|
|
use crate::game::*;
|
|
use crate::map::*;
|
|
|
|
const ORDER_RADIUS: f32 = 18.0;
|
|
const GUARDS: usize = 3;
|
|
|
|
fn ready(e: &Ent) -> bool {
|
|
e.alive && !e.held && e.creature() && !matches!(e.job, Job::Leave | Job::Register | Job::MakeBed(_)) && e.hp > e.max_hp * 0.25
|
|
}
|
|
|
|
impl Game {
|
|
// attack: the enemy (unit, or tower) at (wx, wy)
|
|
pub fn order_attack(&mut self, picked: Option<usize>, wx: f32, wy: f32) {
|
|
let unit = picked.filter(|&j| j < self.ents.len() && self.is_hostile(&self.ents[j])).or_else(|| {
|
|
self.ents.iter().position(|e| self.is_hostile(e) && (e.x - wx).powi(2) + (e.y - wy).powi(2) < 1.5 * 1.5)
|
|
});
|
|
let tower = if unit.is_none() {
|
|
self.towers.iter().find(|t| t.hero && (t.pos().0 - wx).powi(2) + (t.pos().1 - wy).powi(2) < 1.2 * 1.2).map(|t| (t.id, t.pos()))
|
|
} else {
|
|
None
|
|
};
|
|
let (tx, ty) = match (unit, tower) {
|
|
(Some(j), _) => (self.ents[j].x, self.ents[j].y),
|
|
(None, Some((_, p))) => p,
|
|
_ => {
|
|
self.hint(31, "hint.order_attack");
|
|
self.sfx_ui(Sfx::Error);
|
|
return;
|
|
}
|
|
};
|
|
let mut n = 0;
|
|
for e in self.ents.iter_mut().filter(|e| ready(e) && (e.x - tx).powi(2) + (e.y - ty).powi(2) < ORDER_RADIUS * ORDER_RADIUS) {
|
|
e.job = match (unit, tower) {
|
|
(Some(_), _) => Job::Attack(0),
|
|
(None, Some((id, _))) => Job::AttackTower(id),
|
|
_ => continue,
|
|
};
|
|
e.path.clear();
|
|
e.repath = 0.0;
|
|
n += 1;
|
|
}
|
|
if let Some(j) = unit {
|
|
let id = self.ents[j].id;
|
|
for e in self.ents.iter_mut() {
|
|
if e.job == Job::Attack(0) {
|
|
e.job = Job::Attack(id);
|
|
}
|
|
}
|
|
}
|
|
if n == 0 {
|
|
self.hint(31, "hint.order_nobody");
|
|
return;
|
|
}
|
|
self.fx(7, tx, ty, 0.0, 0.0, 0.6, 0xff3040ff);
|
|
self.sfx_ui(Sfx::FlagSet);
|
|
self.msg("msg.order_attack", vec![n.to_string()]);
|
|
}
|
|
|
|
// guard: a few creatures take positions in the room at (x, y)
|
|
pub fn order_guard(&mut self, x: i32, y: i32) {
|
|
if !inb(x, y) {
|
|
return;
|
|
}
|
|
let i = idx(x, y);
|
|
let kind = self.map.t[i].kind;
|
|
if self.map.t[i].owner != self.me || room_index(kind).is_none() && kind != Kind::Floor {
|
|
self.hint(31, "hint.order_guard");
|
|
self.sfx_ui(Sfx::Error);
|
|
return;
|
|
}
|
|
// the spots: tiles of the same room (or floor) around the click
|
|
let mut spots: Vec<usize> = Vec::new();
|
|
for dy in -3..=3 {
|
|
for dx in -3..=3 {
|
|
if let Some(t) = self.map.get(x + dx, y + dy) {
|
|
let j = idx(x + dx, y + dy);
|
|
if t.kind == kind && t.owner == self.me && t.reserved == 0 && !self.ents.iter().any(|e| e.job == Job::Guard(j)) {
|
|
spots.push(j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
spots.sort_by_key(|&j| {
|
|
let (sx, sy) = xy(j);
|
|
(sx - x).pow(2) + (sy - y).pow(2)
|
|
});
|
|
let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5);
|
|
let mut cands: Vec<usize> = (0..self.ents.len()).filter(|&j| ready(&self.ents[j]) && !matches!(self.ents[j].job, Job::Attack(_) | Job::Guard(_))).collect();
|
|
cands.sort_by(|&a, &b| {
|
|
let da = (self.ents[a].x - fx).powi(2) + (self.ents[a].y - fy).powi(2);
|
|
let db = (self.ents[b].x - fx).powi(2) + (self.ents[b].y - fy).powi(2);
|
|
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
|
});
|
|
let mut n = 0;
|
|
for (&j, &spot) in cands.iter().zip(spots.iter()).take(GUARDS) {
|
|
let cur = self.ents[j].tile();
|
|
let Some(p) = self.map.creature_path(flies(self.ents[j].kind), cur, spot) else { continue };
|
|
let me = self.ents[j].id + 1;
|
|
self.unreserve(me);
|
|
self.map.t[spot].reserved = me;
|
|
let e = &mut self.ents[j];
|
|
e.job = Job::Guard(spot);
|
|
e.path = p;
|
|
e.timer = 120.0;
|
|
n += 1;
|
|
}
|
|
if n == 0 {
|
|
self.hint(31, "hint.order_nobody");
|
|
return;
|
|
}
|
|
self.fx(8, fx, fy, 0.0, 0.0, 0.6, 0xff40d0ff);
|
|
self.sfx_ui(Sfx::FlagSet);
|
|
self.msg("msg.order_guard", vec![n.to_string()]);
|
|
}
|
|
|
|
// right click with an order tool: everybody stands down
|
|
pub fn cancel_orders(&mut self) {
|
|
let mut n = 0;
|
|
for k in 0..self.ents.len() {
|
|
if matches!(self.ents[k].job, Job::Guard(_)) && self.ents[k].creature() {
|
|
let me = self.ents[k].id + 1;
|
|
self.unreserve(me);
|
|
self.ents[k].job = Job::None;
|
|
n += 1;
|
|
}
|
|
}
|
|
if n > 0 {
|
|
self.msg("msg.orders_cancelled", vec![]);
|
|
self.sfx_ui(Sfx::Click);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn creatures_follow_attack_and_guard_orders() {
|
|
let mut g = Game::new(14, 64, 64);
|
|
let (cx, cy) = g.map.core;
|
|
let ids: Vec<u32> = (0..4).map(|k| g.spawn(EK::Ogre, false, cx as f32 - 0.5, cy as f32 + 0.5 + k as f32 * 0.3, 2)).collect();
|
|
let knight = g.spawn(EK::Knight, true, cx as f32 + 2.5, cy as f32 - 1.5, 1);
|
|
let j = g.find(knight).unwrap();
|
|
g.order_attack(Some(j), 0.0, 0.0);
|
|
assert!(ids.iter().all(|&id| g.ents[g.find(id).unwrap()].job == Job::Attack(knight)));
|
|
// guard the treasury ring around the core
|
|
for &id in &ids {
|
|
let j = g.find(id).unwrap();
|
|
g.ents[j].job = Job::None;
|
|
}
|
|
g.order_guard(cx - 1, cy - 1);
|
|
let guards = g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count();
|
|
assert_eq!(guards, 3);
|
|
g.cancel_orders();
|
|
assert_eq!(g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count(), 0);
|
|
}
|
|
}
|