From 444180c20dd4f758e2a9311a7e0ba1a65402a9fe Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 26 Jul 2025 11:44:34 +0200 Subject: [PATCH] Optimizations from server profile (#38290) * Properly cache regexes in chat sanitization/accents Wow I wonder if `new Regex()` has a cost to it *looks at server profile*. * Avoid lag caused by Tippy command completions CompletionHelper.PrototypeIDs explicitly says *not* to use it with EntityPrototype. Unsurprisingly, reporting a completion result for every entity prototype in the game is a *bad idea*. * Add active count metrics to some high-load systems Mover & NPCs I suspect the thing that caused the Leviathan round to shit itself on performance is NPC spam in space or something. So let's verify that. * Enable parallel processing on pow3r again Originally disabled due to a theory of it causing bugs, it was re-enabled on Vulture, and I'm not aware of it having caused any issues there. * Replace hashset with bitflags for AtmosMonitor alert types. Allocating these hashsets was like 20% of the CPU of atmos, somehow. * Cache HashSet used for space movement collider checks Turns out this was a ton of server allocations. Huh. --- .../Components/AtmosAlarmableComponent.cs | 2 +- .../Components/AtmosMonitorComponent.cs | 2 +- .../Monitor/Systems/AtmosAlarmableSystem.cs | 4 +- .../Monitor/Systems/AtmosMonitoringSystem.cs | 22 +- .../Chat/Managers/ChatSanitizationManager.cs | 192 +++++++++--------- .../NPC/Systems/NPCSteeringSystem.cs | 12 ++ Content.Server/NPC/Systems/NPCSystem.cs | 7 + .../Physics/Controllers/MoverController.cs | 7 + .../EntitySystems/ReplacementAccentSystem.cs | 60 +++++- Content.Server/Tips/TipsSystem.cs | 8 +- .../Atmos/Monitor/AtmosAlarmThreshold.cs | 18 +- Content.Shared/CCVar/CCVars.cs | 2 +- .../Movement/Systems/SharedMoverController.cs | 6 +- .../ConfigPresets/WizardsDen/vulture.toml | 3 - 14 files changed, 217 insertions(+), 128 deletions(-) diff --git a/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs b/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs index e291334ad0..cc53df2ecd 100644 --- a/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/AtmosAlarmableComponent.cs @@ -48,7 +48,7 @@ public sealed partial class AtmosAlarmableComponent : Component public HashSet SyncWithTags { get; private set; } = new(); [DataField("monitorAlertTypes")] - public HashSet? MonitorAlertTypes { get; private set; } + public AtmosMonitorThresholdTypeFlags MonitorAlertTypes { get; private set; } /// /// If this device should receive only. If it can only diff --git a/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs b/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs index 830479561d..ffb1fe0d27 100644 --- a/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/AtmosMonitorComponent.cs @@ -59,7 +59,7 @@ public sealed partial class AtmosMonitorComponent : Component public AtmosAlarmType LastAlarmState = AtmosAlarmType.Normal; [DataField("trippedThresholds")] - public HashSet TrippedThresholds = new(); + public AtmosMonitorThresholdTypeFlags TrippedThresholds; /// /// Registered devices in this atmos monitor. Alerts will be sent directly diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs index b6bc4bd303..2dcba3f464 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs @@ -108,9 +108,9 @@ public sealed class AtmosAlarmableSystem : EntitySystem break; } - if (args.Data.TryGetValue(AlertTypes, out HashSet? types) && component.MonitorAlertTypes != null) + if (args.Data.TryGetValue(AlertTypes, out AtmosMonitorThresholdTypeFlags types) && component.MonitorAlertTypes != AtmosMonitorThresholdTypeFlags.None) { - isValid = types.Any(type => component.MonitorAlertTypes.Contains(type)); + isValid = (types & component.MonitorAlertTypes) != 0; } if (!component.NetworkAlarmStates.ContainsKey(args.SenderAddress)) diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs index 520afe0c58..452b300331 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs @@ -207,7 +207,7 @@ public sealed class AtmosMonitorSystem : EntitySystem if (component.MonitorFire && component.LastAlarmState != AtmosAlarmType.Danger) { - component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature); + component.TrippedThresholds |= AtmosMonitorThresholdTypeFlags.Temperature; Alert(uid, AtmosAlarmType.Danger, null, component); // technically??? } @@ -218,7 +218,7 @@ public sealed class AtmosMonitorSystem : EntitySystem && component.TemperatureThreshold.CheckThreshold(args.Temperature, out var temperatureState) && temperatureState > component.LastAlarmState) { - component.TrippedThresholds.Add(AtmosMonitorThresholdType.Temperature); + component.TrippedThresholds |= AtmosMonitorThresholdTypeFlags.Temperature; Alert(uid, AtmosAlarmType.Danger, null, component); } } @@ -259,7 +259,7 @@ public sealed class AtmosMonitorSystem : EntitySystem if (!Resolve(uid, ref monitor)) return; var state = AtmosAlarmType.Normal; - HashSet alarmTypes = new(monitor.TrippedThresholds); + var alarmTypes = monitor.TrippedThresholds; if (monitor.TemperatureThreshold != null && monitor.TemperatureThreshold.CheckThreshold(air.Temperature, out var temperatureState)) @@ -267,11 +267,11 @@ public sealed class AtmosMonitorSystem : EntitySystem if (temperatureState > state) { state = temperatureState; - alarmTypes.Add(AtmosMonitorThresholdType.Temperature); + alarmTypes |= AtmosMonitorThresholdTypeFlags.Temperature; } else if (temperatureState == AtmosAlarmType.Normal) { - alarmTypes.Remove(AtmosMonitorThresholdType.Temperature); + alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Temperature; } } @@ -282,11 +282,11 @@ public sealed class AtmosMonitorSystem : EntitySystem if (pressureState > state) { state = pressureState; - alarmTypes.Add(AtmosMonitorThresholdType.Pressure); + alarmTypes |= AtmosMonitorThresholdTypeFlags.Pressure; } else if (pressureState == AtmosAlarmType.Normal) { - alarmTypes.Remove(AtmosMonitorThresholdType.Pressure); + alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Pressure; } } @@ -306,17 +306,17 @@ public sealed class AtmosMonitorSystem : EntitySystem if (tripped) { - alarmTypes.Add(AtmosMonitorThresholdType.Gas); + alarmTypes |= AtmosMonitorThresholdTypeFlags.Gas; } else { - alarmTypes.Remove(AtmosMonitorThresholdType.Gas); + alarmTypes &= ~AtmosMonitorThresholdTypeFlags.Gas; } } // if the state of the current air doesn't match the last alarm state, // we update the state - if (state != monitor.LastAlarmState || !alarmTypes.SetEquals(monitor.TrippedThresholds)) + if (state != monitor.LastAlarmState || alarmTypes != monitor.TrippedThresholds) { Alert(uid, state, alarmTypes, monitor); } @@ -327,7 +327,7 @@ public sealed class AtmosMonitorSystem : EntitySystem /// /// The alarm state to set this monitor to. /// The alarms that caused this alarm state. - public void Alert(EntityUid uid, AtmosAlarmType state, HashSet? alarms = null, AtmosMonitorComponent? monitor = null) + public void Alert(EntityUid uid, AtmosAlarmType state, AtmosMonitorThresholdTypeFlags? alarms = null, AtmosMonitorComponent? monitor = null) { if (!Resolve(uid, ref monitor)) return; diff --git a/Content.Server/Chat/Managers/ChatSanitizationManager.cs b/Content.Server/Chat/Managers/ChatSanitizationManager.cs index 0c78e45f86..106e5313e6 100644 --- a/Content.Server/Chat/Managers/ChatSanitizationManager.cs +++ b/Content.Server/Chat/Managers/ChatSanitizationManager.cs @@ -12,86 +12,86 @@ namespace Content.Server.Chat.Managers; /// public sealed class ChatSanitizationManager : IChatSanitizationManager { - private static readonly Dictionary ShorthandToEmote = new() - { - { ":)", "chatsan-smiles" }, - { ":]", "chatsan-smiles" }, - { "=)", "chatsan-smiles" }, - { "=]", "chatsan-smiles" }, - { "(:", "chatsan-smiles" }, - { "[:", "chatsan-smiles" }, - { "(=", "chatsan-smiles" }, - { "[=", "chatsan-smiles" }, - { "^^", "chatsan-smiles" }, - { "^-^", "chatsan-smiles" }, - { ":(", "chatsan-frowns" }, - { ":[", "chatsan-frowns" }, - { "=(", "chatsan-frowns" }, - { "=[", "chatsan-frowns" }, - { "):", "chatsan-frowns" }, - { ")=", "chatsan-frowns" }, - { "]:", "chatsan-frowns" }, - { "]=", "chatsan-frowns" }, - { ":D", "chatsan-smiles-widely" }, - { "D:", "chatsan-frowns-deeply" }, - { ":O", "chatsan-surprised" }, - { ":3", "chatsan-smiles" }, - { ":S", "chatsan-uncertain" }, - { ":>", "chatsan-grins" }, - { ":<", "chatsan-pouts" }, - { "xD", "chatsan-laughs" }, - { ":'(", "chatsan-cries" }, - { ":'[", "chatsan-cries" }, - { "='(", "chatsan-cries" }, - { "='[", "chatsan-cries" }, - { ")':", "chatsan-cries" }, - { "]':", "chatsan-cries" }, - { ")'=", "chatsan-cries" }, - { "]'=", "chatsan-cries" }, - { ";-;", "chatsan-cries" }, - { ";_;", "chatsan-cries" }, - { "qwq", "chatsan-cries" }, - { ":u", "chatsan-smiles-smugly" }, - { ":v", "chatsan-smiles-smugly" }, - { ">:i", "chatsan-annoyed" }, - { ":i", "chatsan-sighs" }, - { ":|", "chatsan-sighs" }, - { ":p", "chatsan-stick-out-tongue" }, - { ";p", "chatsan-stick-out-tongue" }, - { ":b", "chatsan-stick-out-tongue" }, - { "0-0", "chatsan-wide-eyed" }, - { "o-o", "chatsan-wide-eyed" }, - { "o.o", "chatsan-wide-eyed" }, - { "._.", "chatsan-surprised" }, - { ".-.", "chatsan-confused" }, - { "-_-", "chatsan-unimpressed" }, - { "smh", "chatsan-unimpressed" }, - { "o/", "chatsan-waves" }, - { "^^/", "chatsan-waves" }, - { ":/", "chatsan-uncertain" }, - { ":\\", "chatsan-uncertain" }, - { "lmao", "chatsan-laughs" }, - { "lmfao", "chatsan-laughs" }, - { "lol", "chatsan-laughs" }, - { "lel", "chatsan-laughs" }, - { "kek", "chatsan-laughs" }, - { "rofl", "chatsan-laughs" }, - { "o7", "chatsan-salutes" }, - { ";_;7", "chatsan-tearfully-salutes" }, - { "idk", "chatsan-shrugs" }, - { ";)", "chatsan-winks" }, - { ";]", "chatsan-winks" }, - { "(;", "chatsan-winks" }, - { "[;", "chatsan-winks" }, - { ":')", "chatsan-tearfully-smiles" }, - { ":']", "chatsan-tearfully-smiles" }, - { "=')", "chatsan-tearfully-smiles" }, - { "=']", "chatsan-tearfully-smiles" }, - { "(':", "chatsan-tearfully-smiles" }, - { "[':", "chatsan-tearfully-smiles" }, - { "('=", "chatsan-tearfully-smiles" }, - { "['=", "chatsan-tearfully-smiles" } - }; + private static readonly (Regex regex, string emoteKey)[] ShorthandToEmote = + [ + Entry(":)", "chatsan-smiles"), + Entry(":]", "chatsan-smiles"), + Entry("=)", "chatsan-smiles"), + Entry("=]", "chatsan-smiles"), + Entry("(:", "chatsan-smiles"), + Entry("[:", "chatsan-smiles"), + Entry("(=", "chatsan-smiles"), + Entry("[=", "chatsan-smiles"), + Entry("^^", "chatsan-smiles"), + Entry("^-^", "chatsan-smiles"), + Entry(":(", "chatsan-frowns"), + Entry(":[", "chatsan-frowns"), + Entry("=(", "chatsan-frowns"), + Entry("=[", "chatsan-frowns"), + Entry("):", "chatsan-frowns"), + Entry(")=", "chatsan-frowns"), + Entry("]:", "chatsan-frowns"), + Entry("]=", "chatsan-frowns"), + Entry(":D", "chatsan-smiles-widely"), + Entry("D:", "chatsan-frowns-deeply"), + Entry(":O", "chatsan-surprised"), + Entry(":3", "chatsan-smiles"), + Entry(":S", "chatsan-uncertain"), + Entry(":>", "chatsan-grins"), + Entry(":<", "chatsan-pouts"), + Entry("xD", "chatsan-laughs"), + Entry(":'(", "chatsan-cries"), + Entry(":'[", "chatsan-cries"), + Entry("='(", "chatsan-cries"), + Entry("='[", "chatsan-cries"), + Entry(")':", "chatsan-cries"), + Entry("]':", "chatsan-cries"), + Entry(")'=", "chatsan-cries"), + Entry("]'=", "chatsan-cries"), + Entry(";-;", "chatsan-cries"), + Entry(";_;", "chatsan-cries"), + Entry("qwq", "chatsan-cries"), + Entry(":u", "chatsan-smiles-smugly"), + Entry(":v", "chatsan-smiles-smugly"), + Entry(">:i", "chatsan-annoyed"), + Entry(":i", "chatsan-sighs"), + Entry(":|", "chatsan-sighs"), + Entry(":p", "chatsan-stick-out-tongue"), + Entry(";p", "chatsan-stick-out-tongue"), + Entry(":b", "chatsan-stick-out-tongue"), + Entry("0-0", "chatsan-wide-eyed"), + Entry("o-o", "chatsan-wide-eyed"), + Entry("o.o", "chatsan-wide-eyed"), + Entry("._.", "chatsan-surprised"), + Entry(".-.", "chatsan-confused"), + Entry("-_-", "chatsan-unimpressed"), + Entry("smh", "chatsan-unimpressed"), + Entry("o/", "chatsan-waves"), + Entry("^^/", "chatsan-waves"), + Entry(":/", "chatsan-uncertain"), + Entry(":\\", "chatsan-uncertain"), + Entry("lmao", "chatsan-laughs"), + Entry("lmfao", "chatsan-laughs"), + Entry("lol", "chatsan-laughs"), + Entry("lel", "chatsan-laughs"), + Entry("kek", "chatsan-laughs"), + Entry("rofl", "chatsan-laughs"), + Entry("o7", "chatsan-salutes"), + Entry(";_;7", "chatsan-tearfully-salutes"), + Entry("idk", "chatsan-shrugs"), + Entry(";)", "chatsan-winks"), + Entry(";]", "chatsan-winks"), + Entry("(;", "chatsan-winks"), + Entry("[;", "chatsan-winks"), + Entry(":')", "chatsan-tearfully-smiles"), + Entry(":']", "chatsan-tearfully-smiles"), + Entry("=')", "chatsan-tearfully-smiles"), + Entry("=']", "chatsan-tearfully-smiles"), + Entry("(':", "chatsan-tearfully-smiles"), + Entry("[':", "chatsan-tearfully-smiles"), + Entry("('=", "chatsan-tearfully-smiles"), + Entry("['=", "chatsan-tearfully-smiles"), + ]; [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly ILocalizationManager _loc = default!; @@ -125,21 +125,8 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager // -1 is just a canary for nothing found yet var lastEmoteIndex = -1; - foreach (var (shorthand, emoteKey) in ShorthandToEmote) + foreach (var (r, emoteKey) in ShorthandToEmote) { - // We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise. - var escaped = Regex.Escape(shorthand); - - // So there are 2 cases: - // - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line - // Delete the word and the whitespace before - // - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line - // Delete the word and the punctuation if it exists. - var pattern = - $@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))"; - - var r = new Regex(pattern, RegexOptions.RightToLeft | RegexOptions.IgnoreCase); - // We're using sanitized as the original message until the end so that we can make sure the indices of // the emotes are accurate. var lastMatch = r.Match(sanitized); @@ -159,4 +146,21 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager sanitized = message.Trim(); return emote is not null; } + + private static (Regex regex, string emoteKey) Entry(string shorthand, string emoteKey) + { + // We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise. + var escaped = Regex.Escape(shorthand); + + // So there are 2 cases: + // - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line + // Delete the word and the whitespace before + // - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line + // Delete the word and the punctuation if it exists. + var pattern = new Regex( + $@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))", + RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.Compiled); + + return (pattern, emoteKey); + } } diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.cs index 6a736f3bc9..3585711860 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.cs @@ -30,11 +30,16 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using Content.Shared.Prying.Systems; using Microsoft.Extensions.ObjectPool; +using Prometheus; namespace Content.Server.NPC.Systems; public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem { + private static readonly Gauge ActiveSteeringGauge = Metrics.CreateGauge( + "npc_steering_active_count", + "Amount of NPCs trying to actively do steering"); + /* * We use context steering to determine which way to move. * This involves creating an array of possible directions and assigning a value for the desireability of each direction. @@ -87,6 +92,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem private object _obstacles = new(); + private int _activeSteeringCount; + public override void Initialize() { base.Initialize(); @@ -244,12 +251,15 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem }; var curTime = _timing.CurTime; + _activeSteeringCount = 0; + Parallel.For(0, index, options, i => { var (uid, steering, mover, xform) = npcs[i]; Steer(uid, steering, mover, xform, frameTime, curTime); }); + ActiveSteeringGauge.Set(_activeSteeringCount); if (_subscribedSessions.Count > 0) { @@ -324,6 +334,8 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem return; } + Interlocked.Increment(ref _activeSteeringCount); + var agentRadius = steering.Radius; var worldPos = _transform.GetWorldPosition(xform); var (layer, mask) = _physics.GetHardCollision(uid); diff --git a/Content.Server/NPC/Systems/NPCSystem.cs b/Content.Server/NPC/Systems/NPCSystem.cs index c7690cb295..27b2a1691d 100644 --- a/Content.Server/NPC/Systems/NPCSystem.cs +++ b/Content.Server/NPC/Systems/NPCSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.NPC; using Content.Shared.NPC.Systems; +using Prometheus; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Player; @@ -19,6 +20,10 @@ namespace Content.Server.NPC.Systems /// public sealed partial class NPCSystem : EntitySystem { + private static readonly Gauge ActiveGauge = Metrics.CreateGauge( + "npc_active_count", + "Amount of NPCs that are actively processing"); + [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly HTNSystem _htn = default!; [Dependency] private readonly MobStateSystem _mobState = default!; @@ -138,6 +143,8 @@ namespace Content.Server.NPC.Systems // Add your system here. _htn.UpdateNPC(ref _count, _maxUpdates, frameTime); + + ActiveGauge.Set(Count()); } public void OnMobStateChange(EntityUid uid, HTNComponent component, MobStateChangedEvent args) diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index f0a723f3c0..5c87de1863 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -7,6 +7,7 @@ using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; +using Prometheus; using Robust.Shared.Physics.Components; using Robust.Shared.Player; using DroneConsoleComponent = Content.Server.Shuttles.DroneConsoleComponent; @@ -17,6 +18,10 @@ namespace Content.Server.Physics.Controllers; public sealed class MoverController : SharedMoverController { + private static readonly Gauge ActiveMoverGauge = Metrics.CreateGauge( + "physics_active_mover_count", + "Active amount of InputMovers being processed by MoverController"); + [Dependency] private readonly ThrusterSystem _thruster = default!; [Dependency] private readonly SharedTransformSystem _xformSystem = default!; @@ -97,6 +102,8 @@ public sealed class MoverController : SharedMoverController HandleMobMovement(mover, frameTime); } + ActiveMoverGauge.Set(_movers.Count); + HandleShuttleMovement(frameTime); } diff --git a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs index 5b215e9bea..c285063d2d 100644 --- a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs @@ -19,9 +19,21 @@ namespace Content.Server.Speech.EntitySystems [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ILocalizationManager _loc = default!; + private readonly Dictionary, (Regex regex, string replacement)[]> + _cachedReplacements = new(); + public override void Initialize() { SubscribeLocalEvent(OnAccent); + + _proto.PrototypesReloaded += OnPrototypesReloaded; + } + + public override void Shutdown() + { + base.Shutdown(); + + _proto.PrototypesReloaded -= OnPrototypesReloaded; } private void OnAccent(EntityUid uid, ReplacementAccentComponent component, AccentGetEvent args) @@ -48,27 +60,22 @@ namespace Content.Server.Speech.EntitySystems return prototype.FullReplacements.Length != 0 ? Loc.GetString(_random.Pick(prototype.FullReplacements)) : ""; } - if (prototype.WordReplacements == null) - return message; - // Prohibition of repeated word replacements. // All replaced words placed in the final message are placed here as dashes (___) with the same length. // The regex search goes through this buffer message, from which the already replaced words are crossed out, // ensuring that the replaced words cannot be replaced again. var maskMessage = message; - foreach (var (first, replace) in prototype.WordReplacements) + foreach (var (regex, replace) in GetCachedReplacements(prototype)) { - var f = _loc.GetString(first); - var r = _loc.GetString(replace); // this is kind of slow but its not that bad // essentially: go over all matches, try to match capitalization where possible, then replace // rather than using regex.replace - for (int i = Regex.Count(maskMessage, $@"(? 0; i--) + for (int i = regex.Count(maskMessage); i > 0; i--) { // fetch the match again as the character indices may have changed - Match match = Regex.Match(maskMessage, $@"(? + { + var (first, replace) = kv; + var firstLoc = _loc.GetString(first); + var replaceLoc = _loc.GetString(replace); + + var regex = new Regex($@"(? CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), Loc.GetString("cmd-tippy-auto-1")), + 1 => CompletionResult.FromHintOptions( + CompletionHelper.SessionNames(players: _playerManager), + Loc.GetString("cmd-tippy-auto-1")), 2 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-2")), - 3 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs(), Loc.GetString("cmd-tippy-auto-3")), + 3 => CompletionResult.FromHintOptions( + CompletionHelper.PrototypeIdsLimited(args[2], _prototype), + Loc.GetString("cmd-tippy-auto-3")), 4 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-4")), 5 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-5")), 6 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-6")), diff --git a/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs b/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs index 89d0bf2392..becc5378f2 100644 --- a/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs +++ b/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs @@ -388,9 +388,21 @@ public enum AtmosMonitorLimitType // +/// Bitflags version of +/// +[Flags] +public enum AtmosMonitorThresholdTypeFlags +{ + None = 0, + Temperature = 1 << 0, + Pressure = 1 << 1, + Gas = 1 << 2, } [Serializable, NetSerializable] diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index d68ab16874..87b2da129a 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -36,5 +36,5 @@ public sealed partial class CCVars : CVars /// Set to true to disable parallel processing in the pow3r solver. /// public static readonly CVarDef DebugPow3rDisableParallel = - CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY); + CVarDef.Create("debug.pow3r_disable_parallel", false, CVar.SERVERONLY); } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index e43800dc9f..f8495fcd18 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -72,6 +72,8 @@ public abstract partial class SharedMoverController : VirtualController /// public Dictionary UsedMobMovement = new(); + private readonly HashSet _aroundColliderSet = []; + public override void Initialize() { UpdatesBefore.Add(typeof(TileFrictionController)); @@ -454,7 +456,9 @@ public abstract partial class SharedMoverController : VirtualController var (uid, collider, mover, transform) = entity; var enlargedAABB = _lookup.GetWorldAABB(entity.Owner, transform).Enlarged(mover.GrabRange); - foreach (var otherEntity in lookupSystem.GetEntitiesIntersecting(transform.MapID, enlargedAABB)) + _aroundColliderSet.Clear(); + lookupSystem.GetEntitiesIntersecting(transform.MapID, enlargedAABB, _aroundColliderSet); + foreach (var otherEntity in _aroundColliderSet) { if (otherEntity == uid) continue; // Don't try to push off of yourself! diff --git a/Resources/ConfigPresets/WizardsDen/vulture.toml b/Resources/ConfigPresets/WizardsDen/vulture.toml index 0e89f63741..8eccfa48e1 100644 --- a/Resources/ConfigPresets/WizardsDen/vulture.toml +++ b/Resources/ConfigPresets/WizardsDen/vulture.toml @@ -14,6 +14,3 @@ force_client_hud_version_watermark = true [chat] motd = "\n########################################################\n\n[font size=17]This is a test server. You can play with the newest changes to the game, but these [color=red]changes may not be final or stable[/color], and may be reverted. Please report bugs via our GitHub, forum, or community Discord.[/font]\n\n########################################################\n" - -[debug] -pow3r_disable_parallel = false