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;
/// <summary>
/// Get all sessions that have been preselected for antag.
/// </summary>
- public HashSet<ICommonSession> GetPreSelectedAntagSessions(AntagSelectionComponent? except = null)
+ /// <param name="except">A specific definition to be excluded from the check.</param>
+ public HashSet<ICommonSession> GetPreSelectedAntagSessions(AntagSelectionDefinition? except = null)
{
var result = new HashSet<ICommonSession>();
var query = QueryAllRules();
if (HasComp<EndedGameRuleComponent>(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);
}
}
/// <summary>
/// Get all sessions that have been preselected for antag and are exclusive, i.e. should not be paired with other antags.
/// </summary>
- public HashSet<ICommonSession> GetPreSelectedExclusiveAntagSessions(AntagSelectionComponent? except = null)
+ /// <param name="except">A specific definition to be excluded from the check.</param>
+ // 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<ICommonSession> GetPreSelectedExclusiveAntagSessions(AntagSelectionDefinition? except = null)
{
var result = new HashSet<ICommonSession>();
var query = QueryAllRules();
if (HasComp<EndedGameRuleComponent>(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;
}
}
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;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
// arbitrary random number to give late joining some mild interest.
public const float LateJoinRandomChance = 0.5f;
// eventually this should probably store the players per definition with some kind of unique identifier.
// something to figure out later.
- var query = QueryActiveRules();
+ var query = QueryAllRules();
var rules = new List<(EntityUid, AntagSelectionComponent)>();
- 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<ActiveGameRuleComponent>(uid) ||
+ (HasComp<DelayedStartRuleComponent>(uid) && antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn)) //IntraPlayerSpawn selects antags before spawning, but doesn't activate until after.
+ rules.Add((uid, antag));
}
RobustRandom.Shuffle(rules);
if (!antag.Definitions.Any(p => 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);
if (!TryGetNextAvailableDefinition((uid, antag), out var def, players))
continue;
- if (TryMakeAntag((uid, antag), args.Player, def.Value))
+ var onlyPreSelect = (antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn && !antag.AssignmentComplete); // Don't wanna give them antag status if the rule hasn't assigned its existing ones yet
+
+ if (TryMakeAntag((uid, antag), args.Player, def.Value, onlyPreSelect: onlyPreSelect))
break;
}
}
break;
}
- if (session != null && ent.Comp.PreSelectedSessions.Contains(session))
+ if (session != null && ent.Comp.PreSelectedSessions.Values.Any(x => x.Contains(session)))
{
Log.Warning($"Somehow picked {session} for an antag when this rule already selected them previously");
continue;
MakeAntag(ent, null, def); // This is for spawner antags
else
{
- ent.Comp.PreSelectedSessions.Add(session); // Selection done!
- Log.Debug($"Selected {session.Name} as antagonist: {ToPrettyString(ent)}");
+ if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
+ ent.Comp.PreSelectedSessions.Add(def, set = new HashSet<ICommonSession>());
+ 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)}");
}
}
}
foreach (var def in ent.Comp.Definitions)
{
- foreach (var session in ent.Comp.PreSelectedSessions)
+ if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
+ continue;
+
+ foreach (var session in set)
{
TryMakeAntag(ent, session, def);
}
/// <summary>
/// Tries to makes a given player into the specified antagonist.
/// </summary>
- public bool TryMakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true)
+ public bool TryMakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true, bool onlyPreSelect = false)
{
if (checkPref && !HasPrimaryAntagPreference(session, def))
return false;
if (!IsSessionValid(ent, session, def) || !IsEntityValid(session?.AttachedEntity, def))
return false;
- MakeAntag(ent, session, def, ignoreSpawner);
+ if (onlyPreSelect && session != null)
+ {
+ if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
+ ent.Comp.PreSelectedSessions.Add(def, set = new HashSet<ICommonSession>());
+ 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;
}
if (session != null)
{
+ if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
+ ent.Comp.PreSelectedSessions.Add(def, set = new HashSet<ICommonSession>());
+ set.Add(session);
ent.Comp.AssignedSessions.Add(session);
// we shouldn't be blocking the entity if they're just a ghost or smth.
if (session != null)
{
ent.Comp.AssignedSessions.Remove(session);
- ent.Comp.PreSelectedSessions.Remove(session);
+ ent.Comp.PreSelectedSessions[def].Remove(session);
}
return;
if (session != null)
{
ent.Comp.AssignedSessions.Remove(session);
- ent.Comp.PreSelectedSessions.Remove(session);
+ ent.Comp.PreSelectedSessions[def].Remove(session);
}
return;
SendBriefing(session, def.Briefing);
Log.Debug($"Assigned {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
+ _adminLogger.Add(LogType.AntagSelection, $"Assigned {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
}
var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def);
{
if (_role.MindIsAntagonist(mind))
return false;
- if (GetPreSelectedAntagSessions(ent.Comp).Contains(session)) // Used for rules where the antag has been selected, but not started yet
+ if (GetPreSelectedAntagSessions(def).Contains(session)) // Used for rules where the antag has been selected, but not started yet
return false;
break;
}
{
if (_role.MindIsExclusiveAntagonist(mind))
return false;
- if (GetPreSelectedExclusiveAntagSessions(ent.Comp).Contains(session))
+ if (GetPreSelectedExclusiveAntagSessions(def).Contains(session))
return false;
break;
}