fix: the attach and mount teardown path uses try_ commands and child-side ChildOf
A parent-side add_child panics against an entity a same-frame teardown already despawned; spawn with ChildOf on the child and use try_insert/try_remove/ try_despawn so the same-frame churn warns instead of aborting.
This commit is contained in:
@@ -115,7 +115,7 @@ fn setup_skinned_instance(
|
||||
commands
|
||||
.entity(entity)
|
||||
.try_insert(rig)
|
||||
.remove::<benilla_world::rig_palette::RigStarved>();
|
||||
.try_remove::<benilla_world::rig_palette::RigStarved>();
|
||||
slot
|
||||
}
|
||||
// Nothing to skin: no starvation marker, or `heal_rig_starved` would rebuild the body for a
|
||||
@@ -191,7 +191,7 @@ fn arm_sequence_clock(
|
||||
commands
|
||||
.entity(entity)
|
||||
.try_insert(AnimationTransitions::new())
|
||||
.remove::<benilla_world::rig_anim::AnimParked>();
|
||||
.try_remove::<benilla_world::rig_anim::AnimParked>();
|
||||
}
|
||||
_ => {
|
||||
commands
|
||||
@@ -203,7 +203,7 @@ fn arm_sequence_clock(
|
||||
))
|
||||
// A rebuilt rig is born live: a stale park marker would desync the LOD gate's
|
||||
// edge-triggered bookkeeping.
|
||||
.remove::<benilla_world::rig_anim::AnimParked>();
|
||||
.try_remove::<benilla_world::rig_anim::AnimParked>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,9 +361,11 @@ pub(super) fn attach_entity_visuals(
|
||||
} else if let Some(&super::mount::MountChild(child)) = mount_child {
|
||||
// Dismounted while pending: drop the mount it ordered.
|
||||
if let Ok(mut ec) = commands.get_entity(child) {
|
||||
ec.despawn();
|
||||
ec.try_despawn();
|
||||
}
|
||||
commands.entity(entity).remove::<super::mount::MountChild>();
|
||||
commands
|
||||
.entity(entity)
|
||||
.try_remove::<super::mount::MountChild>();
|
||||
}
|
||||
let kind = match net.kind {
|
||||
EntityKind::GameObject => ModelKind::GameObject,
|
||||
@@ -397,7 +399,7 @@ pub(super) fn attach_entity_visuals(
|
||||
_ => {
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove::<benilla_world::interior::ContainmentAttach>();
|
||||
.try_remove::<benilla_world::interior::ContainmentAttach>();
|
||||
}
|
||||
}
|
||||
let object = WorldObject {
|
||||
@@ -425,8 +427,11 @@ pub(super) fn attach_entity_visuals(
|
||||
// dispatches on the mount's model (`0x7106c0`) and tilts through its node.
|
||||
let mut joints_root = rider_root;
|
||||
if d.terrain_tilt != 0 && rider_root == entity {
|
||||
// Child-side `ChildOf`, not `add_child`: a parent-side add panics against a
|
||||
// unit that a same-frame teardown despawned mid-build.
|
||||
let node = commands
|
||||
.spawn((
|
||||
ChildOf(entity),
|
||||
super::conform::ConformNode {
|
||||
// The ground and yaw source: the unit, or a mount child's
|
||||
// host.
|
||||
@@ -437,7 +442,6 @@ pub(super) fn attach_entity_visuals(
|
||||
Visibility::default(),
|
||||
))
|
||||
.id();
|
||||
commands.entity(entity).add_child(node);
|
||||
joints_root = node;
|
||||
}
|
||||
setup_skinned_instance(
|
||||
@@ -663,7 +667,7 @@ pub(super) fn attach_entity_visuals(
|
||||
} else if reattached {
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove::<benilla_world::model_fade::UnitAppearFade>();
|
||||
.try_remove::<benilla_world::model_fade::UnitAppearFade>();
|
||||
}
|
||||
// Size is `OBJECT_FIELD_SCALE_X` alone: the server already folds the DBC scale into
|
||||
// it (vmangos `Unit::GetScaleForDisplayId`, `Unit.cpp:9579`), and the reference
|
||||
@@ -834,19 +838,21 @@ pub(super) fn attach_entity_visuals(
|
||||
id: net.display_id.unwrap_or(0),
|
||||
detail: String::new(),
|
||||
};
|
||||
commands.entity(entity).with_children(|parent| {
|
||||
parent.spawn((
|
||||
Mesh3d(mesh),
|
||||
MeshMaterial3d(material),
|
||||
Transform::from_translation(Vec3::Y * lift),
|
||||
object,
|
||||
benilla_world::interact::CreaturePickPart,
|
||||
// No `RenderSubmesh`: the picker casts against the cube's box.
|
||||
benilla_world::interact::PickBox,
|
||||
// Counted by `WOW_UNIT_VISUALS`.
|
||||
super::FallbackCube,
|
||||
));
|
||||
});
|
||||
// Child-side `ChildOf`, like every model part in `dress`: the parent-side
|
||||
// `with_children` would `add_related` against a unit that a same-frame teardown
|
||||
// already despawned, and panic where the child spawn only warns.
|
||||
commands.spawn((
|
||||
ChildOf(entity),
|
||||
Mesh3d(mesh),
|
||||
MeshMaterial3d(material),
|
||||
Transform::from_translation(Vec3::Y * lift),
|
||||
object,
|
||||
benilla_world::interact::CreaturePickPart,
|
||||
// No `RenderSubmesh`: the picker casts against the cube's box.
|
||||
benilla_world::interact::PickBox,
|
||||
// Counted by `WOW_UNIT_VISUALS`.
|
||||
super::FallbackCube,
|
||||
));
|
||||
}
|
||||
}
|
||||
// The mount the visual was built on, for `reseat_mounts` to diff; stamped on a cube too,
|
||||
@@ -864,7 +870,7 @@ pub(super) fn attach_entity_visuals(
|
||||
.try_insert(VisualAttached)
|
||||
// The display the visual was built with, for `refresh_live_display` to diff.
|
||||
.try_insert(super::live_display::AppliedDisplay(net.display_id))
|
||||
.remove::<super::equipment::Reattached>();
|
||||
.try_remove::<super::equipment::Reattached>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,10 +191,10 @@ pub(in crate::entities) fn redress_player_looks(
|
||||
// A billboard card is a world root, so it is reaped by name.
|
||||
if let Some(card) = dp.card {
|
||||
if let Ok(mut ec) = commands.get_entity(card) {
|
||||
ec.despawn();
|
||||
ec.try_despawn();
|
||||
}
|
||||
}
|
||||
commands.entity(child).despawn();
|
||||
commands.entity(child).try_despawn();
|
||||
hidden += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -45,8 +45,11 @@ pub(super) fn spawn_mount_child(
|
||||
scale: f32,
|
||||
fade_skip: bool,
|
||||
) -> Entity {
|
||||
// Child-side `ChildOf(host)`, not `add_child`: a parent-side add panics against a host that a
|
||||
// same-frame teardown despawned mid-build.
|
||||
let child = commands
|
||||
.spawn((
|
||||
ChildOf(host),
|
||||
NetEntity {
|
||||
kind: EntityKind::Unit,
|
||||
display_id: Some(display),
|
||||
@@ -60,7 +63,6 @@ pub(super) fn spawn_mount_child(
|
||||
if fade_skip {
|
||||
commands.entity(child).insert(super::equipment::Reattached);
|
||||
}
|
||||
commands.entity(host).add_child(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));
|
||||
@@ -79,13 +81,13 @@ fn spawn_seat_anchor(
|
||||
) -> Entity {
|
||||
let anchor = commands
|
||||
.spawn((
|
||||
ChildOf(joint),
|
||||
Transform::from_translation(offset)
|
||||
.with_scale(Vec3::splat(1.0 / mount_scale.max(0.001))),
|
||||
Visibility::default(),
|
||||
benilla_world::rig_anim::RigFrame(rider),
|
||||
))
|
||||
.id();
|
||||
commands.entity(joint).add_child(anchor);
|
||||
anchor
|
||||
}
|
||||
|
||||
@@ -135,9 +137,9 @@ pub(super) fn seat_or_spawn_mount(
|
||||
let built = children.get(child).map(|(_, _, n)| n.display_id).ok();
|
||||
if built != Some(Some(mount_display)) {
|
||||
if let Ok(mut ec) = commands.get_entity(child) {
|
||||
ec.despawn();
|
||||
ec.try_despawn();
|
||||
}
|
||||
commands.entity(unit).remove::<MountChild>();
|
||||
commands.entity(unit).try_remove::<MountChild>();
|
||||
return Seat::Wait;
|
||||
}
|
||||
let Ok((true, Some(bones), _)) = children.get(child) else {
|
||||
@@ -185,12 +187,12 @@ fn ground_frame(
|
||||
}
|
||||
let node = commands
|
||||
.spawn((
|
||||
ChildOf(unit),
|
||||
super::conform::ConformNode { unit, mode: tilt },
|
||||
Transform::default(),
|
||||
Visibility::default(),
|
||||
))
|
||||
.id();
|
||||
commands.entity(unit).add_child(node);
|
||||
node
|
||||
}
|
||||
|
||||
@@ -202,15 +204,16 @@ fn reseat_rig(commands: &mut Commands, rig: &mut RigPose, frame: Entity, conform
|
||||
return;
|
||||
}
|
||||
let anchors: Vec<Entity> = rig.anchors.iter().map(|&(_, a)| a).collect();
|
||||
if !anchors.is_empty() {
|
||||
commands.entity(frame).add_children(&anchors);
|
||||
for anchor in anchors {
|
||||
// Child-side, so a frame despawned in the same frame warns rather than panicking.
|
||||
commands.entity(anchor).try_insert(ChildOf(frame));
|
||||
}
|
||||
let old = std::mem::replace(&mut rig.joints_root, frame);
|
||||
rig.pose_dirty = true;
|
||||
// A conform node the rig just left is empty, so it goes; a seat anchor dies with its mount.
|
||||
if conform {
|
||||
if let Ok(mut ec) = commands.get_entity(old) {
|
||||
ec.despawn();
|
||||
ec.try_despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,11 +257,11 @@ pub(super) fn reseat_mounts(
|
||||
let Ok(seated_root) = poses.get(entity).map(|r| r.joints_root) else {
|
||||
if let Some(&MountChild(child)) = child {
|
||||
if let Ok(mut ec) = commands.get_entity(child) {
|
||||
ec.despawn();
|
||||
ec.try_despawn();
|
||||
}
|
||||
commands.entity(entity).remove::<MountChild>();
|
||||
commands.entity(entity).try_remove::<MountChild>();
|
||||
}
|
||||
commands.entity(entity).insert(AppliedMount(live));
|
||||
commands.entity(entity).try_insert(AppliedMount(live));
|
||||
continue;
|
||||
};
|
||||
let child = child.map(|&MountChild(c)| c);
|
||||
@@ -276,13 +279,13 @@ pub(super) fn reseat_mounts(
|
||||
}
|
||||
if let Some(child) = child {
|
||||
if let Ok(mut ec) = commands.get_entity(child) {
|
||||
ec.despawn();
|
||||
ec.try_despawn();
|
||||
}
|
||||
}
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove::<MountChild>()
|
||||
.insert(AppliedMount(0));
|
||||
.try_remove::<MountChild>()
|
||||
.try_insert(AppliedMount(0));
|
||||
continue;
|
||||
}
|
||||
// ── Leg 2 · `0x607a00`: build the mount model and attach the body to its seat.
|
||||
@@ -302,10 +305,10 @@ pub(super) fn reseat_mounts(
|
||||
let leaving_conform = conforms.contains(seated_root);
|
||||
reseat_rig(&mut commands, &mut rig, anchor, leaving_conform);
|
||||
}
|
||||
commands.entity(entity).insert(AppliedMount(live));
|
||||
commands.entity(entity).try_insert(AppliedMount(live));
|
||||
}
|
||||
Seat::UnitMatrix => {
|
||||
commands.entity(entity).insert(AppliedMount(live));
|
||||
commands.entity(entity).try_insert(AppliedMount(live));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user