Masters for a match: the four sides red, green, blue and yellow (side 4 gets its own owner), and start_masters gives every master his core and his starting area
This commit is contained in:
1 parent
35ddfa134c
commit
87890adc53
10 files changed
+132
-9
No files matched your search
+11
-2
@@ -58,5 +58,14 @@ per turn. Fog of war, particles and the camera are local and not part of the sha
|
|||||||
| E | Alliances and coop rules, AI masters for free and abandoned slots | planned |
|
| E | Alliances and coop rules, AI masters for free and abandoned slots | planned |
|
||||||
| F | Coop campaign: 20 missions with story (EN/DE) | planned |
|
| F | Coop campaign: 20 missions with story (EN/DE) | planned |
|
||||||
|
|
||||||
Still open on the way to C: player owners (today side 1 is the humans' and 2/3 the rivals' - four
|
Still open on the way to C: the turn/turn-boundary client loop, the state checksum, and heroes and
|
||||||
player sides need their own `Owner`s), the turn/turn-boundary client loop, and the state checksum.
|
alliances for the new sides (today the hero gate path always leads to side 0's core, and 2/3 are the
|
||||||
|
rivals' sides in free play).
|
||||||
|
|
||||||
|
## Masters and sides
|
||||||
|
|
||||||
|
A match plays on the four master sides `0, 2, 3, 4` (side 1 is the heroes'): red (side 0, `Owner::Player`),
|
||||||
|
green (`Owner::Rival1`), blue (`Owner::Rival2`) and yellow (`Owner::Rival3`). `Game::start_masters(sides,
|
||||||
|
local)` carves a 9x9 starting area and a 3x3 soul core for every master and remembers his economy in a
|
||||||
|
`MasterState`; the places are worked out from the map size alone, so all clients agree without passing them
|
||||||
|
over the network.
|
||||||
@@ -603,6 +603,7 @@ msg.payday_walk = Zahltag! {0} Kreaturen holen sich ihren Lohn aus der Schatzkam
|
|||||||
msg.wage_unpaid = Dein {0} hat kein Gold für seinen Lohn gefunden und ist wütend!
|
msg.wage_unpaid = Dein {0} hat kein Gold für seinen Lohn gefunden und ist wütend!
|
||||||
owner.rival1 = gehört Mossgrim, dem grünen Meister
|
owner.rival1 = gehört Mossgrim, dem grünen Meister
|
||||||
owner.rival2 = gehört Lady Nightwell, der blauen Meisterin
|
owner.rival2 = gehört Lady Nightwell, der blauen Meisterin
|
||||||
|
owner.rival3 = gehört dem gelben Meister
|
||||||
|
|
||||||
# rival masters (free game)
|
# rival masters (free game)
|
||||||
newgame.difficulty = Gegner
|
newgame.difficulty = Gegner
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ msg.payday_walk = Payday! {0} creatures fetch their wages from the treasury ({1}
|
|||||||
msg.wage_unpaid = Your {0} found no gold for its wage and is furious!
|
msg.wage_unpaid = Your {0} found no gold for its wage and is furious!
|
||||||
owner.rival1 = held by Mossgrim, the green master
|
owner.rival1 = held by Mossgrim, the green master
|
||||||
owner.rival2 = held by Lady Nightwell, the blue master
|
owner.rival2 = held by Lady Nightwell, the blue master
|
||||||
|
owner.rival3 = held by the yellow master
|
||||||
|
|
||||||
# rival masters (free game)
|
# rival masters (free game)
|
||||||
newgame.difficulty = Opponents
|
newgame.difficulty = Opponents
|
||||||
|
|||||||
+16
-1
@@ -169,7 +169,7 @@ impl Game {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::game::{EK, Job, STEP};
|
use crate::game::{EK, Job, STEP};
|
||||||
use crate::map::idx;
|
use crate::map::{idx, Kind, Owner, mh, mw};
|
||||||
|
|
||||||
fn game(seed: u32) -> Game {
|
fn game(seed: u32) -> Game {
|
||||||
let mut g = Game::new(seed, 64, 64);
|
let mut g = Game::new(seed, 64, 64);
|
||||||
@@ -272,4 +272,19 @@ mod tests {
|
|||||||
let json = serde_json::to_string(&c).unwrap();
|
let json = serde_json::to_string(&c).unwrap();
|
||||||
assert_eq!(serde_json::from_str::<Command>(&json).unwrap(), c);
|
assert_eq!(serde_json::from_str::<Command>(&json).unwrap(), c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_match_command_builds_on_the_ground_of_its_own_master() {
|
||||||
|
let mut g = Game::new(13, 96, 128);
|
||||||
|
g.init_fog();
|
||||||
|
assert!(g.start_masters(&[0, 2], 0), "a match of two masters");
|
||||||
|
let home = g.map.t.iter().position(|t| t.kind == Kind::Core && t.owner == Owner::Rival1).expect("the green master got his core");
|
||||||
|
let (cx, cy) = ((home % mw() as usize) as i32, (home / mh() as usize) as i32);
|
||||||
|
let gold = g.gold;
|
||||||
|
g.apply_turn(&[Command { side: 2, seq: 0, act: Act::Room { room: 0, r: Rect::new(cx + 4, cy + 4, cx + 4, cy + 4) } }], 1);
|
||||||
|
let t = &g.map.t[idx(cx + 4, cy + 4)];
|
||||||
|
assert_eq!(t.kind, Kind::Treasury, "he builds on his own ground");
|
||||||
|
assert_eq!(t.owner, Owner::Rival1);
|
||||||
|
assert_eq!(g.gold, gold, "my gold is untouched");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+88
-3
@@ -1287,9 +1287,9 @@ impl Game {
|
|||||||
self.put_active(target);
|
self.put_active(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
// a new master on `side` with its core at (cx, cy) (top-left of the 3x3 core). Used by the
|
// a new master on `side` with his core at (cx, cy) (top-left of the 3x3 core); single player
|
||||||
// multiplayer lobby (and its tests); single player games have no stored masters.
|
// games have no stored masters
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)] // used by the tests; the *Multiplayer* menu (step D) will call it
|
||||||
pub fn add_master(&mut self, side: u8, core: (i32, i32)) {
|
pub fn add_master(&mut self, side: u8, core: (i32, i32)) {
|
||||||
let mut m = self.take_active();
|
let mut m = self.take_active();
|
||||||
let me = std::mem::replace(&mut m, MasterState::default());
|
let me = std::mem::replace(&mut m, MasterState::default());
|
||||||
@@ -1311,6 +1311,62 @@ impl Game {
|
|||||||
self.masters.push(fresh);
|
self.masters.push(fresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A match: `sides` (out of 0, 2, 3, 4 - side 1 is the heroes') play against each other and
|
||||||
|
// `local` is the master who plays at this computer. Side 0 keeps the core of the generated map
|
||||||
|
// (that is where the hero gate path leads); the other masters get their cores at fixed places
|
||||||
|
// worked out from the map size alone, so every client of a match puts them in the same spots.
|
||||||
|
#[allow(dead_code)] // used by the tests; the *Multiplayer* menu (step D) will call it
|
||||||
|
pub fn start_masters(&mut self, sides: &[u8], local: u8) -> bool {
|
||||||
|
let sides: Vec<u8> = sides.iter().copied().filter(|s| Owner::MASTER_SIDES.contains(s)).collect();
|
||||||
|
if sides.len() < 2 || !sides.contains(&0) || !sides.contains(&local) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let (w, h) = (mw() as i32, mh() as i32);
|
||||||
|
if w < 72 || h < 72 {
|
||||||
|
return false; // the master areas need room
|
||||||
|
}
|
||||||
|
let cores: Vec<(i32, i32)> = sides.iter().map(|&s| match s { 0 => self.map.core, 2 => (w - 30, 12), 3 => (w - 30, h - 16), _ => (14, h - 16) }).collect();
|
||||||
|
for (k, side) in sides.iter().enumerate() {
|
||||||
|
if *side == self.me_side {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.carve_master_area(*side, cores[k]);
|
||||||
|
self.add_master(*side, cores[k]);
|
||||||
|
}
|
||||||
|
self.local_side = local;
|
||||||
|
self.switch_to(local);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// the starting area of a master: a 9x9 room of his own floor and his 3x3 soul core
|
||||||
|
#[allow(dead_code)] // called by start_masters only
|
||||||
|
fn carve_master_area(&mut self, side: u8, core: (i32, i32)) {
|
||||||
|
let owner = Owner::of_side(side);
|
||||||
|
for y in core.1 - 3..core.1 + 6 {
|
||||||
|
for x in core.0 - 3..core.0 + 6 {
|
||||||
|
if x < 2 || y < 2 || x >= mw() as i32 - 2 || y >= mh() as i32 - 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let t = &mut self.map.t[idx(x, y)];
|
||||||
|
if matches!(t.kind, Kind::Portal | Kind::Gate | Kind::Core) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
t.kind = Kind::Floor;
|
||||||
|
t.owner = owner;
|
||||||
|
t.gold = 0;
|
||||||
|
t.marked = false;
|
||||||
|
t.reinforced = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for y in 0..3 {
|
||||||
|
for x in 0..3 {
|
||||||
|
let t = &mut self.map.t[idx(core.0 + x, core.1 + y)];
|
||||||
|
t.kind = Kind::Core;
|
||||||
|
t.owner = owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn update_units(&mut self, dt: f32) {
|
pub(crate) fn update_units(&mut self, dt: f32) {
|
||||||
for i in 0..self.ents.len() {
|
for i in 0..self.ents.len() {
|
||||||
if !self.ents[i].alive || self.ents[i].held {
|
if !self.ents[i].alive || self.ents[i].held {
|
||||||
@@ -5341,6 +5397,35 @@ mod tests {
|
|||||||
assert!(e.alive && g.map.walk(e.x as i32, e.y as i32));
|
assert!(e.alive && g.map.walk(e.x as i32, e.y as i32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_match_gives_every_master_his_core_and_his_ground() {
|
||||||
|
let mut g = Game::new(7, 96, 128);
|
||||||
|
g.init_fog();
|
||||||
|
assert!(g.start_masters(&[0, 2, 3, 4], 2), "four masters, and I am the green one");
|
||||||
|
assert_eq!(g.local_side, 2);
|
||||||
|
assert_eq!(g.masters.len(), 3);
|
||||||
|
for side in Owner::MASTER_SIDES {
|
||||||
|
let owner = Owner::of_side(side);
|
||||||
|
assert!(g.is_master(side));
|
||||||
|
let cores = g.map.t.iter().filter(|t| t.kind == Kind::Core && t.owner == owner).count();
|
||||||
|
assert_eq!(cores, 9, "master {side} has his soul core");
|
||||||
|
let ground = g.map.t.iter().filter(|t| t.kind == Kind::Floor && t.owner == owner).count();
|
||||||
|
assert!(ground >= 40, "master {side} has a starting area of his own");
|
||||||
|
g.switch_to(side);
|
||||||
|
assert_eq!(g.core_hp, CORE_HP, "every core starts intact");
|
||||||
|
assert_eq!(g.gold, 1500.0, "and every master with the same gold");
|
||||||
|
}
|
||||||
|
g.switch_to(2);
|
||||||
|
let green = g.map.core;
|
||||||
|
g.switch_to(4);
|
||||||
|
assert_ne!(g.map.core, green, "the masters live apart");
|
||||||
|
for _ in 0..50 {
|
||||||
|
g.update(STEP);
|
||||||
|
}
|
||||||
|
assert_eq!(g.sim_steps, 50, "every master is simulated");
|
||||||
|
assert_eq!(g.me_side, 2, "the saddle goes back to the master who plays here");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lava_burns_and_needs_stone_bridges() {
|
fn lava_burns_and_needs_stone_bridges() {
|
||||||
let (mut g, rx) = river_game(true);
|
let (mut g, rx) = river_game(true);
|
||||||
|
|||||||
+4
-1
@@ -79,6 +79,7 @@ pub const T_GUARDPOST: u32 = 174;
|
|||||||
pub const T_ARENA: u32 = 178;
|
pub const T_ARENA: u32 = 178;
|
||||||
pub const T_ALTAR: u32 = 182;
|
pub const T_ALTAR: u32 = 182;
|
||||||
pub const I_ORDERS: u32 = 186; // attack, guard
|
pub const I_ORDERS: u32 = 186; // attack, guard
|
||||||
|
pub const T_RIVAL3: u32 = 189; // 4 variants: floor of the yellow master
|
||||||
|
|
||||||
pub struct Gfx {
|
pub struct Gfx {
|
||||||
pub atlas: Texture2D,
|
pub atlas: Texture2D,
|
||||||
@@ -1035,7 +1036,7 @@ fn gen_atlas() -> Image {
|
|||||||
}
|
}
|
||||||
// floors of the rival masters: like the player's claimed floor, in their colours
|
// floors of the rival masters: like the player's claimed floor, in their colours
|
||||||
for v in 0..4u32 {
|
for v in 0..4u32 {
|
||||||
for (id, base, grout, mark) in [(T_RIVAL1, (44, 74, 50), (20, 36, 24), (90, 170, 100)), (T_RIVAL2, (44, 54, 86), (20, 24, 44), (90, 120, 200))] {
|
for (id, base, grout, mark) in [(T_RIVAL1, (44, 74, 50), (20, 36, 24), (90, 170, 100)), (T_RIVAL2, (44, 54, 86), (20, 24, 44), (90, 120, 200)), (T_RIVAL3, (74, 62, 26), (36, 30, 12), (210, 185, 70))] {
|
||||||
cv.cell(id + v, |x, y| {
|
cv.cell(id + v, |x, y| {
|
||||||
let col = stones(x, y, v + 250, base, grout, 10);
|
let col = stones(x, y, v + 250, base, grout, 10);
|
||||||
if (x % 8 == 4 && y % 8 == 4) && hash(x / 8, y / 8, v + 251) % 2 == 0 {
|
if (x % 8 == 4 && y % 8 == 4) && hash(x / 8, y / 8, v + 251) % 2 == 0 {
|
||||||
@@ -1815,6 +1816,8 @@ impl Gfx {
|
|||||||
T_RIVAL1 + v
|
T_RIVAL1 + v
|
||||||
} else if owner == Owner::Rival2 {
|
} else if owner == Owner::Rival2 {
|
||||||
T_RIVAL2 + v
|
T_RIVAL2 + v
|
||||||
|
} else if owner == Owner::Rival3 {
|
||||||
|
T_RIVAL3 + v
|
||||||
} else if hero {
|
} else if hero {
|
||||||
T_HEROFLOOR + v
|
T_HEROFLOOR + v
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
-1
@@ -145,9 +145,10 @@ pub enum Owner {
|
|||||||
Hero,
|
Hero,
|
||||||
Rival1, // AI dungeon masters (free play, campaign)
|
Rival1, // AI dungeon masters (free play, campaign)
|
||||||
Rival2,
|
Rival2,
|
||||||
|
Rival3, // the fourth master side: a third rival, or a human master in a match
|
||||||
}
|
}
|
||||||
|
|
||||||
const OWNERS: [Owner; 5] = [Owner::None, Owner::Player, Owner::Hero, Owner::Rival1, Owner::Rival2];
|
const OWNERS: [Owner; 6] = [Owner::None, Owner::Player, Owner::Hero, Owner::Rival1, Owner::Rival2, Owner::Rival3];
|
||||||
|
|
||||||
impl Owner {
|
impl Owner {
|
||||||
pub fn of_side(side: u8) -> Owner {
|
pub fn of_side(side: u8) -> Owner {
|
||||||
@@ -156,9 +157,14 @@ impl Owner {
|
|||||||
1 => Owner::Hero,
|
1 => Owner::Hero,
|
||||||
2 => Owner::Rival1,
|
2 => Owner::Rival1,
|
||||||
3 => Owner::Rival2,
|
3 => Owner::Rival2,
|
||||||
|
4 => Owner::Rival3,
|
||||||
_ => Owner::None,
|
_ => Owner::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the masters of a match: the four sides a dungeon master can play (1 is the heroes' side)
|
||||||
|
#[allow(dead_code)] // the sides of a match; the lobby (step C/D) uses them
|
||||||
|
pub const MASTER_SIDES: [u8; 4] = [0, 2, 3, 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short serde names (only used by version 1 savegames, newer ones use the packed codec)
|
// Short serde names (only used by version 1 savegames, newer ones use the packed codec)
|
||||||
|
|||||||
@@ -2555,6 +2555,7 @@ pub fn minimap_color(tile: &Tile) -> [u8; 4] {
|
|||||||
Owner::Hero => [170, 175, 190, 255],
|
Owner::Hero => [170, 175, 190, 255],
|
||||||
Owner::Rival1 => [70, 150, 80, 255],
|
Owner::Rival1 => [70, 150, 80, 255],
|
||||||
Owner::Rival2 => [70, 100, 180, 255],
|
Owner::Rival2 => [70, 100, 180, 255],
|
||||||
|
Owner::Rival3 => [150, 130, 50, 255],
|
||||||
Owner::None => [70, 64, 60, 255],
|
Owner::None => [70, 64, 60, 255],
|
||||||
},
|
},
|
||||||
Kind::Treasury => [200, 160, 60, 255],
|
Kind::Treasury => [200, 160, 60, 255],
|
||||||
@@ -2565,6 +2566,7 @@ pub fn minimap_color(tile: &Tile) -> [u8; 4] {
|
|||||||
Kind::Core => match tile.owner {
|
Kind::Core => match tile.owner {
|
||||||
Owner::Rival1 => [90, 240, 110, 255],
|
Owner::Rival1 => [90, 240, 110, 255],
|
||||||
Owner::Rival2 => [100, 150, 255, 255],
|
Owner::Rival2 => [100, 150, 255, 255],
|
||||||
|
Owner::Rival3 => [250, 220, 90, 255],
|
||||||
_ => [255, 60, 60, 255],
|
_ => [255, 60, 60, 255],
|
||||||
},
|
},
|
||||||
Kind::Portal => [190, 90, 240, 255],
|
Kind::Portal => [190, 90, 240, 255],
|
||||||
|
|||||||
@@ -1300,6 +1300,7 @@ pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec
|
|||||||
Owner::Hero => "owner.hero",
|
Owner::Hero => "owner.hero",
|
||||||
Owner::Rival1 => "owner.rival1",
|
Owner::Rival1 => "owner.rival1",
|
||||||
Owner::Rival2 => "owner.rival2",
|
Owner::Rival2 => "owner.rival2",
|
||||||
|
Owner::Rival3 => "owner.rival3",
|
||||||
Owner::None => "owner.none",
|
Owner::None => "owner.none",
|
||||||
};
|
};
|
||||||
match t.kind {
|
match t.kind {
|
||||||
|
|||||||
Reference in new issue
Block a user