From f1cb0ca37ab2a32f9b9b1a089b015baf9c4675b3 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 8 Jan 2023 07:04:09 +1300 Subject: [PATCH] Misc replay related changes (#13250) --- .../Systems/Chat/ChatUIController.cs | 17 +++++++++++++---- .../Systems/Chat/Widgets/ChatBox.xaml.cs | 12 +++++++++++- .../Administration/GamePrototypeLoadManager.cs | 17 +++++++++++++++++ .../Administration/NetworkResourceManager.cs | 15 +++++++++++++++ .../Fluids/EntitySystems/SpraySystem.cs | 3 ++- .../Administration/IGamePrototypeLoadManager.cs | 12 ++++++++++++ .../SharedNetworkResourceManager.cs | 11 +++++++++++ .../SharedHandsSystem.Interactions.cs | 3 +++ .../Projectiles/SharedProjectileSystem.cs | 4 ++-- .../EntitySystems/SharedTimedDespawnSystem.cs | 6 ++++++ .../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 +- 11 files changed, 93 insertions(+), 9 deletions(-) diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 4ecde7deb3..8116946787 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -40,6 +40,7 @@ public sealed class ChatUIController : UIController [Dependency] private readonly IClientNetManager _net = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IStateManager _state = default!; + [Dependency] private readonly IGameTiming _timing = default!; [UISystemDependency] private readonly ExamineSystem? _examine = default; [UISystemDependency] private readonly GhostSystem? _ghost = default; @@ -122,7 +123,7 @@ public sealed class ChatUIController : UIController /// private readonly Dictionary _unreadMessages = new(); - public readonly List History = new(); + public readonly List<(GameTick, ChatMessage)> History = new(); // Maintains which channels a client should be able to filter (for showing in the chatbox) // and select (for attempting to send on). @@ -639,12 +640,12 @@ public sealed class ChatUIController : UIController private void OnChatMessage(MsgChatMessage message) => ProcessChatMessage(message.Message); - public void ProcessChatMessage(ChatMessage msg) + public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true) { // Log all incoming chat to repopulate when filter is un-toggled if (!msg.HideChat) { - History.Add(msg); + History.Add((_timing.CurTick, msg)); MessageAdded?.Invoke(msg); if (!msg.Read) @@ -660,7 +661,7 @@ public sealed class ChatUIController : UIController } // Local messages that have an entity attached get a speech bubble. - if (msg.SenderEntity == default) + if (!speechBubble || msg.SenderEntity == default) return; switch (msg.Channel) @@ -711,6 +712,14 @@ public sealed class ChatUIController : UIController _typingIndicator?.ClientChangedChatText(); } + public void Repopulate() + { + foreach (var chat in _chats) + { + chat.Repopulate(); + } + } + private readonly record struct SpeechBubbleData(string Message, SpeechBubble.SpeechType Type); private sealed class SpeechBubbleQueueData diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs index 04d7e42ad1..da81066e0b 100644 --- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs @@ -71,13 +71,23 @@ public partial class ChatBox : UIWidget UpdateSelectedChannel(); } + public void Repopulate() + { + Contents.Clear(); + + foreach (var message in _controller.History) + { + OnMessageAdded(message.Item2); + } + } + private void OnChannelFilter(ChatChannel channel, bool active) { Contents.Clear(); foreach (var message in _controller.History) { - OnMessageAdded(message); + OnMessageAdded(message.Item2); } if (active) diff --git a/Content.Server/Administration/GamePrototypeLoadManager.cs b/Content.Server/Administration/GamePrototypeLoadManager.cs index 77a4550a15..b8ed602f61 100644 --- a/Content.Server/Administration/GamePrototypeLoadManager.cs +++ b/Content.Server/Administration/GamePrototypeLoadManager.cs @@ -3,6 +3,8 @@ using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Network; using Robust.Shared.Prototypes; +using Robust.Shared.Replays; +using Robust.Shared.Serialization.Markdown.Mapping; namespace Content.Server.Administration; @@ -11,6 +13,7 @@ namespace Content.Server.Administration; /// public sealed class GamePrototypeLoadManager : IGamePrototypeLoadManager { + [Dependency] private readonly IReplayRecordingManager _replay = default!; [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; @@ -18,11 +21,22 @@ public sealed class GamePrototypeLoadManager : IGamePrototypeLoadManager [Dependency] private readonly ILocalizationManager _localizationManager = default!; private readonly List _loadedPrototypes = new(); + public IReadOnlyList LoadedPrototypes => _loadedPrototypes; public void Initialize() { _netManager.RegisterNetMessage(ClientLoadsPrototype); _netManager.Connected += NetManagerOnConnected; + _replay.OnRecordingStarted += OnStartReplayRecording; + } + + private void OnStartReplayRecording((MappingDataNode, List) initReplayData) + { + // replays will need information about currently loaded prototypes + foreach (var prototype in _loadedPrototypes) + { + initReplayData.Item2.Add(new ReplayPrototypeUploadMsg { PrototypeData = prototype }); + } } public void SendGamePrototype(string prototype) @@ -47,6 +61,9 @@ public sealed class GamePrototypeLoadManager : IGamePrototypeLoadManager private void LoadPrototypeData(string prototypeData) { _loadedPrototypes.Add(prototypeData); + + _replay.QueueReplayMessage(new ReplayPrototypeUploadMsg { PrototypeData = prototypeData }); + var msg = new GamePrototypeLoadMessage { PrototypeData = prototypeData diff --git a/Content.Server/Administration/NetworkResourceManager.cs b/Content.Server/Administration/NetworkResourceManager.cs index 25b8f9de93..93a6955965 100644 --- a/Content.Server/Administration/NetworkResourceManager.cs +++ b/Content.Server/Administration/NetworkResourceManager.cs @@ -5,6 +5,8 @@ using Content.Shared.CCVar; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; +using Robust.Shared.Replays; +using Robust.Shared.Serialization.Markdown.Mapping; namespace Content.Server.Administration; @@ -15,6 +17,7 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager [Dependency] private readonly IServerNetManager _serverNetManager = default!; [Dependency] private readonly IConfigurationManager _cfgManager = default!; [Dependency] private readonly IServerDbManager _serverDb = default!; + [Dependency] private readonly IReplayRecordingManager _replay = default!; [ViewVariables] public bool Enabled { get; private set; } = true; [ViewVariables] public float SizeLimit { get; private set; } = 0f; @@ -30,6 +33,16 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager _cfgManager.OnValueChanged(CCVars.ResourceUploadingStoreEnabled, value => StoreUploaded = value, true); AutoDelete(_cfgManager.GetCVar(CCVars.ResourceUploadingStoreDeletionDays)); + _replay.OnRecordingStarted += OnStartReplayRecording; + } + + private void OnStartReplayRecording((MappingDataNode, List) initReplayData) + { + // replays will need information about currently loaded extra resources + foreach (var (path, data) in ContentRoot.GetAllFiles()) + { + initReplayData.Item2.Add(new ReplayResourceUploadMsg { RelativePath = path, Data = data }); + } } /// @@ -63,6 +76,8 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager channel.SendMessage(msg); } + _replay.QueueReplayMessage(new ReplayResourceUploadMsg { RelativePath = msg.RelativePath, Data = msg.Data }); + if (!StoreUploaded) return; diff --git a/Content.Server/Fluids/EntitySystems/SpraySystem.cs b/Content.Server/Fluids/EntitySystems/SpraySystem.cs index e6b8a0eeec..f7897a59f9 100644 --- a/Content.Server/Fluids/EntitySystems/SpraySystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpraySystem.cs @@ -20,6 +20,7 @@ public sealed class SpraySystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly VaporSystem _vaporSystem = default!; @@ -118,7 +119,7 @@ public sealed class SpraySystem : EntitySystem _vaporSystem.Start(vaporComponent, vaporXform, impulseDirection, component.SprayVelocity, target, component.SprayAliveTime, args.User); } - SoundSystem.Play(component.SpraySound.GetSound(), Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.125f)); + _audio.PlayPvs(component.SpraySound, uid, component.SpraySound.Params.WithVariation(0.125f)); RaiseLocalEvent(uid, new RefreshItemCooldownEvent(curTime, curTime + TimeSpan.FromSeconds(component.CooldownTime)), true); diff --git a/Content.Shared/Administration/IGamePrototypeLoadManager.cs b/Content.Shared/Administration/IGamePrototypeLoadManager.cs index 20db4d2b64..b02ad69ced 100644 --- a/Content.Shared/Administration/IGamePrototypeLoadManager.cs +++ b/Content.Shared/Administration/IGamePrototypeLoadManager.cs @@ -1,3 +1,6 @@ +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + namespace Content.Shared.Administration; public interface IGamePrototypeLoadManager @@ -5,3 +8,12 @@ public interface IGamePrototypeLoadManager public void Initialize(); public void SendGamePrototype(string prototype); } + +// TODO REPLAYS +// Figure out a way to just directly save NetMessage objects to replays. This just uses IRobustSerializer as a crutch. + +[Serializable, NetSerializable] +public sealed class ReplayPrototypeUploadMsg +{ + public string PrototypeData = default!; +} diff --git a/Content.Shared/Administration/SharedNetworkResourceManager.cs b/Content.Shared/Administration/SharedNetworkResourceManager.cs index fcb6fc754e..e1dc13a836 100644 --- a/Content.Shared/Administration/SharedNetworkResourceManager.cs +++ b/Content.Shared/Administration/SharedNetworkResourceManager.cs @@ -1,5 +1,6 @@ using Robust.Shared.ContentPack; using Robust.Shared.Network; +using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Administration; @@ -38,4 +39,14 @@ public abstract class SharedNetworkResourceManager : IDisposable // MemoryContentRoot uses a ReaderWriterLockSlim, which we need to dispose of. ContentRoot.Dispose(); } + + // TODO REPLAYS + // Figure out a way to just directly save NetMessage objects to replays. This just uses IRobustSerializer as a crutch. + + [Serializable, NetSerializable] + public sealed class ReplayResourceUploadMsg + { + public byte[] Data = default!; + public ResourcePath RelativePath = default!; + } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs index bc771a6351..96f5e915fc 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -77,6 +77,9 @@ public abstract partial class SharedHandsSystem : EntitySystem if (!TryComp(session?.AttachedEntity, out SharedHandsComponent? component)) return; + if (!_actionBlocker.CanInteract(session.AttachedEntity.Value, null)) + return; + if (component.ActiveHand == null || component.Hands.Count < 2) return; diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index ae7000b461..101bc49eba 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -32,7 +32,7 @@ namespace Content.Shared.Projectiles } [NetSerializable, Serializable] - protected sealed class ProjectileComponentState : ComponentState + public sealed class ProjectileComponentState : ComponentState { public ProjectileComponentState(EntityUid shooter, bool ignoreShooter) { @@ -45,7 +45,7 @@ namespace Content.Shared.Projectiles } [Serializable, NetSerializable] - protected sealed class ImpactEffectEvent : EntityEventArgs + public sealed class ImpactEffectEvent : EntityEventArgs { public string Prototype; public EntityCoordinates Coordinates; diff --git a/Content.Shared/Spawners/EntitySystems/SharedTimedDespawnSystem.cs b/Content.Shared/Spawners/EntitySystems/SharedTimedDespawnSystem.cs index 3129ca0fdc..87f848c06d 100644 --- a/Content.Shared/Spawners/EntitySystems/SharedTimedDespawnSystem.cs +++ b/Content.Shared/Spawners/EntitySystems/SharedTimedDespawnSystem.cs @@ -7,6 +7,12 @@ public abstract class SharedTimedDespawnSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; + public override void Initialize() + { + base.Initialize(); + UpdatesOutsidePrediction = true; + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 569d66496a..f1e7ea8f39 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -404,7 +404,7 @@ public abstract partial class SharedGunSystem : EntitySystem /// Used for animated effects on the client. /// [Serializable, NetSerializable] - protected sealed class HitscanEvent : EntityEventArgs + public sealed class HitscanEvent : EntityEventArgs { public List<(EntityCoordinates coordinates, Angle angle, SpriteSpecifier Sprite, float Distance)> Sprites = new(); }