diff --git a/Content.Server/Doors/Systems/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs index 5e03a7ccce..033a71a385 100644 --- a/Content.Server/Doors/Systems/DoorSystem.cs +++ b/Content.Server/Doors/Systems/DoorSystem.cs @@ -44,16 +44,21 @@ public sealed class DoorSystem : SharedDoorSystem SubscribeLocalEvent(OnEmagged); } - protected override void OnInit(EntityUid uid, DoorComponent door, ComponentInit args) + protected override void SetCollidable(EntityUid uid, bool collidable, + DoorComponent? door = null, + PhysicsComponent? physics = null, + OccluderComponent? occluder = null) { - base.OnInit(uid, door, args); + if (!Resolve(uid, ref door)) + return; - if (door.State == DoorState.Open - && door.ChangeAirtight - && TryComp(uid, out AirtightComponent? airtight)) - { - _airtightSystem.SetAirblocked(airtight, false); - } + if (door.ChangeAirtight && TryComp(uid, out AirtightComponent? airtight)) + _airtightSystem.SetAirblocked(airtight, collidable); + + // Pathfinding / AI stuff. + RaiseLocalEvent(new AccessReaderChangeMessage(uid, collidable)); + + base.SetCollidable(uid, collidable, door, physics, occluder); } // TODO AUDIO PREDICT Figure out a better way to handle sound and prediction. For now, this works well enough? @@ -250,39 +255,6 @@ public sealed class DoorSystem : SharedDoorSystem TryOpen(uid, door, otherUid); } - public override void OnPartialOpen(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null) - { - if (!Resolve(uid, ref door, ref physics)) - return; - - base.OnPartialOpen(uid, door, physics); - - if (door.ChangeAirtight && TryComp(uid, out AirtightComponent? airtight)) - { - _airtightSystem.SetAirblocked(airtight, false); - } - - // Path-finding. Has nothing directly to do with access readers. - RaiseLocalEvent(new AccessReaderChangeMessage(uid, false)); - } - - public override bool OnPartialClose(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null) - { - if (!Resolve(uid, ref door, ref physics)) - return false; - - if (!base.OnPartialClose(uid, door, physics)) - return false; - - // update airtight, if we did not crush something. - if (door.ChangeAirtight && door.CurrentlyCrushing.Count == 0 && TryComp(uid, out AirtightComponent? airtight)) - _airtightSystem.SetAirblocked(airtight, true); - - // Path-finding. Has nothing directly to do with access readers. - RaiseLocalEvent(new AccessReaderChangeMessage(uid, true)); - return true; - } - private void OnMapInit(EntityUid uid, DoorComponent door, MapInitEvent args) { // Ensure that the construction component is aware of the board container. diff --git a/Content.Shared/Doors/Components/DoorComponent.cs b/Content.Shared/Doors/Components/DoorComponent.cs index a8b0357ff3..b90e482fdf 100644 --- a/Content.Shared/Doors/Components/DoorComponent.cs +++ b/Content.Shared/Doors/Components/DoorComponent.cs @@ -1,23 +1,19 @@ -using System; -using System.Collections.Generic; using Content.Shared.Damage; using Content.Shared.Sound; using Content.Shared.Tools; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.ViewVariables; +using Robust.Shared.Timing; using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth; namespace Content.Shared.Doors.Components; [NetworkedComponent] [RegisterComponent] -public sealed class DoorComponent : Component +public sealed class DoorComponent : Component, ISerializationHooks { /// /// The current state of the door -- whether it is open, closed, opening, or closing. @@ -72,6 +68,7 @@ public sealed class DoorComponent : Component /// Whether the door is currently partially closed or open. I.e., when the door is "closing" and is already opaque, /// but not yet actually closed. /// + [DataField("partial")] public bool Partial; #endregion @@ -142,9 +139,38 @@ public sealed class DoorComponent : Component /// /// List of EntityUids of entities we're currently crushing. Cleared in OnPartialOpen(). /// + [DataField("currentlyCrushing")] public List CurrentlyCrushing = new(); #endregion + #region Serialization + /// + /// Time until next state change. Because apparently might not get saved/restored. + /// + [DataField("SecondsUntilStateChange")] + private float? _secondsUntilStateChange; + + void ISerializationHooks.BeforeSerialization() + { + if (NextStateChange == null) + { + _secondsUntilStateChange = null; + return; + }; + + var curTime = IoCManager.Resolve().CurTime; + _secondsUntilStateChange = (float) (NextStateChange.Value - curTime).TotalSeconds; + } + + void ISerializationHooks.AfterDeserialization() + { + if (_secondsUntilStateChange == null || _secondsUntilStateChange.Value > 0) + return; + + NextStateChange = IoCManager.Resolve().CurTime + TimeSpan.FromSeconds(_secondsUntilStateChange.Value); + } + #endregion + [DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? BoardPrototype; diff --git a/Content.Shared/Doors/Systems/SharedDoorSystem.cs b/Content.Shared/Doors/Systems/SharedDoorSystem.cs index ea720a741b..f6540e3a6d 100644 --- a/Content.Shared/Doors/Systems/SharedDoorSystem.cs +++ b/Content.Shared/Doors/Systems/SharedDoorSystem.cs @@ -6,16 +6,10 @@ using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Stunnable; using Robust.Shared.Audio; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; using Robust.Shared.Physics; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Timing; -using System; -using System.Collections.Generic; using System.Linq; namespace Content.Shared.Doors.Systems; @@ -59,18 +53,33 @@ public abstract class SharedDoorSystem : EntitySystem SubscribeLocalEvent(PreventCollision); } - protected virtual void OnInit(EntityUid uid, DoorComponent door, ComponentInit args) + private void OnInit(EntityUid uid, DoorComponent door, ComponentInit args) { - // if the door state is not standard (i.e., door starts open), make sure collision & occlusion are properly set. - if (door.State == DoorState.Open) + if (door.NextStateChange != null) + _activeDoors.Add(door); + else { - if (TryComp(uid, out PhysicsComponent? physics)) - physics.CanCollide = false; - - if (door.Occludes && TryComp(uid, out OccluderComponent? occluder)) - occluder.Enabled = false; + // Make sure doors are not perpetually stuck opening or closing. + if (door.State == DoorState.Opening) + { + // force to open. + door.State = DoorState.Open; + door.Partial = false; + } + if (door.State == DoorState.Closing) + { + // force to closed. + door.State = DoorState.Closed; + door.Partial = false; + } } + // should this door have collision and the like enabled? + var collidable = door.State == DoorState.Closed + || door.State == DoorState.Closing && door.Partial + || door.State == DoorState.Opening && !door.Partial; + + SetCollidable(uid, collidable, door); UpdateAppearance(uid, door); } @@ -268,21 +277,17 @@ public abstract class SharedDoorSystem : EntitySystem /// /// Called when the door is partially opened. The door becomes transparent and stops colliding with entities. /// - public virtual void OnPartialOpen(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null) + public void OnPartialOpen(EntityUid uid, DoorComponent? door = null) { - if (!Resolve(uid, ref door, ref physics)) + if (!Resolve(uid, ref door)) return; - // we can't be crushing anyone anymore, since we're opening - door.CurrentlyCrushing.Clear(); - physics.CanCollide = false; + SetCollidable(uid, false, door); door.Partial = true; door.NextStateChange = GameTiming.CurTime + door.CloseTimeTwo; _activeDoors.Add(door); door.Dirty(); - if (door.Occludes && TryComp(uid, out OccluderComponent? occluder)) - occluder.Enabled = false; } #endregion @@ -327,11 +332,12 @@ public abstract class SharedDoorSystem : EntitySystem /// Called when the door is partially closed. This is when the door becomes "solid". If this process fails (e.g., a /// mob entered the door as it was closing), then this returns false. Otherwise, returns true; /// - public virtual bool OnPartialClose(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null) + public bool OnPartialClose(EntityUid uid, DoorComponent? door = null, PhysicsComponent? physics = null) { if (!Resolve(uid, ref door, ref physics)) return false; + SetCollidable(uid, true, door, physics); door.Partial = true; door.Dirty(); @@ -344,13 +350,9 @@ public abstract class SharedDoorSystem : EntitySystem return false; } - physics.CanCollide = true; door.NextStateChange = GameTiming.CurTime + door.CloseTimeTwo; _activeDoors.Add(door); - - if (door.Occludes && TryComp(uid, out OccluderComponent? occluder)) - occluder.Enabled = true; - + // Crush any entities. Note that we don't check airlock safety here. This should have been checked before // the door closed. Crush(uid, door, physics); @@ -359,6 +361,24 @@ public abstract class SharedDoorSystem : EntitySystem #endregion #region Collisions + protected virtual void SetCollidable(EntityUid uid, bool collidable, + DoorComponent? door = null, + PhysicsComponent? physics = null, + OccluderComponent? occluder = null) + { + if (!Resolve(uid, ref door)) + return; + + if (Resolve(uid, ref physics, false)) + physics.CanCollide = collidable; + + if (!collidable) + door.CurrentlyCrushing.Clear(); + + if (door.Occludes && Resolve(uid, ref occluder, false)) + occluder.Enabled = collidable; + } + /// /// Crushes everyone colliding with us by more than %. ///