Co-op campaign matches and command markers: mission relay, allied masters, temporary flags

This commit is contained in:
Richard Zink committed 2026-11-04 20:07:06 +01:00
1 parent dc22e27184
commit 5928acf7f6
12 files changed
+433 -27

No files matched your search

+74 -5
View File
@@ -611,11 +611,43 @@ pub struct Game {
#[serde(skip)]
pub ai_difficulty: u8, // difficulty used when a left side becomes an AI rival
#[serde(skip)]
pub coop: bool, // co-op match: the playable master sides are on one side against the heroes
#[serde(skip)]
pub marks: Vec<Mark>, // short-lived markers for commands given by the masters
#[serde(skip)]
pub hints: bool, // show hint messages (setting)
#[serde(skip, default = "hint_times")]
hint_t: [f32; 32], // last time each hint slot was shown
}
pub const MARK_DIG: u8 = 0;
pub const MARK_ROOM: u8 = 1;
pub const MARK_SELL: u8 = 2;
pub const MARK_BRIDGE: u8 = 3;
pub const MARK_STEEL: u8 = 4;
pub const MARK_TOWER: u8 = 5;
pub const MARK_TRAP: u8 = 6;
pub const MARK_SPELL: u8 = 7;
pub const MARK_LIGHTNING: u8 = 8;
pub const MARK_HEAL: u8 = 9;
pub const MARK_SUMMON: u8 = 10;
pub const MARK_FLAG: u8 = 11;
pub const MARK_PICKUP: u8 = 12;
pub const MARK_DROP: u8 = 13;
pub const MARK_SLAP: u8 = 14;
pub const MARK_ATTACK: u8 = 15;
pub const MARK_GUARD: u8 = 16;
pub const MARK_CANCEL: u8 = 17;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Mark {
pub x: f32,
pub y: f32,
pub kind: u8,
pub side: u8,
pub until: u64,
}
fn hint_times() -> [f32; 32] {
[-1000.0; 32]
}
@@ -794,6 +826,8 @@ impl Game {
netplay: None,
ai_sides: Vec::new(),
ai_difficulty: 1,
coop: false,
marks: Vec::new(),
hint_t: hint_times(),
reserve: 0.0,
incoming: Vec::new(),
@@ -1237,6 +1271,9 @@ impl Game {
std::mem::take(&mut self.pending)
};
self.apply_turn(&cmds, steps);
if !self.marks.is_empty() {
self.marks.retain(|m| self.sim_steps < m.until);
}
// what only the camera cares about runs in real time: it is not part of the shared state
self.update_particles(dt);
for f in self.fx.iter_mut() {
@@ -1271,6 +1308,14 @@ impl Game {
side == self.me_side || self.masters.iter().any(|m| m.side == side)
}
fn is_hostile_master(&self, side: u8) -> bool {
self.coop && Owner::MASTER_SIDES.contains(&self.me_side) && Owner::MASTER_SIDES.contains(&side)
}
pub(crate) fn is_hostile(&self, e: &Ent) -> bool {
e.alive && !e.held && e.captive == 0 && !e.chained && e.side() != self.me_side && !self.is_hostile_master(e.side())
}
pub fn is_rival_side(&self, side: u8) -> bool {
self.rivals.iter().any(|r| r.alive && r.side == side)
}
@@ -2316,8 +2361,9 @@ impl Game {
let mut best = None;
let mut bd = radius * radius;
for o in &self.ents {
// every other side is an enemy
if !o.alive || o.held || o.side() == e.side() || o.captive != 0 || o.chained || o.invis > 0.0 {
// every other side is an enemy; co-op master sides fight the heroes, not each other
let coop_allies = self.coop && Owner::MASTER_SIDES.contains(&e.side()) && Owner::MASTER_SIDES.contains(&o.side());
if !o.alive || o.held || o.side() == e.side() || coop_allies || o.captive != 0 || o.chained || o.invis > 0.0 {
continue;
}
let d = (o.x - e.x).powi(2) + (o.y - e.y).powi(2);
@@ -2789,8 +2835,9 @@ impl Game {
self.contested.iter_mut().for_each(|c| *c = false);
}
for e in &self.ents {
// any enemy of the player (heroes and the rivals' units) keeps ground from being claimed
if !e.alive || e.held || e.captive != 0 || e.side() == 0 {
// any enemy of the player (heroes and the rivals' units) keeps ground from being claimed.
// In co-op matches, allied master sides do not block the player's own ground claims.
if !e.alive || e.held || e.captive != 0 || e.side() == 0 || (self.coop && Owner::MASTER_SIDES.contains(&e.side())) {
continue;
}
let (x, y) = (e.x as i32, e.y as i32);
@@ -3788,6 +3835,21 @@ impl Game {
}
}
pub fn add_mark(&mut self, x: f32, y: f32, kind: u8, side: u8, duration: f32) {
if duration <= 0.0 || !x.is_finite() || !y.is_finite() {
return;
}
let until = self.sim_steps + (duration / STEP).ceil() as u64;
if let Some(m) = self.marks.iter_mut().find(|m| (m.x - x).abs() < 0.75 && (m.y - y).abs() < 0.75 && m.kind == kind && m.side == side) {
m.until = m.until.max(until);
return;
}
if self.marks.len() >= 80 {
self.marks.remove(0);
}
self.marks.push(Mark { x, y, kind, side, until });
}
fn creature_choose(&mut self, e: &mut Ent) {
let me = e.id + 1;
let cur = e.tile();
@@ -4928,7 +4990,14 @@ impl Game {
// a unit the tower shoots at
fn tower_enemy(&self, t: &Tower, e: &Ent) -> bool {
e.captive == 0 && !e.chained && if t.hero { e.side() != 1 } else { e.side() != 0 }
let target = if t.hero {
e.side() != 1
} else if self.coop {
e.side() == 1
} else {
e.side() != 0
};
e.captive == 0 && !e.chained && target
}
// a hero attacks a tower; false = tower gone or unreachable