diff --git a/crates/benilla-app/src/entities/attach/mod.rs b/crates/benilla-app/src/entities/attach/mod.rs index a4a04b53..5979e073 100644 --- a/crates/benilla-app/src/entities/attach/mod.rs +++ b/crates/benilla-app/src/entities/attach/mod.rs @@ -114,7 +114,7 @@ fn setup_skinned_instance( // loops. commands .entity(entity) - .insert(rig) + .try_insert(rig) .remove::(); slot } @@ -125,7 +125,7 @@ fn setup_skinned_instance( // Table full: static bind-pose parts until `heal_rig_starved` rebuilds with a slot. commands .entity(entity) - .insert(benilla_world::rig_palette::RigStarved); + .try_insert(benilla_world::rig_palette::RigStarved); 0 } }; @@ -135,7 +135,7 @@ fn setup_skinned_instance( if let Some(drive) = benilla_world::rig_anim::GlobalSeqDrive::new_rig(&anims.global_bones, nbones) { - commands.entity(entity).insert(drive); + commands.entity(entity).try_insert(drive); } } Some(RigBuild { pose, slot }) @@ -170,14 +170,14 @@ fn arm_sequence_clock( } } } - commands.entity(entity).insert(( + commands.entity(entity).try_insert(( player, AnimationGraphHandle(anims.graph.clone()), anims.clone(), )); match kind { EntityKind::GameObject if go_state_machine => { - commands.entity(entity).insert(( + commands.entity(entity).try_insert(( // Cross-fades each transition over the clip's blend-in time, from the seed. transitions, crate::go_anim::GoAnim::default(), @@ -190,13 +190,13 @@ fn arm_sequence_clock( EntityKind::Corpse => { commands .entity(entity) - .insert(AnimationTransitions::new()) + .try_insert(AnimationTransitions::new()) .remove::(); } _ => { commands .entity(entity) - .insert(( + .try_insert(( // Cross-fades over each clip's blend-in time, so a gait change eases. AnimationTransitions::new(), AnimDriver::default(), @@ -375,14 +375,16 @@ pub(super) fn attach_entity_visuals( let bake_center = dm.map(|d| d.bake_center_local).unwrap_or(Vec3::ZERO); // One ground-shade state per object (the reference's `[obj+0xe0]`), the same 2.5/0.5 // MCSH chase for every kind; `insert_if_new` so a rebuild keeps the ramped state. + // The `try_` forms: a unit torn down in this frame (common in a battleground's stream + // churn) is gone when these apply, and `insert` would panic where `despawn` only warns. commands .entity(entity) - .insert_if_new(benilla_world::entity_shade::GroundShade::default()); + .try_insert_if_new(benilla_world::entity_shade::GroundShade::default()); // Held items share the body's interior verdict (the reference aliases the wearer's // collector into each item, `0x718960`), so they fold at the body's centre. commands .entity(entity) - .insert(benilla_world::interior::BodyBakeCenter(bake_center)); + .try_insert(benilla_world::interior::BodyBakeCenter(bake_center)); // The light node's attach mode (`[node+0x90]` bit 13, set from the TYPEMASK at // `0x613e10`/`0x670db0`, dispatched at `0x6a86d0`): a GameObject by containment at its // bounds centre (`0x6a8c10`), a unit by down-ray at its position (`0x6a8a20`). @@ -390,7 +392,7 @@ pub(super) fn attach_entity_visuals( EntityKind::GameObject => { commands .entity(entity) - .insert(benilla_world::interior::ContainmentAttach); + .try_insert(benilla_world::interior::ContainmentAttach); } _ => { commands @@ -505,7 +507,7 @@ pub(super) fn attach_entity_visuals( for m in &d.markers { markers.entry(m.ident).or_insert((m.bone, m.offset)); } - commands.entity(entity).insert(super::BoneAttach { + commands.entity(entity).try_insert(super::BoneAttach { points: d .attachments .iter() @@ -524,7 +526,7 @@ pub(super) fn attach_entity_visuals( if spine.is_some() || head.is_some() { commands .entity(entity) - .insert(crate::creature_anim::BodyTwist::new(spine, head)); + .try_insert(crate::creature_anim::BodyTwist::new(spine, head)); } } // A character model carries every hair, facial and body geoset, and is shared by its @@ -590,7 +592,7 @@ pub(super) fn attach_entity_visuals( .map(|c| bevy::camera::primitives::Aabb::from_min_max(c.bounds_min, c.bounds_max)) .or_else(|| union_aabb(model.unwrap_or_default().iter().filter_map(|p| p.aabb))); if let Some(aabb) = election_bound { - commands.entity(entity).insert(super::ModelBound(aabb)); + commands.entity(entity).try_insert(super::ModelBound(aabb)); } // A billboard card rides its live joint, so card anchors resolve here, over the spawn // loop's geoset predicate: a hidden variant's card bone spawns no anchor. @@ -657,7 +659,7 @@ pub(super) fn attach_entity_visuals( if unit_will_fade { commands .entity(entity) - .insert(benilla_world::model_fade::UnitAppearFade::Pending { since: now }); + .try_insert(benilla_world::model_fade::UnitAppearFade::Pending { since: now }); } else if reattached { commands .entity(entity) @@ -676,25 +678,25 @@ pub(super) fn attach_entity_visuals( if let (EntityKind::Player, Some(e)) = (net.kind, equipment) { commands .entity(entity) - .insert(super::equipment::AppliedEquipment(*e)); + .try_insert(super::equipment::AppliedEquipment(*e)); } // The camera's framing pivot, model-local, before scale. - commands.entity(entity).insert(CameraPivot { + commands.entity(entity).try_insert(CameraPivot { height_local: dm.map(|d| d.pivot_height_local).unwrap_or(0.0), // The pivot's drop while swimming, the reference's third preset `cam+0x124` // (`0x50ccf6`); 0 without a Swim sequence. swim_drop_local: dm.map(|d| d.swim_pivot_drop_local).unwrap_or(0.0), }); // The overhead fallback for a model with no PlayerName attachment (`0x608640`). - commands.entity(entity).insert(super::OverheadFallback( + commands.entity(entity).try_insert(super::OverheadFallback( dm.map(|d| d.bbox_z_local).unwrap_or(0.0), )); // The chat bubble's height: the Stand box's Z, not the posed PlayerName attachment. - commands.entity(entity).insert(super::StandBoxHeight( + commands.entity(entity).try_insert(super::StandBoxHeight( dm.map(|d| d.stand_box_z_local).unwrap_or(0.0), )); // The selection ring's model-local radius, scaled by the unit's scale. - commands.entity(entity).insert(SelectionRadius( + commands.entity(entity).try_insert(SelectionRadius( dm.map(|d| d.ground_radius_local).unwrap_or(0.0), )); // Emitters ride their host bone's anchor, origin rebased into the bone frame @@ -778,7 +780,7 @@ pub(super) fn attach_entity_visuals( } // The pose lands last, with every anchor the build resolved into it. if let Some(rb) = skin { - commands.entity(entity).insert(rb.pose); + commands.entity(entity).try_insert(rb.pose); } // A GameObject with a hull collides; an anchored transport stays Kinematic, as its arm // labelled it, since its body moves every frame. @@ -789,7 +791,7 @@ pub(super) fn attach_entity_visuals( } else { RigidBody::Static }; - commands.entity(entity).insert((body, col)); + commands.entity(entity).try_insert((body, col)); // This lane bypasses the streamer's attach queue, so it stamps the collider // set itself. collider_epoch.bump(); @@ -855,13 +857,13 @@ pub(super) fn attach_entity_visuals( .map_or(0, |s| s.0.unit_mount_display_id()); commands .entity(entity) - .insert(super::mount::AppliedMount(applied)); + .try_insert(super::mount::AppliedMount(applied)); } commands .entity(entity) - .insert(VisualAttached) + .try_insert(VisualAttached) // The display the visual was built with, for `refresh_live_display` to diff. - .insert(super::live_display::AppliedDisplay(net.display_id)) + .try_insert(super::live_display::AppliedDisplay(net.display_id)) .remove::(); } } diff --git a/crates/benilla-app/src/entities/mount.rs b/crates/benilla-app/src/entities/mount.rs index b016f0a9..8f151359 100644 --- a/crates/benilla-app/src/entities/mount.rs +++ b/crates/benilla-app/src/entities/mount.rs @@ -61,7 +61,9 @@ pub(super) fn spawn_mount_child( commands.entity(child).insert(super::equipment::Reattached); } commands.entity(host).add_child(child); - commands.entity(host).insert(MountChild(child)); + // `try_`: the host may be torn down in the same frame this build runs (a battleground's + // stream churn), and a plain insert would panic where `despawn` only warns. + commands.entity(host).try_insert(MountChild(child)); child }