From fc6638d7e0c05ddf368313b6cf62630442bcb1d9 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 18 Sep 2023 02:09:21 +0100 Subject: [PATCH] gateway changes (#20304) Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Gateway/UI/GatewayWindow.xaml.cs | 9 +- .../Gateway/Components/GatewayComponent.cs | 25 ++++-- .../Components/GatewayDestinationComponent.cs | 16 ++-- .../Gateway/Systems/GatewaySystem.cs | 84 +++++++++++++------ .../Systems/LinkedEntitySystem.cs | 23 ++++- Resources/Locale/en-US/gateway/gateway.ftl | 4 + 6 files changed, 121 insertions(+), 40 deletions(-) diff --git a/Content.Client/Gateway/UI/GatewayWindow.xaml.cs b/Content.Client/Gateway/UI/GatewayWindow.xaml.cs index 00293065dc..f221908432 100644 --- a/Content.Client/Gateway/UI/GatewayWindow.xaml.cs +++ b/Content.Client/Gateway/UI/GatewayWindow.xaml.cs @@ -83,7 +83,7 @@ public sealed partial class GatewayWindow : FancyWindow, var readyLabel = new Label { - Text = ReadyText(now, nextReady), + Text = ReadyText(now, nextReady, busy), Margin = new Thickness(10f, 0f, 0f, 0f) }; _readyLabels.Add(readyLabel); @@ -163,13 +163,16 @@ public sealed partial class GatewayWindow : FancyWindow, var dest = _destinations[i]; var nextReady = dest.Item3; var busy = dest.Item4; - _readyLabels[i].Text = ReadyText(now, nextReady); + _readyLabels[i].Text = ReadyText(now, nextReady, busy); _openButtons[i].Disabled = _current != null || busy || now < nextReady; } } - private string ReadyText(TimeSpan now, TimeSpan nextReady) + private string ReadyText(TimeSpan now, TimeSpan nextReady, bool busy) { + if (busy) + return Loc.GetString("gateway-window-already-active"); + if (now < nextReady) { var time = nextReady - now; diff --git a/Content.Server/Gateway/Components/GatewayComponent.cs b/Content.Server/Gateway/Components/GatewayComponent.cs index 7e2a76c655..5145cb6b08 100644 --- a/Content.Server/Gateway/Components/GatewayComponent.cs +++ b/Content.Server/Gateway/Components/GatewayComponent.cs @@ -11,10 +11,25 @@ namespace Content.Server.Gateway.Components; public sealed partial class GatewayComponent : Component { /// - /// Sound to play when opening or closing the portal. + /// Sound to play when opening the portal. /// + /// + /// Originally named PortalSound as it was used for opening and closing. + /// [DataField("portalSound")] - public SoundSpecifier PortalSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg"); + public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg"); + + /// + /// Sound to play when closing the portal. + /// + [DataField] + public SoundSpecifier CloseSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg"); + + /// + /// Sound to play when trying to open or close the portal and missing access. + /// + [DataField] + public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); /// /// Every other gateway destination on the server. @@ -22,19 +37,19 @@ public sealed partial class GatewayComponent : Component /// /// Added on startup and when a new destination portal is created. /// - [ViewVariables] + [DataField] public HashSet Destinations = new(); /// /// The time at which the portal will be closed. /// - [ViewVariables(VVAccess.ReadWrite), DataField("nextClose", customTypeSerializer:typeof(TimeOffsetSerializer))] + [ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan NextClose; /// /// The time at which the portal was last opened. /// Only used for UI. /// - [ViewVariables] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan LastOpen; } diff --git a/Content.Server/Gateway/Components/GatewayDestinationComponent.cs b/Content.Server/Gateway/Components/GatewayDestinationComponent.cs index 50aff0b24a..41a41457f2 100644 --- a/Content.Server/Gateway/Components/GatewayDestinationComponent.cs +++ b/Content.Server/Gateway/Components/GatewayDestinationComponent.cs @@ -13,30 +13,36 @@ public sealed partial class GatewayDestinationComponent : Component /// Whether this destination is shown in the gateway ui. /// If you are making a gateway for an admeme set this once you are ready for players to select it. /// - [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public bool Enabled; /// /// Name as it shows up on the ui of station gateways. /// - [DataField("name"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public string Name = string.Empty; /// /// Time at which this destination is ready to be linked to. /// - [ViewVariables(VVAccess.ReadWrite), DataField("nextReady", customTypeSerializer:typeof(TimeOffsetSerializer))] + [ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer:typeof(TimeOffsetSerializer))] public TimeSpan NextReady; /// /// How long the portal will be open for after linking. /// - [DataField("openTime"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public TimeSpan OpenTime = TimeSpan.FromSeconds(600); /// /// How long the destination is not ready for after the portal closes. /// - [DataField("cooldown"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public TimeSpan Cooldown = TimeSpan.FromSeconds(60); + + /// + /// If true, the portal can be closed by alt clicking it. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool Closeable; } diff --git a/Content.Server/Gateway/Systems/GatewaySystem.cs b/Content.Server/Gateway/Systems/GatewaySystem.cs index 6fd4912917..ae00cd378c 100644 --- a/Content.Server/Gateway/Systems/GatewaySystem.cs +++ b/Content.Server/Gateway/Systems/GatewaySystem.cs @@ -1,14 +1,14 @@ using Content.Server.Gateway.Components; using Content.Shared.Access.Systems; using Content.Shared.Gateway; +using Content.Shared.Popups; using Content.Shared.Teleportation.Components; using Content.Shared.Teleportation.Systems; +using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.Timing; -using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Content.Server.Gateway.Systems; @@ -19,6 +19,7 @@ public sealed class GatewaySystem : EntitySystem [Dependency] private readonly LinkedEntitySystem _linkedEntity = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; public override void Initialize() @@ -31,6 +32,7 @@ public sealed class GatewaySystem : EntitySystem SubscribeLocalEvent(OnDestinationStartup); SubscribeLocalEvent(OnDestinationShutdown); + SubscribeLocalEvent>(OnDestinationGetVerbs); } public override void Update(float frameTime) @@ -78,7 +80,7 @@ public sealed class GatewaySystem : EntitySystem destinations.Add((GetNetEntity(destUid), dest.Name, dest.NextReady, HasComp(destUid))); } - GetDestination(uid, out var current); + _linkedEntity.GetLink(uid, out var current); var state = new GatewayBoundUserInterfaceState(destinations, GetNetEntity(current), comp.NextClose, comp.LastOpen); _ui.TrySetUiState(uid, GatewayUiKey.Key, state); } @@ -95,7 +97,7 @@ public sealed class GatewaySystem : EntitySystem // if the gateway has an access reader check it before allowing opening var user = args.Session.AttachedEntity.Value; - if (!_accessReader.IsAllowed(user, uid)) + if (CheckAccess(user, uid)) return; // can't link if portal is already open on either side, the destination is invalid or on cooldown @@ -123,18 +125,21 @@ public sealed class GatewaySystem : EntitySystem // close automatically after time is up comp.NextClose = comp.LastOpen + destComp.OpenTime; - _audio.PlayPvs(comp.PortalSound, uid); - _audio.PlayPvs(comp.PortalSound, dest); + _audio.PlayPvs(comp.OpenSound, uid); + _audio.PlayPvs(comp.OpenSound, dest); UpdateUserInterface(uid, comp); UpdateAppearance(uid); UpdateAppearance(dest); } - private void ClosePortal(EntityUid uid, GatewayComponent comp) + private void ClosePortal(EntityUid uid, GatewayComponent? comp = null) { + if (!Resolve(uid, ref comp)) + return; + RemComp(uid); - if (!GetDestination(uid, out var dest)) + if (!_linkedEntity.GetLink(uid, out var dest)) return; if (TryComp(dest, out var destComp)) @@ -143,8 +148,8 @@ public sealed class GatewaySystem : EntitySystem destComp.NextReady = _timing.CurTime + destComp.Cooldown; } - _audio.PlayPvs(comp.PortalSound, uid); - _audio.PlayPvs(comp.PortalSound, dest.Value); + _audio.PlayPvs(comp.CloseSound, uid); + _audio.PlayPvs(comp.CloseSound, dest.Value); _linkedEntity.TryUnlink(uid, dest.Value); RemComp(dest.Value); @@ -153,22 +158,6 @@ public sealed class GatewaySystem : EntitySystem UpdateAppearance(dest.Value); } - private bool GetDestination(EntityUid uid, [NotNullWhen(true)] out EntityUid? dest) - { - dest = null; - if (TryComp(uid, out var linked)) - { - var first = linked.LinkedEntities.FirstOrDefault(); - if (first != EntityUid.Invalid) - { - dest = first; - return true; - } - } - - return false; - } - private void OnDestinationStartup(EntityUid uid, GatewayDestinationComponent comp, ComponentStartup args) { var query = EntityQueryEnumerator(); @@ -190,4 +179,47 @@ public sealed class GatewaySystem : EntitySystem UpdateUserInterface(gatewayUid, gateway); } } + + private void OnDestinationGetVerbs(EntityUid uid, GatewayDestinationComponent comp, GetVerbsEvent args) + { + if (!comp.Closeable || !args.CanInteract || !args.CanAccess) + return; + + // a portal is open so add verb to close it + args.Verbs.Add(new AlternativeVerb() + { + Act = () => TryClose(uid, args.User), + Text = Loc.GetString("gateway-close-portal") + }); + } + + private void TryClose(EntityUid uid, EntityUid user) + { + // portal already closed so cant close it + if (!_linkedEntity.GetLink(uid, out var source)) + return; + + // not allowed to close it + if (CheckAccess(user, source.Value)) + return; + + ClosePortal(source.Value); + } + + /// + /// Checks the user's access. Makes popup and plays sound if missing access. + /// Returns whether access was missing. + /// + private bool CheckAccess(EntityUid user, EntityUid uid, GatewayComponent? comp = null) + { + if (!Resolve(uid, ref comp)) + return false; + + if (_accessReader.IsAllowed(user, uid)) + return false; + + _popup.PopupEntity(Loc.GetString("gateway-access-denied"), user); + _audio.PlayPvs(comp.AccessDeniedSound, uid); + return true; + } } diff --git a/Content.Shared/Teleportation/Systems/LinkedEntitySystem.cs b/Content.Shared/Teleportation/Systems/LinkedEntitySystem.cs index 12667e4886..73e686e8bd 100644 --- a/Content.Shared/Teleportation/Systems/LinkedEntitySystem.cs +++ b/Content.Shared/Teleportation/Systems/LinkedEntitySystem.cs @@ -1,6 +1,7 @@ -using System.Linq; using Content.Shared.Teleportation.Components; using Robust.Shared.GameStates; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Content.Shared.Teleportation.Systems; @@ -113,5 +114,25 @@ public sealed class LinkedEntitySystem : EntitySystem return success; } + /// + /// Get the first entity this entity is linked to. + /// If multiple are linked only the first one is picked. + /// + public bool GetLink(EntityUid uid, [NotNullWhen(true)] out EntityUid? dest, LinkedEntityComponent? comp = null) + { + dest = null; + if (!Resolve(uid, ref comp, false)) + return false; + + var first = comp.LinkedEntities.FirstOrDefault(); + if (first != default) + { + dest = first; + return true; + } + + return false; + } + #endregion } diff --git a/Resources/Locale/en-US/gateway/gateway.ftl b/Resources/Locale/en-US/gateway/gateway.ftl index 2e6a75554b..bebc82b60f 100644 --- a/Resources/Locale/en-US/gateway/gateway.ftl +++ b/Resources/Locale/en-US/gateway/gateway.ftl @@ -1,6 +1,10 @@ gateway-window-title = Gateway gateway-window-ready = Ready! gateway-window-ready-in = Ready in: {$time}s +gateway-window-already-active = Already active gateway-window-open-portal = Open Portal gateway-window-no-destinations = No destinations found. gateway-window-portal-closing = Portal closing + +gateway-access-denied = Access denied! +gateway-close-portal = Close Portal -- 2.51.2