From 7157feb9bdbc096c02db2fc20b938657d4175b84 Mon Sep 17 00:00:00 2001 From: JohnM <234386938+treuenten@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:18:51 +0800 Subject: [PATCH] Bind the script glass functions so Strike loads mp_strike's map script breaks its "brokenglass01" panes in main() with GetGlassArray and DestroyGlass. Neither native was bound, and an unbound native refuses the whole program at install ("2 unbound natives: destroyglass getglassarray"), so the map never loaded. The glass reset now also reads the map's named glass table (a targetname and its piece indices), and the session installs it next to the glass panes as a GlassNames resource. getglassarray, getglass, destroyglass (the existing script shatter), isglassdestroyed and getglassorigin are bound on top of it. The map script only runs at the next scheduler pass, after the glass is installed. Co-Authored-By: Claude Opus 5.5 --- crates/asset_world/src/glass_catalog.rs | 29 ++++++++++ crates/session/src/match_apply.rs | 12 ++++ crates/sim/src/carrier.rs | 9 +++ crates/sim/src/script/host/natives/engine.rs | 61 +++++++++++++++++++- crates/sim/src/script/mod.rs | 2 +- 5 files changed, 111 insertions(+), 2 deletions(-) diff --git a/crates/asset_world/src/glass_catalog.rs b/crates/asset_world/src/glass_catalog.rs index 67cd21d..914175f 100644 --- a/crates/asset_world/src/glass_catalog.rs +++ b/crates/asset_world/src/glass_catalog.rs @@ -152,6 +152,8 @@ pub struct FxGlassReset { pub piece_limit: usize, pub geo_data_limit: usize, + + pub names: Vec<(String, Vec)>, } impl FxGlassReset { @@ -263,9 +265,36 @@ pub fn build_fx_glass_reset(stream: &ZoneStream<'_>) -> Option { geo_cursor, piece_limit: g.piece_limit, geo_data_limit: g.geo_data_limit, + names: glass_names(stream), }) } +fn glass_names(s: &ZoneStream<'_>) -> Vec<(String, Vec)> { + let Some(g) = s.glass_data() else { + return Vec::new(); + }; + let Some(rows) = g.names else { + return Vec::new(); + }; + let mut out = Vec::with_capacity(g.name_count); + for i in 0..g.name_count { + let row = rows.at(i * s.layout(sz::G_GLASS_NAME, 24)); + let Some(name) = cstr_field(s, row, 0).filter(|name| !name.is_empty()) else { + continue; + }; + let count = s.u16_at(row, s.layout(6, 10)).unwrap_or(0) as usize; + let pieces = match s.ptr_at(row, s.layout(8, 16)) { + Ok(ZonePtr::Offset(q)) => { + let q = s.resolve_alias(q); + (0..count).map_while(|n| s.u16_at(q, n * 2).ok()).collect() + } + _ => Vec::new(), + }; + out.push((name, pieces)); + } + out +} + fn glass_material_edge( namespace: asset_material::AssetNamespace, hint: &str, diff --git a/crates/session/src/match_apply.rs b/crates/session/src/match_apply.rs index 768a83b..1cabde7 100644 --- a/crates/session/src/match_apply.rs +++ b/crates/session/src/match_apply.rs @@ -520,6 +520,18 @@ pub fn apply_prepared_match( }) .collect(); sim.world_objects_mut().install_glass_panes(panes); + sim.set_glass_names( + glass + .names + .iter() + .map(|(name, pieces)| { + ( + name.clone(), + pieces.iter().copied().map(u32::from).collect(), + ) + }) + .collect(), + ); } sim.world_objects_mut() .set_map_round_epoch(load_key.match_key.match_epoch); diff --git a/crates/sim/src/carrier.rs b/crates/sim/src/carrier.rs index a766d1f..accf9e0 100644 --- a/crates/sim/src/carrier.rs +++ b/crates/sim/src/carrier.rs @@ -176,6 +176,15 @@ impl SimWorld { crate::script::set_dvar(&mut self.ecs, name, value); } + pub fn set_glass_names(&mut self, names: Vec<(String, Vec)>) { + if !names.is_empty() { + let pieces: usize = names.iter().map(|(_, pieces)| pieces.len()).sum(); + diag::info!(Sim, "glass: {} named sets, {pieces} pieces", names.len()); + } + self.ecs + .insert_resource(crate::script::GlassNames(names.into_iter().collect())); + } + pub fn gsc_realm(&self) -> Option { self.ecs .resource::() diff --git a/crates/sim/src/script/host/natives/engine.rs b/crates/sim/src/script/host/natives/engine.rs index ddb0699..96b489c 100644 --- a/crates/sim/src/script/host/natives/engine.rs +++ b/crates/sim/src/script/host/natives/engine.rs @@ -7,7 +7,7 @@ use super::math::distance_sq; use crate::bullet_collision::{ MASK_PLAYER_SOLID, MASK_SHOT, PLAYER_MAXS, PLAYER_MINS, TraceOutcome, }; -use crate::script::{Arc, ArrayKey, Namespace, NativeRegistry, Runtime, Value}; +use crate::script::{Arc, ArrayKey, BTreeMap, Namespace, NativeRegistry, Resource, Runtime, Value}; use bevy_ecs::prelude::World; const ZERO: [f32; 3] = [0.0; 3]; @@ -757,6 +757,7 @@ pub(crate) fn register(registry: &mut NativeRegistry) { register_motion(registry); register_attachments(registry); register_sound_and_fx(registry); + register_glass(registry); register_entity_state(registry); super::super::hud::register(registry); register_traces(registry); @@ -2017,6 +2018,64 @@ fn register_level(registry: &mut NativeRegistry) { unavailable!("IW4 party termination is not bound to the IW4L party lifecycle": "endparty"); } +#[derive(Resource, Default)] +pub struct GlassNames(pub BTreeMap>); + +fn glass_named(world: &World, name: &str) -> Vec { + let Some(pieces) = world + .get_resource::() + .and_then(|names| names.0.get(name)) + else { + return Vec::new(); + }; + pieces.clone() +} + +fn glass_id(args: &[Value], index: usize) -> Result { + u32::try_from(int(args, index)?).map_err(|_| "glass id must not be negative".into()) +} + +fn register_glass(registry: &mut NativeRegistry) { + use Namespace::Function; + + registry.register(Function, "getglassarray", |world, _, args| { + let name = string(args, 0)?; + let ids = glass_named(world, &name) + .into_iter() + .map(|id| Value::Int(id as i32)) + .collect(); + new_array(world, ids) + }); + registry.register(Function, "getglass", |world, _, args| { + let name = string(args, 0)?; + Ok(glass_named(world, &name) + .first() + .map_or(Value::Undefined, |&id| Value::Int(id as i32))) + }); + registry.register(Function, "destroyglass", |world, _, args| { + let id = glass_id(args, 0)?; + let now = i32::try_from(super::super::players::now_ms(world)).unwrap_or(i32::MAX); + crate::frame::FrameWorld::from_world(world) + .world_objects_mut() + .script_destroy_glass(id, now); + Ok(Value::Undefined) + }); + registry.register(Function, "isglassdestroyed", |world, _, args| { + let id = glass_id(args, 0)?; + let solid = crate::frame::FrameWorld::from_world(world) + .world_objects() + .glass_is_solid(id); + Ok(Value::Int(i32::from(!solid))) + }); + registry.register(Function, "getglassorigin", |world, _, args| { + let id = glass_id(args, 0)?; + Ok(crate::frame::FrameWorld::from_world(world) + .world_objects() + .glass_pane(id) + .map_or(Value::Undefined, |pane| Value::Vector(pane.origin))) + }); +} + fn register_weapon_facts(registry: &mut NativeRegistry) { use Namespace::Function; diff --git a/crates/sim/src/script/mod.rs b/crates/sim/src/script/mod.rs index 04b2551..466b1bf 100644 --- a/crates/sim/src/script/mod.rs +++ b/crates/sim/src/script/mod.rs @@ -27,7 +27,7 @@ pub(crate) use host::entity_damage::{ }; pub(crate) use host::mechanics::Mechanics; pub(crate) use host::mechanics::advance_mechanics; -pub use host::natives::engine::{EXIT_LEVEL, MAP_RESTART}; +pub use host::natives::engine::{EXIT_LEVEL, GlassNames, MAP_RESTART}; pub(crate) use host::natives::iw4::set_dvar; pub(crate) use host::players::{ answer_join, answer_menu, apply_disconnects, apply_player_links, choose_class,