From cdc1a70c03f0dea4dc871236aedbac9794348fef Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Sat, 25 Dec 2021 02:02:14 +0100 Subject: [PATCH 1/5] Allow admins to change instrument values with VV. Just set DirtyRenderer to true after your changes are done, on the server. --- Content.Server/Instruments/InstrumentSystem.cs | 6 ++++++ .../Instruments/SharedInstrumentComponent.cs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index ed5161abe3..fbd379ac1c 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -149,6 +149,12 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem foreach (var instrument in EntityManager.EntityQuery(true)) { + if (instrument.DirtyRenderer) + { + instrument.Dirty(); + instrument.DirtyRenderer = false; + } + if ((instrument.BatchesDropped >= MaxMidiBatchesDropped || instrument.LaggedBatches >= MaxMidiLaggedBatches) && instrument.InstrumentPlayer != null && instrument.RespectMidiLimits) diff --git a/Content.Shared/Instruments/SharedInstrumentComponent.cs b/Content.Shared/Instruments/SharedInstrumentComponent.cs index f7c171442e..4918a7d693 100644 --- a/Content.Shared/Instruments/SharedInstrumentComponent.cs +++ b/Content.Shared/Instruments/SharedInstrumentComponent.cs @@ -20,19 +20,19 @@ public class SharedInstrumentComponent : Component [ViewVariables] public uint LastSequencerTick { get; set; } - [DataField("program")] + [DataField("program"), ViewVariables(VVAccess.ReadWrite)] public byte InstrumentProgram { get; set; } - [DataField("bank")] + [DataField("bank"), ViewVariables(VVAccess.ReadWrite)] public byte InstrumentBank { get; set; } - [DataField("allowPercussion")] + [DataField("allowPercussion"), ViewVariables(VVAccess.ReadWrite)] public bool AllowPercussion { get; set; } - [DataField("allowProgramChange")] + [DataField("allowProgramChange"), ViewVariables(VVAccess.ReadWrite)] public bool AllowProgramChange { get ; set; } - [DataField("respectMidiLimits")] + [DataField("respectMidiLimits"), ViewVariables(VVAccess.ReadWrite)] public bool RespectMidiLimits { get; set; } [ViewVariables(VVAccess.ReadWrite)] From 1f8152cb02f6191da5462ceb34810a061c03a98a Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 25 Dec 2021 02:07:12 +0100 Subject: [PATCH 2/5] Cache the last 3 rounds of admin logs in memory Reduces send logs time from 2/10/45 seconds to 2 milliseconds Not thread safe Removes LogRecord --- .../UI/Logs/AdminLogsControl.xaml.cs | 10 +- .../Tests/Administration/Logs/AddTests.cs | 71 ++++---- .../Tests/Administration/Logs/FilterTests.cs | 5 +- .../Tests/Administration/Logs/QueryTests.cs | 2 +- .../Logs/AdminLogSystem.Cache.cs | 151 ++++++++++++++++++ .../Administration/Logs/AdminLogSystem.cs | 35 +++- .../Administration/Logs/AdminLogsEui.cs | 14 +- .../Administration/Logs/LogFilter.cs | 2 + .../Administration/Logs/LogRecord.cs | 36 ----- Content.Server/Database/ServerDbBase.cs | 4 +- Content.Server/Database/ServerDbManager.cs | 6 +- .../GameTicking/Events/RoundStartingEvent.cs | 2 +- .../Administration/AdminLogsEuiState.cs | 4 +- 13 files changed, 233 insertions(+), 109 deletions(-) create mode 100644 Content.Server/Administration/Logs/AdminLogSystem.Cache.cs delete mode 100644 Content.Server/Administration/Logs/LogRecord.cs diff --git a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs index eb0167fbab..8b8fea5efd 100644 --- a/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs +++ b/Content.Client/Administration/UI/Logs/AdminLogsControl.xaml.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using Content.Client.Administration.UI.CustomControls; using Content.Shared.Administration.Logs; using Content.Shared.Database; @@ -390,11 +391,12 @@ public partial class AdminLogsControl : Control UpdateLogs(); } - public void AddLogs(SharedAdminLog[] logs) + public void AddLogs(List logs) { - for (var i = 0; i < logs.Length; i++) + var span = CollectionsMarshal.AsSpan(logs); + for (var i = 0; i < span.Length; i++) { - ref var log = ref logs[i]; + ref var log = ref span[i]; var separator = new HSeparator(); var label = new AdminLogLabel(ref log, separator); label.Visible = ShouldShowLog(label); @@ -404,7 +406,7 @@ public partial class AdminLogsControl : Control } } - public void SetLogs(SharedAdminLog[] logs) + public void SetLogs(List logs) { LogsContainer.RemoveAllChildren(); AddLogs(logs); diff --git a/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs b/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs index c32c5932dd..57bc5f113c 100644 --- a/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs +++ b/Content.IntegrationTests/Tests/Administration/Logs/AddTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Content.Server.Administration.Logs; using Content.Server.Database; using Content.Server.GameTicking; +using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Database; using NUnit.Framework; @@ -89,6 +90,7 @@ public class AddTests : ContentIntegrationTest var sSystems = server.ResolveDependency(); var sAdminLogSystem = sSystems.GetEntitySystem(); + var sGamerTicker = sSystems.GetEntitySystem(); var guid = Guid.NewGuid(); @@ -100,27 +102,27 @@ public class AddTests : ContentIntegrationTest sAdminLogSystem.Add(LogType.Unknown, $"{entity} test log: {guid}"); }); - LogRecord log = null; + SharedAdminLog log = default; await WaitUntil(server, async () => { - var logs = sAdminLogSystem.CurrentRoundLogs(new LogFilter + var logs = await sAdminLogSystem.CurrentRoundLogs(new LogFilter { Search = guid.ToString() }); - await foreach (var found in logs) + if (logs.Count == 0) { - log = found; - return true; + return false; } - return false; + log = logs.First(); + return true; }); var filter = new LogFilter { - Round = log.RoundId, + Round = sGamerTicker.RoundId, Search = log.Message, Types = new HashSet {log.Type}, }; @@ -137,9 +139,8 @@ public class AddTests : ContentIntegrationTest } [Test] - [TestCase(500, false)] - [TestCase(500, true)] - public async Task BulkAddLogs(int amount, bool parallel) + [TestCase(500)] + public async Task BulkAddLogs(int amount) { var server = StartServer(new ServerContentIntegrationOption { @@ -162,33 +163,16 @@ public class AddTests : ContentIntegrationTest var coordinates = GetMainEntityCoordinates(sMaps); var entity = sEntities.SpawnEntity(null, coordinates); - if (parallel) + for (var i = 0; i < amount; i++) { - Parallel.For(0, amount, _ => - { - sAdminLogSystem.Add(LogType.Unknown, $"{entity:Entity} test log."); - }); - } - else - { - for (var i = 0; i < amount; i++) - { - sAdminLogSystem.Add(LogType.Unknown, $"{entity:Entity} test log."); - } + sAdminLogSystem.Add(LogType.Unknown, $"{entity:Entity} test log."); } }); await WaitUntil(server, async () => { - var messages = sAdminLogSystem.CurrentRoundLogs(); - var count = 0; - - await foreach (var _ in messages) - { - count++; - } - - return count >= amount; + var messages = await sAdminLogSystem.CurrentRoundLogs(); + return messages.Count >= amount; }); } @@ -225,15 +209,14 @@ public class AddTests : ContentIntegrationTest await WaitUntil(server, async () => { - var logs = sAdminLogSystem.CurrentRoundLogs(); - - await foreach (var log in logs) + var logs = await sAdminLogSystem.CurrentRoundLogs(); + if (logs.Count == 0) { - Assert.That(log.Players, Does.Contain(playerGuid)); - return true; + return false; } - return false; + Assert.That(logs.First().Players, Does.Contain(playerGuid)); + return true; }); } @@ -273,27 +256,27 @@ public class AddTests : ContentIntegrationTest sGamerTicker.StartRound(true); }); - LogRecord log = null; + SharedAdminLog log = default; await WaitUntil(server, async () => { - var logs = sAdminLogSystem.CurrentRoundLogs(new LogFilter + var logs = await sAdminLogSystem.CurrentRoundLogs(new LogFilter { Search = guid.ToString() }); - await foreach (var found in logs) + if (logs.Count == 0) { - log = found; - return true; + return false; } - return false; + log = logs.First(); + return true; }); var filter = new LogFilter { - Round = log.RoundId, + Round = sGamerTicker.RoundId, Search = log.Message, Types = new HashSet {log.Type}, }; diff --git a/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs b/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs index b56009565a..7487a2ff15 100644 --- a/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs +++ b/Content.IntegrationTests/Tests/Administration/Logs/FilterTests.cs @@ -1,5 +1,4 @@ using System; -using System.Threading; using System.Threading.Tasks; using Content.Server.Administration.Logs; using Content.Shared.Administration.Logs; @@ -84,13 +83,13 @@ public class FilterTests : ContentIntegrationTest var firstFound = false; var secondFound = false; - var both = sAdminLogSystem.CurrentRoundLogs(new LogFilter + var both = await sAdminLogSystem.CurrentRoundLogs(new LogFilter { Search = commonGuidStr, DateOrder = order }); - await foreach (var log in both) + foreach (var log in both) { if (!log.Message.Contains(commonGuidStr)) { diff --git a/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs b/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs index 390384abc1..522eedd3f8 100644 --- a/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs +++ b/Content.IntegrationTests/Tests/Administration/Logs/QueryTests.cs @@ -59,7 +59,7 @@ public class QueryTests : ContentIntegrationTest await WaitUntil(server, async () => { - await foreach (var _ in sAdminLogSystem.All(filter)) + foreach (var _ in await sAdminLogSystem.All(filter)) { return true; } diff --git a/Content.Server/Administration/Logs/AdminLogSystem.Cache.cs b/Content.Server/Administration/Logs/AdminLogSystem.Cache.cs new file mode 100644 index 0000000000..67853790af --- /dev/null +++ b/Content.Server/Administration/Logs/AdminLogSystem.Cache.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Database; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Prometheus; + +namespace Content.Server.Administration.Logs; + +public partial class AdminLogSystem +{ + private const int MaxRoundsCached = 3; + private const int LogListInitialSize = 30_000; + + private readonly int _logTypes = Enum.GetValues().Length; + + // TODO ADMIN LOGS make this thread safe or remove thread safety from the main partial class + private readonly Dictionary> _roundsLogCache = new(MaxRoundsCached); + + private static readonly Gauge CacheRoundCount = Metrics.CreateGauge( + "admin_logs_cache_round_count", + "How many rounds are in cache."); + + private static readonly Gauge CacheLogCount = Metrics.CreateGauge( + "admin_logs_cache_log_count", + "How many logs are in cache."); + + // TODO ADMIN LOGS cache previous {MaxRoundsCached} rounds on startup + private void CacheNewRound() + { + List list; + var oldestRound = CurrentRoundId - MaxRoundsCached; + + if (_roundsLogCache.Remove(oldestRound, out var oldestList)) + { + list = oldestList; + list.Clear(); + } + else + { + list = new List(LogListInitialSize); + } + + _roundsLogCache.Add(CurrentRoundId, list); + CacheRoundCount.Set(_roundsLogCache.Count); + } + + private void CacheLog(AdminLog log) + { + var players = log.Players.Select(player => player.PlayerUserId).ToArray(); + var record = new SharedAdminLog(log.Id, log.Type, log.Impact, log.Date, log.Message, players); + + CacheLog(record); + } + + private void CacheLog(QueuedLog log) + { + CacheLog(log.Log); + } + + private void CacheLog(SharedAdminLog log) + { + // TODO ADMIN LOGS remove redundant data and don't do a dictionary lookup per log + var cache = _roundsLogCache[CurrentRoundId]; + cache.Add(log); + CacheLogCount.Set(cache.Count); + } + + private void CacheLogs(IEnumerable logs) + { + var cache = _roundsLogCache[CurrentRoundId]; + cache.AddRange(logs); + CacheLogCount.Set(cache.Count); + } + + private bool TryGetCache(int roundId, [NotNullWhen(true)] out List? cache) + { + return _roundsLogCache.TryGetValue(roundId, out cache); + } + + private bool TrySearchCache(LogFilter? filter, [NotNullWhen(true)] out List? results) + { + if (filter?.Round == null || !TryGetCache(filter.Round.Value, out var cache)) + { + results = null; + return false; + } + + // TODO ADMIN LOGS a better heuristic than linq spaghetti + var query = cache.AsEnumerable(); + + query = filter.DateOrder switch + { + DateOrder.Ascending => query, + DateOrder.Descending => query.Reverse(), + _ => throw new ArgumentOutOfRangeException(nameof(filter), + $"Unknown {nameof(DateOrder)} value {filter.DateOrder}") + }; + + if (filter.LogsSent != 0) + { + query = query.Skip(filter.LogsSent); + } + + if (filter.Search != null) + { + query = query.Where(log => log.Message.Contains(filter.Search, StringComparison.OrdinalIgnoreCase)); + } + + if (filter.Types != null && filter.Types.Count != _logTypes) + { + query = query.Where(log => filter.Types.Contains(log.Type)); + } + + if (filter.Impacts != null) + { + query = query.Where(log => filter.Impacts.Contains(log.Impact)); + } + + if (filter.Before != null) + { + query = query.Where(log => log.Date < filter.Before); + } + + if (filter.After != null) + { + query = query.Where(log => log.Date > filter.After); + } + + if (filter.AnyPlayers != null) + { + query = query.Where(log => filter.AnyPlayers.Any(filterPlayer => log.Players.Contains(filterPlayer))); + } + + if (filter.AllPlayers != null) + { + query = query.Where(log => filter.AllPlayers.All(filterPlayer => log.Players.Contains(filterPlayer))); + } + + if (filter.Limit != null) + { + query = query.Take(filter.Limit.Value); + } + + // TODO ADMIN LOGS array pool + results = query.ToList(); + return true; + } +} diff --git a/Content.Server/Administration/Logs/AdminLogSystem.cs b/Content.Server/Administration/Logs/AdminLogSystem.cs index 2fed0d4b18..d437e4ac54 100644 --- a/Content.Server/Administration/Logs/AdminLogSystem.cs +++ b/Content.Server/Administration/Logs/AdminLogSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Content.Server.Database; using Content.Server.GameTicking; @@ -75,7 +76,10 @@ public partial class AdminLogSystem : SharedAdminLogSystem private readonly ConcurrentQueue _logQueue = new(); private readonly ConcurrentQueue _preRoundLogQueue = new(); + // Per round private int CurrentRoundId => _gameTicker.RoundId; + private int _currentLogId; + private int NextLogId => Interlocked.Increment(ref _currentLogId); public override void Initialize() { @@ -195,6 +199,7 @@ public partial class AdminLogSystem : SharedAdminLogSystem foreach (var queued in _preRoundLogQueue) { queued.Log.RoundId = _gameTicker.RoundId; + CacheLog(queued); } copy.AddRange(_preRoundLogQueue); @@ -227,6 +232,9 @@ public partial class AdminLogSystem : SharedAdminLogSystem private void RoundStarting(RoundStartingEvent ev) { + Interlocked.Exchange(ref _currentLogId, 0); + CacheNewRound(); + if (_metricsEnabled) { PreRoundQueueCapReached.Set(0); @@ -237,12 +245,16 @@ public partial class AdminLogSystem : SharedAdminLogSystem private async void Add(LogType type, LogImpact impact, string message, JsonDocument json, List players, List<(int id, string? name)> entities) { + var logId = NextLogId; + var date = DateTime.UtcNow; + var log = new AdminLog { + Id = logId, RoundId = CurrentRoundId, Type = type, Impact = impact, - Date = DateTime.UtcNow, + Date = date, Message = message, Json = json, Players = new List(players.Count) @@ -254,6 +266,7 @@ public partial class AdminLogSystem : SharedAdminLogSystem { var player = new AdminLogPlayer { + LogId = logId, PlayerUserId = id }; @@ -267,6 +280,7 @@ public partial class AdminLogSystem : SharedAdminLogSystem else { _logQueue.Enqueue(queued); + CacheLog(log); } } @@ -289,9 +303,22 @@ public partial class AdminLogSystem : SharedAdminLogSystem Add(type, LogImpact.Medium, ref handler); } - public IAsyncEnumerable All(LogFilter? filter = null) + public async Task> All(LogFilter? filter = null) { - return _db.GetAdminLogs(filter); + if (TrySearchCache(filter, out var results)) + { + return results; + } + + var initialSize = Math.Min(filter?.Limit ?? 0, 1000); + var list = new List(initialSize); + + await foreach (var log in _db.GetAdminLogs(filter).WithCancellation(filter?.CancellationToken ?? default)) + { + list.Add(log); + } + + return list; } public IAsyncEnumerable AllMessages(LogFilter? filter = null) @@ -309,7 +336,7 @@ public partial class AdminLogSystem : SharedAdminLogSystem return _db.GetRound(roundId); } - public IAsyncEnumerable CurrentRoundLogs(LogFilter? filter = null) + public Task> CurrentRoundLogs(LogFilter? filter = null) { filter ??= new LogFilter(); filter.Round = CurrentRoundId; diff --git a/Content.Server/Administration/Logs/AdminLogsEui.cs b/Content.Server/Administration/Logs/AdminLogsEui.cs index 72b169b158..97260873fe 100644 --- a/Content.Server/Administration/Logs/AdminLogsEui.cs +++ b/Content.Server/Administration/Logs/AdminLogsEui.cs @@ -147,21 +147,17 @@ public sealed class AdminLogsEui : BaseEui stopwatch.Start(); // TODO ADMIN LOGS array pool - var logs = new List(_clientBatchSize); + List logs = default!; await Task.Run(async () => { - var results = await Task.Run(() => _logSystem.All(_filter)); - - await foreach (var record in results.WithCancellation(_logSendCancellation.Token)) - { - var log = new SharedAdminLog(record.Id, record.Type, record.Impact, record.Date, record.Message, record.Players); - logs.Add(log); - } + logs = await _logSystem.All(_filter); }, _filter.CancellationToken); if (logs.Count > 0) { + _filter.LogsSent += logs.Count; + var largestId = _filter.DateOrder switch { DateOrder.Ascending => ^1, @@ -172,7 +168,7 @@ public sealed class AdminLogsEui : BaseEui _filter.LastLogId = logs[largestId].Id; } - var message = new NewLogs(logs.ToArray(), replace); + var message = new NewLogs(logs, replace); SendMessage(message); diff --git a/Content.Server/Administration/Logs/LogFilter.cs b/Content.Server/Administration/Logs/LogFilter.cs index b917ff36b1..9dc609e917 100644 --- a/Content.Server/Administration/Logs/LogFilter.cs +++ b/Content.Server/Administration/Logs/LogFilter.cs @@ -28,6 +28,8 @@ public sealed class LogFilter public int? LastLogId { get; set; } + public int LogsSent { get; set; } + public int? Limit { get; set; } public DateOrder DateOrder { get; set; } = DateOrder.Descending; diff --git a/Content.Server/Administration/Logs/LogRecord.cs b/Content.Server/Administration/Logs/LogRecord.cs deleted file mode 100644 index 6f32939350..0000000000 --- a/Content.Server/Administration/Logs/LogRecord.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Server.Database; -using Content.Shared.Administration.Logs; -using Content.Shared.Database; - -namespace Content.Server.Administration.Logs; - -public class LogRecord -{ - public LogRecord( - int id, - int roundId, - LogType type, - LogImpact impact, - DateTime date, - string message, - Guid[] players) - { - Id = id; - RoundId = roundId; - Type = type; - Impact = impact; - Date = date; - Message = message; - Players = players; - } - - public int Id { get; } - public int RoundId { get; } - public LogType Type { get; } - public LogImpact Impact { get; } - public DateTime Date { get; } - public string Message { get; } - public Guid[] Players { get; } -} diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index 9b219ba4d8..a86d3c3a4e 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -640,7 +640,7 @@ namespace Content.Server.Database } } - public async IAsyncEnumerable GetAdminLogs(LogFilter? filter = null) + public async IAsyncEnumerable GetAdminLogs(LogFilter? filter = null) { await using var db = await GetDb(); var query = await GetAdminLogsQuery(db.DbContext, filter); @@ -654,7 +654,7 @@ namespace Content.Server.Database players[i] = log.Players[i].PlayerUserId; } - yield return new LogRecord(log.Id, log.RoundId, log.Type, log.Impact, log.Date, log.Message, players); + yield return new SharedAdminLog(log.Id, log.Type, log.Impact, log.Date, log.Message, players); } } diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 701eaa3f58..a9a978edd9 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Content.Server.Administration.Logs; +using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Preferences; using Microsoft.Data.Sqlite; @@ -19,7 +20,6 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Maths; using Robust.Shared.Network; -using Logger = Robust.Shared.Log.Logger; using LogLevel = Robust.Shared.Log.LogLevel; using MSLogLevel = Microsoft.Extensions.Logging.LogLevel; @@ -135,7 +135,7 @@ namespace Content.Server.Database Task AddAdminLogs(List logs); IAsyncEnumerable GetAdminLogMessages(LogFilter? filter = null); - IAsyncEnumerable GetAdminLogs(LogFilter? filter = null); + IAsyncEnumerable GetAdminLogs(LogFilter? filter = null); IAsyncEnumerable GetAdminLogsJson(LogFilter? filter = null); #endregion @@ -346,7 +346,7 @@ namespace Content.Server.Database return _db.GetAdminLogMessages(filter); } - public IAsyncEnumerable GetAdminLogs(LogFilter? filter = null) + public IAsyncEnumerable GetAdminLogs(LogFilter? filter = null) { return _db.GetAdminLogs(filter); } diff --git a/Content.Server/GameTicking/Events/RoundStartingEvent.cs b/Content.Server/GameTicking/Events/RoundStartingEvent.cs index da7395e7b6..4edf1371a3 100644 --- a/Content.Server/GameTicking/Events/RoundStartingEvent.cs +++ b/Content.Server/GameTicking/Events/RoundStartingEvent.cs @@ -3,7 +3,7 @@ namespace Content.Server.GameTicking.Events; /// -/// Raised at the start of +/// Raised at the start of , after round id has been incremented /// public class RoundStartingEvent : EntityEventArgs { diff --git a/Content.Shared/Administration/AdminLogsEuiState.cs b/Content.Shared/Administration/AdminLogsEuiState.cs index 551a5ecbb0..3921a3bbb5 100644 --- a/Content.Shared/Administration/AdminLogsEuiState.cs +++ b/Content.Shared/Administration/AdminLogsEuiState.cs @@ -33,13 +33,13 @@ public static class AdminLogsEuiMsg [Serializable, NetSerializable] public sealed class NewLogs : EuiMessageBase { - public NewLogs(SharedAdminLog[] logs, bool replace) + public NewLogs(List logs, bool replace) { Logs = logs; Replace = replace; } - public SharedAdminLog[] Logs { get; set; } + public List Logs { get; set; } public bool Replace { get; set; } } From 944b4fb073296fd49998430f57c9311c5ca91456 Mon Sep 17 00:00:00 2001 From: Vera Aguilera Puerto Date: Sat, 25 Dec 2021 02:10:32 +0100 Subject: [PATCH 3/5] Mapping command shows markers on the client. Bit of a hack to work around client-side only command permissions being broken. --- Content.Client/Commands/DebugCommands.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Client/Commands/DebugCommands.cs b/Content.Client/Commands/DebugCommands.cs index 799db2db84..0562447586 100644 --- a/Content.Client/Commands/DebugCommands.cs +++ b/Content.Client/Commands/DebugCommands.cs @@ -97,6 +97,7 @@ namespace Content.Client.Commands shell.ConsoleHost.RegisteredCommands["togglelight"].Execute(shell, string.Empty, Array.Empty()); shell.ConsoleHost.RegisteredCommands["showsubfloorforever"].Execute(shell, string.Empty, Array.Empty()); + shell.ConsoleHost.RegisteredCommands["showmarkers"].Execute(shell, string.Empty, Array.Empty()); shell.RemoteExecuteCommand(argStr); } From be6cb75122d40f87133441793b8751b4d7c0d817 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Fri, 24 Dec 2021 17:32:33 -0800 Subject: [PATCH 4/5] Add a wait time for the rules popup (#5823) * Create new rules popup * Implement accept and quit buttons * Add rules accept timer Forces the player to read the rules by making them wait. Speed reading the rules took me just under 45 seconds which means it'll take longer than that if someone's reading this for the first time. * Fix info rules header * Change _rulesPopup to local variable --- Content.Client/HUD/GameHud.cs | 9 --- Content.Client/Info/RulesAndInfoWindow.cs | 2 +- Content.Client/Info/RulesControl.xaml | 6 ++ Content.Client/Info/RulesControl.xaml.cs | 23 +++++++ Content.Client/Info/RulesManager.cs | 34 ++++++++-- Content.Client/Info/RulesPopup.xaml | 24 +++++++ Content.Client/Info/RulesPopup.xaml.cs | 69 +++++++++++++++++++++ Content.Shared/CCVar/CCVars.cs | 10 +++ Resources/Locale/en-US/info/info-window.ftl | 4 -- Resources/Locale/en-US/info/rules.ftl | 5 ++ 10 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 Content.Client/Info/RulesControl.xaml create mode 100644 Content.Client/Info/RulesControl.xaml.cs create mode 100644 Content.Client/Info/RulesPopup.xaml create mode 100644 Content.Client/Info/RulesPopup.xaml.cs create mode 100644 Resources/Locale/en-US/info/rules.ftl diff --git a/Content.Client/HUD/GameHud.cs b/Content.Client/HUD/GameHud.cs index e3a02e0170..95d601b98f 100644 --- a/Content.Client/HUD/GameHud.cs +++ b/Content.Client/HUD/GameHud.cs @@ -6,7 +6,6 @@ using Content.Client.Info; using Content.Client.Resources; using Content.Client.Stylesheets; using Content.Client.Targeting; -using Content.Shared; using Content.Shared.CCVar; using Content.Shared.HUD; using Content.Shared.Input; @@ -304,8 +303,6 @@ namespace Content.Client.HUD _rulesAndInfoWindow = new RulesAndInfoWindow(); - IoCManager.Resolve().OpenRulesAndInfoWindow += OpenRulesAndInfoWindow; - _rulesAndInfoWindow.OnClose += () => _buttonInfo.Pressed = false; _inputManager.SetInputCommand(ContentKeyFunctions.OpenInfo, @@ -431,12 +428,6 @@ namespace Content.Client.HUD LC.SetGrowVertical(VoteContainer, LC.GrowDirection.End); } - private void OpenRulesAndInfoWindow() - { - _rulesAndInfoWindow.OpenCentered(); - _buttonInfo.Pressed = true; - } - private void ButtonInfoOnOnToggled() { _buttonInfo.StyleClasses.Remove(TopButton.StyleClassRedTopButton); diff --git a/Content.Client/Info/RulesAndInfoWindow.cs b/Content.Client/Info/RulesAndInfoWindow.cs index 817c114269..383d6c6a25 100644 --- a/Content.Client/Info/RulesAndInfoWindow.cs +++ b/Content.Client/Info/RulesAndInfoWindow.cs @@ -43,7 +43,7 @@ namespace Content.Client.Info private void PopulateRules(Info rulesList) { - AddSection(rulesList, Loc.GetString("ui-info-header-rules"), "Rules.txt", true); + AddSection(rulesList, Loc.GetString("ui-rules-header"), "Rules.txt", true); } private void PopulateTutorial(Info tutorialList) diff --git a/Content.Client/Info/RulesControl.xaml b/Content.Client/Info/RulesControl.xaml new file mode 100644 index 0000000000..3b24764688 --- /dev/null +++ b/Content.Client/Info/RulesControl.xaml @@ -0,0 +1,6 @@ + diff --git a/Content.Client/Info/RulesControl.xaml.cs b/Content.Client/Info/RulesControl.xaml.cs new file mode 100644 index 0000000000..6675715b30 --- /dev/null +++ b/Content.Client/Info/RulesControl.xaml.cs @@ -0,0 +1,23 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.IoC; +using Robust.Shared.Localization; + +namespace Content.Client.Info; + +[GenerateTypedNameReferences] +public partial class RulesControl : BoxContainer +{ + [Dependency] private readonly IResourceCache _resourceManager = default!; + + public RulesControl() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + AddChild(new InfoSection(Loc.GetString("ui-rules-header"), + _resourceManager.ContentFileReadAllText($"/Server Info/Rules.txt"), true)); + } +} diff --git a/Content.Client/Info/RulesManager.cs b/Content.Client/Info/RulesManager.cs index 3bccbddc77..2986b9c244 100644 --- a/Content.Client/Info/RulesManager.cs +++ b/Content.Client/Info/RulesManager.cs @@ -1,10 +1,11 @@ using System; using System.Globalization; -using System.IO; using Content.Client.Lobby; using Content.Client.Viewport; using Content.Shared.CCVar; +using Robust.Client.Console; using Robust.Client.State; +using Robust.Client.UserInterface; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.IoC; @@ -16,9 +17,9 @@ public sealed class RulesManager { [Dependency] private readonly IResourceManager _resource = default!; [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IStateManager _stateManager = default!; - - public event Action? OpenRulesAndInfoWindow; + [Dependency] private readonly IClientConsoleHost _consoleHost = default!; public void Initialize() { @@ -39,8 +40,10 @@ public sealed class RulesManager else SaveLastReadTime(); - if (showRules) - OpenRulesAndInfoWindow?.Invoke(); + if (!showRules) + return; + + ShowRules(_configManager.GetCVar(CCVars.RulesWaitTime)); } /// @@ -52,4 +55,25 @@ public sealed class RulesManager sw.Write(DateTime.UtcNow.ToUniversalTime()); } + + private void ShowRules(float time) + { + var rulesPopup = new RulesPopup + { + Timer = time + }; + rulesPopup.OnQuitPressed += OnQuitPressed; + rulesPopup.OnAcceptPressed += OnAcceptPressed; + _userInterfaceManager.RootControl.AddChild(rulesPopup); + } + + private void OnQuitPressed() + { + _consoleHost.ExecuteCommand("quit"); + } + + private void OnAcceptPressed() + { + SaveLastReadTime(); + } } diff --git a/Content.Client/Info/RulesPopup.xaml b/Content.Client/Info/RulesPopup.xaml new file mode 100644 index 0000000000..dc004af5a2 --- /dev/null +++ b/Content.Client/Info/RulesPopup.xaml @@ -0,0 +1,24 @@ + + + + + + + + public static readonly CVarDef SalvageForced = CVarDef.Create("salvage.forced", "", CVar.SERVERONLY); + + /* + * Rules + */ + + /// + /// Time that players have to wait before rules can be accepted. + /// + public static readonly CVarDef RulesWaitTime = + CVarDef.Create("rules.time", 45f, CVar.SERVER | CVar.REPLICATED); } } diff --git a/Resources/Locale/en-US/info/info-window.ftl b/Resources/Locale/en-US/info/info-window.ftl index fccbcdb06c..77446221ce 100644 --- a/Resources/Locale/en-US/info/info-window.ftl +++ b/Resources/Locale/en-US/info/info-window.ftl @@ -5,10 +5,6 @@ ui-info-title = Information ui-info-tab-rules = Server Rules ui-info-tab-tutorial = Tutorial -## Rules tab - -ui-info-header-rules = SS14 Official Server Rules - ## Tutorial tab ui-info-text-controls = You can review and rebind SS14s controls in the diff --git a/Resources/Locale/en-US/info/rules.ftl b/Resources/Locale/en-US/info/rules.ftl new file mode 100644 index 0000000000..9973bf8728 --- /dev/null +++ b/Resources/Locale/en-US/info/rules.ftl @@ -0,0 +1,5 @@ +# Rules + +ui-rules-header = SS14 Official Server Rules +ui-rules-accept = I have read and agree to follow the rules +ui-rules-wait = The accept button will be enabled after {$time} seconds. From 6120870b7079c2c02c139abeb43f2fb96d940682 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 24 Dec 2021 20:33:36 -0500 Subject: [PATCH 5/5] Automatic changelog update --- Resources/Changelog/Changelog.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index b4e7ba7732..9a893de869 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,10 +1,4 @@ Entries: -- author: PJB - changes: - - {message: 'Restart round votes now start with everybody selecting "no", effectively - requiring 80% of the whole server to agree.', type: Tweak} - id: 271 - time: '2021-07-21T13:26:06.988275+00:00' - author: PJB changes: - {message: You can now only make the same type of vote once every 4 minutes., type: Tweak} @@ -2870,3 +2864,9 @@ Entries: - {message: Added three salvage maps, type: Add} id: 772 time: '2021-12-24T22:56:46.0000000+00:00' +- author: ShadowCommander + changes: + - {message: Rules popup on first connection now has a timer to make sure that they + get read., type: Tweak} + id: 773 + time: '2021-12-25T01:32:33.0000000+00:00'