Multiplayer continues after a player leaves: AI side takeover, background match while menus are open, match chat, five-tile fog start
This commit is contained in:
1 parent
2721638910
commit
dc22e27184
11 files changed
+436
-70
No files matched your search
+58
-1
@@ -69,6 +69,35 @@ impl Game {
|
||||
self.netplay.is_some()
|
||||
}
|
||||
|
||||
// a seat without a human in a started match: that side keeps playing as a rival-style AI
|
||||
pub fn sync_ai_sides(&mut self, ai: &[u8], difficulty: u8) {
|
||||
if self.netplay.is_none() {
|
||||
return;
|
||||
}
|
||||
self.ai_difficulty = difficulty;
|
||||
self.ai_sides = ai
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|side| *side != self.local_side)
|
||||
.collect();
|
||||
for side in self.ai_sides.clone() {
|
||||
let core = if side == self.me_side {
|
||||
continue;
|
||||
} else if let Some(m) = self.masters.iter().find(|m| m.side == side) {
|
||||
Some(m.core)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let Some(core) = core else { continue };
|
||||
if let Some(k) = self.masters.iter().position(|m| m.side == side) {
|
||||
self.masters.remove(k);
|
||||
}
|
||||
if !self.is_rival_side(side) {
|
||||
self.add_rival(side, core.0 + 1, core.1 + 1, self.ai_difficulty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the relay ended a match, or the player leaves it: the world stops being a turn-based match
|
||||
pub fn end_match(&mut self) {
|
||||
self.netplay = None;
|
||||
@@ -80,7 +109,7 @@ impl Game {
|
||||
}
|
||||
|
||||
// the server confirmed a turn: run the orders of all masters, then the fixed steps of the turn.
|
||||
// A client that is paused or finished leaves the turn out (a match pauses while somebody is away).
|
||||
// A local client that is paused or finished leaves the turn out; empty match seats are AI sides.
|
||||
pub fn receive_turn(&mut self, cmds: &[Command]) -> u32 {
|
||||
if self.netplay.is_none() || self.state != 0 || self.paused {
|
||||
return 0;
|
||||
@@ -225,6 +254,21 @@ impl Game {
|
||||
mix(&mut h, *springs as u64);
|
||||
mixp(&mut h, Some(*core));
|
||||
}
|
||||
for r in self.rivals.iter() {
|
||||
mixp(&mut h, Some(r.core));
|
||||
mix(&mut h, r.side as u64);
|
||||
mix(&mut h, r.alive as u64);
|
||||
mix(&mut h, r.radius as u64);
|
||||
mix(&mut h, r.difficulty as u64);
|
||||
mixf(&mut h, r.hp);
|
||||
mixf(&mut h, r.grow_t);
|
||||
mixf(&mut h, r.spawn_t);
|
||||
mixf(&mut h, r.attack_t);
|
||||
mixf(&mut h, r.power);
|
||||
}
|
||||
for side in self.ai_sides.iter() {
|
||||
mix(&mut h, *side as u64);
|
||||
}
|
||||
h
|
||||
}
|
||||
}
|
||||
@@ -296,6 +340,19 @@ mod tests {
|
||||
assert_eq!(g.worker_count(), 5, "the summon counts as the master's own worker");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_lost_seat_in_a_match_is_converted_to_the_same_ai_side() {
|
||||
let (mut a, mut b) = (client(31, 0), client(31, 2));
|
||||
a.sync_ai_sides(&[3], 1);
|
||||
b.sync_ai_sides(&[3], 1);
|
||||
assert_eq!(a.checksum(), b.checksum(), "both clients convert side 3 the same way");
|
||||
assert!(a.is_rival_side(3) && b.is_rival_side(3));
|
||||
assert!(!a.is_master(3) && !b.is_master(3));
|
||||
a.receive_turn(&[]);
|
||||
b.receive_turn(&[]);
|
||||
assert_eq!(a.checksum(), b.checksum(), "and keep playing it identically");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_client_without_the_next_turn_stands_still_and_its_orders_wait() {
|
||||
let mut g = client(22, 0);
|
||||
|
||||
Reference in new issue
Block a user