Activatable UI component (#5184)
* Transfer most Instrument UI logic to a new component, ActivatableUIComponent * Move more ActivatableUIComponent stuff to ECS * ActivatableUI component ignore on client * ActivatableUI: Get rid of component interfaces where possible * Add in adminOnly attribute for activatable UIs This is so that porting #4926 to this will be easier * Transition Solar Control Computer to ActivatableUI * Move communications console to ActivatableUI * Move cargo console to ActivatableUI * Move ID card console to ActivatableUI * ActivatableUI: Make things more amiable to entity tests adding components weirdly * ActivatableUI: Use handling or lack thereof of events properly * ActivatableUI: component dependency issue resolution stuffs * ActivatableUISystem: Fix #5258 * More fixes because master did stuffo * Check for HandDeselectedEvent again because otherwise active-hand check doesn't work * Move just a bit more code into the system, introduce a workaround for #5258 * Purge the player status detection stuff * Oh and some obsolete stuff too
This commit is contained in:
@@ -216,6 +216,8 @@ namespace Content.Client.Entry
|
||||
"Log",
|
||||
"Hoe",
|
||||
"Seed",
|
||||
"ActivatableUI",
|
||||
"ActivatableUIRequiresPower",
|
||||
"BotanySharp",
|
||||
"PlantSampleTaker",
|
||||
"Internals",
|
||||
|
||||
@@ -21,8 +21,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.Access.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate, IInteractUsing, IBreakAct
|
||||
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IInteractUsing, IBreakAct
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
@@ -207,17 +206,6 @@ namespace Content.Server.Access.Components
|
||||
UserInterface?.SetState(newState);
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Powered) return;
|
||||
|
||||
UserInterface?.Open(actor.PlayerSession);
|
||||
}
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
var item = eventArgs.Using;
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
using System;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// This component is used as a base class for classes like SolarControlConsoleComponent.
|
||||
/// These components operate the server-side logic for the "primary UI" of a computer.
|
||||
/// That means showing the UI when a user activates it, for example.
|
||||
/// </summary>
|
||||
public abstract class BaseComputerUserInterfaceComponent : Component
|
||||
{
|
||||
protected readonly object UserInterfaceKey;
|
||||
|
||||
[ViewVariables] protected BoundUserInterface? UserInterface => Owner.GetUIOrNull(UserInterfaceKey);
|
||||
[ViewVariables] public bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered;
|
||||
|
||||
public BaseComputerUserInterfaceComponent(object key)
|
||||
{
|
||||
UserInterfaceKey = key;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (UserInterface != null)
|
||||
UserInterface.OnReceiveMessage += OnReceiveUIMessageCallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal callback used to grab session and session attached entity before any more work is done.
|
||||
/// This is so that sessionEntity is always available to checks up and down the line.
|
||||
/// </summary>
|
||||
private void OnReceiveUIMessageCallback(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
var session = obj.Session;
|
||||
var sessionEntity = session.AttachedEntity;
|
||||
if (sessionEntity == null)
|
||||
return; // No session entity, so we're probably not able to touch this.
|
||||
OnReceiveUnfilteredUserInterfaceMessage(obj, sessionEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to handle messages from the UI before filtering them.
|
||||
/// Calling base is necessary if you want this class to have any meaning.
|
||||
/// </summary>
|
||||
protected void OnReceiveUnfilteredUserInterfaceMessage(ServerBoundUserInterfaceMessage obj, IEntity sessionEntity)
|
||||
{
|
||||
// "Across all computers" "anti-cheats" ought to be put here or at some parent level (BaseDeviceUserInterfaceComponent?)
|
||||
// Determine some facts about the session.
|
||||
// Powered?
|
||||
if (!Powered)
|
||||
{
|
||||
sessionEntity.PopupMessageCursor(Loc.GetString("base-computer-ui-component-not-powered"));
|
||||
return; // Not powered, so this computer should probably do nothing.
|
||||
}
|
||||
// Can we interact?
|
||||
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(sessionEntity.Uid))
|
||||
{
|
||||
sessionEntity.PopupMessageCursor(Loc.GetString("base-computer-ui-component-cannot-interact"));
|
||||
return;
|
||||
}
|
||||
// Good to go!
|
||||
OnReceiveUserInterfaceMessage(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to handle messages from the UI.
|
||||
/// Calling base is unnecessary.
|
||||
/// These messages will automatically be blocked if the user shouldn't be able to access this computer, or if the computer has lost power.
|
||||
/// </summary>
|
||||
protected virtual void OnReceiveUserInterfaceMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
// Nothing!
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
#pragma warning disable 618
|
||||
base.HandleMessage(message, component);
|
||||
#pragma warning restore 618
|
||||
switch (message)
|
||||
{
|
||||
case PowerChangedMessage powerChanged:
|
||||
PowerReceiverOnOnPowerStateChanged(powerChanged);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PowerReceiverOnOnPowerStateChanged(PowerChangedMessage e)
|
||||
{
|
||||
if (!e.Powered)
|
||||
{
|
||||
// We need to kick off users who are using it when it loses power.
|
||||
UserInterface?.CloseAll();
|
||||
// Now alert subclass.
|
||||
ComputerLostPower();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this if you want the computer to do something when it loses power (i.e. reset state)
|
||||
/// All UIs should have been closed by the time this is called.
|
||||
/// Calling base is unnecessary.
|
||||
/// </summary>
|
||||
public virtual void ComputerLostPower()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called from ComputerUIActivatorSystem.
|
||||
/// Override this to add additional activation conditions of some sort.
|
||||
/// Calling base runs standard activation logic.
|
||||
/// *This remains inside the component for overridability.*
|
||||
/// </summary>
|
||||
public virtual void ActivateThunk(ActivateInWorldEvent eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Powered)
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("base-computer-ui-component-not-powered"));
|
||||
return;
|
||||
}
|
||||
|
||||
UserInterface?.Open(actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.Cargo.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class CargoConsoleComponent : SharedCargoConsoleComponent, IActivate
|
||||
public class CargoConsoleComponent : SharedCargoConsoleComponent
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
@@ -203,18 +202,6 @@ namespace Content.Server.Cargo.Components
|
||||
}
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Powered)
|
||||
return;
|
||||
|
||||
UserInterface?.Open(actor.PlayerSession);
|
||||
}
|
||||
|
||||
private void UpdateUIState()
|
||||
{
|
||||
if (_bankAccount == null || !Owner.IsValid())
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Cleanable;
|
||||
using Content.Server.Coordinates.Helpers;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
|
||||
@@ -21,8 +21,7 @@ using Timer = Robust.Shared.Timing.Timer;
|
||||
namespace Content.Server.Communications
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate
|
||||
public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
@@ -121,23 +120,5 @@ namespace Content.Server.Communications
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenUserInterface(IPlayerSession session)
|
||||
{
|
||||
UserInterface?.Open(session);
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
return;
|
||||
/*
|
||||
if (!Powered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
*/
|
||||
OpenUserInterface(actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Computer
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ComputerUIActivatorSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BaseComputerUserInterfaceComponent, ActivateInWorldEvent>(HandleActivate);
|
||||
}
|
||||
|
||||
private void HandleActivate(EntityUid uid, BaseComputerUserInterfaceComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
component.ActivateThunk(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Server.WireHacking;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Examine;
|
||||
|
||||
@@ -25,27 +25,11 @@ namespace Content.Server.Instruments
|
||||
{
|
||||
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class InstrumentComponent
|
||||
: SharedInstrumentComponent,
|
||||
IDropped,
|
||||
IHandSelected,
|
||||
IHandDeselected,
|
||||
IActivate,
|
||||
IUse,
|
||||
IThrown
|
||||
: SharedInstrumentComponent
|
||||
{
|
||||
private InstrumentSystem _instrumentSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The client channel currently playing the instrument, or null if there's none.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private IPlayerSession? _instrumentPlayer;
|
||||
|
||||
[DataField("handheld")]
|
||||
private bool _handheld;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _playing = false;
|
||||
|
||||
@@ -115,11 +99,7 @@ namespace Content.Server.Instruments
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the instrument is an item which can be held or not.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Handheld => _handheld;
|
||||
public IPlayerSession? InstrumentPlayer => Owner.GetComponentOrNull<ActivatableUIComponent>()?.CurrentSingleUser;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the instrument is currently playing or not.
|
||||
@@ -135,41 +115,12 @@ namespace Content.Server.Instruments
|
||||
}
|
||||
}
|
||||
|
||||
public IPlayerSession? InstrumentPlayer
|
||||
{
|
||||
get => _instrumentPlayer;
|
||||
private set
|
||||
{
|
||||
Playing = false;
|
||||
|
||||
if (_instrumentPlayer != null)
|
||||
_instrumentPlayer.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
|
||||
_instrumentPlayer = value;
|
||||
|
||||
if (value != null)
|
||||
_instrumentPlayer!.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(InstrumentUiKey.Key);
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.Session != _instrumentPlayer || e.NewStatus != SessionStatus.Disconnected) return;
|
||||
InstrumentPlayer = null;
|
||||
Clean();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnClosed += UserInterfaceOnClosed;
|
||||
}
|
||||
|
||||
_instrumentSystem = EntitySystem.Get<InstrumentSystem>();
|
||||
}
|
||||
|
||||
@@ -190,7 +141,7 @@ namespace Content.Server.Instruments
|
||||
switch (message)
|
||||
{
|
||||
case InstrumentMidiEventMessage midiEventMsg:
|
||||
if (!Playing || session != _instrumentPlayer || InstrumentPlayer == null) return;
|
||||
if (!Playing || session != InstrumentPlayer || InstrumentPlayer == null) return;
|
||||
|
||||
var send = true;
|
||||
|
||||
@@ -237,12 +188,12 @@ namespace Content.Server.Instruments
|
||||
_lastSequencerTick = Math.Max(maxTick, minTick);
|
||||
break;
|
||||
case InstrumentStartMidiMessage startMidi:
|
||||
if (session != _instrumentPlayer)
|
||||
if (session != InstrumentPlayer)
|
||||
break;
|
||||
Playing = true;
|
||||
break;
|
||||
case InstrumentStopMidiMessage stopMidi:
|
||||
if (session != _instrumentPlayer)
|
||||
if (session != InstrumentPlayer)
|
||||
break;
|
||||
Playing = false;
|
||||
Clean();
|
||||
@@ -250,99 +201,20 @@ namespace Content.Server.Instruments
|
||||
}
|
||||
}
|
||||
|
||||
private void Clean()
|
||||
public void Clean()
|
||||
{
|
||||
if (Playing)
|
||||
{
|
||||
#pragma warning disable 618
|
||||
SendNetworkMessage(new InstrumentStopMidiMessage());
|
||||
#pragma warning restore 618
|
||||
}
|
||||
Playing = false;
|
||||
_lastSequencerTick = 0;
|
||||
_batchesDropped = 0;
|
||||
_laggedBatches = 0;
|
||||
}
|
||||
|
||||
void IDropped.Dropped(DroppedEventArgs eventArgs)
|
||||
{
|
||||
Clean();
|
||||
#pragma warning disable 618
|
||||
SendNetworkMessage(new InstrumentStopMidiMessage());
|
||||
#pragma warning restore 618
|
||||
InstrumentPlayer = null;
|
||||
UserInterface?.CloseAll();
|
||||
}
|
||||
|
||||
void IThrown.Thrown(ThrownEventArgs eventArgs)
|
||||
{
|
||||
Clean();
|
||||
#pragma warning disable 618
|
||||
SendNetworkMessage(new InstrumentStopMidiMessage());
|
||||
#pragma warning restore 618
|
||||
InstrumentPlayer = null;
|
||||
UserInterface?.CloseAll();
|
||||
}
|
||||
|
||||
void IHandSelected.HandSelected(HandSelectedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.User == null || !eventArgs.User.TryGetComponent(out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
var session = actor.PlayerSession;
|
||||
|
||||
if (session.Status != SessionStatus.InGame) return;
|
||||
|
||||
InstrumentPlayer = session;
|
||||
}
|
||||
|
||||
void IHandDeselected.HandDeselected(HandDeselectedEventArgs eventArgs)
|
||||
{
|
||||
Clean();
|
||||
#pragma warning disable 618
|
||||
SendNetworkMessage(new InstrumentStopMidiMessage());
|
||||
#pragma warning restore 618
|
||||
UserInterface?.CloseAll();
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (Handheld)
|
||||
return;
|
||||
|
||||
InteractInstrument(eventArgs.User);
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
InteractInstrument(eventArgs.User);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InteractInstrument(IEntity user)
|
||||
{
|
||||
if (!user.TryGetComponent(out ActorComponent? actor)) return;
|
||||
|
||||
if ((!Handheld && InstrumentPlayer != null)
|
||||
|| (Handheld && actor.PlayerSession != InstrumentPlayer)
|
||||
|| !EntitySystem.Get<ActionBlockerSystem>().CanInteract(user.Uid)) return;
|
||||
|
||||
InstrumentPlayer = actor.PlayerSession;
|
||||
OpenUserInterface(InstrumentPlayer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void UserInterfaceOnClosed(IPlayerSession player)
|
||||
{
|
||||
if (Handheld || player != InstrumentPlayer) return;
|
||||
|
||||
Clean();
|
||||
InstrumentPlayer = null;
|
||||
#pragma warning disable 618
|
||||
SendNetworkMessage(new InstrumentStopMidiMessage());
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
private void OpenUserInterface(IPlayerSession session)
|
||||
{
|
||||
UserInterface?.Toggle(session);
|
||||
}
|
||||
|
||||
public override void Update(float delta)
|
||||
{
|
||||
base.Update(delta);
|
||||
@@ -350,37 +222,22 @@ namespace Content.Server.Instruments
|
||||
var maxMidiLaggedBatches = _instrumentSystem.MaxMidiLaggedBatches;
|
||||
var maxMidiBatchDropped = _instrumentSystem.MaxMidiBatchesDropped;
|
||||
|
||||
if (_instrumentPlayer != null
|
||||
&& (_instrumentPlayer.AttachedEntityUid == null
|
||||
|| !EntitySystem.Get<ActionBlockerSystem>().CanInteract(_instrumentPlayer.AttachedEntityUid.Value)))
|
||||
{
|
||||
InstrumentPlayer = null;
|
||||
Clean();
|
||||
UserInterface?.CloseAll();
|
||||
}
|
||||
|
||||
if ((_batchesDropped >= maxMidiBatchDropped
|
||||
|| _laggedBatches >= maxMidiLaggedBatches)
|
||||
&& InstrumentPlayer != null && _respectMidiLimits)
|
||||
{
|
||||
var mob = InstrumentPlayer.AttachedEntity;
|
||||
|
||||
#pragma warning disable 618
|
||||
SendNetworkMessage(new InstrumentStopMidiMessage());
|
||||
#pragma warning restore 618
|
||||
Playing = false;
|
||||
|
||||
// Just in case
|
||||
Clean();
|
||||
UserInterface?.CloseAll();
|
||||
|
||||
if (mob != null)
|
||||
{
|
||||
EntitySystem.Get<StunSystem>().TryParalyze(mob.Uid, TimeSpan.FromSeconds(1));
|
||||
Clean();
|
||||
|
||||
Owner.PopupMessage(mob, "instrument-component-finger-cramps-max-message");
|
||||
}
|
||||
|
||||
InstrumentPlayer = null;
|
||||
}
|
||||
|
||||
_timer += delta;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Server.UserInterface;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -20,6 +21,8 @@ namespace Content.Server.Instruments
|
||||
_cfg.OnValueChanged(CCVars.MaxMidiEventsPerBatch, OnMaxMidiEventsPerBatchChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.MaxMidiBatchesDropped, OnMaxMidiBatchesDroppedChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.MaxMidiLaggedBatches, OnMaxMidiLaggedBatchesChanged, true);
|
||||
|
||||
SubscribeLocalEvent<InstrumentComponent, ActivatableUIPlayerChangedEvent>(InstrumentNeedsClean);
|
||||
}
|
||||
|
||||
public int MaxMidiEventsPerSecond { get; private set; }
|
||||
@@ -47,6 +50,11 @@ namespace Content.Server.Instruments
|
||||
MaxMidiEventsPerSecond = obj;
|
||||
}
|
||||
|
||||
private void InstrumentNeedsClean(EntityUid uid, InstrumentComponent component, ActivatableUIPlayerChangedEvent ev)
|
||||
{
|
||||
component.Clean();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using Content.Server.Containers;
|
||||
using Content.Server.GameObjects;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class ActivatableUIRequiresPowerComponent : Component
|
||||
{
|
||||
public override string Name => "ActivatableUIRequiresPower";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Linq;
|
||||
using Content.Shared;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Power.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ActivatableUIRequiresPowerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActivatableUISystem _activatableUISystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIRequiresPowerComponent, ActivatableUIOpenAttemptEvent>(OnActivate);
|
||||
SubscribeLocalEvent<ActivatableUIRequiresPowerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled) return;
|
||||
if (EntityManager.TryGetComponent<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
|
||||
{
|
||||
args.User.PopupMessageCursor(Loc.GetString("base-computer-ui-component-not-powered"));
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, ActivatableUIRequiresPowerComponent component, PowerChangedEvent args)
|
||||
{
|
||||
if (!args.Powered)
|
||||
_activatableUISystem.CloseAll(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Content.Shared.Solar;
|
||||
using Content.Server.Solar.EntitySystems;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -11,45 +11,8 @@ using Robust.Shared.Maths;
|
||||
namespace Content.Server.Solar.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(BaseComputerUserInterfaceComponent))]
|
||||
public class SolarControlConsoleComponent : BaseComputerUserInterfaceComponent
|
||||
public class SolarControlConsoleComponent : Component
|
||||
{
|
||||
public override string Name => "SolarControlConsole";
|
||||
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
private PowerSolarSystem _powerSolarSystem = default!;
|
||||
|
||||
public SolarControlConsoleComponent() : base(SolarControlConsoleUiKey.Key) { }
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_powerSolarSystem = _entitySystemManager.GetEntitySystem<PowerSolarSystem>();
|
||||
}
|
||||
|
||||
public void UpdateUIState()
|
||||
{
|
||||
UserInterface?.SetState(new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun));
|
||||
}
|
||||
|
||||
protected override void OnReceiveUserInterfaceMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
switch (obj.Message)
|
||||
{
|
||||
case SolarControlConsoleAdjustMessage msg:
|
||||
if (double.IsFinite(msg.Rotation))
|
||||
{
|
||||
_powerSolarSystem.TargetPanelRotation = msg.Rotation.Reduced();
|
||||
}
|
||||
if (double.IsFinite(msg.AngularVelocity))
|
||||
{
|
||||
var degrees = msg.AngularVelocity.Degrees;
|
||||
degrees = Math.Clamp(degrees, -PowerSolarSystem.MaxPanelVelocityDegrees, PowerSolarSystem.MaxPanelVelocityDegrees);
|
||||
_powerSolarSystem.TargetPanelVelocity = Angle.FromDegrees(degrees);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
using System;
|
||||
using Content.Server.Solar.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Solar;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.Solar.EntitySystems
|
||||
{
|
||||
@@ -10,22 +16,53 @@ namespace Content.Server.Solar.EntitySystems
|
||||
[UsedImplicitly]
|
||||
internal sealed class PowerSolarControlConsoleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private PowerSolarSystem _powerSolarSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Timer used to avoid updating the UI state every frame (which would be overkill)
|
||||
/// </summary>
|
||||
private float _updateTimer;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SolarControlConsoleComponent, ServerBoundUserInterfaceMessage>(OnUIMessage);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_updateTimer += frameTime;
|
||||
if (_updateTimer >= 1)
|
||||
{
|
||||
_updateTimer -= 1;
|
||||
var state = new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun);
|
||||
foreach (var component in EntityManager.EntityQuery<SolarControlConsoleComponent>())
|
||||
{
|
||||
component.UpdateUIState();
|
||||
component.Owner.GetUIOrNull(SolarControlConsoleUiKey.Key)?.SetState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUIMessage(EntityUid uid, SolarControlConsoleComponent component, ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
if (component.Deleted) return;
|
||||
switch (obj.Message)
|
||||
{
|
||||
case SolarControlConsoleAdjustMessage msg:
|
||||
if (double.IsFinite(msg.Rotation))
|
||||
{
|
||||
_powerSolarSystem.TargetPanelRotation = msg.Rotation.Reduced();
|
||||
}
|
||||
if (double.IsFinite(msg.AngularVelocity))
|
||||
{
|
||||
var degrees = msg.AngularVelocity.Degrees;
|
||||
degrees = Math.Clamp(degrees, -PowerSolarSystem.MaxPanelVelocityDegrees, PowerSolarSystem.MaxPanelVelocityDegrees);
|
||||
_powerSolarSystem.TargetPanelVelocity = Angle.FromDegrees(degrees);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
60
Content.Server/UserInterface/ActivatableUIComponent.cs
Normal file
60
Content.Server/UserInterface/ActivatableUIComponent.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Content.Shared.Instruments;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.UserInterface
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class ActivatableUIComponent : Component,
|
||||
ISerializationHooks
|
||||
{
|
||||
public override string Name => "ActivatableUI";
|
||||
|
||||
[ViewVariables]
|
||||
public Enum? Key { get; set; }
|
||||
|
||||
[ViewVariables] public BoundUserInterface? UserInterface => (Key != null) ? Owner.GetUIOrNull(Key) : null;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("inHandsOnly")]
|
||||
public bool InHandsOnly { get; set; } = false;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("singleUser")]
|
||||
public bool SingleUser { get; set; } = false;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("adminOnly")]
|
||||
public bool AdminOnly { get; set; } = false;
|
||||
|
||||
[DataField("key", readOnly: true, required: true)]
|
||||
private string _keyRaw = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The client channel currently using the object, or null if there's none/not single user.
|
||||
/// NOTE: DO NOT DIRECTLY SET, USE ActivatableUISystem.SetCurrentSingleUser
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IPlayerSession? CurrentSingleUser;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
if (reflectionManager.TryParseEnumReference(_keyRaw, out var key))
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
154
Content.Server/UserInterface/ActivatableUISystem.cs
Normal file
154
Content.Server/UserInterface/ActivatableUISystem.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System.Linq;
|
||||
using Content.Shared;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Server.Administration.Managers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.UserInterface
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ActivatableUISystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>((uid, aui, _) => CloseAll(uid, aui));
|
||||
SubscribeLocalEvent<ActivatableUIComponent, UnequippedHandEvent>((uid, aui, _) => CloseAll(uid, aui));
|
||||
// *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it
|
||||
SubscribeLocalEvent<ActivatableUIComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<ActivatableUIComponent, BoundUIClosedEvent>(OnUIClose);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled) return;
|
||||
if (component.InHandsOnly) return;
|
||||
args.Handled = InteractUI(args.User, component);
|
||||
}
|
||||
|
||||
private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled) return;
|
||||
args.Handled = InteractUI(args.User, component);
|
||||
}
|
||||
|
||||
private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args)
|
||||
{
|
||||
CloseAll(uid, aui);
|
||||
}
|
||||
|
||||
private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
|
||||
{
|
||||
if (args.Session != component.CurrentSingleUser) return;
|
||||
if (args.UiKey != component.Key) return;
|
||||
SetCurrentSingleUser(uid, null, component);
|
||||
}
|
||||
|
||||
private bool InteractUI(IEntity user, ActivatableUIComponent aui)
|
||||
{
|
||||
if (!user.TryGetComponent(out ActorComponent? actor)) return false;
|
||||
|
||||
if (aui.AdminOnly && !_adminManager.IsAdmin(actor.PlayerSession)) return false;
|
||||
|
||||
if (!_actionBlockerSystem.CanInteract(user.Uid))
|
||||
{
|
||||
user.PopupMessageCursor(Loc.GetString("base-computer-ui-component-cannot-interact"));
|
||||
return true;
|
||||
}
|
||||
|
||||
var ui = aui.UserInterface;
|
||||
if (ui == null) return false;
|
||||
|
||||
if (aui.SingleUser && (aui.CurrentSingleUser != null) && (actor.PlayerSession != aui.CurrentSingleUser))
|
||||
{
|
||||
// If we get here, supposedly, the object is in use.
|
||||
// Check with BUI that it's ACTUALLY in use just in case.
|
||||
// Since this could brick the object if it goes wrong.
|
||||
if (ui.SubscribedSessions.Count != 0) return false;
|
||||
}
|
||||
|
||||
// If we've gotten this far, fire a cancellable event that indicates someone is about to activate this.
|
||||
// This is so that stuff can require further conditions (like power).
|
||||
var oae = new ActivatableUIOpenAttemptEvent(user);
|
||||
RaiseLocalEvent(aui.OwnerUid, oae, false);
|
||||
if (oae.Cancelled) return false;
|
||||
|
||||
SetCurrentSingleUser(aui.OwnerUid, actor.PlayerSession, aui);
|
||||
ui.Toggle(actor.PlayerSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetCurrentSingleUser(EntityUid uid, IPlayerSession? v, ActivatableUIComponent? aui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref aui))
|
||||
return;
|
||||
if (!aui.SingleUser)
|
||||
return;
|
||||
|
||||
aui.CurrentSingleUser = v;
|
||||
|
||||
RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent(), false);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var component in EntityManager.EntityQuery<ActivatableUIComponent>(true))
|
||||
{
|
||||
var ui = component.UserInterface;
|
||||
if (ui == null) continue;
|
||||
// Done to skip an allocation on anything that's not in use.
|
||||
if (ui.SubscribedSessions.Count == 0) continue;
|
||||
// Must ToList in order to close things safely.
|
||||
foreach (var session in ui.SubscribedSessions.ToArray())
|
||||
{
|
||||
if (session.AttachedEntityUid == null || !_actionBlockerSystem.CanInteract(session.AttachedEntityUid.Value))
|
||||
{
|
||||
ui.Close(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref aui, false)) return;
|
||||
aui.UserInterface?.CloseAll();
|
||||
}
|
||||
}
|
||||
|
||||
public class ActivatableUIOpenAttemptEvent : CancellableEntityEventArgs
|
||||
{
|
||||
public IEntity User { get; }
|
||||
public ActivatableUIOpenAttemptEvent(IEntity who)
|
||||
{
|
||||
User = who;
|
||||
}
|
||||
}
|
||||
|
||||
public class ActivatableUIPlayerChangedEvent : EntityEventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,10 @@
|
||||
description: That's an instrument.
|
||||
components:
|
||||
- type: Instrument
|
||||
handheld: true
|
||||
- type: ActivatableUI
|
||||
inHandsOnly: true
|
||||
singleUser: true
|
||||
key: enum.InstrumentUiKey.Key
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.InstrumentUiKey.Key
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
abstract: true
|
||||
components:
|
||||
- type: Instrument
|
||||
handheld: false
|
||||
- type: ActivatableUI
|
||||
inHandsOnly: false
|
||||
singleUser: true
|
||||
key: enum.InstrumentUiKey.Key
|
||||
- type: InteractionOutline
|
||||
- type: Rotatable
|
||||
rotateWhileAnchored: true
|
||||
|
||||
@@ -126,6 +126,9 @@
|
||||
- type: AccessReader
|
||||
access: [["HeadOfPersonnel"]]
|
||||
- type: IdCardConsole
|
||||
- type: ActivatableUI
|
||||
key: enum.IdCardConsoleUiKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.IdCardConsoleUiKey.Key
|
||||
@@ -177,6 +180,9 @@
|
||||
key: generic_key
|
||||
screen: comm
|
||||
- type: CommunicationsConsole
|
||||
- type: ActivatableUI
|
||||
key: enum.CommunicationsConsoleUiKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.CommunicationsConsoleUiKey.Key
|
||||
@@ -200,6 +206,9 @@
|
||||
key: generic_key
|
||||
screen: solar_screen
|
||||
- type: SolarControlConsole
|
||||
- type: ActivatableUI
|
||||
key: enum.SolarControlConsoleUiKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.SolarControlConsoleUiKey.Key
|
||||
@@ -278,6 +287,9 @@
|
||||
# - AtmosphericsWaterVapor
|
||||
# - AtmosphericsPlasma
|
||||
# - AtmosphericsTritium
|
||||
- type: ActivatableUI
|
||||
key: enum.CargoConsoleUiKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.CargoConsoleUiKey.Key
|
||||
|
||||
Reference in New Issue
Block a user