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 <noreply@anthropic.com>
This commit is contained in:
JohnM
2026-10-10 01:18:51 +08:00
parent 9c4d4320f4
commit 7157feb9bd
5 changed files with 111 additions and 2 deletions
+29
View File
@@ -152,6 +152,8 @@ pub struct FxGlassReset {
pub piece_limit: usize,
pub geo_data_limit: usize,
pub names: Vec<(String, Vec<u16>)>,
}
impl FxGlassReset {
@@ -263,9 +265,36 @@ pub fn build_fx_glass_reset(stream: &ZoneStream<'_>) -> Option<FxGlassReset> {
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<u16>)> {
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,
+12
View File
@@ -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);
+9
View File
@@ -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<u32>)>) {
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<crate::script::Realm> {
self.ecs
.resource::<crate::script::Runtime>()
+60 -1
View File
@@ -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<String, Vec<u32>>);
fn glass_named(world: &World, name: &str) -> Vec<u32> {
let Some(pieces) = world
.get_resource::<GlassNames>()
.and_then(|names| names.0.get(name))
else {
return Vec::new();
};
pieces.clone()
}
fn glass_id(args: &[Value], index: usize) -> Result<u32, String> {
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;
+1 -1
View File
@@ -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,