diff --git a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
index 6c649888c1..cc866db8d9 100644
--- a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
+++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
@@ -47,9 +47,9 @@ namespace Content.Client.Disposal.Systems
foreach (var inter in userInterface.Interfaces)
{
- if (inter is DisposalUnitBoundUserInterface disposals)
+ if (inter is DisposalUnitBoundUserInterface boundInterface)
{
- return disposals.Window?.UpdateState(state) != false;
+ return boundInterface.UpdateWindowState(state) != false;
}
}
diff --git a/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs b/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs
index dd4965210b..d9a31c534f 100644
--- a/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs
+++ b/Content.Client/Disposal/UI/DisposalUnitBoundUserInterface.cs
@@ -1,7 +1,10 @@
using Content.Client.Disposal.Components;
using Content.Client.Disposal.Systems;
+using Content.Shared.Disposal;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
@@ -9,12 +12,13 @@ using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
namespace Content.Client.Disposal.UI
{
///
- /// Initializes a and updates it when new server messages are received.
+ /// Initializes a or a and updates it when new server messages are received.
///
[UsedImplicitly]
public sealed class DisposalUnitBoundUserInterface : BoundUserInterface
{
- public DisposalUnitWindow? Window;
+ public MailingUnitWindow? MailingUnitWindow;
+ public DisposalUnitWindow? DisposalUnitWindow;
public DisposalUnitBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
@@ -27,35 +31,66 @@ namespace Content.Client.Disposal.UI
// the pressure lerp up.
}
+ private void TargetSelected(ItemList.ItemListSelectedEventArgs args)
+ {
+ var item = args.ItemList[args.ItemIndex];
+ SendMessage(new TargetSelectedMessage(item.Text));
+ }
+
protected override void Open()
{
base.Open();
- Window = new DisposalUnitWindow();
+ if (UiKey is MailingUnitUiKey)
+ {
+ MailingUnitWindow = new MailingUnitWindow();
- Window.OpenCentered();
- Window.OnClose += Close;
+ MailingUnitWindow.OpenCenteredRight();
+ MailingUnitWindow.OnClose += Close;
- Window.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
- Window.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
- Window.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
+ MailingUnitWindow.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
+ MailingUnitWindow.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
+ MailingUnitWindow.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
+
+ MailingUnitWindow.TargetListContainer.OnItemSelected += TargetSelected;
+ }
+ else if(UiKey is DisposalUnitUiKey)
+ {
+ DisposalUnitWindow = new DisposalUnitWindow();
+
+ DisposalUnitWindow.OpenCenteredRight();
+ DisposalUnitWindow.OnClose += Close;
+
+ DisposalUnitWindow.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
+ DisposalUnitWindow.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
+ DisposalUnitWindow.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
+ }
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
- if (state is not DisposalUnitBoundUserInterfaceState cast)
+ if (state is not MailingUnitBoundUserInterfaceState && state is not DisposalUnitBoundUserInterfaceState)
{
return;
}
- Window?.UpdateState(cast);
-
- // Kinda icky but we just want client to handle its own lerping and not flood bandwidth for it.
if (!IoCManager.Resolve().TryGetComponent(Owner.Owner, out DisposalUnitComponent? component)) return;
- component.UiState = cast;
+ switch (state)
+ {
+ case MailingUnitBoundUserInterfaceState mailingUnitState:
+ MailingUnitWindow?.UpdateState(mailingUnitState);
+ component.UiState = mailingUnitState.DisposalState;
+ break;
+
+ case DisposalUnitBoundUserInterfaceState disposalUnitState:
+ DisposalUnitWindow?.UpdateState(disposalUnitState);
+ component.UiState = disposalUnitState;
+ break;
+ }
+
EntitySystem.Get().UpdateActive(component, true);
}
@@ -63,10 +98,17 @@ namespace Content.Client.Disposal.UI
{
base.Dispose(disposing);
- if (disposing)
- {
- Window?.Dispose();
- }
+ if (!disposing) return;
+
+ MailingUnitWindow?.Dispose();
+ DisposalUnitWindow?.Dispose();
+ }
+
+ public bool? UpdateWindowState(DisposalUnitBoundUserInterfaceState state)
+ {
+ return UiKey is DisposalUnitUiKey
+ ? DisposalUnitWindow?.UpdateState(state)
+ : MailingUnitWindow?.UpdatePressure(state.FullPressureTime);
}
}
}
diff --git a/Content.Client/Disposal/UI/DisposalUnitWindow.xaml b/Content.Client/Disposal/UI/DisposalUnitWindow.xaml
index 56a1dc89c3..c8acef98ea 100644
--- a/Content.Client/Disposal/UI/DisposalUnitWindow.xaml
+++ b/Content.Client/Disposal/UI/DisposalUnitWindow.xaml
@@ -1,5 +1,6 @@
@@ -13,7 +14,7 @@
-
/// 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(pressure);
Power.Pressed = state.Powered;
Engage.Pressed = state.Engaged;
- return !state.Powered || pressure >= 1.0f;
+ return !state.Powered || PressureBar.UpdatePressure(state.FullPressureTime);
}
}
}
diff --git a/Content.Client/Disposal/UI/MailingUnitWindow.xaml b/Content.Client/Disposal/UI/MailingUnitWindow.xaml
new file mode 100644
index 0000000000..c57ca7b4c1
--- /dev/null
+++ b/Content.Client/Disposal/UI/MailingUnitWindow.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Disposal/UI/MailingUnitWindow.xaml.cs b/Content.Client/Disposal/UI/MailingUnitWindow.xaml.cs
new file mode 100644
index 0000000000..797e20ae7d
--- /dev/null
+++ b/Content.Client/Disposal/UI/MailingUnitWindow.xaml.cs
@@ -0,0 +1,50 @@
+using Content.Shared.Disposal;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Disposal.UI
+{
+ ///
+ /// Client-side UI used to control a
+ ///
+ [GenerateTypedNameReferences]
+ public sealed partial class MailingUnitWindow : DefaultWindow
+ {
+ public MailingUnitWindow()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ ///
+ /// Update the interface state for the disposals window.
+ ///
+ /// true if we should stop updating every frame.
+ public bool UpdateState(MailingUnitBoundUserInterfaceState state)
+ {
+ var disposalState = state.DisposalState;
+
+ Title = Loc.GetString("ui-mailing-unit-window-title", ("tag", state.Tag ?? " "));
+ UnitState.Text = disposalState.UnitState;
+ var pressureReached = PressureBar.UpdatePressure(disposalState.FullPressureTime);
+ Power.Pressed = disposalState.Powered;
+ Engage.Pressed = disposalState.Engaged;
+
+ //UnitTag.Text = state.Tag;
+ Target.Text = state.Target;
+
+ TargetListContainer.Clear();
+ foreach (var target in state.TargetList)
+ {
+ TargetListContainer.AddItem(target);
+ }
+
+ return !disposalState.Powered || pressureReached;
+ }
+
+ public bool UpdatePressure(TimeSpan stateFullPressureTime)
+ {
+ return PressureBar.UpdatePressure(stateFullPressureTime);
+ }
+ }
+}
diff --git a/Content.Client/Disposal/UI/PressureBar.cs b/Content.Client/Disposal/UI/PressureBar.cs
new file mode 100644
index 0000000000..bff95ca430
--- /dev/null
+++ b/Content.Client/Disposal/UI/PressureBar.cs
@@ -0,0 +1,54 @@
+using Content.Shared.Disposal;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Disposal.UI;
+
+public sealed class PressureBar : ProgressBar
+{
+ public bool UpdatePressure(TimeSpan fullTime)
+ {
+ var currentTime = IoCManager.Resolve().CurTime;
+ var pressure = (float) Math.Min(1.0f, 1.0f - (fullTime.TotalSeconds - currentTime.TotalSeconds) * SharedDisposalUnitSystem.PressurePerSecond);
+ UpdatePressureBar(pressure);
+ return pressure >= 1.0f;
+ }
+
+ private void UpdatePressureBar(float pressure)
+ {
+ Value = pressure;
+
+ var normalized = pressure / 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.
+ ForegroundStyleBoxOverride ??= new StyleBoxFlat();
+
+ var foregroundStyleBoxOverride = (StyleBoxFlat) ForegroundStyleBoxOverride;
+ foregroundStyleBoxOverride.BackgroundColor =
+ Color.FromHsv(new Vector4(finalHue, saturation, value, alpha));
+ }
+}
diff --git a/Content.Server/Configurable/ConfigurationSystem.cs b/Content.Server/Configurable/ConfigurationSystem.cs
index 01dfc6a779..66a2a56b4a 100644
--- a/Content.Server/Configurable/ConfigurationSystem.cs
+++ b/Content.Server/Configurable/ConfigurationSystem.cs
@@ -1,6 +1,7 @@
using Content.Shared.Interaction;
using Content.Shared.Tools.Components;
using Robust.Server.GameObjects;
+using Robust.Shared.Containers;
using static Content.Shared.Configurable.SharedConfigurationComponent;
namespace Content.Server.Configurable;
@@ -16,8 +17,9 @@ public sealed class ConfigurationSystem : EntitySystem
SubscribeLocalEvent(OnUpdate);
SubscribeLocalEvent(OnStartup);
SubscribeLocalEvent(OnInteractUsing);
+ SubscribeLocalEvent(OnInsert);
}
-
+
private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args)
{
if (args.Handled)
@@ -57,8 +59,31 @@ public sealed class ConfigurationSystem : EntitySystem
UpdateUi(uid, component);
- // TODO raise event.
+ var updatedEvent = new ConfigurationUpdatedEvent(component);
+ RaiseLocalEvent(uid, updatedEvent, false);
+
// TODO support float (spinbox) and enum (drop-down) configurations
// TODO support verbs.
}
+
+ private void OnInsert(EntityUid uid, ConfigurationComponent component, ContainerIsInsertingAttemptEvent args)
+ {
+ if (!TryComp(args.EntityUid, out ToolComponent? tool) || !tool.Qualities.Contains(component.QualityNeeded))
+ return;
+
+ args.Cancel();
+ }
+
+ ///
+ /// Sent when configuration values got changes
+ ///
+ public sealed class ConfigurationUpdatedEvent : EntityEventArgs
+ {
+ public ConfigurationComponent Configuration;
+
+ public ConfigurationUpdatedEvent(ConfigurationComponent configuration)
+ {
+ Configuration = configuration;
+ }
+ }
}
diff --git a/Content.Server/Disposal/Mailing/MailingUnitComponent.cs b/Content.Server/Disposal/Mailing/MailingUnitComponent.cs
new file mode 100644
index 0000000000..598c082688
--- /dev/null
+++ b/Content.Server/Disposal/Mailing/MailingUnitComponent.cs
@@ -0,0 +1,30 @@
+using Content.Shared.Disposal.Components;
+
+namespace Content.Server.Disposal.Mailing;
+
+[Access(typeof(MailingUnitSystem))]
+[RegisterComponent]
+public sealed class MailingUnitComponent : Component
+{
+ ///
+ /// List of targets the mailing unit can send to.
+ /// Each target is just a disposal routing tag
+ ///
+ [DataField("targetList")]
+ public readonly List TargetList = new();
+
+ ///
+ /// The target that gets attached to the disposal holders tag list on flush
+ ///
+ [DataField("target")]
+ public string? Target;
+
+ ///
+ /// The tag for this mailing unit
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("tag")]
+ public string? Tag;
+
+ public SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState? DisposalUnitInterfaceState;
+}
diff --git a/Content.Server/Disposal/Mailing/MailingUnitSystem.cs b/Content.Server/Disposal/Mailing/MailingUnitSystem.cs
new file mode 100644
index 0000000000..dfb85947bb
--- /dev/null
+++ b/Content.Server/Disposal/Mailing/MailingUnitSystem.cs
@@ -0,0 +1,205 @@
+using Content.Server.Configurable;
+using Content.Server.DeviceNetwork;
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.DeviceNetwork.Systems;
+using Content.Server.Disposal.Unit.EntitySystems;
+using Content.Server.Power.Components;
+using Content.Server.UserInterface;
+using Content.Shared.Disposal;
+using Content.Shared.Interaction;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Disposal.Mailing;
+
+public sealed class MailingUnitSystem : EntitySystem
+{
+ [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
+ [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
+
+ private const string MailTag = "mail";
+
+ private const string TagConfigurationKey = "tag";
+
+ private const string NetTag = "tag";
+ private const string NetSrc = "src";
+ private const string NetTarget = "target";
+ private const string NetCmdSent = "mail_sent";
+ private const string NetCmdRequest = "get_mailer_tag";
+ private const string NetCmdResponse = "mailer_tag";
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnPacketReceived);
+ SubscribeLocalEvent(OnBeforeFlush);
+ SubscribeLocalEvent(OnConfigurationUpdated);
+ SubscribeLocalEvent(HandleActivate);
+ SubscribeLocalEvent(OnDisposalUnitUIStateChange);
+ SubscribeLocalEvent(OnTargetSelected);
+ }
+
+
+ private void OnComponentInit(EntityUid uid, MailingUnitComponent component, ComponentInit args)
+ {
+ UpdateTargetList(uid, component);
+ }
+
+ private void OnPacketReceived(EntityUid uid, MailingUnitComponent component, DeviceNetworkPacketEvent args)
+ {
+ if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || !IsPowered(uid))
+ return;
+
+ switch (command)
+ {
+ case NetCmdRequest:
+ SendTagRequestResponse(uid, args, component.Tag);
+ break;
+ case NetCmdResponse when args.Data.TryGetValue(NetTag, out string? tag):
+ //Add the received tag request response to the list of targets
+ component.TargetList.Add(tag!);
+ UpdateUserInterface(component);
+ break;
+ }
+ }
+
+ ///
+ /// Sends the given tag as a response to a if it's not null
+ ///
+ private void SendTagRequestResponse(EntityUid uid, DeviceNetworkPacketEvent args, string? tag)
+ {
+ if (tag == null)
+ return;
+
+ var payload = new NetworkPayload
+ {
+ [DeviceNetworkConstants.Command] = NetCmdResponse,
+ [NetTag] = tag
+ };
+
+ _deviceNetworkSystem.QueuePacket(uid, args.Address, payload, args.Frequency);
+ }
+
+ ///
+ /// Prevents the unit from flushing if no target is selected
+ ///
+ private void OnBeforeFlush(EntityUid uid, MailingUnitComponent component, DisposalUnitSystem.BeforeDisposalFlushEvent args)
+ {
+ if (string.IsNullOrEmpty(component.Target))
+ {
+ args.Cancel();
+ return;
+ }
+
+ args.Tags.Add(MailTag);
+ args.Tags.Add(component.Target);
+
+ BroadcastSentMessage(uid, component);
+ }
+
+ ///
+ /// Broadcast that a mail was sent including the src and target tags
+ ///
+ private void BroadcastSentMessage(EntityUid uid, MailingUnitComponent component, DeviceNetworkComponent? device = null)
+ {
+ if (string.IsNullOrEmpty(component.Tag) || string.IsNullOrEmpty(component.Target) || !Resolve(uid, ref device))
+ return;
+
+ var payload = new NetworkPayload
+ {
+ [DeviceNetworkConstants.Command] = NetCmdSent,
+ [NetSrc] = component.Tag,
+ [NetTarget] = component.Target
+ };
+
+ _deviceNetworkSystem.QueuePacket(uid, null, payload, null, device);
+ }
+
+ ///
+ /// Clears the units target list and broadcasts a .
+ /// The target list will then get populated with responses from all active mailing units on the same grid
+ ///
+ private void UpdateTargetList(EntityUid uid, MailingUnitComponent component, DeviceNetworkComponent? device = null)
+ {
+ if (!Resolve(uid, ref device, false))
+ return;
+
+ var payload = new NetworkPayload
+ {
+ [DeviceNetworkConstants.Command] = NetCmdRequest
+ };
+
+ component.TargetList.Clear();
+ _deviceNetworkSystem.QueuePacket(uid, null, payload, null, device);
+ }
+
+ ///
+ /// Gets called when the units tag got updated
+ ///
+ private void OnConfigurationUpdated(EntityUid uid, MailingUnitComponent component, ConfigurationSystem.ConfigurationUpdatedEvent args)
+ {
+ var configuration = args.Configuration.Config;
+ if (!configuration.ContainsKey(TagConfigurationKey) || configuration[TagConfigurationKey] == string.Empty)
+ {
+ component.Tag = null;
+ return;
+ }
+
+ component.Tag = configuration[TagConfigurationKey];
+ UpdateUserInterface(component);
+ }
+
+ private void HandleActivate(EntityUid uid, MailingUnitComponent component, ActivateInWorldEvent args)
+ {
+ if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
+ {
+ return;
+ }
+
+ args.Handled = true;
+ UpdateTargetList(uid, component);
+ _userInterfaceSystem.GetUiOrNull(uid, MailingUnitUiKey.Key)?.Open(actor.PlayerSession);
+ }
+
+ ///
+ /// Gets called when the disposal unit components ui state changes. This is required because the mailing unit requires a disposal unit component and overrides its ui
+ ///
+ private void OnDisposalUnitUIStateChange(EntityUid uid, MailingUnitComponent component, DisposalUnitSystem.DisposalUnitUIStateUpdatedEvent args)
+ {
+ component.DisposalUnitInterfaceState = args.State;
+ UpdateUserInterface(component);
+ }
+
+ private void UpdateUserInterface(MailingUnitComponent component)
+ {
+ if (component.DisposalUnitInterfaceState == null)
+ return;
+
+ var state = new MailingUnitBoundUserInterfaceState(component.DisposalUnitInterfaceState, component.Target, component.TargetList, component.Tag);
+ component.Owner.GetUIOrNull(MailingUnitUiKey.Key)?.SetState(state);
+ }
+
+ private void OnTargetSelected(EntityUid uid, MailingUnitComponent component, TargetSelectedMessage args)
+ {
+ if (string.IsNullOrEmpty(args.target))
+ {
+ component.Target = null;
+ }
+
+ component.Target = args.target;
+ UpdateUserInterface(component);
+
+ }
+
+ ///
+ /// Checks if the unit is powered if an is present
+ ///
+ /// True if the power receiver component is powered or not present
+ private bool IsPowered(EntityUid uid, ApcPowerReceiverComponent? powerReceiver = null)
+ {
+ if (Resolve(uid, ref powerReceiver) && !powerReceiver.Powered)
+ return false;
+
+ return true;
+ }
+}
diff --git a/Content.Server/Disposal/Tube/Components/DisposalEntryComponent.cs b/Content.Server/Disposal/Tube/Components/DisposalEntryComponent.cs
index 9b6c0d1845..dbc65ab25d 100644
--- a/Content.Server/Disposal/Tube/Components/DisposalEntryComponent.cs
+++ b/Content.Server/Disposal/Tube/Components/DisposalEntryComponent.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Disposal.Unit.Components;
using Content.Server.Disposal.Unit.EntitySystems;
@@ -14,7 +14,7 @@ namespace Content.Server.Disposal.Tube.Components
private const string HolderPrototypeId = "DisposalHolder";
- public bool TryInsert(DisposalUnitComponent from)
+ public bool TryInsert(DisposalUnitComponent from, IEnumerable? tags = default)
{
var holder = _entMan.SpawnEntity(HolderPrototypeId, _entMan.GetComponent(Owner).MapPosition);
var holderComponent = _entMan.GetComponent(holder);
@@ -27,6 +27,9 @@ namespace Content.Server.Disposal.Tube.Components
EntitySystem.Get().Merge(holderComponent.Air, from.Air);
from.Air.Clear();
+ if (tags != default)
+ holderComponent.Tags.UnionWith(tags);
+
return EntitySystem.Get().EnterTube((holderComponent).Owner, Owner, holderComponent, null, this);
}
diff --git a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs
index c5e0adffa5..a58f9e7d80 100644
--- a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs
+++ b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs
@@ -26,45 +26,11 @@ namespace Content.Server.Disposal.Tube
SubscribeLocalEvent(BodyTypeChanged);
SubscribeLocalEvent(OnRelayMovement);
SubscribeLocalEvent(OnBreak);
- SubscribeLocalEvent>(AddOpenUIVerbs);
- SubscribeLocalEvent>(AddOpenUIVerbs);
SubscribeLocalEvent(OnOpenRouterUIAttempt);
SubscribeLocalEvent(OnOpenTaggerUIAttempt);
}
- private void AddOpenUIVerbs(EntityUid uid, DisposalTaggerComponent component, GetVerbsEvent args)
- {
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- if (!EntityManager.TryGetComponent(args.User, out var actor))
- return;
- var player = actor.PlayerSession;
-
- InteractionVerb verb = new();
- verb.Text = Loc.GetString("configure-verb-get-data-text");
- verb.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png";
- verb.Act = () => component.OpenUserInterface(actor);
- args.Verbs.Add(verb);
- }
-
- private void AddOpenUIVerbs(EntityUid uid, DisposalRouterComponent component, GetVerbsEvent args)
- {
- if (!args.CanAccess || !args.CanInteract)
- return;
-
- if (!EntityManager.TryGetComponent(args.User, out var actor))
- return;
- var player = actor.PlayerSession;
-
- InteractionVerb verb = new();
- verb.Text = Loc.GetString("configure-verb-get-data-text");
- verb.IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png";
- verb.Act = () => component.OpenUserInterface(actor);
- args.Verbs.Add(verb);
- }
-
private void OnRelayMovement(EntityUid uid, DisposalTubeComponent component, ref ContainerRelayMovementEntityEvent args)
{
if (_gameTiming.CurTime < component.LastClang + DisposalTubeComponent.ClangDelay)
diff --git a/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs b/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs
index 15000e54f2..074a0d8657 100644
--- a/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs
+++ b/Content.Server/Disposal/Unit/Components/DisposalUnitComponent.cs
@@ -24,6 +24,10 @@ namespace Content.Server.Disposal.Unit.Components
[DataField("pressure")]
public float Pressure = 1f;
+ [ViewVariables]
+ [DataField("autoEngageEnabled")]
+ public bool AutomaticEngage = true;
+
[ViewVariables(VVAccess.ReadWrite)]
[DataField("autoEngageTime")]
public readonly TimeSpan AutomaticEngageTime = TimeSpan.FromSeconds(30);
diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs
index 41cde6f613..b24ede063b 100644
--- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs
+++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs
@@ -131,7 +131,8 @@ namespace Content.Server.Disposal.Unit.EntitySystems
{
// This is not an interaction, activation, or alternative verb type because unfortunately most users are
// unwilling to accept that this is where they belong and don't want to accidentally climb inside.
- if (!args.CanAccess ||
+ if (!component.MobsCanEnter ||
+ !args.CanAccess ||
!args.CanInteract ||
component.Container.ContainedEntities.Contains(args.User) ||
!_actionBlockerSystem.CanMove(args.User))
@@ -423,6 +424,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
if (oldPressure < 1 && state == SharedDisposalUnitComponent.PressureState.Ready)
{
UpdateVisualState(component);
+ UpdateInterface(component, component.Powered);
if (component.Engaged)
{
@@ -512,6 +514,16 @@ namespace Content.Server.Disposal.Unit.EntitySystems
return false;
}
+ //Allows the MailingUnitSystem to add tags or prevent flushing
+ var beforeFlushArgs = new BeforeDisposalFlushEvent();
+ RaiseLocalEvent(component.Owner, beforeFlushArgs, false);
+
+ if (beforeFlushArgs.Cancelled)
+ {
+ Disengage(component);
+ return false;
+ }
+
var xform = Transform(component.Owner);
if (!TryComp(xform.GridUid, out IMapGridComponent? grid))
return false;
@@ -536,7 +548,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
component.Air = environment.Remove(transferMoles);
}
- entryComponent.TryInsert(component);
+ entryComponent.TryInsert(component, beforeFlushArgs.Tags);
component.AutomaticEngageToken?.Cancel();
component.AutomaticEngageToken = null;
@@ -558,6 +570,9 @@ namespace Content.Server.Disposal.Unit.EntitySystems
var stateString = Loc.GetString($"{component.State}");
var state = new SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState(EntityManager.GetComponent(component.Owner).EntityName, stateString, EstimatedFullPressure(component), powered, component.Engaged);
component.Owner.GetUIOrNull(SharedDisposalUnitComponent.DisposalUnitUiKey.Key)?.SetState(state);
+
+ var stateUpdatedEvent = new DisposalUnitUIStateUpdatedEvent(state);
+ RaiseLocalEvent(component.Owner, stateUpdatedEvent, false);
}
private TimeSpan EstimatedFullPressure(DisposalUnitComponent component)
@@ -686,7 +701,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
///
public void TryQueueEngage(DisposalUnitComponent component)
{
- if (component.Deleted || !component.Powered && component.Container.ContainedEntities.Count == 0)
+ if (component.Deleted || !component.AutomaticEngage || !component.Powered && component.Container.ContainedEntities.Count == 0)
{
return;
}
@@ -713,5 +728,28 @@ namespace Content.Server.Disposal.Unit.EntitySystems
UpdateVisualState(component);
}
+
+ ///
+ /// Sent before the disposal unit flushes it's contents.
+ /// Allows adding tags for sorting and preventing the disposal unit from flushing.
+ ///
+ public sealed class BeforeDisposalFlushEvent : CancellableEntityEventArgs
+ {
+ public List Tags = new();
+ }
+
+ ///
+ /// Sent before the disposal unit flushes it's contents.
+ /// Allows adding tags for sorting and preventing the disposal unit from flushing.
+ ///
+ public sealed class DisposalUnitUIStateUpdatedEvent : EntityEventArgs
+ {
+ public SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState State;
+
+ public DisposalUnitUIStateUpdatedEvent(SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState state)
+ {
+ State = state;
+ }
+ }
}
}
diff --git a/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs b/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs
index d8562ea606..b93c15dcab 100644
--- a/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs
+++ b/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs
@@ -13,6 +13,10 @@ namespace Content.Shared.Disposal.Components
///
public List RecentlyEjected = new();
+
+ [DataField("mobsCanEnter")]
+ public bool MobsCanEnter = true;
+
[Serializable, NetSerializable]
public enum Visuals : byte
{
diff --git a/Content.Shared/Disposal/MailingUnitBoundUserInterfaceState.cs b/Content.Shared/Disposal/MailingUnitBoundUserInterfaceState.cs
new file mode 100644
index 0000000000..4f67af67a4
--- /dev/null
+++ b/Content.Shared/Disposal/MailingUnitBoundUserInterfaceState.cs
@@ -0,0 +1,31 @@
+using Content.Shared.Disposal.Components;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Disposal;
+
+[Serializable, NetSerializable]
+public sealed class MailingUnitBoundUserInterfaceState: BoundUserInterfaceState, IEquatable
+{
+ public string? Target;
+ public List TargetList;
+ public string? Tag;
+ public SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState DisposalState;
+
+ public MailingUnitBoundUserInterfaceState(SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState disposalState, string? target, List targetList, string? tag)
+ {
+ DisposalState = disposalState;
+ Target = target;
+ TargetList = targetList;
+ Tag = tag;
+ }
+
+ public bool Equals(MailingUnitBoundUserInterfaceState? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return DisposalState.Equals(other.DisposalState)
+ && Target == other.Target
+ && TargetList.Equals(other.TargetList)
+ && Tag == other.Tag;
+ }
+}
diff --git a/Content.Shared/Disposal/MailingUnitUiMessages.cs b/Content.Shared/Disposal/MailingUnitUiMessages.cs
new file mode 100644
index 0000000000..42c3a521a9
--- /dev/null
+++ b/Content.Shared/Disposal/MailingUnitUiMessages.cs
@@ -0,0 +1,23 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Disposal;
+
+[Serializable, NetSerializable]
+public enum MailingUnitUiKey : byte
+{
+ Key
+}
+
+///
+/// Message data sent from client to server when a disposal unit ui button is pressed.
+///
+[Serializable, NetSerializable]
+public sealed class TargetSelectedMessage : BoundUserInterfaceMessage
+{
+ public readonly string? target;
+
+ public TargetSelectedMessage(string? target)
+ {
+ this.target = target;
+ }
+}
diff --git a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs
index 6b11d77d76..01c363459e 100644
--- a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs
+++ b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs
@@ -66,11 +66,15 @@ namespace Content.Shared.Disposal
return false;
}
+ //Check if the entity is a mob and if mobs can be inserted
+ if (EntityManager.HasComponent(entity) && !component.MobsCanEnter)
+ return false;
if (!EntityManager.TryGetComponent(entity, out IPhysBody? physics) ||
!physics.CanCollide && storable == null)
{
- if (!(EntityManager.TryGetComponent(entity, out MobStateComponent? damageState) && damageState.IsDead()))
+ if (!(EntityManager.TryGetComponent(entity, out MobStateComponent? damageState) &&
+ (!component.MobsCanEnter || damageState.IsDead())))
{
return false;
}
diff --git a/Resources/Locale/en-US/devices/device-network.ftl b/Resources/Locale/en-US/devices/device-network.ftl
index 79a5b095a4..7db4359812 100644
--- a/Resources/Locale/en-US/devices/device-network.ftl
+++ b/Resources/Locale/en-US/devices/device-network.ftl
@@ -2,6 +2,7 @@
device-frequency-prototype-name-atmos = Atmospheric Devices
device-frequency-prototype-name-suit-sensors = Suit Sensors
device-frequency-prototype-name-lights = Smart Lights
+device-frequency-prototype-name-mailing-units = Mailing Units
## camera frequencies
device-frequency-prototype-name-surveillance-camera-test = Subnet Test
diff --git a/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl b/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl
index 3f6d8d449b..8f5d4d23bd 100644
--- a/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl
+++ b/Resources/Locale/en-US/disposal/mailing/components/disposal-mailing-unit-component.ftl
@@ -1,25 +1,8 @@
## UI
-disposal-mailing-unit-window-state-label = State:
-disposal-mailing-unit-window-ready-state = Ready
-disposal-mailing-unit-pressure-label = Pressure:
-disposal-mailing-unit-handle-label = Handle:
-disposal-mailing-unit-engage-button = Engage
-disposal-mailing-unit-eject-label = Eject:
-disposal-mailing-unit-eject-button = Eject Contents
-disposal-mailing-unit-power-button = Power
-disposal-mailing-unit-destination-select-label = Select a destination:
-disposal-mailing-unit-unit-self-reference = This unit:
-disposal-mailing-unit-is-valid-interaction-cannot-interact = You can't do that!
-disposal-mailing-unit-is-valid-interaction-cannot-reach = You can't reach there!
-disposal-mailing-unit-is-valid-interaction-no-hands = You have no hands.
+ui-mailing-unit-window-title = {$tag} mailing unit
-## SelfInsertVerb
-
-disposal-self-insert-verb-get-data-text = Jump inside
-
-## FlushVerb
-
-disposal-flush-verb-get-data-text = Flush
-
-disposal-eject-verb-contents = contents
+ui-mailing-unit-button-flush = Send
+ui-mailing-unit-destination-select-label = Select a destination:
+ui-mailing-unit-self-reference-label = This unit:
+ui-mailing-unit-target-label = Destination:
diff --git a/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl b/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl
index b62d842ee3..c485649316 100644
--- a/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl
+++ b/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl
@@ -10,6 +10,11 @@ ui-disposal-unit-button-flush = Flush
ui-disposal-unit-button-eject = Eject Contents
ui-disposal-unit-button-power = Power
-## No hands
+## FlushVerb
+disposal-flush-verb-get-data-text = Flush
+## SelfInsertVerb
+disposal-self-insert-verb-get-data-text = Jump inside
+
+## No hands
disposal-unit-no-hands = You don't have hands!
diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml
index e3c2410f31..616f7a633b 100644
--- a/Resources/Prototypes/Catalog/Research/technologies.yml
+++ b/Resources/Prototypes/Catalog/Research/technologies.yml
@@ -239,6 +239,7 @@
- ComputerTelevisionCircuitboard
- AirAlarmElectronics
- FireAlarmElectronics
+ - MailingUnitElectronics
- HolofanProjector
- type: technology
diff --git a/Resources/Prototypes/Device/devicenet_frequencies.yml b/Resources/Prototypes/Device/devicenet_frequencies.yml
index 9f3c93f798..fab75a95fa 100644
--- a/Resources/Prototypes/Device/devicenet_frequencies.yml
+++ b/Resources/Prototypes/Device/devicenet_frequencies.yml
@@ -64,3 +64,8 @@
id: SmartLight #used by powered lights.
name: device-frequency-prototype-name-lights
frequency: 1173
+
+- type: deviceFrequency
+ id: MailingUnit
+ name: device-frequency-prototype-name-mailing-units
+ frequency: 2300
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/disposal.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/disposal.yml
new file mode 100644
index 0000000000..03a91fef61
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/disposal.yml
@@ -0,0 +1,13 @@
+- type: entity
+ id: MailingUnitElectronics
+ parent: BaseItem
+ name: mailing unit electronics
+ description: An electronics board used in mailing units
+ components:
+ - type: Tag
+ tags:
+ - MailingUnitElectronics
+ - DroneUsable
+ - type: Sprite
+ sprite: Objects/Misc/module.rsi
+ state: net_wired
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 74203e7fc2..ee968f1bc6 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -181,6 +181,7 @@
- APCElectronics
- AirAlarmElectronics
- FireAlarmElectronics
+ - MailingUnitElectronics
- Bucket
- MopItem
- SprayBottle
diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
index fb3302261d..e017b41bea 100644
--- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
+++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml
@@ -85,3 +85,57 @@
interfaces:
- key: enum.DisposalUnitUiKey.Key
type: DisposalUnitBoundUserInterface
+
+
+- type: entity
+ id: MailingUnit
+ parent: DisposalUnitBase
+ name: mailing unit
+ description: A pneumatic mail delivery unit.
+ components:
+ - type: Sprite
+ netsync: false
+ sprite: Structures/Piping/disposal.rsi
+ layers:
+ - state: conmailing
+ map: [ "enum.DisposalUnitVisualLayers.Base" ]
+ - state: mailover-handle
+ map: [ "enum.DisposalUnitVisualLayers.Handle" ]
+ - state: dispover-ready
+ map: [ "enum.DisposalUnitVisualLayers.Light" ]
+ - type: Construction
+ graph: DisposalMachine
+ node: mailing_unit
+ - type: DisposalUnit
+ flushTime: 2
+ autoEngageEnabled: false
+ mobsCanEnter: false
+ - type: MailingUnit
+ - type: DeviceNetwork
+ deviceNetId: Wired
+ receiveFrequencyId: MailingUnit
+ transmitFrequencyId: MailingUnit
+ - type: WiredNetworkConnection
+ - type: Configuration
+ config:
+ tag:
+ - type: Appearance
+ visuals:
+ - type: DisposalUnitVisualizer
+ state_unanchored: conmailing
+ state_anchored: mailing
+ state_charging: mailing-charging
+ overlay_charging: dispover-charge
+ overlay_ready: dispover-ready
+ overlay_full: dispover-full
+ overlay_engaged: mailover-handle
+ state_flush: mailing-flush
+ flush_sound:
+ path: /Audio/Machines/disposalflush.ogg
+ flush_time: 2
+ - type: UserInterface
+ interfaces:
+ - key: enum.MailingUnitUiKey.Key
+ type: DisposalUnitBoundUserInterface
+ - key: enum.ConfigurationUiKey.Key
+ type: ConfigurationBoundUserInterface
diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_machines.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_machines.yml
index 4179a795fc..7a5e0d9c20 100644
--- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_machines.yml
+++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_machines.yml
@@ -68,12 +68,15 @@
steps:
- tool: Screwing
doAfter: 0.25
- #- to: frame_mailing
- # steps:
- # - ... # 1x mailing unit electronics
- # amount: 1
- # - tool: Screwing
- # doAfter: 0.25
+ - to: frame_mailing
+ steps:
+ - tag: MailingUnitElectronics
+ name: Mailing Unit Electronics
+ icon:
+ sprite: "Objects/Misc/module.rsi"
+ state: "net_wired"
+ - tool: Screwing
+ doAfter: 0.25
- node: frame_unit
entity: DisposalMachineFrame
actions:
@@ -105,6 +108,37 @@
steps:
- tool: Prying
doAfter: 1
+ - node: frame_mailing
+ entity: DisposalMachineFrame
+ actions:
+ - !type:SpriteStateChange
+ state: "frame_unit"
+ edges:
+ - to: frame_electronics
+ steps:
+ - tool: Prying
+ doAfter: 0.25
+ - to: mailing_unit
+ conditions:
+ - !type:EntityAnchored
+ steps:
+ - material: Steel
+ amount: 1
+ doAfter: 1
+ - node: mailing_unit
+ entity: MailingUnit
+ edges:
+ - to: frame_mailing
+ completed:
+ - !type:SpawnPrototype
+ prototype: SheetSteel1
+ amount: 1
+ - !type:EmptyAllContainers
+ conditions:
+ - !type:EntityAnchored
+ steps:
+ - tool: Prying
+ doAfter: 1
- node: frame_inlet
entity: DisposalMachineFrame
actions:
diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml
index a3460f9f1c..4d4290297c 100644
--- a/Resources/Prototypes/Recipes/Construction/utilities.yml
+++ b/Resources/Prototypes/Recipes/Construction/utilities.yml
@@ -134,6 +134,19 @@
sprite: Structures/Piping/disposal.rsi
state: "disposal"
+- type: construction
+ name: mailing unit
+ id: MailingUnit
+ graph: DisposalMachine
+ startNode: start
+ targetNode: mailing_unit
+ category: Utilities
+ placementMode: SnapgridCenter
+ canBuildInImpassable: false
+ icon:
+ sprite: Structures/Piping/disposal.rsi
+ state: "mailing"
+
- type: construction
name: disposal pipe
id: DisposalPipe
diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml
index 69a0df1b24..7fd1361808 100644
--- a/Resources/Prototypes/Recipes/Lathes/electronics.yml
+++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml
@@ -7,6 +7,15 @@
Steel: 50
Plastic: 50
+- type: latheRecipe
+ id: MailingUnitElectronics
+ icon: Objects/Misc/module.rsi/net_wired.png
+ result: MailingUnitElectronics
+ completetime: 4
+ materials:
+ Steel: 50
+ Plastic: 50
+
- type: latheRecipe
id: DoorElectronics
icon: Objects/Misc/module.rsi/door_electronics.png
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index c5c8fef176..c012ad82ab 100644
--- a/Resources/Prototypes/tags.yml
+++ b/Resources/Prototypes/tags.yml
@@ -305,6 +305,9 @@
- type: Tag
id: Matchstick
+- type: Tag
+ id: MailingUnitElectronics
+
- type: Tag
id: Medkit
diff --git a/Resources/Textures/Structures/Piping/disposal.rsi/conmailing.png b/Resources/Textures/Structures/Piping/disposal.rsi/conmailing.png
new file mode 100644
index 0000000000..0dfa469e0e
Binary files /dev/null and b/Resources/Textures/Structures/Piping/disposal.rsi/conmailing.png differ
diff --git a/Resources/Textures/Structures/Piping/disposal.rsi/mailing-charging.png b/Resources/Textures/Structures/Piping/disposal.rsi/mailing-charging.png
new file mode 100644
index 0000000000..bec83a4c5c
Binary files /dev/null and b/Resources/Textures/Structures/Piping/disposal.rsi/mailing-charging.png differ
diff --git a/Resources/Textures/Structures/Piping/disposal.rsi/mailing-flush.png b/Resources/Textures/Structures/Piping/disposal.rsi/mailing-flush.png
new file mode 100644
index 0000000000..5cf4654016
Binary files /dev/null and b/Resources/Textures/Structures/Piping/disposal.rsi/mailing-flush.png differ
diff --git a/Resources/Textures/Structures/Piping/disposal.rsi/mailing.png b/Resources/Textures/Structures/Piping/disposal.rsi/mailing.png
new file mode 100644
index 0000000000..d51164e862
Binary files /dev/null and b/Resources/Textures/Structures/Piping/disposal.rsi/mailing.png differ
diff --git a/Resources/Textures/Structures/Piping/disposal.rsi/mailover-handle.png b/Resources/Textures/Structures/Piping/disposal.rsi/mailover-handle.png
new file mode 100644
index 0000000000..b41bfe495b
Binary files /dev/null and b/Resources/Textures/Structures/Piping/disposal.rsi/mailover-handle.png differ
diff --git a/Resources/Textures/Structures/Piping/disposal.rsi/meta.json b/Resources/Textures/Structures/Piping/disposal.rsi/meta.json
index f11b589795..fd0ed2ac25 100644
--- a/Resources/Textures/Structures/Piping/disposal.rsi/meta.json
+++ b/Resources/Textures/Structures/Piping/disposal.rsi/meta.json
@@ -16,6 +16,15 @@
]
]
},
+ {
+ "name": "conmailing",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
{
"name": "conpipe-c",
"directions": 4,
@@ -187,6 +196,15 @@
]
]
},
+ {
+ "name": "mailing",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
{
"name": "disposal-charging",
"directions": 1,
@@ -196,6 +214,15 @@
]
]
},
+ {
+ "name": "mailing-charging",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
{
"name": "disposal-flush",
"directions": 1,
@@ -213,6 +240,23 @@
]
]
},
+ {
+ "name": "mailing-flush",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.5,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
{
"name": "dispover-charge",
"directions": 1,
@@ -242,6 +286,15 @@
]
]
},
+ {
+ "name": "mailover-handle",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
{
"name": "dispover-ready",
"directions": 1,