Block scripts and triggers: data model, Rhai translation and runtime
Editor missions keep a painted map or roll a random map per campaign run, leave out goals they cannot have, and run their triggers in the fixed step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5315709776
commit
5701124c4f
13 files changed
+1835
-483
No files matched your search
@@ -589,6 +589,98 @@ fn start_mission_def(d: &MissionDef, i: usize, carry: Option<&Carry>, coop: bool
|
||||
g
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ editor missions
|
||||
|
||||
// a mission definition from editor generator settings (random maps and new painted maps)
|
||||
pub fn generated_def(d: &crate::campaign_doc::MissionDoc, seed: u32) -> MissionDef {
|
||||
let mut out = mission_def_from_doc(d);
|
||||
out.seed = seed;
|
||||
let g = &d.gen;
|
||||
let mut rng = Rng(seed | 1);
|
||||
for k in 0..g.rivers.min(3) {
|
||||
out.rivers.push(river(false, k % 2 == 0, 0.3 + 0.2 * k as f32 + rng.f() * 0.08, 4, 1 + (rng.next() % 2)));
|
||||
}
|
||||
for k in 0..g.lava.min(2) {
|
||||
out.rivers.push(river(true, k % 2 == 1, 0.62 - 0.25 * k as f32, 4, 1));
|
||||
}
|
||||
let lvl = 1 + g.difficulty.min(2) as u32;
|
||||
for k in 0..g.forts.min(3) {
|
||||
let at = [(0.8, 0.5), (0.75, 0.2), (0.75, 0.8)][k as usize];
|
||||
out.forts.push(fort(at, (17, 15), &[3], &[TowerKind::Arrow, TowerKind::Arrow], lvl, 3 + 2 * lvl, lvl, lvl, 1, 1000 * lvl as i32));
|
||||
}
|
||||
for k in 0..g.rivals.min(2) {
|
||||
let (x, y) = [(0.82, 0.22), (0.82, 0.78)][k as usize];
|
||||
out.rivals.push((x, y, g.difficulty.min(2), 2 + k));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// the seed of a random map in one run of a campaign
|
||||
pub fn run_map_seed(run_seed: u32, mission: usize) -> u32 {
|
||||
run_seed.wrapping_mul(2654435761).wrapping_add(mission as u32 * 40503) | 1
|
||||
}
|
||||
|
||||
// a mission of an editor campaign: its painted map or a random map (rolled from the run's
|
||||
// seed), the goals it can actually have, and its block scripts
|
||||
pub fn start_editor_mission(doc: &crate::campaign_doc::MissionDoc, i: usize, run_seed: u32) -> Game {
|
||||
use crate::campaign_doc::MapDoc;
|
||||
let mut g = match &doc.map {
|
||||
MapDoc::Fixed { game } => match load_fixed_map(game) {
|
||||
Some(g) => g,
|
||||
None => start_mission_def(&generated_def(doc, run_map_seed(run_seed, i)), i, None, false),
|
||||
},
|
||||
MapDoc::Random => start_mission_def(&generated_def(doc, run_map_seed(run_seed, i)), i, None, false),
|
||||
};
|
||||
g.msgs.clear();
|
||||
g.state = 0;
|
||||
g.time = 0.0;
|
||||
// goals the map cannot have would be won at once: they are left out
|
||||
let shards = g.map.t.iter().filter(|t| t.kind == Kind::Shard).count() as u32;
|
||||
let heart = g.gate_hp > 0.0 && g.gate_on;
|
||||
let knight_towers = g.towers.iter().any(|t| t.hero);
|
||||
let heroes = g.ents.iter().any(|e| e.alive && e.active_hero()) || doc.waves;
|
||||
let goals: Vec<Goal> = crate::campaign_doc::parse_goals(&doc.goals)
|
||||
.into_iter()
|
||||
.filter(|goal| match goal {
|
||||
Goal::Shards => shards > 0,
|
||||
Goal::Heart => heart,
|
||||
Goal::Towers => knight_towers,
|
||||
Goal::KillAll => heroes,
|
||||
Goal::Rivals => !g.rivals.is_empty(),
|
||||
Goal::Survive(_) => doc.waves,
|
||||
_ => true,
|
||||
})
|
||||
.collect();
|
||||
g.mission = Some(MissionState {
|
||||
index: i,
|
||||
goals,
|
||||
tools: vec![true; crate::ui::TOOLS.len()],
|
||||
waves: doc.waves,
|
||||
wave_limit: doc.wave_limit,
|
||||
gate_defenders: doc.waves,
|
||||
heart,
|
||||
time_limit: doc.time_limit,
|
||||
shards,
|
||||
});
|
||||
g.wave_timer = doc.first_wave;
|
||||
g.gold = doc.gold;
|
||||
if !doc.scripts.is_empty() {
|
||||
g.script = Some(crate::mscript::ScriptState::new(doc.scripts.clone(), doc.zones.clone(), run_map_seed(run_seed, i)));
|
||||
}
|
||||
if !doc.title.is_empty() {
|
||||
g.msg("msg.mission_start_custom", vec![doc.title.clone()]);
|
||||
}
|
||||
g
|
||||
}
|
||||
|
||||
// a painted map: the game state saved by the map editor
|
||||
pub fn load_fixed_map(json: &str) -> Option<Game> {
|
||||
let mut g: Game = serde_json::from_str(json).ok()?;
|
||||
g.map.activate();
|
||||
g.recount();
|
||||
Some(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 {
|
||||
|
||||
Reference in new issue
Block a user