From af2d5d401abfd3ba371203a60195bb3a26eb707b Mon Sep 17 00:00:00 2001 From: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Date: Mon, 18 May 2026 08:23:41 -0500 Subject: [PATCH] Minor improvements for holopads (#40444) * Initial commit * Fixing merge conflict * Merge conflict fixed * Additional fixes * Reverted change * Serialization fix * Holopad UIs will update when one is deleted or changes map * Minor clean up * Removed obsolete variables * Addressed review comments --- Content.Client/Holopad/HolopadWindow.xaml | 7 +- Content.Client/Holopad/HolopadWindow.xaml.cs | 143 +++++++++--------- Content.Server/Holopad/HolopadSystem.cs | 109 ++++++++----- Content.Server/Telephone/TelephoneSystem.cs | 84 ++++++---- Content.Shared/Holopad/HolopadComponent.cs | 28 ++-- Content.Shared/Holopad/SharedHolopadSystem.cs | 14 +- .../Telephone/SharedTelephoneSystem.cs | 9 +- .../Telephone/TelephoneComponent.cs | 48 +++--- 8 files changed, 261 insertions(+), 181 deletions(-) diff --git a/Content.Client/Holopad/HolopadWindow.xaml b/Content.Client/Holopad/HolopadWindow.xaml index 5a7ad05bea..1eb4e14784 100644 --- a/Content.Client/Holopad/HolopadWindow.xaml +++ b/Content.Client/Holopad/HolopadWindow.xaml @@ -1,6 +1,5 @@ @@ -10,7 +9,7 @@ - + @@ -52,8 +51,8 @@ - - + + diff --git a/Content.Client/Holopad/HolopadWindow.xaml.cs b/Content.Client/Holopad/HolopadWindow.xaml.cs index 16ec37bb7e..6a19e46d03 100644 --- a/Content.Client/Holopad/HolopadWindow.xaml.cs +++ b/Content.Client/Holopad/HolopadWindow.xaml.cs @@ -10,7 +10,6 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Timing; using Robust.Shared.Utility; -using System.Linq; namespace Content.Client.Holopad; @@ -19,22 +18,18 @@ public sealed partial class HolopadWindow : FancyWindow { [Dependency] private IEntityManager _entManager = default!; [Dependency] private IPlayerManager _playerManager = default!; + [Dependency] private ILogManager _logManager = default!; [Dependency] private IGameTiming _timing = default!; private readonly SharedHolopadSystem _holopadSystem = default!; private readonly SharedTelephoneSystem _telephoneSystem = default!; private readonly AccessReaderSystem _accessReaderSystem = default!; private readonly PopupSystem _popupSystem = default!; + private readonly ISawmill _sawmill = default!; private EntityUid? _owner = null; private HolopadUiKey _currentUiKey; - private TelephoneState _currentState; - private TelephoneState _previousState; - private TimeSpan _buttonUnlockTime; - private float _updateTimer = 0.25f; - - private const float UpdateTime = 0.25f; - private TimeSpan _buttonUnlockDelay = TimeSpan.FromSeconds(0.5f); + private string _currentSearch = string.Empty; public event Action? SendHolopadStartNewCallMessageAction; public event Action? SendHolopadAnswerCallMessageAction; @@ -43,6 +38,9 @@ public sealed partial class HolopadWindow : FancyWindow public event Action? SendHolopadActivateProjectorMessageAction; public event Action? SendHolopadRequestStationAiMessageAction; + private TimeSpan _updateDelay = TimeSpan.FromSeconds(0.25f); + private TimeSpan _nextUpdate; + public HolopadWindow() { RobustXamlLoader.Load(this); @@ -52,8 +50,7 @@ public sealed partial class HolopadWindow : FancyWindow _telephoneSystem = _entManager.System(); _accessReaderSystem = _entManager.System(); _popupSystem = _entManager.System(); - - _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay; + _sawmill = _logManager.GetSawmill("Holopad"); // Assign button actions AnswerCallButton.OnPressed += args => { OnHolopadAnswerCallMessage(); }; @@ -78,10 +75,6 @@ public sealed partial class HolopadWindow : FancyWindow { BackgroundColor = new Color(82, 82, 82), }; - - EmergencyBroadcastText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-emergency-broadcast-in-progress"))); - SubtitleText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-subtitle"))); - OptionsText.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("holopad-window-options"))); } #region: Button actions @@ -166,16 +159,28 @@ public sealed partial class HolopadWindow : FancyWindow public void UpdateState(Dictionary holopads) { - if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var telephone)) + if (!_entManager.TryGetComponent(_owner, out var telephone)) return; - // Caller ID text - var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId.Item1, telephone.LastCallerId.Item2, Color.LightGray, "Default", 11); - var holoapdId = _telephoneSystem.GetFormattedDeviceIdForEntity(telephone.LastCallerId.Item3, Color.LightGray, "Default", 11); + // Caller and holopad ID text + var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId?.CallerId, telephone.LastCallerId?.CallerJob, Color.LightGray, "Default", 11); + var holopadId = _telephoneSystem.GetFormattedDeviceIdForEntity(telephone.LastCallerId?.DeviceId, Color.LightGray, "Default", 11); - CallerIdText.SetMessage(FormattedMessage.FromMarkupPermissive(callerId)); - HolopadIdText.SetMessage(FormattedMessage.FromMarkupPermissive(holoapdId)); - LockOutIdText.SetMessage(FormattedMessage.FromMarkupPermissive(callerId)); + if (!FormattedMessage.TryFromMarkup(callerId, out var callerIdMsg)) + { + callerIdMsg = FormattedMessage.FromMarkupPermissive(callerId); + _sawmill.Error($"CallerId markup text was incorrectly formatted: {callerIdMsg}"); + } + + if (!FormattedMessage.TryFromMarkup(holopadId, out var holopadIdMsg)) + { + holopadIdMsg = FormattedMessage.FromMarkupPermissive(holopadId); + _sawmill.Error($"HolopadId markup text was incorrectly formatted: {holopadIdMsg}"); + } + + CallerIdText.SetMessage(callerIdMsg); + LockOutIdText.SetMessage(callerIdMsg); + HolopadIdText.SetMessage(holopadIdMsg); // Sort holopads alphabetically var holopadArray = holopads.ToArray(); @@ -183,7 +188,9 @@ public sealed partial class HolopadWindow : FancyWindow // Clear excess children from the contact list while (ContactsList.ChildCount > holopadArray.Length) + { ContactsList.RemoveChild(ContactsList.GetChild(ContactsList.ChildCount - 1)); + } // Make / update required children for (int i = 0; i < holopadArray.Length; i++) @@ -213,55 +220,57 @@ public sealed partial class HolopadWindow : FancyWindow private void UpdateAppearance() { - if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var telephone)) + if (!_entManager.TryGetComponent(_owner, out var telephone)) return; - if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var holopad)) + if (!_entManager.TryGetComponent(_owner, out var holopad)) return; - var hasBroadcastAccess = !_holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)); + var broadcastOnCooldown = _holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)); var localPlayer = _playerManager.LocalSession?.AttachedEntity; + // Update container visibility ControlsLockOutContainer.Visible = _holopadSystem.IsHolopadControlLocked((_owner.Value, holopad), localPlayer); ControlsContainer.Visible = !ControlsLockOutContainer.Visible; - // Temporarily disable the interface buttons when the call state changes to prevent any misclicks - if (_currentState != telephone.CurrentState) + // Update contact button visibility + if (SearchLineEdit.Text != _currentSearch) { - _previousState = _currentState; - _currentState = telephone.CurrentState; - _buttonUnlockTime = _timing.CurTime + _buttonUnlockDelay; + _currentSearch = SearchLineEdit.Text; + + foreach (var child in ContactsList.Children) + { + if (child is not HolopadContactButton contactButton) + continue; + + var passesFilter = string.IsNullOrEmpty(SearchLineEdit.Text) || + contactButton.Text?.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase) == true; + + contactButton.Visible = passesFilter; + } } - var lockButtons = _timing.CurTime < _buttonUnlockTime; - - // Make / update required children - foreach (var child in ContactsList.Children) + // Update timers + if (ControlsContainer.Visible) { - if (child is not HolopadContactButton contactButton) - continue; + var cooldown = _holopadSystem.GetHolopadBroadcastCoolDown((_owner.Value, holopad)); + var cooldownString = $"{cooldown.Minutes:00}:{cooldown.Seconds:00}"; - var passesFilter = string.IsNullOrEmpty(SearchLineEdit.Text) || - contactButton.Text?.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase) == true; - - contactButton.Visible = passesFilter; - contactButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons); + StartBroadcastButton.Text = broadcastOnCooldown + ? Loc.GetString("holopad-window-emergency-broadcast-with-countdown", ("countdown", cooldownString)) + : Loc.GetString("holopad-window-emergency-broadcast"); } - // Update control text - var cooldown = _holopadSystem.GetHolopadBroadcastCoolDown((_owner.Value, holopad)); - var cooldownString = $"{cooldown.Minutes:00}:{cooldown.Seconds:00}"; + if (ControlsLockOutContainer.Visible) + { + var lockout = _holopadSystem.GetHolopadControlLockedPeriod((_owner.Value, holopad)); + var lockoutString = $"{lockout.Minutes:00}:{lockout.Seconds:00}"; - StartBroadcastButton.Text = _holopadSystem.IsHolopadBroadcastOnCoolDown((_owner.Value, holopad)) ? - Loc.GetString("holopad-window-emergency-broadcast-with-countdown", ("countdown", cooldownString)) : - Loc.GetString("holopad-window-emergency-broadcast"); + LockOutCountDownText.Text = Loc.GetString("holopad-window-controls-unlock-countdown", ("countdown", lockoutString)); + } - var lockout = _holopadSystem.GetHolopadControlLockedPeriod((_owner.Value, holopad)); - var lockoutString = $"{lockout.Minutes:00}:{lockout.Seconds:00}"; - - LockOutCountDownText.Text = Loc.GetString("holopad-window-controls-unlock-countdown", ("countdown", lockoutString)); - - switch (_currentState) + // Update call status text + switch (telephone.CurrentState) { case TelephoneState.Idle: CallStatusText.Text = Loc.GetString("holopad-window-no-calls-in-progress"); break; @@ -277,7 +286,7 @@ public sealed partial class HolopadWindow : FancyWindow CallStatusText.Text = Loc.GetString("holopad-window-call-in-progress"); break; case TelephoneState.EndingCall: - if (_previousState == TelephoneState.Calling || _previousState == TelephoneState.Idle) + if (telephone.PreviousState == TelephoneState.Calling || telephone.PreviousState == TelephoneState.Idle) CallStatusText.Text = Loc.GetString("holopad-window-call-rejected"); else CallStatusText.Text = Loc.GetString("holopad-window-call-ending"); @@ -285,31 +294,29 @@ public sealed partial class HolopadWindow : FancyWindow } // Update control disability - AnswerCallButton.Disabled = (_currentState != TelephoneState.Ringing || lockButtons); - EndCallButton.Disabled = (_currentState == TelephoneState.Idle || _currentState == TelephoneState.EndingCall || lockButtons); - StartBroadcastButton.Disabled = (_currentState != TelephoneState.Idle || !hasBroadcastAccess || lockButtons); - RequestStationAiButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons); - ActivateProjectorButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons); + AnswerCallButton.Disabled = telephone.CurrentState is not TelephoneState.Ringing; + EndCallButton.Disabled = telephone.CurrentState is TelephoneState.Idle or TelephoneState.EndingCall; + StartBroadcastButton.Disabled = telephone.CurrentState is not TelephoneState.Idle || broadcastOnCooldown; + RequestStationAiButton.Disabled = telephone.CurrentState is not TelephoneState.Idle; + ActivateProjectorButton.Disabled = telephone.CurrentState is not TelephoneState.Idle; // Update control visibility - FetchingAvailableHolopadsContainer.Visible = (ContactsList.ChildCount == 0); - ActiveCallControlsContainer.Visible = (_currentState != TelephoneState.Idle || _currentUiKey == HolopadUiKey.AiRequestWindow); + FetchingAvailableHolopadsContainer.Visible = ContactsList.ChildCount == 0; + ActiveCallControlsContainer.Visible = telephone.CurrentState is not TelephoneState.Idle || _currentUiKey is HolopadUiKey.AiRequestWindow; CallPlacementControlsContainer.Visible = !ActiveCallControlsContainer.Visible; - CallerIdContainer.Visible = (_currentState == TelephoneState.Ringing); - AnswerCallButton.Visible = (_currentState == TelephoneState.Ringing); + CallerIdContainer.Visible = telephone.CurrentState is TelephoneState.Ringing; + AnswerCallButton.Visible = telephone.CurrentState is TelephoneState.Ringing; } protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); - _updateTimer += args.DeltaSeconds; + if (_timing.CurTime < _nextUpdate) + return; - if (_updateTimer >= UpdateTime) - { - _updateTimer -= UpdateTime; - UpdateAppearance(); - } + _nextUpdate = _timing.CurTime + _updateDelay; + UpdateAppearance(); } private sealed class HolopadContactButton : Button diff --git a/Content.Server/Holopad/HolopadSystem.cs b/Content.Server/Holopad/HolopadSystem.cs index 1afd6c2ba1..651db65f5a 100644 --- a/Content.Server/Holopad/HolopadSystem.cs +++ b/Content.Server/Holopad/HolopadSystem.cs @@ -42,9 +42,7 @@ public sealed partial class HolopadSystem : SharedHolopadSystem [Dependency] private IGameTiming _timing = default!; [Dependency] private PvsOverrideSystem _pvs = default!; [Dependency] private SharedPowerStateSystem _powerState = default!; - - private float _updateTimer = 1.0f; - private const float UpdateTime = 1.0f; + [Dependency] private MetaDataSystem _meta = default!; public override void Initialize() { @@ -79,10 +77,10 @@ public sealed partial class HolopadSystem : SharedHolopadSystem SubscribeLocalEvent(OnJumpToCore); SubscribeLocalEvent>(AddToggleProjectorVerb); SubscribeLocalEvent(OnAiRemove); - SubscribeLocalEvent(OnParentChanged); + SubscribeLocalEvent(OnMapUidChanged); SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnAnchorChanged); SubscribeLocalEvent(OnMobStateChanged); - } #region: Holopad UI bound user interface messages @@ -263,6 +261,8 @@ public sealed partial class HolopadSystem : SharedHolopadSystem SetHolopadAmbientState(holopad, this.IsPowered(holopad, EntityManager)); break; } + + UpdateUIState(holopad); } private void OnHoloCallCommenced(Entity source, ref TelephoneCallCommencedEvent args) @@ -315,7 +315,7 @@ public sealed partial class HolopadSystem : SharedHolopadSystem if (receiverHolopad.Comp.Hologram == null) continue; - _appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.State, ev.State); + _appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value, TypingIndicatorVisuals.State, ev.State); } } } @@ -328,6 +328,8 @@ public sealed partial class HolopadSystem : SharedHolopadSystem { if (entity.Comp.User != null) LinkHolopadToUser(entity, entity.Comp.User.Value); + + _meta.AddFlag(entity, MetaDataFlags.ExtraTransformEvents); } private void OnHolopadUserInit(Entity entity, ref ComponentInit args) @@ -343,6 +345,7 @@ public sealed partial class HolopadSystem : SharedHolopadSystem ShutDownHolopad(entity); SetHolopadAmbientState(entity, false); + UpdateAllUIStates(); } private void OnHolopadUserShutdown(Entity entity, ref ComponentShutdown args) @@ -444,15 +447,25 @@ public sealed partial class HolopadSystem : SharedHolopadSystem _telephoneSystem.EndTelephoneCalls((entity, entityTelephone)); } - private void OnParentChanged(Entity entity, ref EntParentChangedMessage args) + private void OnMapUidChanged(Entity entity, ref MapUidChangedEvent args) { UpdateHolopadControlLockoutStartTime(entity); + UpdateAllUIStates(); } private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) { if (args.Powered) + { UpdateHolopadControlLockoutStartTime(entity); + } + + UpdateAllUIStates(); + } + + private void OnAnchorChanged(Entity entity, ref AnchorStateChangedEvent args) + { + UpdateAllUIStates(); } private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) @@ -472,27 +485,37 @@ public sealed partial class HolopadSystem : SharedHolopadSystem { base.Update(frameTime); - _updateTimer += frameTime; - - if (_updateTimer >= UpdateTime) + var query = AllEntityQuery(); + while (query.MoveNext(out var uid, out var holopadUser, out var xform)) { - _updateTimer -= UpdateTime; + if (HasComp(uid)) + continue; - var query = AllEntityQuery(); - while (query.MoveNext(out var uid, out var holopad, out var telephone, out var xform)) + foreach (var holopad in holopadUser.LinkedHolopads) { - UpdateUIState((uid, holopad), telephone); - - if (holopad.User != null && - !HasComp(holopad.User) && - !_xformSystem.InRange((holopad.User.Value, Transform(holopad.User.Value)), (uid, xform), telephone.ListeningRange)) + if (TryComp(holopad, out var telephone) && + !_xformSystem.InRange((holopad.Owner, Transform(holopad)), (uid, xform), telephone.ListeningRange)) { - UnlinkHolopadFromUser((uid, holopad), holopad.User.Value); + UnlinkHolopadFromUser(holopad, (uid, holopadUser)); } } } } + public void UpdateAllUIStates() + { + var querySources = AllEntityQuery(); + while (querySources.MoveNext(out var uid, out var holopad, out var telephone, out var ui)) + { + var uiKey = HasComp(uid) ? HolopadUiKey.AiActionWindow : HolopadUiKey.InteractionWindow; + + if (!_userInterfaceSystem.IsUiOpen((uid, ui), uiKey)) + continue; + + UpdateUIState((uid, holopad), telephone); + } + } + public void UpdateUIState(Entity entity, TelephoneComponent? telephone = null) { if (!Resolve(entity.Owner, ref telephone, false)) @@ -571,23 +594,26 @@ public sealed partial class HolopadSystem : SharedHolopadSystem return; } - if (!TryComp(user, out var holopadUser)) - holopadUser = AddComp(user.Value); + var holopadUser = EnsureComp(user.Value); + var userEnt = (user.Value, holopadUser); - if (user != entity.Comp.User?.Owner) + if (user != entity.Comp.User) { // Removes the old user from the holopad - UnlinkHolopadFromUser(entity, entity.Comp.User); + if (TryComp(entity.Comp.User, out var oldHolopadUser)) + { + UnlinkHolopadFromUser(entity, (entity.Comp.User.Value, oldHolopadUser)); + } // Assigns the new user in their place - holopadUser.LinkedHolopads.Add(entity); - entity.Comp.User = (user.Value, holopadUser); + holopadUser?.LinkedHolopads.Add(entity); + entity.Comp.User = user.Value; } // Add the new user to PVS and sync their appearance with any // holopads connected to the one they are using _pvs.AddGlobalOverride(user.Value); - SyncHolopadHologramAppearanceWithTarget(entity, entity.Comp.User); + SyncHolopadHologramAppearanceWithTarget(entity, userEnt); } private void UnlinkHolopadFromUser(Entity entity, Entity? user) @@ -611,14 +637,14 @@ public sealed partial class HolopadSystem : SharedHolopadSystem { foreach (var linkedHolopad in GetLinkedHolopads(entity)) { - if (linkedHolopad.Comp.Hologram == null) + if (!TryComp(linkedHolopad.Comp.Hologram, out var holopadHologram)) continue; if (user == null) - _appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.State, false); + _appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value, TypingIndicatorVisuals.State, false); - linkedHolopad.Comp.Hologram.Value.Comp.LinkedEntity = user; - Dirty(linkedHolopad.Comp.Hologram.Value); + holopadHologram.LinkedEntity = user; + Dirty(linkedHolopad.Comp.Hologram.Value, holopadHologram); } } @@ -626,8 +652,8 @@ public sealed partial class HolopadSystem : SharedHolopadSystem { entity.Comp.ControlLockoutOwner = null; - if (entity.Comp.Hologram != null) - DeleteHologram(entity.Comp.Hologram.Value, entity); + if (TryComp(entity.Comp.Hologram, out var holopadHologram)) + DeleteHologram((entity.Comp.Hologram.Value, holopadHologram), entity); // Check if the associated holopad user is an AI if (HasComp(entity.Comp.User) && @@ -643,9 +669,9 @@ public sealed partial class HolopadSystem : SharedHolopadSystem _telephoneSystem.EndTelephoneCalls((stationAiCore.Owner, stationAiCoreTelephone)); } } - else + else if (TryComp(entity.Comp.User, out var holopadUser)) { - UnlinkHolopadFromUser(entity, entity.Comp.User); + UnlinkHolopadFromUser(entity, (entity.Comp.User.Value, holopadUser)); } Dirty(entity); @@ -749,14 +775,16 @@ public sealed partial class HolopadSystem : SharedHolopadSystem // Lock out the controls of all involved holopads for a set duration source.Comp.ControlLockoutOwner = user; - source.Comp.ControlLockoutStartTime = _timing.CurTime; + source.Comp.ControlLockoutEndTime = _timing.CurTime + source.Comp.ControlLockoutDuration; + source.Comp.ControlLockoutCoolDownEndTime = _timing.CurTime + source.Comp.ControlLockoutCoolDown; Dirty(source); foreach (var receiver in GetLinkedHolopads(source)) { receiver.Comp.ControlLockoutOwner = user; - receiver.Comp.ControlLockoutStartTime = _timing.CurTime; + receiver.Comp.ControlLockoutEndTime = _timing.CurTime + source.Comp.ControlLockoutDuration; + receiver.Comp.ControlLockoutCoolDownEndTime = _timing.CurTime + source.Comp.ControlLockoutCoolDown; Dirty(receiver); } @@ -796,15 +824,20 @@ public sealed partial class HolopadSystem : SharedHolopadSystem if (!_telephoneSystem.IsSourceInRangeOfReceiver(sourceTelephoneEntity, receiverTelephoneEntity)) continue; - if (receiverHolopad.ControlLockoutStartTime > source.Comp.ControlLockoutStartTime) + if (receiverHolopad.ControlLockoutEndTime > source.Comp.ControlLockoutEndTime || + receiverHolopad.ControlLockoutCoolDownEndTime > source.Comp.ControlLockoutCoolDownEndTime) { - source.Comp.ControlLockoutStartTime = receiverHolopad.ControlLockoutStartTime; + source.Comp.ControlLockoutEndTime = receiverHolopad.ControlLockoutEndTime; + source.Comp.ControlLockoutCoolDownEndTime = receiverHolopad.ControlLockoutCoolDownEndTime; + isDirty = true; } } if (isDirty) + { Dirty(source); + } } private void SetHolopadAmbientState(Entity entity, bool isEnabled) diff --git a/Content.Server/Telephone/TelephoneSystem.cs b/Content.Server/Telephone/TelephoneSystem.cs index 1ab5425416..050961677e 100644 --- a/Content.Server/Telephone/TelephoneSystem.cs +++ b/Content.Server/Telephone/TelephoneSystem.cs @@ -105,7 +105,9 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem RaiseLocalEvent(args.MessageSource, nameEv); // Determine if speech should be relayed via the telephone itself or a designated speaker - var speaker = entity.Comp.Speaker != null ? entity.Comp.Speaker.Value.Owner : entity.Owner; + var speaker = entity.Comp.Speaker != null + ? entity.Comp.Speaker.Value + : entity.Owner; var name = Loc.GetString("chat-telephone-name-relay", ("originalName", nameEv.VoiceName), @@ -130,8 +132,13 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem if (IsTelephoneEngaged(entity)) { - foreach (var receiver in telephone.LinkedTelephones) + foreach (var receiverUid in telephone.LinkedTelephones) { + if (!TryComp(receiverUid, out var receiverTelephone)) + continue; + + var receiver = (receiverUid, receiverTelephone); + if (!IsSourceInRangeOfReceiver(entity, receiver) && !IsSourceInRangeOfReceiver(receiver, entity)) { @@ -144,28 +151,28 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem { // Try to play ring tone if ringing case TelephoneState.Ringing: - if (_timing.CurTime > telephone.StateStartTime + TimeSpan.FromSeconds(telephone.RingingTimeout)) + if (_timing.CurTime > telephone.StateStartTime + telephone.RingingTimeout) EndTelephoneCalls(entity); else if (telephone.RingTone != null && _timing.CurTime > telephone.NextRingToneTime) { _audio.PlayPvs(telephone.RingTone, uid); - telephone.NextRingToneTime = _timing.CurTime + TimeSpan.FromSeconds(telephone.RingInterval); + telephone.NextRingToneTime = _timing.CurTime + telephone.RingInterval; } break; // Try to hang up if there has been no recent in-call activity case TelephoneState.InCall: - if (_timing.CurTime > telephone.StateStartTime + TimeSpan.FromSeconds(telephone.IdlingTimeout)) + if (_timing.CurTime > telephone.StateStartTime + telephone.IdlingTimeout) EndTelephoneCalls(entity); break; // Try to terminate if the telephone has finished hanging up case TelephoneState.EndingCall: - if (_timing.CurTime > telephone.StateStartTime + TimeSpan.FromSeconds(telephone.HangingUpTimeout)) + if (_timing.CurTime > telephone.StateStartTime + telephone.HangingUpTimeout) TerminateTelephoneCalls(entity); break; @@ -180,12 +187,21 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem if (IsTelephoneEngaged(source)) return; + // Save the user as the last caller + source.Comp.LastCallerId = GetNameAndJobOfCallingEntity(user); + Dirty(source); + + // Attempt to call all receivers foreach (var receiver in receivers) + { TryCallTelephone(source, receiver, user, options); + } // If no connections could be made, hang up the telephone if (!IsTelephoneEngaged(source)) + { EndTelephoneCalls(source); + } } public void CallTelephone(Entity source, Entity receiver, EntityUid user, TelephoneCallOptions? options = null) @@ -222,12 +238,12 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem var callerInfo = GetNameAndJobOfCallingEntity(user); // Base the name of the device on its label - string? deviceName = null; - if (TryComp(source, out var label)) - deviceName = label.CurrentLabel; + { + callerInfo.DeviceId = label.CurrentLabel; + } - receiver.Comp.LastCallerId = (callerInfo.Item1, callerInfo.Item2, deviceName); // This will be networked when the state changes + receiver.Comp.LastCallerId = callerInfo; // This will be networked when the state changes receiver.Comp.LinkedTelephones.Add(source); receiver.Comp.Muted = options?.MuteReceiver == true; @@ -257,8 +273,12 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem if (receiver.Comp.LinkedTelephones.Count != 1) return; - var source = receiver.Comp.LinkedTelephones.First(); - CommenceTelephoneCall(source, receiver); + var sourceUid = receiver.Comp.LinkedTelephones.First(); + + if (!TryComp(sourceUid, out var sourceTelephone)) + return; + + CommenceTelephoneCall((sourceUid, sourceTelephone), receiver); } private void CommenceTelephoneCall(Entity source, Entity receiver) @@ -311,13 +331,18 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem private void HandleEndingTelephoneCalls(Entity entity, TelephoneState newState) { - foreach (var linkedTelephone in entity.Comp.LinkedTelephones) + foreach (var linkedUid in entity.Comp.LinkedTelephones) { - if (!linkedTelephone.Comp.LinkedTelephones.Remove(entity)) + if (!TryComp(linkedUid, out var linkedTelephone)) continue; - if (!IsTelephoneEngaged(linkedTelephone)) - EndTelephoneCalls(linkedTelephone); + if (!linkedTelephone.LinkedTelephones.Remove(entity)) + continue; + + var linked = (linkedUid, linkedTelephone); + + if (!IsTelephoneEngaged(linked)) + EndTelephoneCalls(linked); } entity.Comp.LinkedTelephones.Clear(); @@ -372,10 +397,13 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem var evReceivedMessage = new TelephoneMessageReceivedEvent(message, chatMsg, messageSource, source); - foreach (var receiver in source.Comp.LinkedTelephones) + foreach (var receiverUid in source.Comp.LinkedTelephones) { - RaiseLocalEvent(receiver, ref evReceivedMessage); - receiver.Comp.StateStartTime = _timing.CurTime; + if (!TryComp(receiverUid, out var receiverTelephone)) + continue; + + RaiseLocalEvent(receiverUid, ref evReceivedMessage); + receiverTelephone.StateStartTime = _timing.CurTime; } if (name != Name(messageSource)) @@ -390,6 +418,7 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem { var oldState = entity.Comp.CurrentState; + entity.Comp.PreviousState = entity.Comp.CurrentState; entity.Comp.CurrentState = newState; entity.Comp.StateStartTime = _timing.CurTime; Dirty(entity); @@ -419,24 +448,21 @@ public sealed partial class TelephoneSystem : SharedTelephoneSystem entity.Comp.Speaker = speaker; } - private (string?, string?) GetNameAndJobOfCallingEntity(EntityUid uid) + private TelephoneCallRecord GetNameAndJobOfCallingEntity(EntityUid uid) { - string? presumedName = null; - string? presumedJob = null; + var record = new TelephoneCallRecord(); if (HasComp(uid) || HasComp(uid)) { - presumedName = Name(uid); - return (presumedName, presumedJob); + record.CallerId = Name(uid); } - - if (_idCardSystem.TryFindIdCard(uid, out var idCard)) + else if (_idCardSystem.TryFindIdCard(uid, out var idCard)) { - presumedName = string.IsNullOrWhiteSpace(idCard.Comp.FullName) ? null : idCard.Comp.FullName; - presumedJob = idCard.Comp.LocalizedJobTitle; + record.CallerId = string.IsNullOrWhiteSpace(idCard.Comp.FullName) ? null : idCard.Comp.FullName; + record.CallerJob = idCard.Comp.LocalizedJobTitle; } - return (presumedName, presumedJob); + return record; } public bool IsSourceAbleToReachReceiver(Entity source, Entity receiver) diff --git a/Content.Shared/Holopad/HolopadComponent.cs b/Content.Shared/Holopad/HolopadComponent.cs index 98f05b03f9..d4618b37cc 100644 --- a/Content.Shared/Holopad/HolopadComponent.cs +++ b/Content.Shared/Holopad/HolopadComponent.cs @@ -11,21 +11,21 @@ namespace Content.Shared.Holopad; /// /// Holopads also require a to function /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] [Access(typeof(SharedHolopadSystem))] public sealed partial class HolopadComponent : Component { /// /// The entity being projected by the holopad /// - [ViewVariables] - public Entity? Hologram; + [DataField] + public EntityUid? Hologram; /// /// The entity using the holopad /// - [ViewVariables] - public Entity? User; + [DataField] + public EntityUid? User; /// /// Proto ID for the user's hologram @@ -36,26 +36,32 @@ public sealed partial class HolopadComponent : Component /// /// The entity that has locked out the controls of this device /// - [ViewVariables, AutoNetworkedField] + [DataField, AutoNetworkedField] public EntityUid? ControlLockoutOwner = null; /// - /// The game tick the control lockout was initiated + /// The time the control lockout will end /// - [ViewVariables, AutoNetworkedField] - public TimeSpan ControlLockoutStartTime; + [DataField, AutoNetworkedField, AutoPausedField] + public TimeSpan ControlLockoutEndTime; + + /// + /// The time the control lockout cool down will end + /// + [DataField, AutoNetworkedField, AutoPausedField] + public TimeSpan ControlLockoutCoolDownEndTime; /// /// The duration that the control lockout will last in seconds /// [DataField] - public float ControlLockoutDuration { get; private set; } = 90f; + public TimeSpan ControlLockoutDuration { get; private set; } = TimeSpan.FromSeconds(90); /// /// The duration before the controls can be lockout again in seconds /// [DataField] - public float ControlLockoutCoolDown { get; private set; } = 180f; + public TimeSpan ControlLockoutCoolDown { get; private set; } = TimeSpan.FromSeconds(180); } #region: Event messages diff --git a/Content.Shared/Holopad/SharedHolopadSystem.cs b/Content.Shared/Holopad/SharedHolopadSystem.cs index 63c91928c2..8029e3f201 100644 --- a/Content.Shared/Holopad/SharedHolopadSystem.cs +++ b/Content.Shared/Holopad/SharedHolopadSystem.cs @@ -8,10 +8,7 @@ public abstract partial class SharedHolopadSystem : EntitySystem public bool IsHolopadControlLocked(Entity entity, EntityUid? user = null) { - if (entity.Comp.ControlLockoutStartTime == TimeSpan.Zero) - return false; - - if (entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutDuration) < _timing.CurTime) + if (_timing.CurTime > entity.Comp.ControlLockoutEndTime) return false; if (entity.Comp.ControlLockoutOwner == null || entity.Comp.ControlLockoutOwner == user) @@ -22,15 +19,12 @@ public abstract partial class SharedHolopadSystem : EntitySystem public TimeSpan GetHolopadControlLockedPeriod(Entity entity) { - return entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutDuration) - _timing.CurTime; + return entity.Comp.ControlLockoutEndTime - _timing.CurTime; } public bool IsHolopadBroadcastOnCoolDown(Entity entity) { - if (entity.Comp.ControlLockoutStartTime == TimeSpan.Zero) - return false; - - if (entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutCoolDown) < _timing.CurTime) + if (_timing.CurTime > entity.Comp.ControlLockoutCoolDownEndTime) return false; return true; @@ -38,6 +32,6 @@ public abstract partial class SharedHolopadSystem : EntitySystem public TimeSpan GetHolopadBroadcastCoolDown(Entity entity) { - return entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutCoolDown) - _timing.CurTime; + return entity.Comp.ControlLockoutCoolDownEndTime - _timing.CurTime; } } diff --git a/Content.Shared/Telephone/SharedTelephoneSystem.cs b/Content.Shared/Telephone/SharedTelephoneSystem.cs index 78a72d59a5..b576fd381e 100644 --- a/Content.Shared/Telephone/SharedTelephoneSystem.cs +++ b/Content.Shared/Telephone/SharedTelephoneSystem.cs @@ -1,3 +1,4 @@ +using Robust.Shared.Utility; using System.Linq; namespace Content.Shared.Telephone; @@ -21,15 +22,15 @@ public abstract class SharedTelephoneSystem : EntitySystem if (presumedJob != null) callerId = Loc.GetString("chat-telephone-caller-id-with-job", - ("callerName", presumedName), - ("callerJob", presumedJob), + ("callerName", FormattedMessage.RemoveMarkupPermissive(presumedName)), + ("callerJob", FormattedMessage.RemoveMarkupPermissive(presumedJob)), ("color", fontColor), ("fontType", fontType), ("fontSize", fontSize)); else callerId = Loc.GetString("chat-telephone-caller-id-without-job", - ("callerName", presumedName), + ("callerName", FormattedMessage.RemoveMarkupPermissive(presumedName)), ("color", fontColor), ("fontType", fontType), ("fontSize", fontSize)); @@ -48,7 +49,7 @@ public abstract class SharedTelephoneSystem : EntitySystem } return Loc.GetString("chat-telephone-device-id", - ("deviceName", deviceName), + ("deviceName", FormattedMessage.RemoveMarkupPermissive(deviceName)), ("color", fontColor), ("fontType", fontType), ("fontSize", fontSize)); diff --git a/Content.Shared/Telephone/TelephoneComponent.cs b/Content.Shared/Telephone/TelephoneComponent.cs index 89748d78a4..834453edeb 100644 --- a/Content.Shared/Telephone/TelephoneComponent.cs +++ b/Content.Shared/Telephone/TelephoneComponent.cs @@ -1,12 +1,11 @@ using Content.Shared.Chat; -using Content.Shared.Speech; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Serialization; namespace Content.Shared.Telephone; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] [Access(typeof(SharedTelephoneSystem))] public sealed partial class TelephoneComponent : Component { @@ -14,19 +13,19 @@ public sealed partial class TelephoneComponent : Component /// Sets how long the telephone will ring before it automatically hangs up /// [DataField] - public float RingingTimeout = 30; + public TimeSpan RingingTimeout = TimeSpan.FromSeconds(30); /// /// Sets how long the telephone can remain idle in-call before it automatically hangs up /// [DataField] - public float IdlingTimeout = 60; + public TimeSpan IdlingTimeout = TimeSpan.FromSeconds(60); /// /// Sets how long the telephone will stay in the hanging up state before return to idle /// [DataField] - public float HangingUpTimeout = 2; + public TimeSpan HangingUpTimeout = TimeSpan.FromSeconds(2); /// /// Tone played while the phone is ringing @@ -38,7 +37,7 @@ public sealed partial class TelephoneComponent : Component /// Sets the number of seconds before the next ring tone is played /// [DataField] - public float RingInterval = 2f; + public TimeSpan RingInterval = TimeSpan.FromSeconds(2); /// /// The time at which the next tone will be played @@ -90,8 +89,8 @@ public sealed partial class TelephoneComponent : Component /// /// Speech is relayed through this entity instead of the telephone /// - [ViewVariables(VVAccess.ReadOnly)] - public Entity? Speaker = null; + [DataField] + public EntityUid? Speaker = null; /// /// Telephone number for this device @@ -99,41 +98,56 @@ public sealed partial class TelephoneComponent : Component /// /// For future use - a system for generating and handling telephone numbers has not been implemented yet /// - [ViewVariables] + [DataField] public int TelephoneNumber = -1; /// - /// Linked telephone + /// Other telephones that have been linked to this one /// - [ViewVariables] - public HashSet> LinkedTelephones = new(); + [DataField] + public HashSet LinkedTelephones = new(); /// /// Defines the current state the telephone is in /// - [ViewVariables, AutoNetworkedField] + [DataField, AutoNetworkedField] public TelephoneState CurrentState = TelephoneState.Idle; + /// + /// Defines the previous state the telephone was in + /// + [DataField, AutoNetworkedField] + public TelephoneState PreviousState = TelephoneState.Idle; + /// /// The game tick the current state started /// - [ViewVariables] + [DataField, AutoPausedField] public TimeSpan StateStartTime; /// /// Sets whether the telphone can pick up nearby speech /// - [ViewVariables] + [DataField] public bool Muted = false; /// /// The presumed name and/or job of the last person to call this telephone /// and the name of the device that they used to do so /// - [ViewVariables, AutoNetworkedField] - public (string?, string?, string?) LastCallerId; + [DataField, AutoNetworkedField] + public TelephoneCallRecord? LastCallerId; } +/// +/// A telephone call record. +/// +/// The name of the person who placed the call. +/// The job of the person who placed the call. +/// The name of the device used to make the call. +[Serializable, NetSerializable] +public record struct TelephoneCallRecord(string? CallerId, string? CallerJob, string? DeviceId); + #region: Telephone events ///