From 593d300abd613ec68194c8a5275858c44930b849 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sat, 8 Nov 2025 11:53:29 -0600 Subject: [PATCH 1/3] FEAT: Add support for LUAL records (openMW) --- libs/esp/src/macros.rs | 1 + libs/esp/src/traits/editor_id.rs | 6 + libs/esp/src/traits/sort_objects.rs | 1 + libs/esp/src/types.rs | 3 + libs/esp/src/types/flags.rs | 13 ++ libs/esp/src/types/omw_lualist.rs | 189 ++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 libs/esp/src/types/omw_lualist.rs diff --git a/libs/esp/src/macros.rs b/libs/esp/src/macros.rs index 39e74a4..7c63d44 100644 --- a/libs/esp/src/macros.rs +++ b/libs/esp/src/macros.rs @@ -93,4 +93,5 @@ make_delegate!( SoundGen Dialogue DialogueInfo + ScriptConfigList ); diff --git a/libs/esp/src/traits/editor_id.rs b/libs/esp/src/traits/editor_id.rs index b8cd585..f5aa46b 100644 --- a/libs/esp/src/traits/editor_id.rs +++ b/libs/esp/src/traits/editor_id.rs @@ -32,6 +32,12 @@ impl EditorId for Header { } } +impl EditorId for ScriptConfigList { + fn editor_id(&self) -> Cow<'_, str> { + "".into() + } +} + impl EditorId for Skill { fn editor_id(&self) -> Cow<'_, str> { self.skill_id.display().into() diff --git a/libs/esp/src/traits/sort_objects.rs b/libs/esp/src/traits/sort_objects.rs index b8afee4..f42a24b 100644 --- a/libs/esp/src/traits/sort_objects.rs +++ b/libs/esp/src/traits/sort_objects.rs @@ -50,6 +50,7 @@ impl Plugin { TES3Object::SoundGen(obj) => (38, obj.sort_hint(), &*obj.id), TES3Object::Dialogue(obj) => (39, obj.sort_hint(), ""), // Preserve DIAL/INFO order TES3Object::DialogueInfo(obj) => (39, obj.sort_hint(), ""), // ^ + TES3Object::ScriptConfigList(obj) => (40, obj.sort_hint(), ""), } }); unsafe { apply_isort(&mut indices, &mut self.objects) }; diff --git a/libs/esp/src/types.rs b/libs/esp/src/types.rs index 41e2103..5fdca70 100644 --- a/libs/esp/src/types.rs +++ b/libs/esp/src/types.rs @@ -36,6 +36,7 @@ mod lockpick; mod magiceffect; mod miscitem; mod npc; +mod omw_lualist; mod pathgrid; mod plugin; mod probe; @@ -89,6 +90,7 @@ pub use lockpick::*; pub use magiceffect::*; pub use miscitem::*; pub use npc::*; +pub use omw_lualist::*; pub use pathgrid::*; pub use plugin::*; pub use probe::*; @@ -153,4 +155,5 @@ pub enum TES3Object { #[tag("PGRD")] PathGrid(PathGrid), #[tag("DIAL")] Dialogue(Dialogue), #[tag("INFO")] DialogueInfo(DialogueInfo), + #[tag("LUAL")] ScriptConfigList(ScriptConfigList) } diff --git a/libs/esp/src/types/flags.rs b/libs/esp/src/types/flags.rs index 7dd5a88..90f7348 100644 --- a/libs/esp/src/types/flags.rs +++ b/libs/esp/src/types/flags.rs @@ -253,3 +253,16 @@ bitflags! { const NOT_PLAYABLE = 0x2; } } + +bitflags! { + #[esp_meta] + #[repr(transparent)] + #[derive(LoadSave, Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct OMWScriptAttachFlag: u32 { + const GLOBAL = 0x01; + const CUSTOM = 0x02; + const PLAYER = 0x04; + const MERGE = 0x08; + const MENU = 0x10; + } +} diff --git a/libs/esp/src/types/omw_lualist.rs b/libs/esp/src/types/omw_lualist.rs new file mode 100644 index 0000000..9964cb0 --- /dev/null +++ b/libs/esp/src/types/omw_lualist.rs @@ -0,0 +1,189 @@ +// internal imports +use crate::prelude::*; + +#[esp_meta] +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct PerInstanceConfig { + pub attach: bool, + pub mast_idx: i32, + pub ref_idx: u32, + pub data: Vec, +} + +#[esp_meta] +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct PerRecordConfig { + pub attach: bool, + pub id: String, + pub data: Vec, +} + +#[esp_meta] +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct ScriptConfig { + pub path: String, + pub init_data: Vec, // We'll need to reimplement openmw's serializer too :/ + pub flags: OMWScriptAttachFlag, + pub types: Vec, + pub records: Vec, + pub instances: Vec, +} + +#[esp_meta] +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ScriptConfigList { + pub scripts: Vec, + pub flags: ObjectFlags, +} + +impl Load for ScriptConfigList { + fn load(stream: &mut Reader<'_>) -> io::Result { + let mut this: Self = default(); + let mut script_config: ScriptConfig = ScriptConfig::default(); + + this.flags = stream.load()?; + + let mut last_tag: Option<[u8; 4]> = None; + while let Ok(tag) = stream.load() { + match &tag { + b"LUAS" => { + if script_config != ScriptConfig::default() { + this.scripts.push(std::mem::take(&mut script_config)); + } + + script_config.path = stream.load()?; + } + b"LUAF" => { + let size: u32 = stream.load()?; + assert!(size % 4 == 0, "Incorrect LUAF Size!"); + + script_config.flags = stream.load()?; + + for _ in 0..(size / 4 - 1) { + let type_string: String = stream.load_string(4)?; + script_config.types.push(type_string); + } + } + b"LUAI" => { + let mut per_instance = PerInstanceConfig::default(); + stream.skip(size_of::() as u32)?; + + let attach = stream.load::()?; + per_instance.attach = attach != 0; + + per_instance.ref_idx = stream.load()?; + per_instance.mast_idx = stream.load()?; + + script_config.instances.push(per_instance); + } + b"LUAR" => { + let mut per_record = PerRecordConfig::default(); + let size: u32 = stream.load()?; + + let attach = stream.load::()?; + per_record.attach = attach != 0; + + per_record.id = stream.load_string(size as usize - 1)?; + + script_config.records.push(per_record); + } + b"LUAD" => { + let size: u32 = stream.load()?; + let data: Vec = stream.load_seq(size)?; + + if let Some(tag) = &last_tag { + match tag { + b"LUAF" => script_config.init_data = data, + b"LUAI" => { + let last_instance = script_config + .instances + .last_mut() + .expect("No instance found in scriptConfig!"); + last_instance.data = data; + } + b"LUAR" => { + script_config + .records + .last_mut() + .expect("No record found in scriptConfig!") + .data = data + } + _ => { + Reader::error(format!( + "Unexpected Data Tag Preceding LUAD: {}::{}", + this.tag_str(), + tag.to_str_lossy() + ))?; + } + } + } + } + _ => { + Reader::error(format!("Unexpected Tag: {}::{}", this.tag_str(), tag.to_str_lossy()))?; + } + } + + last_tag = Some(tag); + } + + if script_config != ScriptConfig::default() { + this.scripts.push(std::mem::take(&mut script_config)) + } + + Ok(this) + } +} + +impl Save for ScriptConfigList { + fn save(&self, stream: &mut Writer) -> io::Result<()> { + stream.save(&self.flags)?; + for script_config in &self.scripts { + stream.save(b"LUAS")?; + stream.save_string_without_null_terminator(&script_config.path)?; + + stream.save(b"LUAF")?; + let length = 4 + (4 * script_config.types.len()); + stream.save(&(length as u32))?; + stream.save(&script_config.flags)?; + + for attach_type in &script_config.types { + stream.save_vec(attach_type.as_bytes().into())?; + } + + if script_config.init_data.len() > 0 { + stream.save(b"LUAD")?; + stream.save(&script_config.init_data)?; + } + + for record in &script_config.records { + stream.save(b"LUAR")?; + let length = record.id.len() as u32; + stream.save(&(length + 1))?; + stream.save_as::(record.attach)?; + stream.save_bytes(&record.id[..].as_bytes())?; + + if record.data.len() > 0 { + stream.save(b"LUAD")?; + stream.save(&record.data)?; + } + } + + for instance in &script_config.instances { + stream.save(b"LUAI")?; + + stream.save(&9)?; + stream.save_as::(instance.attach)?; + + stream.save(&instance.ref_idx)?; + stream.save(&instance.mast_idx)?; + + if instance.data.len() > 0 { + stream.save(b"LUAD")?; + stream.save(&instance.data)?; + } + } + } + + Ok(()) + } +} From 0f30b93a146c0d538775b4869094242fde9434bb Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 16 Dec 2025 18:24:37 -0600 Subject: [PATCH 2/3] FIX: Update reference field names --- libs/esp/src/types/omw_lualist.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/esp/src/types/omw_lualist.rs b/libs/esp/src/types/omw_lualist.rs index 9964cb0..96c704c 100644 --- a/libs/esp/src/types/omw_lualist.rs +++ b/libs/esp/src/types/omw_lualist.rs @@ -5,8 +5,8 @@ use crate::prelude::*; #[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct PerInstanceConfig { pub attach: bool, - pub mast_idx: i32, - pub ref_idx: u32, + pub mast_index: i32, + pub ref_index: u32, pub data: Vec, } From 95a4fa7d0e2a7669fb4fce2b841cc226125b69cc Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sun, 18 Jan 2026 13:13:23 -0600 Subject: [PATCH 3/3] FIX: Forgot to correct actual param name --- libs/esp/src/types/omw_lualist.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/esp/src/types/omw_lualist.rs b/libs/esp/src/types/omw_lualist.rs index 96c704c..9964cb0 100644 --- a/libs/esp/src/types/omw_lualist.rs +++ b/libs/esp/src/types/omw_lualist.rs @@ -5,8 +5,8 @@ use crate::prelude::*; #[derive(Clone, Debug, Eq, PartialEq, Default)] pub struct PerInstanceConfig { pub attach: bool, - pub mast_index: i32, - pub ref_index: u32, + pub mast_idx: i32, + pub ref_idx: u32, pub data: Vec, }