From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Date: Thu, 13 Mar 2025 18:07:52 +0000 (+0100) Subject: Improvements to antag-before-job selection system (#35822) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=1945c7d7c6ddb1eaa7bbcf81ecb716834910df01;p=space-station-14.git Improvements to antag-before-job selection system (#35822) * Fix the latejoin-antag-deficit bug, add datafield, add logging * Fix multiple roles being made for single-role defs, --- diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs index c89e4df312..93b5fa6136 100644 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -97,7 +97,7 @@ public sealed partial class AntagSelectionSystem var countOffset = 0; foreach (var otherDef in ent.Comp.Definitions) { - countOffset += Math.Clamp((poolSize - countOffset) / otherDef.PlayerRatio, otherDef.Min, otherDef.Max) * otherDef.PlayerRatio; + countOffset += Math.Clamp((poolSize - countOffset) / otherDef.PlayerRatio, otherDef.Min, otherDef.Max) * otherDef.PlayerRatio; // Note: Is the PlayerRatio necessary here? Seems like it can cause issues for defs with varied PlayerRatio. } // make sure we don't double-count the current selection countOffset -= Math.Clamp(poolSize / def.PlayerRatio, def.Min, def.Max) * def.PlayerRatio; @@ -362,7 +362,8 @@ public sealed partial class AntagSelectionSystem /// /// Get all sessions that have been preselected for antag. /// - public HashSet GetPreSelectedAntagSessions(AntagSelectionComponent? except = null) + /// A specific definition to be excluded from the check. + public HashSet GetPreSelectedAntagSessions(AntagSelectionDefinition? except = null) { var result = new HashSet(); var query = QueryAllRules(); @@ -371,15 +372,13 @@ public sealed partial class AntagSelectionSystem if (HasComp(uid)) continue; - if (comp == except) - continue; - - if (!comp.PreSelectionsComplete) - continue; - foreach (var def in comp.Definitions) { - result.UnionWith(comp.PreSelectedSessions); + if (def.Equals(except)) + continue; + + if (comp.PreSelectedSessions.TryGetValue(def, out var set)) + result.UnionWith(set); } } @@ -389,7 +388,11 @@ public sealed partial class AntagSelectionSystem /// /// Get all sessions that have been preselected for antag and are exclusive, i.e. should not be paired with other antags. /// - public HashSet GetPreSelectedExclusiveAntagSessions(AntagSelectionComponent? except = null) + /// A specific definition to be excluded from the check. + // Note: This is a bit iffy since technically this exclusive definition is defined via the MultiAntagSetting, while there's a separately tracked antagExclusive variable in the mindrole. + // We can't query that however since there's no guarantee the mindrole has been given out yet when checking pre-selected antags. + // I don't think there's any instance where they differ, but it's something to be aware of for a potential future refactor. + public HashSet GetPreSelectedExclusiveAntagSessions(AntagSelectionDefinition? except = null) { var result = new HashSet(); var query = QueryAllRules(); @@ -398,17 +401,14 @@ public sealed partial class AntagSelectionSystem if (HasComp(uid)) continue; - if (comp == except) - continue; - - if (!comp.PreSelectionsComplete) - continue; - foreach (var def in comp.Definitions) { - if (def.MultiAntagSetting == AntagAcceptability.None) + if (def.Equals(except)) + continue; + + if (def.MultiAntagSetting == AntagAcceptability.None && comp.PreSelectedSessions.TryGetValue(def, out var set)) { - result.UnionWith(comp.PreSelectedSessions); + result.UnionWith(set); break; } } diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 298fa61a67..c8b2a7d4bb 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -12,8 +12,10 @@ using Content.Server.Roles; using Content.Server.Roles.Jobs; using Content.Server.Shuttles.Components; using Content.Server.Station.Events; +using Content.Shared.Administration.Logs; using Content.Shared.Antag; using Content.Shared.Clothing; +using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Shared.GameTicking.Components; using Content.Shared.Ghost; @@ -47,6 +49,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem(); - while (query.MoveNext(out var uid, out _, out var antag, out _)) + while (query.MoveNext(out var uid, out var antag, out _)) { - rules.Add((uid, antag)); + if (HasComp(uid) || + (HasComp(uid) && antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn)) //IntraPlayerSpawn selects antags before spawning, but doesn't activate until after. + rules.Add((uid, antag)); } RobustRandom.Shuffle(rules); @@ -158,7 +163,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem p.LateJoinAdditional)) continue; - DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn); + DebugTools.AssertNotEqual(antag.SelectionTime, AntagSelectionTime.PrePlayerSpawn); // do not count players in the lobby for the antag ratio var players = _playerManager.NetworkedSessions.Count(x => x.AttachedEntity != null); @@ -166,7 +171,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem x.Contains(session))) { Log.Warning($"Somehow picked {session} for an antag when this rule already selected them previously"); continue; @@ -283,8 +290,11 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem()); + set.Add(session); // Selection done! + Log.Debug($"Pre-selected {session.Name} as antagonist: {ToPrettyString(ent)}"); + _adminLogger.Add(LogType.AntagSelection, $"Pre-selected {session.Name} as antagonist: {ToPrettyString(ent)}"); } } } @@ -300,7 +310,10 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem /// Tries to makes a given player into the specified antagonist. /// - public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true) + public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true, bool onlyPreSelect = false) { if (checkPref && !HasPrimaryAntagPreference(session, def)) return false; @@ -320,7 +333,19 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem()); + set.Add(session); + Log.Debug($"Pre-selected {session!.Name} as antagonist: {ToPrettyString(ent)}"); + _adminLogger.Add(LogType.AntagSelection, $"Pre-selected {session.Name} as antagonist: {ToPrettyString(ent)}"); + } + else + { + MakeAntag(ent, session, def, ignoreSpawner); + } + return true; } @@ -334,6 +359,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem()); + set.Add(session); ent.Comp.AssignedSessions.Add(session); // we shouldn't be blocking the entity if they're just a ghost or smth. @@ -359,7 +387,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem - /// Cached sessions of players who are chosen yet not given the role yet. + /// Cached sessions of antag definitions and selected players. Players in this dict are not guaranteed to have been assigned the role yet. /// - public HashSet PreSelectedSessions = new(); + [DataField] + public Dictionary>PreSelectedSessions = new(); /// /// Cached sessions of players who are chosen. Used so we don't have to rebuild the pool multiple times in a tick. diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index 5ebb100daf..a868d0e384 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -449,9 +449,14 @@ public enum LogType /// An atmos networked device (such as a vent or pump) has had its settings changed, usually through an air alarm /// AtmosDeviceSetting = 97, - + /// /// Commands related to admemes. Stuff like config changes, etc. /// AdminCommands = 98, + + /// + /// A player was selected or assigned antag status + /// + AntagSelection = 99, }