aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-07-18 21:18:49 +0300
committerValentin Popov <valentin@popov.link>2026-07-18 21:18:49 +0300
commit14096788fccf47e24a93d7b13f8124e84381f339 (patch)
tree8d3755a209fb2f58ecb76208becc1153bd5efcc4 /crates
parentd91e63fa9a5d311b8c4e398da2b0d91ecc6f9c9a (diff)
downloadfparkan-14096788fccf47e24a93d7b13f8124e84381f339.tar.xz
fparkan-14096788fccf47e24a93d7b13f8124e84381f339.zip
feat(runtime): apply clan init varsets
Diffstat (limited to 'crates')
-rw-r--r--crates/fparkan-runtime/src/lib.rs86
1 files changed, 86 insertions, 0 deletions
diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs
index 8095925..771a1d4 100644
--- a/crates/fparkan-runtime/src/lib.rs
+++ b/crates/fparkan-runtime/src/lib.rs
@@ -36,6 +36,7 @@ use fparkan_prototype::{
use fparkan_resource::{resource_name, CachedResourceRepository, ResourceRepository};
use fparkan_script::{
Handler19DwordWrite, Handler19InitInput, ScriptDispatchSelector, ScriptPackage, VarSet,
+ VarSetDefault,
};
use fparkan_terrain::SurfaceQuery;
use fparkan_vfs::{Vfs, VfsError};
@@ -285,6 +286,18 @@ pub struct MissionScriptInitState {
pub writes: Vec<Handler19DwordWrite>,
}
+/// Per-clan mutable-value model of one instantiated mission varset.
+///
+/// Declarations remain shared source metadata; these values are the runtime
+/// cells that later recovered handlers will read and write.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct MissionScriptVarSetState {
+ /// TMA clan index owning this instantiated varset.
+ pub clan_index: usize,
+ /// Values in the original varset declaration order.
+ pub values: Vec<VarSetDefault>,
+}
+
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct MissionLoadOptions {
fail_after_registered_objects: Option<usize>,
@@ -296,6 +309,7 @@ struct LoadedMissionScripts {
bundles: Vec<MissionScriptBundle>,
varset: Option<MissionScriptVarSet>,
init_states: Vec<MissionScriptInitState>,
+ varset_states: Vec<MissionScriptVarSetState>,
}
/// Selects how much of the resolved mission asset graph to prepare.
@@ -387,6 +401,8 @@ pub struct LoadedMission {
pub script_varset_declaration_count: usize,
/// `Handler(19)` Init results resolved for mission clans.
pub script_init_state_count: usize,
+ /// Instantiated per-clan varsets whose Init writes have been applied.
+ pub script_varset_state_count: usize,
}
/// Frame result.
@@ -426,6 +442,7 @@ struct LoadedMissionState {
script_bundles: Vec<MissionScriptBundle>,
script_varset: Option<MissionScriptVarSet>,
script_init_states: Vec<MissionScriptInitState>,
+ script_varset_states: Vec<MissionScriptVarSetState>,
}
/// Engine error.
@@ -1099,6 +1116,7 @@ fn load_mission_with_options_and_progress(
.as_ref()
.map_or(0, |varset| varset.declarations.declarations.len()),
script_init_state_count: loaded_scripts.init_states.len(),
+ script_varset_state_count: loaded_scripts.varset_states.len(),
};
engine.world = new_runtime_world;
@@ -1115,6 +1133,7 @@ fn load_mission_with_options_and_progress(
script_bundles: loaded_scripts.bundles,
script_varset: loaded_scripts.varset,
script_init_states: loaded_scripts.init_states,
+ script_varset_states: loaded_scripts.varset_states,
});
Ok((summary, trace))
}
@@ -1225,6 +1244,15 @@ pub fn loaded_mission_script_init_states(engine: &Engine) -> Option<&[MissionScr
.map(|state| state.script_init_states.as_slice())
}
+/// Returns instantiated per-clan varsets after proven Init writes are applied.
+#[must_use]
+pub fn loaded_mission_script_varset_states(engine: &Engine) -> Option<&[MissionScriptVarSetState]> {
+ engine
+ .loaded
+ .as_ref()
+ .map(|state| state.script_varset_states.as_slice())
+}
+
/// Returns terrain runtime data for the loaded mission.
#[must_use]
pub fn loaded_terrain(engine: &Engine) -> Option<&TerrainWorld> {
@@ -1401,13 +1429,58 @@ fn load_mission_scripts(
)?,
None => Vec::new(),
};
+ let varset_states = match &varset {
+ Some(varset) => materialize_script_varset_states(&bundles, varset, &init_states)?,
+ None => Vec::new(),
+ };
Ok(LoadedMissionScripts {
bundles,
varset,
init_states,
+ varset_states,
})
}
+fn materialize_script_varset_states(
+ bundles: &[MissionScriptBundle],
+ varset: &MissionScriptVarSet,
+ init_states: &[MissionScriptInitState],
+) -> Result<Vec<MissionScriptVarSetState>, EngineError> {
+ let defaults: Vec<_> = varset
+ .declarations
+ .declarations
+ .iter()
+ .map(|declaration| declaration.default_value)
+ .collect();
+ bundles
+ .iter()
+ .map(|bundle| {
+ let mut values = defaults.clone();
+ for init in init_states
+ .iter()
+ .filter(|init| init.clan_index == bundle.clan_index)
+ {
+ for write in &init.writes {
+ let Some(value) = values.get_mut(write.index as usize) else {
+ return Err(EngineError::ScriptInit {
+ clan_index: bundle.clan_index,
+ message: format!(
+ "Handler(19) write index {} is outside the instantiated varset",
+ write.index
+ ),
+ });
+ };
+ *value = VarSetDefault::Dword(write.value);
+ }
+ }
+ Ok(MissionScriptVarSetState {
+ clan_index: bundle.clan_index,
+ values,
+ })
+ })
+ .collect()
+}
+
fn resolve_handler19_init_states(
clan_anchors: &[[f32; 2]],
bundles: &[MissionScriptBundle],
@@ -1680,6 +1753,19 @@ mod tests {
],
}]
);
+ let runtime_varsets = materialize_script_varset_states(&bundles, &varset, &states)
+ .expect("materialized per-clan varset");
+ assert_eq!(
+ runtime_varsets,
+ vec![MissionScriptVarSetState {
+ clan_index: 1,
+ values: vec![
+ VarSetDefault::Dword(728),
+ VarSetDefault::Dword(449),
+ VarSetDefault::Dword(1),
+ ],
+ }]
+ );
}
#[test]