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,