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

+26 -5
View File
@@ -16,7 +16,7 @@ pub const KINDS: [&str; 30] = [
];
// ground a Terrain block can make
pub const TERRAIN: [&str; 6] = ["floor", "earth", "rock", "water", "lava", "gold"];
pub const TERRAIN: [&str; 7] = ["floor", "earth", "rock", "water", "lava", "gold", "grove"];
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "b")]
@@ -42,6 +42,8 @@ pub enum Block {
Terrain { zone: String, kind: String },
Kill { zone: String, side: i32 },
Trigger { name: String, on: bool },
Weather { kind: String, secs: Val }, // the weather above ground (secs 0: until changed again)
TalentXp { amount: Val }, // experience for the player's talents
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -70,6 +72,7 @@ pub enum Val {
Or { a: Box<Val>, b: Box<Val> },
Not { a: Box<Val> },
EventKind { kind: String }, // the unit that set off the trigger is of this kind
WeatherIs { kind: String }, // the weather above ground is ...
}
pub const MATH_OPS: [&str; 4] = ["+", "-", "*", "/"];
@@ -89,7 +92,7 @@ impl Val {
pub fn ty(&self) -> Ty {
match self {
Val::Bool { .. } | Val::Cmp { .. } | Val::And { .. } | Val::Or { .. } | Val::Not { .. } | Val::EventKind { .. } => Ty::Bool,
Val::Bool { .. } | Val::Cmp { .. } | Val::And { .. } | Val::Or { .. } | Val::Not { .. } | Val::EventKind { .. } | Val::WeatherIs { .. } => Ty::Bool,
_ => Ty::Num,
}
}
@@ -113,6 +116,7 @@ impl Val {
"or" => Val::Or { a: Box::new(Val::Bool { b: true }), b: Box::new(Val::Bool { b: false }) },
"not" => Val::Not { a: Box::new(Val::Bool { b: true }) },
"eventkind" => Val::EventKind { kind: "Knight".into() },
"weatheris" => Val::WeatherIs { kind: "rain".into() },
_ => return None,
})
}
@@ -135,16 +139,20 @@ impl Val {
Val::Or { .. } => "or",
Val::Not { .. } => "not",
Val::EventKind { .. } => "eventkind",
Val::WeatherIs { .. } => "weatheris",
}
}
}
// value kinds by type, for the value picker
pub const NUM_VALUES: [&str; 9] = ["num", "var", "gold", "mana", "time", "creatures", "inzone", "random", "math"];
pub const BOOL_VALUES: [&str; 6] = ["cmp", "and", "or", "not", "bool", "eventkind"];
pub const BOOL_VALUES: [&str; 7] = ["cmp", "and", "or", "not", "bool", "eventkind", "weatheris"];
// the weather a block can set or ask for (weather.rs: FAIR, RAIN, FOG, STORM)
pub const WEATHER: [&str; 4] = ["fair", "rain", "fog", "storm"];
// statement blocks in the palette: (category, kind)
pub const PALETTE: [(&str, &str); 21] = [
pub const PALETTE: [(&str, &str); 23] = [
("say", "message"),
("say", "objective"),
("say", "hint"),
@@ -156,6 +164,8 @@ pub const PALETTE: [(&str, &str); 21] = [
("world", "camera"),
("world", "gold"),
("world", "mana"),
("world", "weather"),
("world", "talentxp"),
("logic", "if"),
("logic", "repeat"),
("logic", "until"),
@@ -185,6 +195,8 @@ impl Block {
"camera" => Block::Camera { zone: String::new() },
"gold" => Block::Gold { amount: n(500.0) },
"mana" => Block::Mana { amount: n(100.0) },
"weather" => Block::Weather { kind: "storm".into(), secs: n(0.0) },
"talentxp" => Block::TalentXp { amount: n(300.0) },
"if" => Block::If { cond: Val::template("cmp").unwrap(), then: Vec::new(), els: Vec::new() },
"repeat" => Block::Repeat { times: n(3.0), body: Vec::new() },
"until" => Block::Until { cond: Val::template("cmp").unwrap(), body: Vec::new() },
@@ -212,6 +224,8 @@ impl Block {
Block::Camera { .. } => "camera",
Block::Gold { .. } => "gold",
Block::Mana { .. } => "mana",
Block::Weather { .. } => "weather",
Block::TalentXp { .. } => "talentxp",
Block::If { .. } => "if",
Block::Repeat { .. } => "repeat",
Block::Until { .. } => "until",
@@ -258,6 +272,7 @@ fn check_val(v: &Val, want: Ty, n: &Names, path: &[usize], out: &mut Vec<Problem
Val::Creatures { kind, .. } if !kind.is_empty() && !KINDS.contains(&kind.as_str()) => out.push(p("blk.err_kind")),
Val::InZone { zone, .. } if !n.zones.iter().any(|z| z == zone) => out.push(p("blk.err_zone")),
Val::EventKind { kind } if !KINDS.contains(&kind.as_str()) => out.push(p("blk.err_kind")),
Val::WeatherIs { kind } if !WEATHER.contains(&kind.as_str()) => out.push(p("blk.err_weather")),
Val::Random { a, b } | Val::Math { a, b, .. } => {
check_val(a, Ty::Num, n, path, out);
check_val(b, Ty::Num, n, path, out);
@@ -295,7 +310,13 @@ fn check_list(list: &[Block], n: &Names, path: &mut Vec<usize>, out: &mut Vec<Pr
}
}
Block::Wave { count } => check_val(count, Ty::Num, n, path, out),
Block::Gold { amount } | Block::Mana { amount } => check_val(amount, Ty::Num, n, path, out),
Block::Gold { amount } | Block::Mana { amount } | Block::TalentXp { amount } => check_val(amount, Ty::Num, n, path, out),
Block::Weather { kind, secs } => {
if !WEATHER.contains(&kind.as_str()) {
out.push(p("blk.err_weather", path));
}
check_val(secs, Ty::Num, n, path, out);
}
Block::SetVar { name, value } | Block::AddVar { name, value } => {
if !valid_var(name) {
out.push(p("blk.err_var", path));