Tutorial: Crookfinger leads a new master through the first steps
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1fb6a6ab58
commit
160935bf40
23 files changed
+501
-3
No files matched your search
@@ -238,6 +238,16 @@ pub fn demo_campaign() -> CampaignDoc {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn the_tutorial_is_a_valid_campaign() {
|
||||
let doc = super::tutorial_campaign();
|
||||
assert!(doc.validate().is_empty(), "{:?}", doc.validate());
|
||||
assert_eq!(doc.missions.len(), 1);
|
||||
let g = crate::campaign::start_editor_mission(&doc.missions[0], 0, 1);
|
||||
assert!(g.script.is_some());
|
||||
assert!(!g.ents.iter().any(|e| e.alive && e.active_hero()), "no knight before the last step");
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -278,3 +288,94 @@ mod tests {
|
||||
assert!(g.mission.as_ref().unwrap().goals.contains(&Goal::Shards), "the shard of the outpost is still there");
|
||||
}
|
||||
}
|
||||
|
||||
// The tutorial: Crookfinger leads a new master step by step - dig the tunnel, claim the hall,
|
||||
// build a lair and a mushroom garden, wait for the first creatures, slap one, and beat the first
|
||||
// two knights. Each step switches on the trigger of the next one. Played like an editor campaign
|
||||
// (title menu: Tutorial); texts come from the language files (tut.*).
|
||||
pub fn tutorial_campaign() -> CampaignDoc {
|
||||
let mut doc = CampaignDoc { name: tr("tut.name"), author: "DungeonCraft".into(), language: crate::i18n::language(), ..CampaignDoc::default() };
|
||||
let mut m = MissionDoc {
|
||||
key: "tutorial".into(),
|
||||
title: tr("tut.title"),
|
||||
story: tr("tut.story").replace("\\n", "\n"),
|
||||
size: (64, 64),
|
||||
seed: 777,
|
||||
gold: 3000.0,
|
||||
goals: Vec::new(),
|
||||
gen: GenDoc { rivers: 0, lava: 0, forts: 0, rivals: 0, difficulty: 0, surface: false },
|
||||
pos: (40.0, 60.0),
|
||||
..MissionDoc::default()
|
||||
};
|
||||
let saved = (crate::map::mw(), crate::map::mh());
|
||||
let mut g = crate::campaign::start_editor_mission(&m, 0, 777);
|
||||
let (cx, cy) = g.map.core;
|
||||
let (w, h) = (g.map.w as i32, g.map.h as i32);
|
||||
let cl = |x: i32, y: i32| (x.clamp(3, w - 4), y.clamp(3, h - 4));
|
||||
// east of the heart: a tunnel of earth to dig, then a hall of open ground behind it
|
||||
let tunnel: Vec<(i32, i32)> = (cx + 4..cx + 8).flat_map(|x| [(x, cy + 1), (x, cy + 2)]).map(|(x, y)| cl(x, y)).collect();
|
||||
let (hx0, hy0) = cl(cx + 8, cy - 2);
|
||||
let (hx1, hy1) = cl(cx + 15, cy + 5);
|
||||
for y in hy0 - 1..=hy1 + 1 {
|
||||
for x in hx0..=hx1 + 1 {
|
||||
let t = &mut g.map.t[crate::map::idx(x, y)];
|
||||
let edge = y == hy0 - 1 || y == hy1 + 1 || x == hx1 + 1;
|
||||
t.kind = if edge { crate::map::Kind::Earth } else { crate::map::Kind::Floor };
|
||||
t.owner = crate::map::Owner::None;
|
||||
t.gold = 0;
|
||||
t.reinforced = false;
|
||||
}
|
||||
}
|
||||
for &(x, y) in &tunnel {
|
||||
let t = &mut g.map.t[crate::map::idx(x, y)];
|
||||
t.kind = crate::map::Kind::Earth;
|
||||
t.owner = crate::map::Owner::None;
|
||||
t.hp = 3.0;
|
||||
}
|
||||
// the knights come from a cave further east
|
||||
let (kx, ky) = cl(cx + 22, cy + 1);
|
||||
for y in ky - 2..=ky + 2 {
|
||||
for x in kx - 2..=kx + 2 {
|
||||
let t = &mut g.map.t[crate::map::idx(x, y)];
|
||||
t.kind = crate::map::Kind::Floor;
|
||||
t.owner = crate::map::Owner::None;
|
||||
}
|
||||
}
|
||||
for x in hx1 + 1..kx - 2 {
|
||||
let t = &mut g.map.t[crate::map::idx(x, cy + 1)];
|
||||
t.kind = crate::map::Kind::Earth;
|
||||
t.owner = crate::map::Owner::None;
|
||||
}
|
||||
g.ents.retain(|e| !e.hero);
|
||||
g.recount();
|
||||
g.init_fog();
|
||||
m.zones = vec![
|
||||
ZoneDoc { name: "tunnel".into(), shape: "tiles".into(), at: None, radius: None, tiles: Some(tunnel), from: None, to: None, width: None },
|
||||
zone_rect("hall", hx0, hy0, hx1, hy1),
|
||||
ZoneDoc { name: "knights".into(), shape: "circle".into(), at: Some((kx as f32, ky as f32)), radius: Some(1.5), tiles: None, from: None, to: None, width: None },
|
||||
];
|
||||
m.map = MapDoc::Fixed { game: serde_json::to_string(&g).unwrap_or_default() };
|
||||
crate::map::set_dims(saved.0, saved.1);
|
||||
|
||||
let s = &mut m.scripts;
|
||||
let obj = |k: &str| Block::Objective { text: tr(k) };
|
||||
let on = |t: &str| Block::Trigger { name: t.into(), on: true };
|
||||
s.start = vec![say("tut.start"), obj("tut.obj1"), Block::SetVar { name: "knights".into(), value: n(0.0) }, Block::Camera { zone: "tunnel".into() }];
|
||||
s.victory = vec![Block::If { cond: cmp(">=", var("knights"), n(2.0)), then: vec![say("tut.win"), Block::Win], els: vec![] }];
|
||||
let off = |t: TriggerDoc| TriggerDoc { enabled: false, ..t };
|
||||
s.triggers = vec![
|
||||
trig("dug", TriggerKind::ZoneDug { zone: "tunnel".into() }, true, vec![say("tut.dug"), obj("tut.obj2"), on("lair")]),
|
||||
off(trig("lair", TriggerKind::RoomBuilt { room: "Lair".into() }, true, vec![say("tut.lair"), obj("tut.obj3"), on("farm")])),
|
||||
off(trig("farm", TriggerKind::RoomBuilt { room: "Farm".into() }, true, vec![say("tut.farm"), obj("tut.obj4"), on("creatures")])),
|
||||
off(trig("creatures", TriggerKind::Value { what: "creatures".into(), var: String::new(), op: ">=".into(), n: 2.0 }, true, vec![say("tut.creatures"), obj("tut.obj5"), on("slap")])),
|
||||
off(trig(
|
||||
"slap",
|
||||
TriggerKind::Slap,
|
||||
true,
|
||||
vec![say("tut.slap"), obj("tut.obj6"), Block::Wait { secs: n(20.0) }, Block::Spawn { side: 1, kind: "Knight".into(), count: n(2.0), level: n(1.0), place: Place::Zone("knights".into()), name: String::new() }, say("tut.knights"), Block::Camera { zone: "knights".into() }, on("knight")],
|
||||
)),
|
||||
off(trig("knight", TriggerKind::UnitDies { side: 1, kind: String::new(), name: String::new() }, false, vec![Block::AddVar { name: "knights".into(), value: n(1.0) }])),
|
||||
];
|
||||
doc.missions.push(m);
|
||||
doc
|
||||
}
|
||||
Reference in new issue
Block a user