Adds disposal mailing units (again) (#7630)

This commit is contained in:
Julian Giebel
2022-08-14 07:57:25 +02:00
committed by GitHub
parent 91ddba9927
commit b2436c22a7
36 changed files with 801 additions and 142 deletions

View File

@@ -0,0 +1,30 @@
using Content.Shared.Disposal.Components;
namespace Content.Server.Disposal.Mailing;
[Access(typeof(MailingUnitSystem))]
[RegisterComponent]
public sealed class MailingUnitComponent : Component
{
/// <summary>
/// List of targets the mailing unit can send to.
/// Each target is just a disposal routing tag
/// </summary>
[DataField("targetList")]
public readonly List<string> TargetList = new();
/// <summary>
/// The target that gets attached to the disposal holders tag list on flush
/// </summary>
[DataField("target")]
public string? Target;
/// <summary>
/// The tag for this mailing unit
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("tag")]
public string? Tag;
public SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState? DisposalUnitInterfaceState;
}

View File

@@ -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<MailingUnitComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<MailingUnitComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<MailingUnitComponent, DisposalUnitSystem.BeforeDisposalFlushEvent>(OnBeforeFlush);
SubscribeLocalEvent<MailingUnitComponent, ConfigurationSystem.ConfigurationUpdatedEvent>(OnConfigurationUpdated);
SubscribeLocalEvent<MailingUnitComponent, ActivateInWorldEvent>(HandleActivate);
SubscribeLocalEvent<MailingUnitComponent, DisposalUnitSystem.DisposalUnitUIStateUpdatedEvent>(OnDisposalUnitUIStateChange);
SubscribeLocalEvent<MailingUnitComponent, TargetSelectedMessage>(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;
}
}
/// <summary>
/// Sends the given tag as a response to a <see cref="NetCmdRequest"/> if it's not null
/// </summary>
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);
}
/// <summary>
/// Prevents the unit from flushing if no target is selected
/// </summary>
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);
}
/// <summary>
/// Broadcast that a mail was sent including the src and target tags
/// </summary>
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);
}
/// <summary>
/// Clears the units target list and broadcasts a <see cref="NetCmdRequest"/>.
/// The target list will then get populated with <see cref="NetCmdResponse"/> responses from all active mailing units on the same grid
/// </summary>
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);
}
/// <summary>
/// Gets called when the units tag got updated
/// </summary>
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);
}
/// <summary>
/// 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
/// </summary>
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);
}
/// <summary>
/// Checks if the unit is powered if an <see cref="ApcPowerReceiverComponent"/> is present
/// </summary>
/// <returns>True if the power receiver component is powered or not present</returns>
private bool IsPowered(EntityUid uid, ApcPowerReceiverComponent? powerReceiver = null)
{
if (Resolve(uid, ref powerReceiver) && !powerReceiver.Powered)
return false;
return true;
}
}

View File

@@ -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<string>? tags = default)
{
var holder = _entMan.SpawnEntity(HolderPrototypeId, _entMan.GetComponent<TransformComponent>(Owner).MapPosition);
var holderComponent = _entMan.GetComponent<DisposalHolderComponent>(holder);
@@ -27,6 +27,9 @@ namespace Content.Server.Disposal.Tube.Components
EntitySystem.Get<AtmosphereSystem>().Merge(holderComponent.Air, from.Air);
from.Air.Clear();
if (tags != default)
holderComponent.Tags.UnionWith(tags);
return EntitySystem.Get<DisposableSystem>().EnterTube((holderComponent).Owner, Owner, holderComponent, null, this);
}

View File

@@ -26,45 +26,11 @@ namespace Content.Server.Disposal.Tube
SubscribeLocalEvent<DisposalTubeComponent, PhysicsBodyTypeChangedEvent>(BodyTypeChanged);
SubscribeLocalEvent<DisposalTubeComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
SubscribeLocalEvent<DisposalTubeComponent, BreakageEventArgs>(OnBreak);
SubscribeLocalEvent<DisposalTaggerComponent, GetVerbsEvent<InteractionVerb>>(AddOpenUIVerbs);
SubscribeLocalEvent<DisposalRouterComponent, GetVerbsEvent<InteractionVerb>>(AddOpenUIVerbs);
SubscribeLocalEvent<DisposalRouterComponent, ActivatableUIOpenAttemptEvent>(OnOpenRouterUIAttempt);
SubscribeLocalEvent<DisposalTaggerComponent, ActivatableUIOpenAttemptEvent>(OnOpenTaggerUIAttempt);
}
private void AddOpenUIVerbs(EntityUid uid, DisposalTaggerComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!EntityManager.TryGetComponent<ActorComponent?>(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<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
if (!EntityManager.TryGetComponent<ActorComponent?>(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)

View File

@@ -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);

View File

@@ -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<MetaDataComponent>(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
/// </summary>
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);
}
/// <summary>
/// Sent before the disposal unit flushes it's contents.
/// Allows adding tags for sorting and preventing the disposal unit from flushing.
/// </summary>
public sealed class BeforeDisposalFlushEvent : CancellableEntityEventArgs
{
public List<string> Tags = new();
}
/// <summary>
/// Sent before the disposal unit flushes it's contents.
/// Allows adding tags for sorting and preventing the disposal unit from flushing.
/// </summary>
public sealed class DisposalUnitUIStateUpdatedEvent : EntityEventArgs
{
public SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState State;
public DisposalUnitUIStateUpdatedEvent(SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState state)
{
State = state;
}
}
}
}