Co-op campaign matches and command markers: mission relay, allied masters, temporary flags
This commit is contained in:
1 parent
dc22e27184
commit
5928acf7f6
12 files changed
+433
-27
No files matched your search
+61
-6
@@ -476,6 +476,15 @@ fn place_cave(g: &mut Game, d: &CaveDef, w: usize, h: usize) {
|
||||
|
||||
// Builds the game of mission i (carry: what the last mission handed over)
|
||||
pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
|
||||
start_mission_mode(i, carry, false)
|
||||
}
|
||||
|
||||
// A co-op campaign mission: the same mission, but without rival masters on playable sides.
|
||||
pub fn start_coop_mission(i: usize, carry: Option<&Carry>) -> Game {
|
||||
start_mission_mode(i, carry, true)
|
||||
}
|
||||
|
||||
fn start_mission_mode(i: usize, carry: Option<&Carry>, coop: bool) -> Game {
|
||||
let all = missions();
|
||||
let d = &all[i.min(all.len() - 1)];
|
||||
let (w, h) = d.size;
|
||||
@@ -498,10 +507,12 @@ pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
|
||||
for c in &d.caves {
|
||||
place_cave(&mut g, c, w, h);
|
||||
}
|
||||
for &(rx, ry, diff, side) in &d.rivals {
|
||||
let x = ((rx * w as f32) as i32).clamp(10, w as i32 - 10);
|
||||
let y = ((ry * h as f32) as i32).clamp(10, h as i32 - 10);
|
||||
g.add_rival(side, x, y, diff);
|
||||
if !coop {
|
||||
for &(rx, ry, diff, side) in &d.rivals {
|
||||
let x = ((rx * w as f32) as i32).clamp(10, w as i32 - 10);
|
||||
let y = ((ry * h as f32) as i32).clamp(10, h as i32 - 10);
|
||||
g.add_rival(side, x, y, diff);
|
||||
}
|
||||
}
|
||||
g.place_dwarf_caves(d.seed);
|
||||
g.add_wilds(d.seed, false);
|
||||
@@ -514,9 +525,14 @@ pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
|
||||
g.gate_on = heart;
|
||||
g.guard_treasures();
|
||||
g.place_all_torches();
|
||||
let goals: Vec<Goal> = if coop {
|
||||
d.goals.iter().filter(|&g| *g != Goal::Rivals).cloned().collect()
|
||||
} else {
|
||||
d.goals.clone()
|
||||
};
|
||||
g.mission = Some(MissionState {
|
||||
index: i,
|
||||
goals: d.goals.clone(),
|
||||
goals,
|
||||
tools: TOOL_UNLOCK.iter().map(|&u| u <= i).collect(),
|
||||
waves: d.waves,
|
||||
wave_limit: d.wave_limit,
|
||||
@@ -548,6 +564,30 @@ pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
|
||||
g
|
||||
}
|
||||
|
||||
// missions that can be played in co-op: they fit the multiplayer master areas and do not
|
||||
// depend on rival masters as their only goal
|
||||
pub fn is_coop_mission(i: usize) -> bool {
|
||||
missions().get(i).map_or(false, |d| d.size.0 >= 72 && d.size.1 >= 72 && d.goals.iter().any(|&g| g != Goal::Rivals))
|
||||
}
|
||||
|
||||
pub fn coop_mission_indexes() -> Vec<usize> {
|
||||
missions()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, d)| d.size.0 >= 72 && d.size.1 >= 72 && d.goals.iter().any(|&g| g != Goal::Rivals))
|
||||
.map(|(i, _)| i)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn next_coop_mission(i: usize, dir: i32) -> usize {
|
||||
let list = coop_mission_indexes();
|
||||
if list.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let pos = list.iter().position(|m| *m == i).unwrap_or(0);
|
||||
list[((pos as i32 + dir).rem_euclid(list.len() as i32)) as usize]
|
||||
}
|
||||
|
||||
// the strongest creatures and a fifth of the gold go along into the next mission
|
||||
pub fn carry_from(g: &Game) -> Carry {
|
||||
let mut c: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect();
|
||||
@@ -635,6 +675,21 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coop_missions_drop_rivals_and_are_not_decided_at_once() {
|
||||
let list = coop_mission_indexes();
|
||||
assert!(list.len() > 3, "co-op campaign needs selectable missions");
|
||||
for &i in &list {
|
||||
assert!(is_coop_mission(i), "mission {} selectable in co-op", i);
|
||||
let g = start_coop_mission(i, None);
|
||||
assert!(g.rivals.is_empty(), "mission {} must not spawn rival masters", i);
|
||||
let m = g.mission.as_ref().unwrap();
|
||||
assert!(!m.goals.iter().any(|g| *g == Goal::Rivals), "mission {} keeps the rival goal", i);
|
||||
assert!(!m.goals.is_empty(), "mission {} has a playable goal after dropping rivals", i);
|
||||
assert!(!m.goals.iter().all(|&goal| g.goal_done(goal)), "mission {} is decided at the start", i);
|
||||
}
|
||||
}
|
||||
|
||||
// A simple scripted player: digs a corridor to the shard, bridges rivers on the way and
|
||||
// sends a few strong creatures along with the flag.
|
||||
fn play_to_shard(i: usize) -> (u8, f32) {
|
||||
@@ -648,7 +703,7 @@ mod tests {
|
||||
match t.kind {
|
||||
Kind::Rock | Kind::Core | Kind::Tower => None,
|
||||
Kind::Water | Kind::Lava => Some(3),
|
||||
k if t.solid() => Some(if t.reinforced { 6 } else { 2 }),
|
||||
_ if t.solid() => Some(if t.reinforced { 6 } else { 2 }),
|
||||
_ => Some(1),
|
||||
}
|
||||
}, |j| j == shard, false)
|
||||
|
||||
Reference in new issue
Block a user