Script blocks for weather and talent experience, groves as ground; the co-op campaign brings rogues and a storm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 13:29:44 +01:00
1 parent 4e3158cc19
commit eb219f6b01
23 files changed
+233 -8

No files matched your search

+40
View File
@@ -109,6 +109,8 @@ enum Act {
Terrain(String, String),
Kill(String, i32),
Trigger(String, bool),
Weather(u8, f64),
TalentXp(f64),
}
// the triggering unit (for "where the unit is" and "the unit is a ...")
@@ -539,6 +541,12 @@ impl Game {
Val::Or { a, b } => (self.cond(s, a, ev) || self.cond(s, b, ev)) as i32 as f64,
Val::Not { a } => (!self.cond(s, a, ev)) as i32 as f64,
Val::EventKind { kind } => (ev.kind.is_some() && ev.kind == kind_by_name(kind)) as i32 as f64,
Val::WeatherIs { kind } => {
// the weather felt now (a change counts once it has come in)
let w = &self.weather;
let now = if w.level > 0.5 { w.shown } else { crate::weather::FAIR };
(crate::blocks::WEATHER.iter().position(|k| k == kind) == Some(now as usize)) as i32 as f64
}
};
if r.is_finite() {
r
@@ -568,6 +576,8 @@ impl Game {
Block::Wave { count } => Act::Wave(self.eval(s, count, ev).round() as i64),
Block::Gold { amount } => Act::Gold(self.eval(s, amount, ev)),
Block::Mana { amount } => Act::Mana(self.eval(s, amount, ev)),
Block::Weather { kind, secs } => Act::Weather(crate::blocks::WEATHER.iter().position(|k| k == kind).unwrap_or(0) as u8, self.eval(s, secs, ev)),
Block::TalentXp { amount } => Act::TalentXp(self.eval(s, amount, ev)),
Block::SetVar { name, value } => {
let v = self.eval(s, value, ev);
s.vars.insert(name.clone(), v);
@@ -824,6 +834,13 @@ impl Game {
}
}
Act::Mana(n) => self.mana = (self.mana + n as f32).clamp(0.0, self.mana_max()),
// the weather comes (softly) and stays the given seconds (0: until a block changes it)
Act::Weather(k, secs) => {
self.weather.kind = k;
self.weather.left = if secs > 0.0 { secs as f32 } else { 1.0e9 };
self.weather.strike_t = self.weather.strike_t.min(5.0);
}
Act::TalentXp(n) => self.add_master_xp(n.max(0.0) as f32),
Act::Win => {
self.state = 1;
self.script_msg("msg.mission_won", vec![]);
@@ -854,6 +871,7 @@ impl Game {
"water" => Kind::Water,
"lava" => Kind::Lava,
"gold" => Kind::Gold,
"grove" => Kind::Grove,
_ => Kind::Floor,
};
for i in zone_tiles(&z) {
@@ -960,6 +978,28 @@ mod tests {
assert!(g.script.as_ref().unwrap().error.is_none(), "{:?}", g.script.as_ref().unwrap().error);
}
#[test]
fn weather_and_talent_blocks_work() {
let mut g = Game::new(3, 64, 64);
g.add_surface(3, 0.5, 2);
let scripts = MissionScripts {
start: vec![
Block::Weather { kind: "storm".into(), secs: Val::num(0.0) },
Block::TalentXp { amount: Val::num(400.0) },
Block::Wait { secs: Val::num(25.0) },
Block::If { cond: Val::WeatherIs { kind: "storm".into() }, then: vec![Block::SetVar { name: "storm".into(), value: Val::num(1.0) }], els: vec![] },
],
..Default::default()
};
g.script = Some(ScriptState::new(scripts, vec![], 7));
run(&mut g, 30.0);
assert_eq!(g.weather.kind, crate::weather::STORM);
assert!(g.weather.left > 1.0e8, "it stays");
assert!(g.talents.xp >= 400.0 && g.talents.free() >= 1);
assert_eq!(g.script.as_ref().unwrap().vars.get("storm"), Some(&1.0), "the condition sees the storm");
assert!(crate::blocks::check(&g.script.as_ref().unwrap().scripts.start, &crate::blocks::Names { zones: &[], triggers: &[], vars: &[] }).iter().all(|p| p.key != "blk.err_weather"));
}
#[test]
fn a_zone_trigger_spawns_and_the_victory_script_wins() {
let mut g = Game::new(3, 64, 64);