using System; using System.Collections.Generic; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Physics.Components; using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Robust.Shared.Physics.Systems; public abstract partial class SharedJointSystem { /* * Relays joints to the top-most parent so joints inside of containers can still function. * This is because we still want the joint to "function" it just needs to affect the parent instead. */ [Serializable, NetSerializable] private sealed class JointRelayComponentState : ComponentState { public HashSet Entities; public JointRelayComponentState(HashSet entities) { Entities = entities; } } private void InitializeRelay() { SubscribeLocalEvent(OnRelayShutdown); SubscribeLocalEvent(OnRelayGetState); SubscribeLocalEvent(OnRelayHandleState); } private void OnRelayGetState(EntityUid uid, JointRelayTargetComponent component, ref ComponentGetState args) { args.State = new JointRelayComponentState(GetNetEntitySet(component.Relayed)); } private void OnRelayHandleState(EntityUid uid, JointRelayTargetComponent component, ref ComponentHandleState args) { if (args.Current is not JointRelayComponentState state) return; EnsureEntitySet(state.Entities, uid, component.Relayed); } private void OnRelayShutdown(EntityUid uid, JointRelayTargetComponent component, ComponentShutdown args) { if (_gameTiming.ApplyingState) return; foreach (var relay in component.Relayed) { if (TerminatingOrDeleted(relay) || !_jointsQuery.TryGetComponent(relay, out var joint)) continue; RefreshRelay(relay, component: joint); } } /// /// Refreshes the joint relay for this entity, prefering its containing container or nothing. /// public void RefreshRelay(EntityUid uid, JointComponent? component = null) { if (!Resolve(uid, ref component, false)) return; EntityUid? relay = null; if (_container.TryGetOuterContainer(uid, Transform(uid), out var container)) { relay = container.Owner; // Validate that the relay target is not being set to our own container. foreach (var joint in component.Joints.Values) { var other = joint.GetOther(uid); if (other == relay) { SetRelay(uid, null, component); return; } } } SetRelay(uid, relay, component); } /// /// Refreshes the joint relay for this entity. /// public void SetRelay(EntityUid uid, EntityUid? relay, JointComponent? component = null) { if (!Resolve(uid, ref component, false)) return; if (component.Relay == relay) return; if (TryComp(component.Relay, out var relayTarget)) { if (relayTarget.Relayed.Remove(uid)) { if (relayTarget.Relayed.Count == 0) { RemCompDeferred(component.Relay.Value); } Dirty(component.Relay.Value, relayTarget); } } component.Relay = relay; if (relay != null) { relayTarget = EnsureComp(relay.Value); if (relayTarget.Relayed.Add(uid)) { _physics.WakeBody(relay.Value); Dirty(relay.Value, relayTarget); } } Dirty(uid, component); #if DEBUG if (component.Relay == null) return; if (TryComp(uid, out JointComponent? jointComp)) { foreach (var joint in jointComp.Joints.Values) { DebugTools.AssertNotEqual(joint.BodyAUid, component.Relay); DebugTools.AssertNotEqual(joint.BodyBUid, component.Relay); } } if (TryComp(component.Relay, out JointComponent? relayJointComp)) { foreach (var joint in relayJointComp.Joints.Values) { DebugTools.AssertNotEqual(joint.BodyAUid, uid); DebugTools.AssertNotEqual(joint.BodyBUid, uid); } } #endif } }