diff --git a/Content.Client/DoAfter/DoAfterComponent.cs b/Content.Client/DoAfter/DoAfterComponent.cs index e759377caf..b7da92698e 100644 --- a/Content.Client/DoAfter/DoAfterComponent.cs +++ b/Content.Client/DoAfter/DoAfterComponent.cs @@ -1,175 +1,15 @@ -using System; -using System.Collections.Generic; using Content.Client.DoAfter.UI; using Content.Shared.DoAfter; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Network; -using Robust.Shared.Players; -using Robust.Shared.Timing; namespace Content.Client.DoAfter { - [RegisterComponent] + [RegisterComponent, Friend(typeof(DoAfterSystem))] public sealed class DoAfterComponent : SharedDoAfterComponent { - public IReadOnlyDictionary DoAfters => _doAfters; - private readonly Dictionary _doAfters = new(); + public readonly Dictionary DoAfters = new(); public readonly List<(TimeSpan CancelTime, ClientDoAfter Message)> CancelledDoAfters = new(); public DoAfterGui? Gui { get; set; } - - [Obsolete("Component Messages are deprecated, use Entity Events instead.")] - public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, netChannel, session); - switch (message) - { - case CancelledDoAfterMessage msg: - Cancel(msg.ID); - break; - } - } - - protected override void OnAdd() - { - base.OnAdd(); - Enable(); - } - - protected override void OnRemove() - { - base.OnRemove(); - Disable(); - } - - /// - /// For handling PVS so we dispose of controls if they go out of range - /// - public void Enable() - { - if (Gui?.Disposed == false) - return; - - Gui = new DoAfterGui {AttachedEntity = Owner}; - - foreach (var (_, doAfter) in _doAfters) - { - Gui.AddDoAfter(doAfter); - } - - foreach (var (_, cancelled) in CancelledDoAfters) - { - Gui.CancelDoAfter(cancelled.ID); - } - } - - public void Disable() - { - Gui?.Dispose(); - Gui = null; - } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - base.HandleComponentState(curState, nextState); - - if (curState is not DoAfterComponentState state) - return; - - var toRemove = new List(); - - foreach (var (id, doAfter) in _doAfters) - { - var found = false; - - foreach (var clientdoAfter in state.DoAfters) - { - if (clientdoAfter.ID == id) - { - found = true; - break; - } - } - - if (!found) - { - toRemove.Add(doAfter); - } - } - - foreach (var doAfter in toRemove) - { - Remove(doAfter); - } - - foreach (var doAfter in state.DoAfters) - { - if (_doAfters.ContainsKey(doAfter.ID)) - continue; - - _doAfters.Add(doAfter.ID, doAfter); - } - - if (Gui == null || Gui.Disposed) - return; - - foreach (var (_, doAfter) in _doAfters) - { - Gui.AddDoAfter(doAfter); - } - } - - /// - /// Remove a DoAfter without showing a cancellation graphic. - /// - /// - public void Remove(ClientDoAfter clientDoAfter) - { - _doAfters.Remove(clientDoAfter.ID); - - var found = false; - - for (var i = CancelledDoAfters.Count - 1; i >= 0; i--) - { - var cancelled = CancelledDoAfters[i]; - - if (cancelled.Message == clientDoAfter) - { - CancelledDoAfters.RemoveAt(i); - found = true; - break; - } - } - - if (!found) - _doAfters.Remove(clientDoAfter.ID); - - Gui?.RemoveDoAfter(clientDoAfter.ID); - } - - /// - /// Mark a DoAfter as cancelled and show a cancellation graphic. - /// - /// Actual removal is handled by DoAfterEntitySystem. - /// - /// - public void Cancel(byte id, TimeSpan? currentTime = null) - { - foreach (var (_, cancelled) in CancelledDoAfters) - { - if (cancelled.ID == id) - return; - } - - if (!_doAfters.ContainsKey(id)) - return; - - var doAfterMessage = _doAfters[id]; - currentTime ??= IoCManager.Resolve().CurTime; - CancelledDoAfters.Add((currentTime.Value, doAfterMessage)); - Gui?.CancelDoAfter(id); - } } } diff --git a/Content.Client/DoAfter/DoAfterSystem.cs b/Content.Client/DoAfter/DoAfterSystem.cs index 76a6e6bafe..9f7f5c613a 100644 --- a/Content.Client/DoAfter/DoAfterSystem.cs +++ b/Content.Client/DoAfter/DoAfterSystem.cs @@ -1,12 +1,13 @@ using System.Linq; +using Content.Client.DoAfter.UI; +using Content.Shared.DoAfter; using Content.Shared.Examine; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Graphics; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; +using Robust.Shared.GameStates; using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Client.DoAfter { @@ -39,6 +40,75 @@ namespace Content.Client.DoAfter base.Initialize(); UpdatesOutsidePrediction = true; SubscribeLocalEvent(HandlePlayerAttached); + SubscribeNetworkEvent(OnCancelledDoAfter); + SubscribeLocalEvent(OnDoAfterStartup); + SubscribeLocalEvent(OnDoAfterShutdown); + SubscribeLocalEvent(OnDoAfterHandleState); + } + + private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args) + { + if (args.Current is not DoAfterComponentState state) + return; + + var toRemove = new RemQueue(); + + foreach (var (id, doAfter) in component.DoAfters) + { + var found = false; + + foreach (var clientdoAfter in state.DoAfters) + { + if (clientdoAfter.ID == id) + { + found = true; + break; + } + } + + if (!found) + { + toRemove.Add(doAfter); + } + } + + foreach (var doAfter in toRemove) + { + Remove(component, doAfter); + } + + foreach (var doAfter in state.DoAfters) + { + if (component.DoAfters.ContainsKey(doAfter.ID)) + continue; + + component.DoAfters.Add(doAfter.ID, doAfter); + } + + if (component.Gui == null || component.Gui.Disposed) + return; + + foreach (var (_, doAfter) in component.DoAfters) + { + component.Gui.AddDoAfter(doAfter); + } + } + + private void OnDoAfterStartup(EntityUid uid, DoAfterComponent component, ComponentStartup args) + { + Enable(component); + } + + private void OnDoAfterShutdown(EntityUid uid, DoAfterComponent component, ComponentShutdown args) + { + Disable(component); + } + + private void OnCancelledDoAfter(CancelledDoAfterMessage ev) + { + if (!TryComp(ev.Uid, out var doAfter)) return; + + Cancel(doAfter, ev.ID); } private void HandlePlayerAttached(PlayerAttachSysMessage message) @@ -46,6 +116,83 @@ namespace Content.Client.DoAfter _attachedEntity = message.AttachedEntity; } + /// + /// For handling PVS so we dispose of controls if they go out of range + /// + public void Enable(DoAfterComponent component) + { + if (component.Gui?.Disposed == false) + return; + + component.Gui = new DoAfterGui {AttachedEntity = component.Owner}; + + foreach (var (_, doAfter) in component.DoAfters) + { + component.Gui.AddDoAfter(doAfter); + } + + foreach (var (_, cancelled) in component.CancelledDoAfters) + { + component.Gui.CancelDoAfter(cancelled.ID); + } + } + + public void Disable(DoAfterComponent component) + { + component.Gui?.Dispose(); + component.Gui = null; + } + + /// + /// Remove a DoAfter without showing a cancellation graphic. + /// + /// + public void Remove(DoAfterComponent component, ClientDoAfter clientDoAfter) + { + component.DoAfters.Remove(clientDoAfter.ID); + + var found = false; + + for (var i = component.CancelledDoAfters.Count - 1; i >= 0; i--) + { + var cancelled = component.CancelledDoAfters[i]; + + if (cancelled.Message == clientDoAfter) + { + component.CancelledDoAfters.RemoveAt(i); + found = true; + break; + } + } + + if (!found) + component.DoAfters.Remove(clientDoAfter.ID); + + component.Gui?.RemoveDoAfter(clientDoAfter.ID); + } + + /// + /// Mark a DoAfter as cancelled and show a cancellation graphic. + /// + /// Actual removal is handled by DoAfterEntitySystem. + /// + /// + public void Cancel(DoAfterComponent component, byte id, TimeSpan? currentTime = null) + { + foreach (var (_, cancelled) in component.CancelledDoAfters) + { + if (cancelled.ID == id) + return; + } + + if (!component.DoAfters.ContainsKey(id)) + return; + + var doAfterMessage = component.DoAfters[id]; + component.CancelledDoAfters.Add((_gameTiming.CurTime, doAfterMessage)); + component.Gui?.CancelDoAfter(id); + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -69,7 +216,7 @@ namespace Content.Client.DoAfter compPos.MapId != entXform.MapID || !viewbox.Contains(compPos.Position)) { - comp.Disable(); + Disable(comp); continue; } @@ -81,11 +228,11 @@ namespace Content.Client.DoAfter compPos, range, ent => ent == comp.Owner || ent == _attachedEntity)) { - comp.Disable(); + Disable(comp); continue; } - comp.Enable(); + Enable(comp); var userGrid = xform.Coordinates; @@ -97,22 +244,20 @@ namespace Content.Client.DoAfter // If we've passed the final time (after the excess to show completion graphic) then remove. if (elapsedTime > doAfter.Delay + ExcessTime) { - comp.Remove(doAfter); + Remove(comp, doAfter); continue; } // Don't predict cancellation if it's already finished. if (elapsedTime > doAfter.Delay) - { continue; - } // Predictions if (doAfter.BreakOnUserMove) { if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold)) { - comp.Cancel(id, currentTime); + Cancel(comp, id, currentTime); continue; } } @@ -123,7 +268,7 @@ namespace Content.Client.DoAfter !Transform(doAfter.Target.Value).Coordinates.InRange(EntityManager, doAfter.TargetGrid, doAfter.MovementThreshold)) { - comp.Cancel(id, currentTime); + Cancel(comp, id, currentTime); continue; } } @@ -136,7 +281,7 @@ namespace Content.Client.DoAfter var cancelled = comp.CancelledDoAfters[i]; if ((currentTime - cancelled.CancelTime).TotalSeconds > ExcessTime) { - comp.Remove(cancelled.Message); + Remove(comp, cancelled.Message); } } } diff --git a/Content.Server/DoAfter/DoAfterComponent.cs b/Content.Server/DoAfter/DoAfterComponent.cs index 50abca5584..383e27160c 100644 --- a/Content.Server/DoAfter/DoAfterComponent.cs +++ b/Content.Server/DoAfter/DoAfterComponent.cs @@ -1,73 +1,14 @@ -using System.Collections.Generic; using Content.Shared.DoAfter; -using Robust.Shared.GameObjects; -using Robust.Shared.Players; namespace Content.Server.DoAfter { - [RegisterComponent] + [RegisterComponent, Friend(typeof(DoAfterSystem))] public sealed class DoAfterComponent : SharedDoAfterComponent { - public IReadOnlyCollection DoAfters => _doAfters.Keys; - private readonly Dictionary _doAfters = new(); + public readonly Dictionary DoAfters = new(); // So the client knows which one to update (and so we don't send all of the do_afters every time 1 updates) // we'll just send them the index. Doesn't matter if it wraps around. - private byte _runningIndex; - - public override ComponentState GetComponentState() - { - var toAdd = new List(); - - foreach (var doAfter in DoAfters) - { - // THE ALMIGHTY PYRAMID - var clientDoAfter = new ClientDoAfter( - _doAfters[doAfter], - doAfter.UserGrid, - doAfter.TargetGrid, - doAfter.StartTime, - doAfter.EventArgs.Delay, - doAfter.EventArgs.BreakOnUserMove, - doAfter.EventArgs.BreakOnTargetMove, - doAfter.EventArgs.MovementThreshold, - doAfter.EventArgs.Target); - - toAdd.Add(clientDoAfter); - } - - return new DoAfterComponentState(toAdd); - } - - public void Add(DoAfter doAfter) - { - _doAfters.Add(doAfter, _runningIndex); - _runningIndex++; - Dirty(); - } - - public void Cancelled(DoAfter doAfter) - { - if (!_doAfters.TryGetValue(doAfter, out var index)) - return; - - _doAfters.Remove(doAfter); -#pragma warning disable 618 - SendNetworkMessage(new CancelledDoAfterMessage(index)); -#pragma warning restore 618 - } - - /// - /// Call when the particular DoAfter is finished. - /// Client should be tracking this independently. - /// - /// - public void Finished(DoAfter doAfter) - { - if (!_doAfters.ContainsKey(doAfter)) - return; - - _doAfters.Remove(doAfter); - } + public byte RunningIndex; } } diff --git a/Content.Server/DoAfter/DoAfterSystem.cs b/Content.Server/DoAfter/DoAfterSystem.cs index f878adaed6..a370d0bad4 100644 --- a/Content.Server/DoAfter/DoAfterSystem.cs +++ b/Content.Server/DoAfter/DoAfterSystem.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Content.Shared.Damage; +using Content.Shared.DoAfter; using Content.Shared.MobState; using JetBrains.Annotations; -using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; namespace Content.Server.DoAfter { @@ -19,27 +18,81 @@ namespace Content.Server.DoAfter public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(HandleDamage); - SubscribeLocalEvent(HandleStateChanged); + SubscribeLocalEvent(OnDamage); + SubscribeLocalEvent(OnStateChanged); + SubscribeLocalEvent(OnDoAfterGetState); } - private void HandleStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args) + public void Add(DoAfterComponent component, DoAfter doAfter) + { + component.DoAfters.Add(doAfter, component.RunningIndex); + component.RunningIndex++; + Dirty(component); + } + + public void Cancelled(DoAfterComponent component, DoAfter doAfter) + { + if (!component.DoAfters.TryGetValue(doAfter, out var index)) + return; + + component.DoAfters.Remove(doAfter); + + RaiseNetworkEvent(new CancelledDoAfterMessage(component.Owner, index)); + } + + /// + /// Call when the particular DoAfter is finished. + /// Client should be tracking this independently. + /// + public void Finished(DoAfterComponent component, DoAfter doAfter) + { + if (!component.DoAfters.ContainsKey(doAfter)) + return; + + component.DoAfters.Remove(doAfter); + } + + private void OnDoAfterGetState(EntityUid uid, DoAfterComponent component, ref ComponentGetState args) + { + var toAdd = new List(component.DoAfters.Count); + + foreach (var (doAfter, _) in component.DoAfters) + { + // THE ALMIGHTY PYRAMID + var clientDoAfter = new ClientDoAfter( + component.DoAfters[doAfter], + doAfter.UserGrid, + doAfter.TargetGrid, + doAfter.StartTime, + doAfter.EventArgs.Delay, + doAfter.EventArgs.BreakOnUserMove, + doAfter.EventArgs.BreakOnTargetMove, + doAfter.EventArgs.MovementThreshold, + doAfter.EventArgs.Target); + + toAdd.Add(clientDoAfter); + } + + args.State = new DoAfterComponentState(toAdd); + } + + private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args) { if (!args.CurrentMobState.IsIncapacitated()) return; - foreach (var doAfter in component.DoAfters) + foreach (var (doAfter, _) in component.DoAfters) { doAfter.Cancel(); } } - public void HandleDamage(EntityUid _, DoAfterComponent component, DamageChangedEvent args) + public void OnDamage(EntityUid _, DoAfterComponent component, DamageChangedEvent args) { if (!args.InterruptsDoAfters || !args.DamageIncreased) return; - foreach (var doAfter in component.DoAfters) + foreach (var (doAfter, _) in component.DoAfters) { if (doAfter.EventArgs.BreakOnDamage) { @@ -54,7 +107,7 @@ namespace Content.Server.DoAfter foreach (var comp in EntityManager.EntityQuery()) { - foreach (var doAfter in comp.DoAfters.ToArray()) + foreach (var (doAfter, _) in comp.DoAfters.ToArray()) { doAfter.Run(frameTime, EntityManager); @@ -75,7 +128,7 @@ namespace Content.Server.DoAfter while (_cancelled.TryDequeue(out var doAfter)) { - comp.Cancelled(doAfter); + Cancelled(comp, doAfter); if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserCancelledEvent != null) RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserCancelledEvent, false); @@ -89,7 +142,7 @@ namespace Content.Server.DoAfter while (_finished.TryDequeue(out var doAfter)) { - comp.Finished(doAfter); + Finished(comp, doAfter); if(EntityManager.EntityExists(doAfter.EventArgs.User) && doAfter.EventArgs.UserFinishedEvent != null) RaiseLocalEvent(doAfter.EventArgs.User, doAfter.EventArgs.UserFinishedEvent, false); @@ -133,13 +186,13 @@ namespace Content.Server.DoAfter // Setup var doAfter = new DoAfter(eventArgs, EntityManager); // Caller's gonna be responsible for this I guess - var doAfterComponent = EntityManager.GetComponent(eventArgs.User); - doAfterComponent.Add(doAfter); + var doAfterComponent = Comp(eventArgs.User); + Add(doAfterComponent, doAfter); return doAfter; } } - public enum DoAfterStatus + public enum DoAfterStatus : byte { Running, Cancelled, diff --git a/Content.Shared/DoAfter/SharedDoAfterComponent.cs b/Content.Shared/DoAfter/SharedDoAfterComponent.cs index 24ef46e82c..010a346aaa 100644 --- a/Content.Shared/DoAfter/SharedDoAfterComponent.cs +++ b/Content.Shared/DoAfter/SharedDoAfterComponent.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Serialization; @@ -24,13 +21,12 @@ namespace Content.Shared.DoAfter } [Serializable, NetSerializable] -#pragma warning disable 618 - public sealed class CancelledDoAfterMessage : ComponentMessage -#pragma warning restore 618 + public sealed class CancelledDoAfterMessage : EntityEventArgs { + public EntityUid Uid; public byte ID { get; } - public CancelledDoAfterMessage(byte id) + public CancelledDoAfterMessage(EntityUid uid, byte id) { ID = id; }