diff --git a/Content.Client/Disposal/Components/DisposalMailingUnitComponent.cs b/Content.Client/Disposal/Components/DisposalMailingUnitComponent.cs deleted file mode 100644 index f182f57cbc..0000000000 --- a/Content.Client/Disposal/Components/DisposalMailingUnitComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Disposal.Components; -using Content.Shared.DragDrop; -using Robust.Shared.GameObjects; - -namespace Content.Client.Disposal.Components -{ - [RegisterComponent] - [ComponentReference(typeof(SharedDisposalMailingUnitComponent))] - public class DisposalMailingUnitComponent : SharedDisposalMailingUnitComponent - { - public override bool DragDropOn(DragDropEvent eventArgs) - { - return false; - } - } -} diff --git a/Content.Client/Disposal/Components/DisposalUnitComponent.cs b/Content.Client/Disposal/Components/DisposalUnitComponent.cs index b843febbca..5a45cbf677 100644 --- a/Content.Client/Disposal/Components/DisposalUnitComponent.cs +++ b/Content.Client/Disposal/Components/DisposalUnitComponent.cs @@ -8,6 +8,16 @@ namespace Content.Client.Disposal.Components [ComponentReference(typeof(SharedDisposalUnitComponent))] public class DisposalUnitComponent : SharedDisposalUnitComponent { + public DisposalUnitBoundUserInterfaceState? UiState; + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + base.HandleComponentState(curState, nextState); + if (curState is not DisposalUnitComponentState state) return; + + RecentlyEjected = state.RecentlyEjected; + } + public override bool DragDropOn(DragDropEvent eventArgs) { return false; diff --git a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs new file mode 100644 index 0000000000..84b55bff5a --- /dev/null +++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using Content.Client.Disposal.Components; +using Content.Client.Disposal.UI; +using Content.Shared.Disposal; +using Robust.Client.GameObjects; + +namespace Content.Client.Disposal.Systems +{ + public sealed class DisposalUnitSystem : SharedDisposalUnitSystem + { + public List PressuringDisposals = new(); + + public void UpdateActive(DisposalUnitComponent component, bool active) + { + if (active) + { + if (!PressuringDisposals.Contains(component)) + PressuringDisposals.Add(component); + } + else + { + PressuringDisposals.Remove(component); + } + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + for (var i = PressuringDisposals.Count - 1; i >= 0; i--) + { + var comp = PressuringDisposals[i]; + if (!UpdateInterface(comp)) continue; + PressuringDisposals.RemoveAt(i); + } + } + + private bool UpdateInterface(DisposalUnitComponent component) + { + if (component.Deleted) return true; + + if (!component.Owner.TryGetComponent(out ClientUserInterfaceComponent? userInterface)) return true; + + var state = component.UiState; + if (state == null) return true; + + foreach (var inter in userInterface.Interfaces) + { + if (inter is DisposalUnitBoundUserInterface disposals) + { + return disposals.Window?.UpdateState(state) != false; + } + } + + return true; + } + } +} diff --git a/Content.Client/Disposal/UI/DisposalMailingUnitBoundUserInterface.cs b/Content.Client/Disposal/UI/DisposalMailingUnitBoundUserInterface.cs deleted file mode 100644 index 84cd3e7185..0000000000 --- a/Content.Client/Disposal/UI/DisposalMailingUnitBoundUserInterface.cs +++ /dev/null @@ -1,72 +0,0 @@ -using JetBrains.Annotations; -using Robust.Client.GameObjects; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.GameObjects; -using static Content.Shared.Disposal.Components.SharedDisposalMailingUnitComponent; - -namespace Content.Client.Disposal.UI -{ - /// - /// Initializes a and updates it when new server messages are received. - /// - [UsedImplicitly] - public class DisposalMailingUnitBoundUserInterface : BoundUserInterface - { - private DisposalMailingUnitWindow? _window; - - public DisposalMailingUnitBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) - { - } - - private void ButtonPressed(UiButton button) - { - SendMessage(new UiButtonPressedMessage(button)); - } - - protected override void Open() - { - base.Open(); - - _window = new DisposalMailingUnitWindow(); - - _window.OpenCentered(); - _window.OnClose += Close; - - _window.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject); - _window.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage); - _window.Power.OnPressed += _ => ButtonPressed(UiButton.Power); - _window.TargetListContainer.OnItemSelected += TargetSelected; - - } - - - protected override void UpdateState(BoundUserInterfaceState state) - { - base.UpdateState(state); - - if (state is not DisposalMailingUnitBoundUserInterfaceState cast) - { - return; - } - - _window?.UpdateState(cast); - } - - private void TargetSelected(ItemList.ItemListSelectedEventArgs item) - { - SendMessage(new UiTargetUpdateMessage(_window?.TargetList[item.ItemIndex])); - //(ノ°Д°)ノ︵ ┻━┻ - if (_window != null) _window.Engage.Disabled = false; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - { - _window?.Dispose(); - } - } - } -} diff --git a/Content.Client/Disposal/UI/DisposalMailingUnitWindow.cs b/Content.Client/Disposal/UI/DisposalMailingUnitWindow.cs deleted file mode 100644 index db6b24044d..0000000000 --- a/Content.Client/Disposal/UI/DisposalMailingUnitWindow.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System.Collections.Generic; -using Content.Shared.Disposal.Components; -using Robust.Client.Graphics; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.Localization; -using Robust.Shared.Maths; -using static Content.Shared.Disposal.Components.SharedDisposalMailingUnitComponent; -using static Robust.Client.UserInterface.Controls.BoxContainer; - -namespace Content.Client.Disposal.UI -{ - /// - /// Client-side UI used to control a - /// - public class DisposalMailingUnitWindow : SS14Window - { - private readonly Label _unitState; - private readonly ProgressBar _pressureBar; - private readonly Label _pressurePercentage; - public readonly Button Engage; - public readonly Button Eject; - public readonly Button Power; - - public readonly ItemList TargetListContainer; - public List TargetList; - private readonly Label _tagLabel; - - public DisposalMailingUnitWindow() - { - MinSize = SetSize = (460, 230); - TargetList = new List(); - Contents.AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - HorizontalExpand = true, - Margin = new Thickness(8, 0), - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label {Text = $"{Loc.GetString("disposal-mailing-unit-window-state-label")} "}, - new Control {MinSize = (4, 0)}, - (_unitState = new Label {Text = Loc.GetString("disposal-mailing-unit-window-ready-state")}) - } - }, - new Control {MinSize = (0, 10)}, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalExpand = true, - Children = - { - new Label {Text = Loc.GetString("disposal-mailing-unit-pressure-label")}, - new Control {MinSize = (4, 0)}, - (_pressureBar = new ProgressBar - { - MinSize = (100, 20), - HorizontalExpand = true, - MinValue = 0, - MaxValue = 1, - Page = 0, - Value = 0.5f, - Children = - { - (_pressurePercentage = new Label()) - } - }) - } - }, - new Control {MinSize = (0, 10)}, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalExpand = true, - Children = - { - new Label {Text = Loc.GetString("disposal-mailing-unit-handle-label")}, - new Control - { - MinSize = (4, 0), - HorizontalExpand = true - }, - (Engage = new Button - { - MinSize = (16, 0), - Text = Loc.GetString("disposal-mailing-unit-engage-button"), - ToggleMode = true, - Disabled = true - }) - } - }, - new Control {MinSize = (0, 10)}, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalExpand = true, - Children = - { - new Label {Text = Loc.GetString("disposal-mailing-unit-eject-label")}, - new Control - { - MinSize = (4, 0), - HorizontalExpand = true - }, - (Eject = new Button - { - MinSize = (16, 0), - Text = Loc.GetString("disposal-mailing-unit-eject-button"), - //HorizontalAlignment = HAlignment.Right - }) - } - }, - new Control {MinSize = (0, 10)}, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - (Power = new CheckButton {Text = Loc.GetString("disposal-mailing-unit-power-button")}), - } - } - } - }, - new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Margin = new Thickness(12, 0, 8, 0), - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label - { - Text = Loc.GetString("disposal-mailing-unit-destination-select-label") - } - } - }, - new Control {MinSize = new Vector2(0, 8)}, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - VerticalExpand = true, - Children = - { - (TargetListContainer = new ItemList - { - SelectMode = ItemList.ItemListSelectMode.Single, - HorizontalExpand = true, - VerticalExpand = true - }) - } - }, - new PanelContainer - { - PanelOverride = new StyleBoxFlat - { - BackgroundColor = Color.FromHex("#ACBDBA") - }, - HorizontalExpand = true, - MinSize = new Vector2(0, 1), - }, - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Margin = new Thickness(4, 0, 0, 0), - Children = - { - new Label - { - Text = Loc.GetString("disposal-mailing-unit-unit-self-reference") - }, - new Control - { - MinSize = new Vector2(4, 0) - }, - (_tagLabel = new Label - { - Text = "-", - VerticalAlignment = VAlignment.Bottom - }) - } - } - } - } - } - } - } - } - } - }); - } - - private void UpdatePressureBar(float pressure) - { - _pressureBar.Value = pressure; - - var normalized = pressure / _pressureBar.MaxValue; - - const float leftHue = 0.0f; // Red - const float middleHue = 0.066f; // Orange - const float rightHue = 0.33f; // Green - const float saturation = 1.0f; // Uniform saturation - const float value = 0.8f; // Uniform value / brightness - const float alpha = 1.0f; // Uniform alpha - - // These should add up to 1.0 or your transition won't be smooth - const float leftSideSize = 0.5f; // Fraction of _chargeBar lerped from leftHue to middleHue - const float rightSideSize = 0.5f; // Fraction of _chargeBar lerped from middleHue to rightHue - - float finalHue; - if (normalized <= leftSideSize) - { - normalized /= leftSideSize; // Adjust range to 0.0 to 1.0 - finalHue = MathHelper.Lerp(leftHue, middleHue, normalized); - } - else - { - normalized = (normalized - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0. - finalHue = MathHelper.Lerp(middleHue, rightHue, normalized); - } - - // Check if null first to avoid repeatedly creating this. - _pressureBar.ForegroundStyleBoxOverride ??= new StyleBoxFlat(); - - var foregroundStyleBoxOverride = (StyleBoxFlat) _pressureBar.ForegroundStyleBoxOverride; - foregroundStyleBoxOverride.BackgroundColor = - Color.FromHsv(new Vector4(finalHue, saturation, value, alpha)); - - var percentage = pressure / _pressureBar.MaxValue * 100; - _pressurePercentage.Text = $" {percentage:0}%"; - } - - public void UpdateState(DisposalMailingUnitBoundUserInterfaceState state) - { - Title = state.UnitName; - _unitState.Text = state.UnitState; - UpdatePressureBar(state.Pressure); - Power.Pressed = state.Powered; - Engage.Pressed = state.Engaged; - PopulateTargetList(state.Tags); - _tagLabel.Text = state.Tag; - TargetList = state.Tags; - } - - private void PopulateTargetList(List tags) - { - TargetListContainer.Clear(); - foreach (var target in tags) - { - TargetListContainer.AddItem(target); - } - } - } -} diff --git a/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs b/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs index 500ee7874c..1168d77a57 100644 --- a/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs +++ b/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs @@ -1,4 +1,6 @@ -using JetBrains.Annotations; +using Content.Client.Disposal.Components; +using Content.Client.Disposal.Systems; +using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Shared.GameObjects; using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent; @@ -11,7 +13,7 @@ namespace Content.Client.Disposal.UI [UsedImplicitly] public class DisposalUnitBoundUserInterface : BoundUserInterface { - private DisposalUnitWindow? _window; + public DisposalUnitWindow? Window; public DisposalUnitBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { @@ -20,20 +22,22 @@ namespace Content.Client.Disposal.UI private void ButtonPressed(UiButton button) { SendMessage(new UiButtonPressedMessage(button)); + // If we get client-side power stuff then we can predict the button presses but for now we won't as it stuffs + // the pressure lerp up. } protected override void Open() { base.Open(); - _window = new DisposalUnitWindow(); + Window = new DisposalUnitWindow(); - _window.OpenCentered(); - _window.OnClose += Close; + Window.OpenCentered(); + Window.OnClose += Close; - _window.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject); - _window.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage); - _window.Power.OnPressed += _ => ButtonPressed(UiButton.Power); + Window.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject); + Window.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage); + Window.Power.OnPressed += _ => ButtonPressed(UiButton.Power); } protected override void UpdateState(BoundUserInterfaceState state) @@ -45,7 +49,13 @@ namespace Content.Client.Disposal.UI return; } - _window?.UpdateState(cast); + Window?.UpdateState(cast); + + // Kinda icky but we just want client to handle its own lerping and not flood bandwidth for it. + if (!Owner.Owner.TryGetComponent(out DisposalUnitComponent? component)) return; + + component.UiState = cast; + EntitySystem.Get().UpdateActive(component, true); } protected override void Dispose(bool disposing) @@ -54,7 +64,7 @@ namespace Content.Client.Disposal.UI if (disposing) { - _window?.Dispose(); + Window?.Dispose(); } } } diff --git a/Content.Client/Disposal/UI/DisposalUnitWindow.cs b/Content.Client/Disposal/UI/DisposalUnitWindow.cs index 627cad95b4..a394993d2d 100644 --- a/Content.Client/Disposal/UI/DisposalUnitWindow.cs +++ b/Content.Client/Disposal/UI/DisposalUnitWindow.cs @@ -1,4 +1,6 @@ +using System; using Content.Client.Stylesheets; +using Content.Shared.Disposal; using Content.Shared.Disposal.Components; using Robust.Client.Graphics; using Robust.Client.UserInterface; @@ -7,6 +9,7 @@ using Robust.Client.UserInterface.CustomControls; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; +using Robust.Shared.Timing; using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent; using static Robust.Client.UserInterface.Controls.BoxContainer; @@ -130,13 +133,23 @@ namespace Content.Client.Disposal.UI Color.FromHsv(new Vector4(finalHue, saturation, value, alpha)); } - public void UpdateState(DisposalUnitBoundUserInterfaceState state) + /// + /// Update the interface state for the disposals window. + /// + /// true if we should stop updating every frame. + public bool UpdateState(DisposalUnitBoundUserInterfaceState state) { + var currentTime = IoCManager.Resolve().CurTime; + var fullTime = state.FullPressureTime; + var pressure = (float) Math.Min(1.0f, 1.0f - (fullTime.TotalSeconds - currentTime.TotalSeconds) * SharedDisposalUnitSystem.PressurePerSecond); + Title = state.UnitName; _unitState.Text = state.UnitState; - UpdatePressureBar(state.Pressure); + UpdatePressureBar(pressure); Power.Pressed = state.Powered; Engage.Pressed = state.Engaged; + + return !state.Powered || pressure >= 1.0f; } } } diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs index a75d936594..f81e4892f3 100644 --- a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs +++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Content.Server.Disposal.Tube.Components; using Content.Server.Disposal.Unit.Components; -using Content.Server.GameObjects.Components; +using Content.Server.Disposal.Unit.EntitySystems; using Content.Server.Power.Components; using NUnit.Framework; using Robust.Shared.GameObjects; @@ -24,7 +24,7 @@ namespace Content.IntegrationTests.Tests.Disposal foreach (var entity in entities) { var insertTask = unit.TryInsert(entity); - Assert.That(unit.CanInsert(entity), Is.EqualTo(result)); + Assert.That(EntitySystem.Get().CanInsert(unit, entity), Is.EqualTo(result)); insertTask.ContinueWith(task => { Assert.That(task.Result, Is.EqualTo(result)); @@ -56,7 +56,7 @@ namespace Content.IntegrationTests.Tests.Disposal Assert.That(unit.ContainedEntities, Is.SupersetOf(entities)); Assert.That(entities.Length, Is.EqualTo(unit.ContainedEntities.Count)); - Assert.That(result, Is.EqualTo(unit.TryFlush())); + Assert.That(result, Is.EqualTo(EntitySystem.Get().TryFlush(unit))); Assert.That(result || entities.Length == 0, Is.EqualTo(unit.ContainedEntities.Count == 0)); } @@ -127,7 +127,7 @@ namespace Content.IntegrationTests.Tests.Disposal // Can't insert, unanchored and unpowered var physics = disposalUnit.GetComponent(); physics.BodyType = BodyType.Dynamic; - Assert.False(unit.Anchored); + Assert.False(unit.Owner.Transform.Anchored); UnitInsertContains(unit, false, human, wrench, disposalUnit, disposalTrunk); // Anchor the disposal unit diff --git a/Content.Server/Disposal/Mailing/DisposalMailingUnitComponent.cs b/Content.Server/Disposal/Mailing/DisposalMailingUnitComponent.cs deleted file mode 100644 index fac13e2b7c..0000000000 --- a/Content.Server/Disposal/Mailing/DisposalMailingUnitComponent.cs +++ /dev/null @@ -1,794 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Content.Server.Construction.Components; -using Content.Server.DeviceNetwork; -using Content.Server.DeviceNetwork.Connections; -using Content.Server.Disposal.Tube.Components; -using Content.Server.Disposal.Unit.Components; -using Content.Server.DoAfter; -using Content.Server.Hands.Components; -using Content.Server.Items; -using Content.Server.Power.Components; -using Content.Server.UserInterface; -using Content.Shared.ActionBlocker; -using Content.Shared.Body.Components; -using Content.Shared.Configurable; -using Content.Shared.Disposal.Components; -using Content.Shared.DragDrop; -using Content.Shared.Interaction; -using Content.Shared.Movement; -using Content.Shared.Notification; -using Content.Shared.Notification.Managers; -using Content.Shared.Sound; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; -using Robust.Shared.Map; -using Robust.Shared.Physics; -using Robust.Shared.Player; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Timing; -using Robust.Shared.ViewVariables; -using Timer = Robust.Shared.Timing.Timer; - -namespace Content.Server.Disposal.Mailing -{ - [RegisterComponent] - [ComponentReference(typeof(SharedDisposalMailingUnitComponent))] - [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(IInteractUsing))] - public class DisposalMailingUnitComponent : SharedDisposalMailingUnitComponent, IInteractHand, IActivate, IInteractUsing, IDragDropOn - { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - - private const string HolderPrototypeId = "DisposalHolder"; - - /// - /// The delay for an entity trying to move out of this unit. - /// - private static readonly TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5); - - /// - /// Last time that an entity tried to exit this disposal unit. - /// - [ViewVariables] - private TimeSpan _lastExitAttempt; - - public static readonly Regex TagRegex = new("^[a-zA-Z0-9, ]*$", RegexOptions.Compiled); - - /// - /// The current pressure of this disposal unit. - /// Prevents it from flushing if it is not equal to or bigger than 1. - /// - [ViewVariables] - [DataField("pressure")] - private float _pressure = 1f; - - private bool _engaged; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("autoEngageTime")] - private readonly TimeSpan _automaticEngageTime = TimeSpan.FromSeconds(30); - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("flushDelay")] - private readonly TimeSpan _flushDelay = TimeSpan.FromSeconds(3); - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("entryDelay")] - private float _entryDelay = 0.5f; - - [DataField("receivedMessageSound")] - private SoundSpecifier _receivedMessageSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - - /// - /// Token used to cancel the automatic engage of a disposal unit - /// after an entity enters it. - /// - private CancellationTokenSource? _automaticEngageToken; - - /// - /// Container of entities inside this disposal unit. - /// - [ViewVariables] - private Container _container = default!; - - [ViewVariables] - private WiredNetworkConnection? _connection; - - [ViewVariables] public IReadOnlyList ContainedEntities => _container.ContainedEntities; - - [ViewVariables] - private readonly List _targetList = new(); - - [ViewVariables] - private string? _target; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("Tag")] - private string _tag = string.Empty; - - [ViewVariables] - public bool Powered => - !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || - receiver.Powered; - - [ViewVariables] - private PressureState State => _pressure >= 1 ? PressureState.Ready : PressureState.Pressurizing; - - [ViewVariables(VVAccess.ReadWrite)] - private bool Engaged - { - get => _engaged; - set - { - var oldEngaged = _engaged; - _engaged = value; - - if (oldEngaged == value) - { - return; - } - - UpdateVisualState(); - } - } - - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalMailingUnitUiKey.Key); - - /// - /// Store the translated state. - /// - private (PressureState State, string Localized) _locState; - - public override bool CanInsert(IEntity entity) - { - if (!Anchored) - { - return false; - } - - if (!entity.TryGetComponent(out IPhysBody? physics) || - !physics.CanCollide) - { - return false; - } - - if (!entity.HasComponent() && - !entity.HasComponent()) - { - return false; - } - return _container.CanInsert(entity); - } - - private void TryQueueEngage() - { - if (!Powered && ContainedEntities.Count == 0) - { - return; - } - - _automaticEngageToken = new CancellationTokenSource(); - - Timer.Spawn(_automaticEngageTime, () => - { - if (!TryFlush()) - { - TryQueueEngage(); - } - }, _automaticEngageToken.Token); - } - - private void AfterInsert(IEntity entity) - { - TryQueueEngage(); - - if (entity.TryGetComponent(out ActorComponent? actor)) - { - UserInterface?.Close(actor.PlayerSession); - } - - UpdateVisualState(); - } - - public async Task TryInsert(IEntity entity, IEntity? user = default) - { - if (!CanInsert(entity)) - return false; - - if (user != null && _entryDelay > 0f) - { - var doAfterSystem = EntitySystem.Get(); - - var doAfterArgs = new DoAfterEventArgs(user, _entryDelay, default, Owner) - { - BreakOnDamage = true, - BreakOnStun = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - NeedHand = false, - }; - - var result = await doAfterSystem.WaitDoAfter(doAfterArgs); - - if (result == DoAfterStatus.Cancelled) - return false; - - } - - if (!_container.Insert(entity)) - return false; - - AfterInsert(entity); - - return true; - } - - private bool TryDrop(IEntity user, IEntity entity) - { - if (!user.TryGetComponent(out HandsComponent? hands)) - { - return false; - } - - if (!CanInsert(entity) || !hands.Drop(entity, _container)) - { - return false; - } - - AfterInsert(entity); - - return true; - } - - private void Remove(IEntity entity) - { - _container.Remove(entity); - - if (ContainedEntities.Count == 0) - { - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - } - - UpdateVisualState(); - } - - private bool CanFlush() - { - return _pressure >= 1 && Powered && Anchored; - } - - private void ToggleEngage() - { - Engaged ^= true; - - if (Engaged && CanFlush()) - { - Timer.Spawn(_flushDelay, () => TryFlush()); - } - } - - public bool TryFlush() - { - if (!CanFlush()) - { - return false; - } - - var grid = _mapManager.GetGrid(Owner.Transform.GridID); - var coords = Owner.Transform.Coordinates; - var entry = grid.GetLocal(coords) - .FirstOrDefault(entity => Owner.EntityManager.ComponentManager.HasComponent(entity)); - - if (entry == default) - { - return false; - } - - var entryComponent = Owner.EntityManager.ComponentManager.GetComponent(entry); - var entities = _container.ContainedEntities.ToList(); - foreach (var entity in _container.ContainedEntities.ToList()) - { - _container.Remove(entity); - } - - if (_target == null) - { - return false; - } - - var holder = CreateTaggedHolder(entities, _target); - - entryComponent.TryInsert(holder); - - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - - _pressure = 0; - - Engaged = false; - - UpdateVisualState(true); - UpdateInterface(); - - if (_connection != null) - { - var data = new Dictionary - { - { NetworkUtils.COMMAND, NET_CMD_SENT }, - { NET_SRC, _tag }, - { NET_TARGET, _target } - }; - - _connection.Broadcast(_connection.Frequency, data); - } - - return true; - } - - private DisposalHolderComponent CreateTaggedHolder(IReadOnlyCollection entities, string tag) - { - var holder = Owner.EntityManager.SpawnEntity(HolderPrototypeId, Owner.Transform.MapPosition); - var holderComponent = holder.GetComponent(); - - holderComponent.Tags.Add(tag); - holderComponent.Tags.Add(TAGS_MAIL); - - foreach (var entity in entities) - { - holderComponent.TryInsert(entity); - } - - return holderComponent; - } - - private void UpdateTargetList() - { - _targetList.Clear(); - var payload = new Dictionary - { - { NetworkUtils.COMMAND, NET_CMD_REQUEST } - }; - - _connection?.Broadcast(_connection.Frequency, payload); - } - - private void TryEjectContents() - { - foreach (var entity in _container.ContainedEntities.ToArray()) - { - Remove(entity); - } - } - - private void TogglePower() - { - if (!Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver)) - { - return; - } - - receiver.PowerDisabled = !receiver.PowerDisabled; - UpdateInterface(); - } - - private void UpdateInterface() - { - string stateString; - - stateString = Loc.GetString($"{State}"); - var state = new DisposalUnitBoundUserInterfaceState(Owner.Name, stateString, _pressure, Powered, Engaged); - UserInterface?.SetState(state); - } - - private bool PlayerCanUse(IEntity? player) - { - if (player == null) - { - return false; - } - - var actionBlocker = EntitySystem.Get(); - - if (!actionBlocker.CanInteract(player) || - !actionBlocker.CanUse(player)) - { - return false; - } - - return true; - } - - private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) - { - if (obj.Session.AttachedEntity == null) - { - return; - } - - if (!PlayerCanUse(obj.Session.AttachedEntity)) - { - return; - } - - if (obj.Message is UiButtonPressedMessage buttonMessage) - { - switch (buttonMessage.Button) - { - case UiButton.Eject: - TryEjectContents(); - break; - case UiButton.Engage: - ToggleEngage(); - break; - case UiButton.Power: - TogglePower(); - SoundSystem.Play(Filter.Pvs(Owner), _receivedMessageSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - if (obj.Message is UiTargetUpdateMessage tagMessage && TagRegex.IsMatch(tagMessage.Target ?? string.Empty)) - { - _target = tagMessage.Target; - } - } - - private void OnConfigUpdate(Dictionary config) - { - if (config.TryGetValue("Tag", out var tag)) - _tag = tag; - } - - public void UpdateVisualState() - { - UpdateVisualState(false); - } - - private void UpdateVisualState(bool flush) - { - if (!Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - return; - } - - - if (!Anchored) - { - appearance.SetData(Visuals.VisualState, VisualState.UnAnchored); - appearance.SetData(Visuals.Handle, HandleState.Normal); - appearance.SetData(Visuals.Light, LightState.Off); - return; - } - else if (_pressure < 1) - { - appearance.SetData(Visuals.VisualState, VisualState.Charging); - } - else - { - appearance.SetData(Visuals.VisualState, VisualState.Anchored); - } - - appearance.SetData(Visuals.Handle, Engaged - ? HandleState.Engaged - : HandleState.Normal); - - if (!Powered) - { - appearance.SetData(Visuals.Light, LightState.Off); - return; - } - - if (flush) - { - appearance.SetData(Visuals.VisualState, VisualState.Flushing); - appearance.SetData(Visuals.Light, LightState.Off); - return; - } - - if (ContainedEntities.Count > 0) - { - appearance.SetData(Visuals.Light, LightState.Full); - return; - } - - appearance.SetData(Visuals.Light, _pressure < 1 - ? LightState.Charging - : LightState.Ready); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - if (!Powered) - { - return; - } - - var oldPressure = _pressure; - - _pressure = _pressure + frameTime > 1 - ? 1 - : _pressure + 0.05f * frameTime; - - if (oldPressure < 1 && _pressure >= 1) - { - UpdateVisualState(); - - if (Engaged) - { - TryFlush(); - } - } - - if (_pressure < 1.0f || oldPressure < 1.0f && _pressure >= 1.0f) - { - UpdateInterface(); - } - } - - private void PowerStateChanged(PowerChangedMessage args) - { - if (!args.Powered) - { - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - } - - UpdateVisualState(); - - if (Engaged && !TryFlush()) - { - TryQueueEngage(); - } - } - - protected override void Initialize() - { - base.Initialize(); - - _container = ContainerHelpers.EnsureContainer(Owner, Name); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += OnUiReceiveMessage; - } - - _connection = new WiredNetworkConnection(OnReceiveNetMessage, false, Owner); - UpdateInterface(); - } - - protected override void Startup() - { - base.Startup(); - - if(!Owner.HasComponent()) - { - Logger.WarningS("VitalComponentMissing", $"Disposal unit {Owner.Uid} is missing an anchorable component"); - } - - UpdateTargetList(); - UpdateVisualState(); - UpdateInterface(); - } - - protected override void OnRemove() - { - if (_container != null) - { - foreach (var entity in _container.ContainedEntities.ToArray()) - { - _container.ForceRemove(entity); - } - } - - UserInterface?.CloseAll(); - - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - - _container = null!; - - _connection!.Close(); - - base.OnRemove(); - } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - switch (message) - { - case SharedConfigurationComponent.ConfigUpdatedComponentMessage msg: - OnConfigUpdate(msg.Config); - break; - case RelayMovementEntityMessage msg: - if (!msg.Entity.TryGetComponent(out HandsComponent? hands) || - hands.Count == 0 || - _gameTiming.CurTime < _lastExitAttempt + ExitAttemptDelay) - { - break; - } - - _lastExitAttempt = _gameTiming.CurTime; - Remove(msg.Entity); - break; - - case PowerChangedMessage powerChanged: - PowerStateChanged(powerChanged); - break; - } - } - - private void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary payload, object _, bool broadcast) - { - if (payload.TryGetValue(NetworkUtils.COMMAND, out var command) && Powered) - { - if (command == NET_CMD_RESPONSE && payload.TryGetValue(NET_TAG, out var tag)) - { - _targetList.Add(tag); - UpdateInterface(); - } - - if (command == NET_CMD_REQUEST) - { - if (_tag == "" || !Powered) - return; - - var data = new Dictionary - { - {NetworkUtils.COMMAND, NET_CMD_RESPONSE}, - {NET_TAG, _tag} - }; - - _connection?.Send(frequency, sender, data); - } - } - } - - private bool IsValidInteraction(ITargetedInteractEventArgs eventArgs) - { - if (!EntitySystem.Get().CanInteract(eventArgs.User)) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("disposal-mailing-unit-is-valid-interaction-cannot-interact")); - return false; - } - - if (eventArgs.User.IsInContainer()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("disposal-mailing-unit-is-valid-interaction-cannot-reach")); - return false; - } - // This popup message doesn't appear on clicks, even when code was seperate. Unsure why. - - if (!eventArgs.User.HasComponent()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("disposal-mailing-unit-is-valid-interaction-no-hands")); - return false; - } - - return true; - } - - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - if (eventArgs.User == null) - { - return false; - } - - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return false; - } - - // Duplicated code here, not sure how else to get actor inside to make UserInterface happy. - - if (IsValidInteraction(eventArgs)) - { - UpdateTargetList(); - UpdateInterface(); - UserInterface?.Open(actor.PlayerSession); - return true; - } - - return false; - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - if (IsValidInteraction(eventArgs)) - { - UserInterface?.Open(actor.PlayerSession); - } - - return; - } - - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - return TryDrop(eventArgs.User, eventArgs.Using); - } - - public override bool CanDragDropOn(DragDropEvent eventArgs) - { - return CanInsert(eventArgs.Dragged); - } - - public override bool DragDropOn(DragDropEvent eventArgs) - { - _ = TryInsert(eventArgs.Dragged, eventArgs.User); - return true; - } - - [Verb] - private sealed class SelfInsertVerb : Verb - { - protected override void GetData(IEntity user, DisposalMailingUnitComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!EntitySystem.Get().CanInteract(user) || - component.ContainedEntities.Contains(user)) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("self-insert-verb-get-data-text"); - } - - protected override void Activate(IEntity user, DisposalMailingUnitComponent component) - { - _ = component.TryInsert(user, user); - } - } - - [Verb] - private sealed class FlushVerb : Verb - { - protected override void GetData(IEntity user, DisposalMailingUnitComponent component, VerbData data) - { - data.Visibility = VerbVisibility.Invisible; - - if (!EntitySystem.Get().CanInteract(user) || - component.ContainedEntities.Contains(user)) - { - return; - } - - data.Visibility = VerbVisibility.Visible; - data.Text = Loc.GetString("flush-verb-get-data-text"); - data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png"; - } - - protected override void Activate(IEntity user, DisposalMailingUnitComponent component) - { - component.Engaged = true; - component.TryFlush(); - } - } - } -} diff --git a/Content.Server/Disposal/Mailing/DisposalMailingUnitSystem.cs b/Content.Server/Disposal/Mailing/DisposalMailingUnitSystem.cs deleted file mode 100644 index 3156ec1ef4..0000000000 --- a/Content.Server/Disposal/Mailing/DisposalMailingUnitSystem.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Robust.Shared.GameObjects; - -namespace Content.Server.Disposal.Mailing -{ - public sealed class DisposalMailingUnitSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(BodyTypeChanged); - } - - private static void BodyTypeChanged( - EntityUid uid, - DisposalMailingUnitComponent component, - PhysicsBodyTypeChangedEvent args) - { - component.UpdateVisualState(); - } - } -} diff --git a/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs b/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs index 4b6987611a..80ccaf411c 100644 --- a/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs +++ b/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs @@ -4,11 +4,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Content.Server.Atmos; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Construction.Components; -using Content.Server.Disposal.Tube.Components; +using Content.Server.Disposal.Unit.EntitySystems; using Content.Server.DoAfter; -using Content.Server.Hands.Components; using Content.Server.Power.Components; using Content.Server.UserInterface; using Content.Shared.ActionBlocker; @@ -16,49 +13,27 @@ using Content.Shared.Acts; using Content.Shared.Atmos; using Content.Shared.Disposal.Components; using Content.Shared.DragDrop; -using Content.Shared.Interaction; -using Content.Shared.Movement; -using Content.Shared.Notification.Managers; -using Content.Shared.Sound; -using Content.Shared.Throwing; using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Log; -using Robust.Shared.Map; using Robust.Shared.Player; -using Robust.Shared.Random; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Timing; using Robust.Shared.ViewVariables; namespace Content.Server.Disposal.Unit.Components { [RegisterComponent] [ComponentReference(typeof(SharedDisposalUnitComponent))] - [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(IInteractUsing))] - public class DisposalUnitComponent : SharedDisposalUnitComponent, IInteractHand, IActivate, IInteractUsing, IThrowCollide, IGasMixtureHolder, IDestroyAct + public class DisposalUnitComponent : SharedDisposalUnitComponent, IGasMixtureHolder, IDestroyAct { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - - public override string Name => "DisposalUnit"; - - /// - /// The delay for an entity trying to move out of this unit. - /// - private static readonly TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5); - /// /// Last time that an entity tried to exit this disposal unit. /// [ViewVariables] - private TimeSpan _lastExitAttempt; + public TimeSpan LastExitAttempt; /// /// The current pressure of this disposal unit. @@ -66,19 +41,15 @@ namespace Content.Server.Disposal.Unit.Components /// [ViewVariables] [DataField("pressure")] - private float _pressure; - - private bool _engaged; + public float Pressure = 1f; [ViewVariables(VVAccess.ReadWrite)] [DataField("autoEngageTime")] - private readonly TimeSpan _automaticEngageTime = TimeSpan.FromSeconds(30); + public readonly TimeSpan _automaticEngageTime = TimeSpan.FromSeconds(30); [ViewVariables(VVAccess.ReadWrite)] [DataField("flushDelay")] - private readonly TimeSpan _flushDelay = TimeSpan.FromSeconds(3); - - [DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); + public readonly TimeSpan FlushDelay = TimeSpan.FromSeconds(3); /// /// Delay from trying to enter disposals ourselves. @@ -97,88 +68,33 @@ namespace Content.Server.Disposal.Unit.Components /// Token used to cancel the automatic engage of a disposal unit /// after an entity enters it. /// - private CancellationTokenSource? _automaticEngageToken; + public CancellationTokenSource? AutomaticEngageToken; /// /// Container of entities inside this disposal unit. /// - [ViewVariables] - private Container _container = default!; + [ViewVariables] public Container Container = default!; - [ViewVariables] public IReadOnlyList ContainedEntities => _container.ContainedEntities; + [ViewVariables] public IReadOnlyList ContainedEntities => Container.ContainedEntities; [ViewVariables] public bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; - [ViewVariables] - private PressureState State => _pressure >= 1 ? PressureState.Ready : PressureState.Pressurizing; + [ViewVariables] public PressureState State => Pressure >= 1 ? PressureState.Ready : PressureState.Pressurizing; [ViewVariables(VVAccess.ReadWrite)] - private bool Engaged - { - get => _engaged; - set - { - var oldEngaged = _engaged; - _engaged = value; + public bool Engaged { get; set; } - if (oldEngaged == value) - { - return; - } - - UpdateVisualState(); - } - } - - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalUnitUiKey.Key); + [ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalUnitUiKey.Key); [DataField("air")] public GasMixture Air { get; set; } = new GasMixture(Atmospherics.CellVolume); - public override bool CanInsert(IEntity entity) - { - if (!base.CanInsert(entity)) - return false; - - return _container.CanInsert(entity); - } - - private void TryQueueEngage() - { - if (!Powered && ContainedEntities.Count == 0) - { - return; - } - - _automaticEngageToken = new CancellationTokenSource(); - - Owner.SpawnTimer(_automaticEngageTime, () => - { - if (!TryFlush()) - { - TryQueueEngage(); - } - }, _automaticEngageToken.Token); - } - - private void AfterInsert(IEntity entity) - { - TryQueueEngage(); - - if (entity.TryGetComponent(out ActorComponent? actor)) - { - UserInterface?.Close(actor.PlayerSession); - } - - UpdateVisualState(); - } - public async Task TryInsert(IEntity entity, IEntity? user = default) { - if (!CanInsert(entity)) + if (!EntitySystem.Get().CanInsert(this, entity)) return false; var delay = user == entity ? _entryDelay : _draggedEntryDelay; @@ -204,130 +120,14 @@ namespace Content.Server.Disposal.Unit.Components return false; } - if (!_container.Insert(entity)) + if (!Container.Insert(entity)) return false; - AfterInsert(entity); + EntitySystem.Get().AfterInsert(this, entity); return true; } - private bool TryDrop(IEntity user, IEntity entity) - { - if (!user.TryGetComponent(out HandsComponent? hands)) - { - return false; - } - - if (!CanInsert(entity) || !hands.Drop(entity, _container)) - { - return false; - } - - AfterInsert(entity); - - return true; - } - - private void Remove(IEntity entity) - { - _container.Remove(entity); - - if (ContainedEntities.Count == 0) - { - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - } - - UpdateVisualState(); - } - - private bool CanFlush() - { - return _pressure >= 1 && Powered && Anchored; - } - - private void ToggleEngage() - { - Engaged ^= true; - - if (Engaged && CanFlush()) - { - Owner.SpawnTimer(_flushDelay, () => TryFlush()); - } - } - - public bool TryFlush() - { - if (!CanFlush()) - { - return false; - } - - var grid = _mapManager.GetGrid(Owner.Transform.GridID); - var coords = Owner.Transform.Coordinates; - var entry = grid.GetLocal(coords) - .FirstOrDefault(entity => Owner.EntityManager.ComponentManager.HasComponent(entity)); - - if (entry == default) - { - return false; - } - - var entryComponent = Owner.EntityManager.ComponentManager.GetComponent(entry); - - var atmosphereSystem = EntitySystem.Get(); - - if (atmosphereSystem.GetTileMixture(Owner.Transform.Coordinates, true) is {Temperature: > 0} environment) - { - var transferMoles = 0.1f * (0.05f * Atmospherics.OneAtmosphere * 1.01f - Air.Pressure) * Air.Volume / (environment.Temperature * Atmospherics.R); - - Air = environment.Remove(transferMoles); - } - - entryComponent.TryInsert(this); - - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - - _pressure = 0; - - Engaged = false; - - UpdateVisualState(true); - UpdateInterface(); - - return true; - } - - public void TryEjectContents() - { - foreach (var entity in _container.ContainedEntities.ToArray()) - { - Remove(entity); - } - } - - private void TogglePower() - { - if (!Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver)) - { - return; - } - - receiver.PowerDisabled = !receiver.PowerDisabled; - UpdateInterface(); - } - - private void UpdateInterface() - { - string stateString; - - stateString = Loc.GetString($"{State}"); - var state = new DisposalUnitBoundUserInterfaceState(Owner.Name, stateString, _pressure, Powered, Engaged); - UserInterface?.SetState(state); - } - private bool PlayerCanUse(IEntity? player) { if (player == null) @@ -346,7 +146,7 @@ namespace Content.Server.Disposal.Unit.Components return true; } - private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) + public void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) { if (obj.Session.AttachedEntity == null) { @@ -366,259 +166,25 @@ namespace Content.Server.Disposal.Unit.Components switch (message.Button) { case UiButton.Eject: - TryEjectContents(); + EntitySystem.Get().TryEjectContents(this); break; case UiButton.Engage: - ToggleEngage(); + EntitySystem.Get().ToggleEngage(this); break; case UiButton.Power: - TogglePower(); - SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); + EntitySystem.Get().TogglePower(this); + SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f)); break; default: throw new ArgumentOutOfRangeException(); } } - public void UpdateVisualState() - { - UpdateVisualState(false); - } - - private void UpdateVisualState(bool flush) - { - if (!Owner.TryGetComponent(out AppearanceComponent? appearance)) - { - return; - } - - if (!Anchored) - { - appearance.SetData(Visuals.VisualState, VisualState.UnAnchored); - appearance.SetData(Visuals.Handle, HandleState.Normal); - appearance.SetData(Visuals.Light, LightState.Off); - return; - } - else if (_pressure < 1) - { - appearance.SetData(Visuals.VisualState, VisualState.Charging); - } - else - { - appearance.SetData(Visuals.VisualState, VisualState.Anchored); - } - - appearance.SetData(Visuals.Handle, Engaged - ? HandleState.Engaged - : HandleState.Normal); - - if (!Powered) - { - appearance.SetData(Visuals.Light, LightState.Off); - return; - } - - if (flush) - { - appearance.SetData(Visuals.VisualState, VisualState.Flushing); - appearance.SetData(Visuals.Light, LightState.Off); - return; - } - - if (ContainedEntities.Count > 0) - { - appearance.SetData(Visuals.Light, LightState.Full); - return; - } - - appearance.SetData(Visuals.Light, _pressure < 1 - ? LightState.Charging - : LightState.Ready); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - if (!Powered) - { - return; - } - - var oldPressure = _pressure; - - _pressure = _pressure + frameTime > 1 - ? 1 - : _pressure + 0.05f * frameTime; - - if (oldPressure < 1 && _pressure >= 1) - { - UpdateVisualState(); - - if (Engaged) - { - TryFlush(); - } - } - - // TODO: Ideally we'd just send the start and end and client could lerp as the bandwidth would be way lower - if (_pressure < 1.0f || oldPressure < 1.0f && _pressure >= 1.0f) - { - UpdateInterface(); - } - } - - private void PowerStateChanged(PowerChangedMessage args) - { - if (!args.Powered) - { - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - } - - UpdateVisualState(); - - if (Engaged && !TryFlush()) - { - TryQueueEngage(); - } - } - - protected override void Initialize() - { - base.Initialize(); - - _container = ContainerHelpers.EnsureContainer(Owner, Name); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += OnUiReceiveMessage; - } - - UpdateInterface(); - } - - protected override void Startup() - { - base.Startup(); - - if(!Owner.HasComponent()) - { - Logger.WarningS("VitalComponentMissing", $"Disposal unit {Owner.Uid} is missing an {nameof(AnchorableComponent)}"); - } - - UpdateVisualState(); - UpdateInterface(); - } - - protected override void OnRemove() - { - foreach (var entity in _container.ContainedEntities.ToArray()) - { - _container.ForceRemove(entity); - } - - UserInterface?.CloseAll(); - - _automaticEngageToken?.Cancel(); - _automaticEngageToken = null; - - _container = null!; - - base.OnRemove(); - } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - switch (message) - { - case RelayMovementEntityMessage msg: - if (!msg.Entity.TryGetComponent(out HandsComponent? hands) || - hands.Count == 0 || - _gameTiming.CurTime < _lastExitAttempt + ExitAttemptDelay) - { - break; - } - - _lastExitAttempt = _gameTiming.CurTime; - Remove(msg.Entity); - break; - - case PowerChangedMessage powerChanged: - PowerStateChanged(powerChanged); - break; - } - } - - bool IsValidInteraction(ITargetedInteractEventArgs eventArgs) - { - if (!EntitySystem.Get().CanInteract(eventArgs.User)) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-cannot=interact")); - return false; - } - - if (eventArgs.User.IsInContainer()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-cannot-reach")); - return false; - } - // This popup message doesn't appear on clicks, even when code was seperate. Unsure why. - - if (!eventArgs.User.HasComponent()) - { - Owner.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-no-hands")); - return false; - } - - return true; - } - - - bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return false; - } - // Duplicated code here, not sure how else to get actor inside to make UserInterface happy. - - if (IsValidInteraction(eventArgs)) - { - UserInterface?.Open(actor.PlayerSession); - return true; - } - - return false; - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out ActorComponent? actor)) - { - return; - } - - if (IsValidInteraction(eventArgs)) - { - UserInterface?.Open(actor.PlayerSession); - } - - return; - } - - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - return TryDrop(eventArgs.User, eventArgs.Using); - } - public override bool CanDragDropOn(DragDropEvent eventArgs) { // Base is redundant given this already calls the base CanInsert // If that changes then update this - return CanInsert(eventArgs.Dragged); + return EntitySystem.Get().CanInsert(this, eventArgs.Dragged); } public override bool DragDropOn(DragDropEvent eventArgs) @@ -627,18 +193,6 @@ namespace Content.Server.Disposal.Unit.Components return true; } - void IThrowCollide.HitBy(ThrowCollideEventArgs eventArgs) - { - if (!CanInsert(eventArgs.Thrown) || - IoCManager.Resolve().NextDouble() > 0.75 || - !_container.Insert(eventArgs.Thrown)) - { - return; - } - - AfterInsert(eventArgs.Thrown); - } - [Verb] private sealed class SelfInsertVerb : Verb { @@ -682,14 +236,13 @@ namespace Content.Server.Disposal.Unit.Components protected override void Activate(IEntity user, DisposalUnitComponent component) { - component.Engaged = true; - component.TryFlush(); + EntitySystem.Get().Engage(component); } } void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) { - TryEjectContents(); + EntitySystem.Get().TryEjectContents(this); } } } diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs index a4c51a9784..22505ac3fc 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs @@ -1,28 +1,537 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Content.Server.Atmos.EntitySystems; using Content.Server.Disposal.Unit.Components; using Content.Server.Construction.Components; +using Content.Server.Disposal.Tube.Components; +using Content.Server.Hands.Components; +using Content.Server.Items; +using Content.Server.Power.Components; +using Content.Shared.ActionBlocker; +using Content.Shared.Atmos; +using Content.Shared.Disposal; +using Content.Shared.Disposal.Components; +using Content.Shared.Interaction; +using Content.Shared.Movement; +using Content.Shared.Notification.Managers; +using Content.Shared.Throwing; +using Robust.Server.GameObjects; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Random; namespace Content.Server.Disposal.Unit.EntitySystems { - public sealed class DisposalUnitSystem : EntitySystem + public sealed class DisposalUnitSystem : SharedDisposalUnitSystem { + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; + + private readonly List _activeDisposals = new(); + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAnchored); SubscribeLocalEvent(OnUnanchored); + // TODO: Predict me when hands predicted + SubscribeLocalEvent(HandleMovement); + SubscribeLocalEvent(HandlePowerChange); + + // Component lifetime + SubscribeLocalEvent(HandleDisposalInit); + SubscribeLocalEvent(HandleDisposalShutdown); + + SubscribeLocalEvent(HandleThrowCollide); + + // Interactions + SubscribeLocalEvent(HandleActivate); + SubscribeLocalEvent(HandleInteractHand); + SubscribeLocalEvent(HandleInteractUsing); } - private static void OnAnchored(EntityUid uid, DisposalUnitComponent component, AnchoredEvent args) + public override void Update(float frameTime) { - component.UpdateVisualState(); + base.Update(frameTime); + for (var i = _activeDisposals.Count - 1; i >= 0; i--) + { + var comp = _activeDisposals[i]; + if (!Update(comp, frameTime)) continue; + _activeDisposals.RemoveAt(i); + } } - private static void OnUnanchored(EntityUid uid, DisposalUnitComponent component, UnanchoredEvent args) + #region UI Handlers + public void ToggleEngage(DisposalUnitComponent component) { - component.UpdateVisualState(); - component.TryEjectContents(); + component.Engaged ^= true; + + if (component.Engaged) + { + Engage(component); + } + else + { + Disengage(component); + } + } + + public void TogglePower(DisposalUnitComponent component) + { + if (!ComponentManager.TryGetComponent(component.Owner.Uid, out ApcPowerReceiverComponent? receiver)) + { + return; + } + + receiver.PowerDisabled = !receiver.PowerDisabled; + UpdateInterface(component, receiver.Powered); + } + #endregion + + #region Eventbus Handlers + private void HandleActivate(EntityUid uid, DisposalUnitComponent component, ActivateInWorldEvent args) + { + if (!args.User.TryGetComponent(out ActorComponent? actor)) + { + return; + } + + args.Handled = true; + + if (IsValidInteraction(args)) + { + component.UserInterface?.Open(actor.PlayerSession); + } + } + + private void HandleInteractHand(EntityUid uid, DisposalUnitComponent component, InteractHandEvent args) + { + if (!args.User.TryGetComponent(out ActorComponent? actor)) return; + + // Duplicated code here, not sure how else to get actor inside to make UserInterface happy. + + if (!IsValidInteraction(args)) return; + component.UserInterface?.Open(actor.PlayerSession); + args.Handled = true; + } + + private void HandleInteractUsing(EntityUid uid, DisposalUnitComponent component, InteractUsingEvent args) + { + if (!args.User.TryGetComponent(out HandsComponent? hands)) + { + return; + } + + if (!CanInsert(component, args.Used) || !hands.Drop(args.Used, component.Container)) + { + return; + } + + AfterInsert(component, args.Used); + args.Handled = true; + } + + /// + /// Thrown items have a chance of bouncing off the unit and not going in. + /// + private void HandleThrowCollide(EntityUid uid, DisposalUnitComponent component, ThrowCollideEvent args) + { + if (!CanInsert(component, args.Thrown) || + _robustRandom.NextDouble() > 0.75 || + !component.Container.Insert(args.Thrown)) + { + return; + } + + AfterInsert(component, args.Thrown); + } + + private void HandleDisposalInit(EntityUid uid, DisposalUnitComponent component, ComponentInit args) + { + component.Container = component.Owner.EnsureContainer(component.Name); + + if (component.UserInterface != null) + { + component.UserInterface.OnReceiveMessage += component.OnUiReceiveMessage; + } + + UpdateInterface(component, component.Powered); + + if (!component.Owner.HasComponent()) + { + Logger.WarningS("VitalComponentMissing", $"Disposal unit {uid} is missing an {nameof(AnchorableComponent)}"); + } + } + + private void HandleDisposalShutdown(EntityUid uid, DisposalUnitComponent component, ComponentShutdown args) + { + foreach (var entity in component.Container.ContainedEntities.ToArray()) + { + component.Container.ForceRemove(entity); + } + + component.UserInterface?.CloseAll(); + + component.AutomaticEngageToken?.Cancel(); + component.AutomaticEngageToken = null; + + component.Container = null!; + _activeDisposals.Remove(component); + } + + private void HandlePowerChange(EntityUid uid, DisposalUnitComponent component, PowerChangedEvent args) + { + // TODO: Need to check the other stuff. + if (!args.Powered) + { + component.AutomaticEngageToken?.Cancel(); + component.AutomaticEngageToken = null; + } + + HandleStateChange(component, args.Powered && component.State == SharedDisposalUnitComponent.PressureState.Pressurizing); + UpdateVisualState(component); + UpdateInterface(component, args.Powered); + + if (component.Engaged && !TryFlush(component)) + { + TryQueueEngage(component); + } + } + + /// + /// Add or remove this disposal from the active ones for updating. + /// + public void HandleStateChange(DisposalUnitComponent component, bool active) + { + if (active) + { + if (!_activeDisposals.Contains(component)) + _activeDisposals.Add(component); + } + else + { + _activeDisposals.Remove(component); + } + } + + private void HandleMovement(EntityUid uid, DisposalUnitComponent component, RelayMovementEntityEvent args) + { + var currentTime = GameTiming.CurTime; + + if (!args.Entity.TryGetComponent(out HandsComponent? hands) || + hands.Count == 0 || + currentTime < component.LastExitAttempt + ExitAttemptDelay) + { + return; + } + + component.LastExitAttempt = currentTime; + Remove(component, args.Entity); + } + + private void OnAnchored(EntityUid uid, DisposalUnitComponent component, AnchoredEvent args) + { + UpdateVisualState(component); + } + + private void OnUnanchored(EntityUid uid, DisposalUnitComponent component, UnanchoredEvent args) + { + UpdateVisualState(component); + TryEjectContents(component); + } + #endregion + + /// + /// Work out if we can stop updating this disposals component i.e. full pressure and nothing colliding. + /// + private bool Update(DisposalUnitComponent component, float frameTime) + { + var oldPressure = component.Pressure; + + component.Pressure = MathF.Min(1.0f, component.Pressure + PressurePerSecond * frameTime); + + var state = component.State; + + if (oldPressure < 1 && state == SharedDisposalUnitComponent.PressureState.Ready) + { + UpdateVisualState(component); + + if (component.Engaged) + { + TryFlush(component); + } + } + + Box2? disposalsBounds = null; + var count = component.RecentlyEjected.Count; + + if (count > 0) + { + if (!component.Owner.TryGetComponent(out PhysicsComponent? disposalsBody)) + { + component.RecentlyEjected.Clear(); + } + else + { + disposalsBounds = disposalsBody.GetWorldAABB(); + } + } + + for (var i = component.RecentlyEjected.Count - 1; i >= 0; i--) + { + var uid = component.RecentlyEjected[i]; + if (EntityManager.EntityExists(uid) && + ComponentManager.TryGetComponent(uid, out PhysicsComponent? body)) + { + // TODO: We need to use a specific collision method (which sloth hasn't coded yet) for actual bounds overlaps. + // Check for itemcomp as we won't just block the disposal unit "sleeping" for something it can't collide with anyway. + if (!ComponentManager.HasComponent(uid) && body.GetWorldAABB().Intersects(disposalsBounds!.Value)) continue; + component.RecentlyEjected.RemoveAt(i); + } + } + + if (count != component.RecentlyEjected.Count) + component.Dirty(); + + return state == SharedDisposalUnitComponent.PressureState.Ready && component.RecentlyEjected.Count == 0; + } + + private bool IsValidInteraction(ITargetedInteractEventArgs eventArgs) + { + if (!Get().CanInteract(eventArgs.User)) + { + eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-cannot=interact")); + return false; + } + + if (eventArgs.User.IsInContainer()) + { + eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-cannot-reach")); + return false; + } + // This popup message doesn't appear on clicks, even when code was seperate. Unsure why. + + if (!eventArgs.User.HasComponent()) + { + eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("ui-disposal-unit-is-valid-interaction-no-hands")); + return false; + } + + return true; + } + + public bool TryFlush(DisposalUnitComponent component) + { + if (component.Deleted || !CanFlush(component)) + { + return false; + } + + var grid = _mapManager.GetGrid(component.Owner.Transform.GridID); + var coords = component.Owner.Transform.Coordinates; + var entry = grid.GetLocal(coords) + .FirstOrDefault(entity => EntityManager.ComponentManager.HasComponent(entity)); + + if (entry == default) + { + return false; + } + + var air = component.Air; + var entryComponent = EntityManager.ComponentManager.GetComponent(entry); + + if (_atmosSystem.GetTileMixture(component.Owner.Transform.Coordinates, true) is {Temperature: > 0} environment) + { + var transferMoles = 0.1f * (0.05f * Atmospherics.OneAtmosphere * 1.01f - air.Pressure) * air.Volume / (environment.Temperature * Atmospherics.R); + + component.Air = environment.Remove(transferMoles); + } + + entryComponent.TryInsert(component); + + component.AutomaticEngageToken?.Cancel(); + component.AutomaticEngageToken = null; + + component.Pressure = 0; + + component.Engaged = false; + + HandleStateChange(component, true); + UpdateVisualState(component, true); + UpdateInterface(component, component.Powered); + + return true; + } + + public void UpdateInterface(DisposalUnitComponent component, bool powered) + { + var stateString = Loc.GetString($"{component.State}"); + var state = new SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState(component.Owner.Name, stateString, EstimatedFullPressure(component), powered, component.Engaged); + component.UserInterface?.SetState(state); + } + + private TimeSpan EstimatedFullPressure(DisposalUnitComponent component) + { + if (component.State == SharedDisposalUnitComponent.PressureState.Ready) return TimeSpan.Zero; + + var currentTime = GameTiming.CurTime; + var pressure = component.Pressure; + + return TimeSpan.FromSeconds(currentTime.TotalSeconds + (1.0f - pressure) / PressurePerSecond); + } + + public void UpdateVisualState(DisposalUnitComponent component) + { + UpdateVisualState(component, false); + } + + public void UpdateVisualState(DisposalUnitComponent component, bool flush) + { + if (!component.Owner.TryGetComponent(out SharedAppearanceComponent? appearance)) + { + return; + } + + if (!component.Owner.Transform.Anchored) + { + appearance.SetData(SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.UnAnchored); + appearance.SetData(SharedDisposalUnitComponent.Visuals.Handle, SharedDisposalUnitComponent.HandleState.Normal); + appearance.SetData(SharedDisposalUnitComponent.Visuals.Light, SharedDisposalUnitComponent.LightState.Off); + return; + } + + appearance.SetData(SharedDisposalUnitComponent.Visuals.VisualState, component.Pressure < 1 ? SharedDisposalUnitComponent.VisualState.Charging : SharedDisposalUnitComponent.VisualState.Anchored); + + appearance.SetData(SharedDisposalUnitComponent.Visuals.Handle, component.Engaged + ? SharedDisposalUnitComponent.HandleState.Engaged + : SharedDisposalUnitComponent.HandleState.Normal); + + if (!component.Powered) + { + appearance.SetData(SharedDisposalUnitComponent.Visuals.Light, SharedDisposalUnitComponent.LightState.Off); + return; + } + + if (flush) + { + appearance.SetData(SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Flushing); + appearance.SetData(SharedDisposalUnitComponent.Visuals.Light, SharedDisposalUnitComponent.LightState.Off); + return; + } + + if (component.ContainedEntities.Count > 0) + { + appearance.SetData(SharedDisposalUnitComponent.Visuals.Light, SharedDisposalUnitComponent.LightState.Full); + return; + } + + appearance.SetData(SharedDisposalUnitComponent.Visuals.Light, component.Pressure < 1 + ? SharedDisposalUnitComponent.LightState.Charging + : SharedDisposalUnitComponent.LightState.Ready); + } + + public void Remove(DisposalUnitComponent component, IEntity entity) + { + component.Container.Remove(entity); + + if (component.ContainedEntities.Count == 0) + { + component.AutomaticEngageToken?.Cancel(); + component.AutomaticEngageToken = null; + } + + if (!component.RecentlyEjected.Contains(entity.Uid)) + component.RecentlyEjected.Add(entity.Uid); + + component.Dirty(); + HandleStateChange(component, true); + UpdateVisualState(component); + } + + public bool CanFlush(DisposalUnitComponent component) + { + return component.State == SharedDisposalUnitComponent.PressureState.Ready && component.Powered && component.Owner.Transform.Anchored; + } + + public void Engage(DisposalUnitComponent component) + { + component.Engaged = true; + UpdateVisualState(component); + UpdateInterface(component, component.Powered); + + if (CanFlush(component)) + { + component.Owner.SpawnTimer(component.FlushDelay, () => TryFlush(component)); + } + } + + public void Disengage(DisposalUnitComponent component) + { + component.Engaged = false; + UpdateVisualState(component); + UpdateInterface(component, component.Powered); + } + + /// + /// Remove all entities currently in the disposal unit. + /// + public void TryEjectContents(DisposalUnitComponent component) + { + foreach (var entity in component.Container.ContainedEntities.ToArray()) + { + Remove(component, entity); + } + } + + public override bool CanInsert(SharedDisposalUnitComponent component, IEntity entity) + { + if (!base.CanInsert(component, entity) || component is not DisposalUnitComponent serverComp) + return false; + + return serverComp.Container.CanInsert(entity); + } + + /// + /// If something is inserted (or the likes) then we'll queue up a flush in the future. + /// + public void TryQueueEngage(DisposalUnitComponent component) + { + if (component.Deleted || !component.Powered && component.ContainedEntities.Count == 0) + { + return; + } + + component.AutomaticEngageToken = new CancellationTokenSource(); + + component.Owner.SpawnTimer(component._automaticEngageTime, () => + { + if (!TryFlush(component)) + { + TryQueueEngage(component); + } + }, component.AutomaticEngageToken.Token); + } + + public void AfterInsert(DisposalUnitComponent component, IEntity entity) + { + TryQueueEngage(component); + + if (entity.TryGetComponent(out ActorComponent? actor)) + { + component.UserInterface?.Close(actor.PlayerSession); + } + + UpdateVisualState(component); } } } diff --git a/Content.Shared/Disposal/Components/SharedDisposalMailingUnitComponent.cs b/Content.Shared/Disposal/Components/SharedDisposalMailingUnitComponent.cs deleted file mode 100644 index 213f6f5cb0..0000000000 --- a/Content.Shared/Disposal/Components/SharedDisposalMailingUnitComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; -using Robust.Shared.Utility; - -namespace Content.Shared.Disposal.Components -{ - public abstract class SharedDisposalMailingUnitComponent : SharedDisposalUnitComponent - { - public override string Name => "DisposalMailingUnit"; - - public const string TAGS_MAIL = "mail"; - - public const string NET_TAG = "tag"; - public const string NET_SRC = "src"; - public const string NET_TARGET = "target"; - public const string NET_CMD_SENT = "mail_sent"; - public const string NET_CMD_REQUEST = "get_mailer_tag"; - public const string NET_CMD_RESPONSE = "mailer_tag"; - - [Serializable, NetSerializable] - public new enum UiButton : byte - { - Eject, - Engage, - Power - } - - [Serializable, NetSerializable] - public class DisposalMailingUnitBoundUserInterfaceState : BoundUserInterfaceState, IEquatable, ICloneable - { - public readonly string UnitName; - public readonly string UnitState; - public readonly float Pressure; - public readonly bool Powered; - public readonly bool Engaged; - public readonly string Tag; - public readonly List Tags; - public readonly string? Target; - - public DisposalMailingUnitBoundUserInterfaceState(string unitName, string unitState, float pressure, bool powered, - bool engaged, string tag, List tags, string? target) - { - UnitName = unitName; - UnitState = unitState; - Pressure = pressure; - Powered = powered; - Engaged = engaged; - Tag = tag; - Tags = tags; - Target = target; - } - - public object Clone() - { - return new DisposalMailingUnitBoundUserInterfaceState(UnitName, UnitState, Pressure, Powered, Engaged, Tag, (List)Tags.Clone(), Target); - } - - public bool Equals(DisposalMailingUnitBoundUserInterfaceState? other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return UnitName == other.UnitName && - UnitState == other.UnitState && - Powered == other.Powered && - Engaged == other.Engaged && - Pressure.Equals(other.Pressure) && - Tag == other.Tag && - Target == other.Target; - } - } - - /// - /// Message data sent from client to server when a mailing unit ui button is pressed. - /// - [Serializable, NetSerializable] - public new class UiButtonPressedMessage : BoundUserInterfaceMessage - { - public readonly UiButton Button; - - public UiButtonPressedMessage(UiButton button) - { - Button = button; - } - } - - /// - /// Message data sent from client to server when the mailing units target is updated. - /// - [Serializable, NetSerializable] - public class UiTargetUpdateMessage : BoundUserInterfaceMessage - { - public readonly string? Target; - - public UiTargetUpdateMessage(string? target) - { - Target = target; - } - } - - [Serializable, NetSerializable] - public enum DisposalMailingUnitUiKey - { - Key - } - } -} diff --git a/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs b/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs index dd030ba32a..f550337a5a 100644 --- a/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs +++ b/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs @@ -1,26 +1,27 @@ using System; -using Content.Shared.Body.Components; +using System.Collections.Generic; using Content.Shared.DragDrop; -using Content.Shared.Item; -using Content.Shared.MobState; using Robust.Shared.GameObjects; -using Robust.Shared.Physics; +using Robust.Shared.GameStates; +using Robust.Shared.Players; using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; namespace Content.Shared.Disposal.Components { + [NetworkedComponent] public abstract class SharedDisposalUnitComponent : Component, IDragDropOn { public override string Name => "DisposalUnit"; - [ViewVariables] - public bool Anchored => - !Owner.TryGetComponent(out IPhysBody? physics) || - physics.BodyType == BodyType.Static; + // TODO: Could maybe turn the contact off instead far more cheaply as farseer (though not box2d) had support for it? + // Need to suss it out. + /// + /// We'll track whatever just left disposals so we know what collision we need to ignore until they stop intersecting our BB. + /// + public List RecentlyEjected = new(); [Serializable, NetSerializable] - public enum Visuals + public enum Visuals : byte { VisualState, Handle, @@ -28,7 +29,7 @@ namespace Content.Shared.Disposal.Components } [Serializable, NetSerializable] - public enum VisualState + public enum VisualState : byte { UnAnchored, Anchored, @@ -37,14 +38,14 @@ namespace Content.Shared.Disposal.Components } [Serializable, NetSerializable] - public enum HandleState + public enum HandleState : byte { Normal, Engaged } [Serializable, NetSerializable] - public enum LightState + public enum LightState : byte { Off, Charging, @@ -53,7 +54,7 @@ namespace Content.Shared.Disposal.Components } [Serializable, NetSerializable] - public enum UiButton + public enum UiButton : byte { Eject, Engage, @@ -61,15 +62,26 @@ namespace Content.Shared.Disposal.Components } [Serializable, NetSerializable] - public enum PressureState + public enum PressureState : byte { Ready, Pressurizing } - public virtual void Update(float frameTime) + public override ComponentState GetComponentState(ICommonSession player) { - return; + return new DisposalUnitComponentState(RecentlyEjected); + } + + [Serializable, NetSerializable] + protected sealed class DisposalUnitComponentState : ComponentState + { + public List RecentlyEjected; + + public DisposalUnitComponentState(List uids) + { + RecentlyEjected = uids; + } } [Serializable, NetSerializable] @@ -77,16 +89,16 @@ namespace Content.Shared.Disposal.Components { public readonly string UnitName; public readonly string UnitState; - public readonly float Pressure; + public readonly TimeSpan FullPressureTime; public readonly bool Powered; public readonly bool Engaged; - public DisposalUnitBoundUserInterfaceState(string unitName, string unitState, float pressure, bool powered, + public DisposalUnitBoundUserInterfaceState(string unitName, string unitState, TimeSpan fullPressureTime, bool powered, bool engaged) { UnitName = unitName; UnitState = unitState; - Pressure = pressure; + FullPressureTime = fullPressureTime; Powered = powered; Engaged = engaged; } @@ -99,7 +111,7 @@ namespace Content.Shared.Disposal.Components UnitState == other.UnitState && Powered == other.Powered && Engaged == other.Engaged && - Pressure.Equals(other.Pressure); + FullPressureTime.Equals(other.FullPressureTime); } } @@ -118,37 +130,15 @@ namespace Content.Shared.Disposal.Components } [Serializable, NetSerializable] - public enum DisposalUnitUiKey + public enum DisposalUnitUiKey : byte { Key } - public virtual bool CanInsert(IEntity entity) - { - if (!Anchored) - return false; - - // TODO: Probably just need a disposable tag. - if (!entity.TryGetComponent(out SharedItemComponent? storable) && - !entity.HasComponent()) - { - return false; - } - - - if (!entity.TryGetComponent(out IPhysBody? physics) || - !physics.CanCollide && storable == null) - { - if (!(entity.TryGetComponent(out IMobStateComponent? damageState) && damageState.IsDead())) { - return false; - } - } - return true; - } - + // TODO: Unfortunately these aren't really ECS yet so soontm public virtual bool CanDragDropOn(DragDropEvent eventArgs) { - return CanInsert(eventArgs.Dragged); + return EntitySystem.Get().CanInsert(this, eventArgs.Dragged); } public abstract bool DragDropOn(DragDropEvent eventArgs); diff --git a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs index 8c8e8980a9..84cc3e0506 100644 --- a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs +++ b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs @@ -1,23 +1,73 @@ -using Content.Shared.Disposal.Components; +using System; +using Content.Shared.Body.Components; +using Content.Shared.Disposal.Components; +using Content.Shared.Item; +using Content.Shared.MobState; +using Content.Shared.Throwing; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Timing; namespace Content.Shared.Disposal { [UsedImplicitly] - public sealed class SharedDisposalUnitSystem : EntitySystem + public abstract class SharedDisposalUnitSystem : EntitySystem { - public override void Update(float frameTime) + [Dependency] protected readonly IGameTiming GameTiming = default!; + + protected static TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5); + + // Percentage + public const float PressurePerSecond = 0.05f; + + public override void Initialize() { - foreach (var comp in ComponentManager.EntityQuery(true)) + base.Initialize(); + SubscribeLocalEvent(HandlePreventCollide); + } + + private void HandlePreventCollide(EntityUid uid, SharedDisposalUnitComponent component, PreventCollideEvent args) + { + var otherBody = args.BodyB.Owner.Uid; + + // Items dropped shouldn't collide but items thrown should + if (ComponentManager.HasComponent(otherBody) && + !ComponentManager.HasComponent(otherBody)) { - comp.Update(frameTime); + args.Cancel(); + return; } - foreach (var comp in ComponentManager.EntityQuery(true)) + if (component.RecentlyEjected.Contains(otherBody)) { - comp.Update(frameTime); + args.Cancel(); } } + + public virtual bool CanInsert(SharedDisposalUnitComponent component, IEntity entity) + { + if (!component.Owner.Transform.Anchored) + return false; + + // TODO: Probably just need a disposable tag. + if (!entity.TryGetComponent(out SharedItemComponent? storable) && + !entity.HasComponent()) + { + return false; + } + + + if (!entity.TryGetComponent(out IPhysBody? physics) || + !physics.CanCollide && storable == null) + { + if (!(entity.TryGetComponent(out IMobStateComponent? damageState) && damageState.IsDead())) { + return false; + } + } + return true; + } } } diff --git a/Content.Shared/Interaction/IActivate.cs b/Content.Shared/Interaction/IActivate.cs index 3135b47697..5719b8b4d1 100644 --- a/Content.Shared/Interaction/IActivate.cs +++ b/Content.Shared/Interaction/IActivate.cs @@ -38,7 +38,7 @@ namespace Content.Shared.Interaction /// Raised when an entity is activated in the world. /// [PublicAPI] - public class ActivateInWorldEvent : HandledEntityEventArgs + public class ActivateInWorldEvent : HandledEntityEventArgs, ITargetedInteractEventArgs { /// /// Entity that activated the target world entity. diff --git a/Content.Shared/Interaction/IInteractHand.cs b/Content.Shared/Interaction/IInteractHand.cs index 511b015534..5fea0adefb 100644 --- a/Content.Shared/Interaction/IInteractHand.cs +++ b/Content.Shared/Interaction/IInteractHand.cs @@ -35,7 +35,7 @@ namespace Content.Shared.Interaction /// Raised directed on a target entity when it is interacted with by a user with an empty hand. /// [PublicAPI] - public class InteractHandEvent : HandledEntityEventArgs + public class InteractHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs { /// /// Entity that triggered the interaction. diff --git a/Content.Shared/Movement/EntitySystems/SharedMoverSystem.cs b/Content.Shared/Movement/EntitySystems/SharedMoverSystem.cs index 36302161f2..f722531c91 100644 --- a/Content.Shared/Movement/EntitySystems/SharedMoverSystem.cs +++ b/Content.Shared/Movement/EntitySystems/SharedMoverSystem.cs @@ -61,6 +61,9 @@ namespace Content.Shared.Movement.EntitySystems { var relayEntityMoveMessage = new RelayMovementEntityMessage(owner); owner.Transform.Parent!.Owner.SendMessage(owner.Transform, relayEntityMoveMessage); + + var relayMoveEvent = new RelayMovementEntityEvent(owner); + owner.EntityManager.EventBus.RaiseLocalEvent(owner.Transform.ParentUid, relayMoveEvent); } } diff --git a/Content.Shared/Movement/RelayMovementEntityMessage.cs b/Content.Shared/Movement/RelayMovementEntityMessage.cs index 9c304d7cf3..cc7ed3ad0e 100644 --- a/Content.Shared/Movement/RelayMovementEntityMessage.cs +++ b/Content.Shared/Movement/RelayMovementEntityMessage.cs @@ -15,4 +15,14 @@ namespace Content.Shared.Movement Entity = entity; } } + + public sealed class RelayMovementEntityEvent : EntityEventArgs + { + public IEntity Entity { get; } + + public RelayMovementEntityEvent(IEntity entity) + { + Entity = entity; + } + } } diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index 70cd566203..8efeed5d13 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -125,7 +125,7 @@ namespace Content.Shared.Throwing { // TODO: Just pass in the bodies directly var collideMsg = new ThrowCollideEvent(user, thrown.Owner, target.Owner); - RaiseLocalEvent(collideMsg); + RaiseLocalEvent(target.Owner.Uid, collideMsg); if (collideMsg.Handled) { return; diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml index 6579cfb242..1a55aed5c7 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml @@ -26,10 +26,11 @@ bounds: "-0.4,-0.25,0.4,0.25" mass: 30 mask: - - SmallImpassable + - Impassable layer: - Opaque - - Impassable + - SmallImpassable + - VaultImpassable - MobImpassable - type: Destructible thresholds: @@ -77,20 +78,3 @@ interfaces: - key: enum.DisposalUnitUiKey.Key type: DisposalUnitBoundUserInterface - -# - type: entity -# parent: DisposalUnitBase -# id: DisposalMailingUnit -# name: disposal mailing unit -# components: -# - type: Configuration -# keys: -# - Tag -# - type: DisposalMailingUnit -# flushTime: 2 -# - type: UserInterface -# interfaces: -# - key: enum.DisposalMailingUnitUiKey.Key -# type: DisposalMailingUnitBoundUserInterface -# - key: enum.ConfigurationUiKey.Key -# type: ConfigurationBoundUserInterface