From a8eee5878ad999f56f474256f4f8a74806f4d4c6 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 5 Jun 2023 16:33:49 +1200 Subject: [PATCH] Misc replay related changes (#17102) --- Content.Client/Actions/ActionsSystem.cs | 2 +- Content.Client/Examine/ExamineSystem.cs | 6 +- .../GameTicking/Managers/ClientGameTicker.cs | 17 ++++- Content.Client/Gameplay/GameplayState.cs | 3 +- .../MainMenu/UI/MainMenuControl.xaml.cs | 5 +- .../RoundEnd/RoundEndSummaryWindow.cs | 2 + .../Systems/Actions/ActionUIController.cs | 47 +++++++------- Content.Server/Chat/Managers/ChatManager.cs | 24 ++++++- Content.Server/GameTicking/GameTicker.cs | 12 +++- .../Stunnable/Systems/StunOnCollideSystem.cs | 6 -- Content.Shared/CCVar/CCVars.cs | 11 ++++ Content.Shared/Chat/ChatChannel.cs | 2 + .../Systems/SharedContentEyeSystem.cs | 7 +++ .../Movement/Systems/SharedMoverController.cs | 63 ++++++++++--------- 14 files changed, 132 insertions(+), 75 deletions(-) diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 08cdb8c832..3eac41d835 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -173,7 +173,7 @@ namespace Content.Client.Actions public void LinkAllActions(ActionsComponent? actions = null) { var player = _playerManager.LocalPlayer?.ControlledEntity; - if (player == null || !Resolve(player.Value, ref actions)) + if (player == null || !Resolve(player.Value, ref actions, false)) { return; } diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs index 9a563a8638..cd09ff5fd1 100644 --- a/Content.Client/Examine/ExamineSystem.cs +++ b/Content.Client/Examine/ExamineSystem.cs @@ -16,6 +16,7 @@ using Robust.Shared.Utility; using System.Linq; using System.Threading; using Content.Shared.Eye.Blinding.Components; +using Robust.Client; using static Content.Shared.Interaction.SharedInteractionSystem; using static Robust.Client.UserInterface.Controls.BoxContainer; @@ -28,6 +29,7 @@ namespace Content.Client.Examine [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly VerbSystem _verbSystem = default!; + [Dependency] private readonly IBaseClient _client = default!; public const string StyleClassEntityTooltip = "entity-tooltip"; @@ -341,10 +343,10 @@ namespace Content.Client.Examine canSeeClearly = false; OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly); - if (entity.IsClientSide()) + if (entity.IsClientSide() + || _client.RunLevel == ClientRunLevel.SinglePlayerGame) // i.e. a replay { message = GetExamineText(entity, playerEnt); - UpdateTooltipInfo(playerEnt.Value, entity, message); } else diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs index f1e29a2ea1..33a20c945a 100644 --- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs +++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs @@ -21,11 +21,18 @@ namespace Content.Client.GameTicking.Managers [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly BackgroundAudioSystem _backgroundAudio = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; [ViewVariables] private bool _initialized; private Dictionary> _jobsAvailable = new(); private Dictionary _stationNames = new(); + /// + /// The current round-end window. Could be used to support re-opening the window after closing it. + /// + private RoundEndSummaryWindow? _window; + [ViewVariables] public bool AreWeReady { get; private set; } [ViewVariables] public bool IsGameStarted { get; private set; } [ViewVariables] public string? LobbySong { get; private set; } @@ -127,13 +134,17 @@ namespace Content.Client.GameTicking.Managers if (message.LobbySong != null) { LobbySong = message.LobbySong; - Get().StartLobbyMusic(); + _backgroundAudio.StartLobbyMusic(); } RestartSound = message.RestartSound; + // Don't open duplicate windows (mainly for replays). + if (_window?.RoundId == message.RoundId) + return; + //This is not ideal at all, but I don't see an immediately better fit anywhere else. - var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager); + _window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager); } private void RoundRestartCleanup(RoundRestartCleanupEvent ev) @@ -147,7 +158,7 @@ namespace Content.Client.GameTicking.Managers return; } - SoundSystem.Play(RestartSound, Filter.Empty()); + _audio.PlayGlobal(RestartSound, Filter.Local(), false); // Cleanup the sound, we only want it to play when the round restarts after it ends normally. RestartSound = null; diff --git a/Content.Client/Gameplay/GameplayState.cs b/Content.Client/Gameplay/GameplayState.cs index 3765753c69..1efee978f3 100644 --- a/Content.Client/Gameplay/GameplayState.cs +++ b/Content.Client/Gameplay/GameplayState.cs @@ -13,7 +13,8 @@ using Robust.Shared.Timing; namespace Content.Client.Gameplay { - public sealed class GameplayState : GameplayStateBase, IMainViewportState + [Virtual] + public class GameplayState : GameplayStateBase, IMainViewportState { [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IOverlayManager _overlayManager = default!; diff --git a/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs b/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs index 5216547b92..e9e90288c6 100644 --- a/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs +++ b/Content.Client/MainMenu/UI/MainMenuControl.xaml.cs @@ -1,13 +1,10 @@ -using Content.Client.Changelog; -using Content.Client.Parallax; -using Robust.Client.AutoGenerated; +using Robust.Client.AutoGenerated; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared; using Robust.Shared.Configuration; -using Robust.Shared.Localization; namespace Content.Client.MainMenu.UI { diff --git a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs index c6d989c0a8..8f83689e80 100644 --- a/Content.Client/RoundEnd/RoundEndSummaryWindow.cs +++ b/Content.Client/RoundEnd/RoundEndSummaryWindow.cs @@ -12,6 +12,7 @@ namespace Content.Client.RoundEnd public sealed class RoundEndSummaryWindow : DefaultWindow { private readonly IEntityManager _entityManager; + public int RoundId; public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, int roundId, RoundEndMessageEvent.RoundEndPlayerInfo[] info, IEntityManager entityManager) @@ -28,6 +29,7 @@ namespace Content.Client.RoundEnd // "clown slipped the crew x times.", "x shots were fired this round.", etc. // Also good for serious info. + RoundId = roundId; var roundEndTabs = new TabContainer(); roundEndTabs.AddChild(MakeRoundEndSummaryTab(gm, roundEnd, roundTimeSpan, roundId)); roundEndTabs.AddChild(MakePlayerManifestoTab(info)); diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index 472ad49a3f..d325009912 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -108,18 +108,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); - LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); - - _window.OnOpen += OnWindowOpened; - _window.OnClose += OnWindowClosed; - _window.ClearButton.OnPressed += OnClearPressed; - _window.SearchBar.OnTextChanged += OnSearchChanged; - _window.FilterButton.OnItemSelected += OnFilterSelected; - - if (_actionsSystem != null) { _actionsSystem.ActionAdded += OnActionAdded; @@ -328,18 +316,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); } @@ -779,10 +755,32 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); + LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); + + _window.OnOpen += OnWindowOpened; + _window.OnClose += OnWindowClosed; + _window.ClearButton.OnPressed += OnClearPressed; + _window.SearchBar.OnTextChanged += OnSearchChanged; + _window.FilterButton.OnItemSelected += OnFilterSelected; + if (ActionsBar == null) { return; @@ -791,7 +789,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0) @@ -259,8 +265,14 @@ namespace Content.Server.Chat.Managers var msg = new ChatMessage(channel, message, wrappedMessage, source, hideChat, colorOverride, audioPath, audioVolume); _netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients); - if (recordReplay) + if (!recordReplay) + return; + + if ((channel & ChatChannel.AdminRelated) == 0 || + _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) + { _replay.QueueReplayMessage(msg); + } } public void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string wrappedMessage, EntityUid source, @@ -283,8 +295,14 @@ namespace Content.Server.Chat.Managers var msg = new ChatMessage(channel, message, wrappedMessage, source, hideChat, colorOverride, audioPath, audioVolume); _netManager.ServerSendToAll(new MsgChatMessage() { Message = msg }); - if (recordReplay) + if (!recordReplay) + return; + + if ((channel & ChatChannel.AdminRelated) == 0 || + _configurationManager.GetCVar(CCVars.ReplayRecordAdminChat)) + { _replay.QueueReplayMessage(msg); + } } public bool MessageCharacterLimit(IPlayerSession? player, string message) diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index a7f7ef5471..4ede0f6f50 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -28,6 +28,9 @@ using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Robust.Shared.Replays; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -65,7 +68,7 @@ namespace Content.Server.GameTicking DebugTools.Assert(_prototypeManager.Index(FallbackOverflowJob).Name == FallbackOverflowJobName, "Overflow role does not have the correct name!"); InitializeGameRules(); - + _replay.OnRecordingStarted += OnRecordingStart; _initialized = true; } @@ -85,6 +88,12 @@ namespace Content.Server.GameTicking base.Shutdown(); ShutdownGameRules(); + _replay.OnRecordingStarted -= OnRecordingStart; + } + + private void OnRecordingStart((MappingDataNode, List) data) + { + data.Item1["roundId"] = new ValueDataNode(RoundId.ToString()); } private void SendServerMessage(string message) @@ -124,5 +133,6 @@ namespace Content.Server.GameTicking [Dependency] private readonly ServerUpdateManager _serverUpdates = default!; [Dependency] private readonly PlayTimeTrackingSystem _playTimeTrackings = default!; [Dependency] private readonly UserDbDataManager _userDb = default!; + [Dependency] private readonly IReplayRecordingManager _replay = default!; } } diff --git a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs index 92a2e91a22..f2ddbcf803 100644 --- a/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs +++ b/Content.Server/Stunnable/Systems/StunOnCollideSystem.cs @@ -25,12 +25,6 @@ namespace Content.Server.Stunnable if (EntityManager.TryGetComponent(target, out var status)) { - StandingStateComponent? standingState = null; - AppearanceComponent? appearance = null; - - // Let the actual methods log errors for these. - Resolve(target, ref standingState, ref appearance, false); - _stunSystem.TryStun(target, TimeSpan.FromSeconds(component.StunAmount), true, status); _stunSystem.TryKnockdown(target, TimeSpan.FromSeconds(component.KnockdownAmount), true, diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index eee95cd2fe..63fcf16bcd 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1581,5 +1581,16 @@ namespace Content.Shared.CCVar /// public static readonly CVarDef GCMaximumTimeMs = CVarDef.Create("entgc.maximum_time_ms", 5, CVar.SERVERONLY); + + /* + * Replays + */ + + /// + /// Whether or not to record admin chat. If replays are being publicly distributes, this should probably be + /// false. + /// + public static readonly CVarDef ReplayRecordAdminChat = + CVarDef.Create("replay.record_admin_chat", false, CVar.SERVERONLY); } } diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index a80deda37a..f14f33666a 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -83,5 +83,7 @@ namespace Content.Shared.Chat /// Channels considered to be IC. /// IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual, + + AdminRelated = Admin | AdminAlert | AdminChat, } } diff --git a/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs b/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs index 456792c853..9c749695a9 100644 --- a/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs +++ b/Content.Shared/Movement/Systems/SharedContentEyeSystem.cs @@ -37,6 +37,7 @@ public abstract class SharedContentEyeSystem : EntitySystem .Register(); Sawmill.Level = LogLevel.Info; + UpdatesOutsidePrediction = true; } private void OnContentZoomRequest(RequestTargetZoomEvent msg, EntitySessionEventArgs args) @@ -112,6 +113,12 @@ public abstract class SharedContentEyeSystem : EntitySystem Dirty(component); } + public void SetMaxZoom(EntityUid uid, Vector2 value, ContentEyeComponent? component = null) + { + if (Resolve(uid, ref component)) + component.MaxZoom = value; + } + private void Zoom(EntityUid uid, bool zoomIn, ContentEyeComponent? component = null) { if (!Resolve(uid, ref component)) diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 0c44e83850..80ee0c38a7 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -133,35 +133,7 @@ namespace Content.Shared.Movement.Systems } } - var angleDiff = Angle.ShortestDistance(mover.RelativeRotation, mover.TargetRelativeRotation); - - // if we've just traversed then lerp to our target rotation. - if (!angleDiff.EqualsApprox(Angle.Zero, 0.001)) - { - var adjustment = angleDiff * 5f * frameTime; - var minAdjustment = 0.01 * frameTime; - - if (angleDiff < 0) - { - adjustment = Math.Min(adjustment, -minAdjustment); - adjustment = Math.Clamp(adjustment, angleDiff, -angleDiff); - } - else - { - adjustment = Math.Max(adjustment, minAdjustment); - adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff); - } - - mover.RelativeRotation += adjustment; - mover.RelativeRotation.FlipPositive(); - Dirty(mover); - } - else if (!angleDiff.Equals(Angle.Zero)) - { - mover.TargetRelativeRotation.FlipPositive(); - mover.RelativeRotation = mover.TargetRelativeRotation; - Dirty(mover); - } + LerpRotation(mover, frameTime); if (!canMove || physicsComponent.BodyStatus != BodyStatus.OnGround @@ -282,6 +254,39 @@ namespace Content.Shared.Movement.Systems PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent); } + public void LerpRotation(InputMoverComponent mover, float frameTime) + { + var angleDiff = Angle.ShortestDistance(mover.RelativeRotation, mover.TargetRelativeRotation); + + // if we've just traversed then lerp to our target rotation. + if (!angleDiff.EqualsApprox(Angle.Zero, 0.001)) + { + var adjustment = angleDiff * 5f * frameTime; + var minAdjustment = 0.01 * frameTime; + + if (angleDiff < 0) + { + adjustment = Math.Min(adjustment, -minAdjustment); + adjustment = Math.Clamp(adjustment, angleDiff, -angleDiff); + } + else + { + adjustment = Math.Max(adjustment, minAdjustment); + adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff); + } + + mover.RelativeRotation += adjustment; + mover.RelativeRotation.FlipPositive(); + Dirty(mover); + } + else if (!angleDiff.Equals(Angle.Zero)) + { + mover.TargetRelativeRotation.FlipPositive(); + mover.RelativeRotation = mover.TargetRelativeRotation; + Dirty(mover); + } + } + private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity) { var speed = velocity.Length;