using Content.Server.Antag.Components;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
+using Content.Server.GameTicking.Events;
using Content.Server.GameTicking.Rules;
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
+ SubscribeLocalEvent<NoJobsAvailableSpawningEvent>(OnJobNotAssigned);
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawning);
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnJobsAssigned);
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
}
}
+ private void OnJobNotAssigned(NoJobsAvailableSpawningEvent args)
+ {
+ // If someone fails to spawn in due to there being no jobs, they should be removed from any preselected antags.
+ // We only care about delayed rules, since if they're active the player should have already been removed via MakeAntag.
+ var query = QueryDelayedRules();
+ while (query.MoveNext(out var uid, out _, out var comp, out _))
+ {
+ if (comp.SelectionTime != AntagSelectionTime.IntraPlayerSpawn)
+ continue;
+
+ if (!comp.RemoveUponFailedSpawn)
+ continue;
+
+ foreach (var def in comp.Definitions)
+ {
+ if (!comp.PreSelectedSessions.TryGetValue(def, out var session))
+ break;
+ session.Remove(args.Player);
+ }
+ }
+ }
+
private void OnSpawnComplete(PlayerSpawnCompleteEvent args)
{
if (!args.LateJoin)
var rules = new List<(EntityUid, AntagSelectionComponent)>();
while (query.MoveNext(out var uid, out var antag, out _))
{
- if (HasComp<ActiveGameRuleComponent>(uid) ||
- (HasComp<DelayedStartRuleComponent>(uid) && antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn)) //IntraPlayerSpawn selects antags before spawning, but doesn't activate until after.
+ if (HasComp<ActiveGameRuleComponent>(uid))
rules.Add((uid, antag));
}
RobustRandom.Shuffle(rules);
if (!TryGetNextAvailableDefinition((uid, antag), out var def, players))
continue;
- 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))
+ if (TryMakeAntag((uid, antag), args.Player, def.Value))
break;
}
}
if (component.AssignmentComplete)
return;
- if (!component.PreSelectionsComplete)
- {
- var players = _playerManager.Sessions
- .Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) &&
- status == PlayerGameStatus.JoinedGame)
- .ToList();
-
- ChooseAntags((uid, component), players, midround: true);
- }
+ var players = _playerManager.Sessions
+ .Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) &&
+ status == PlayerGameStatus.JoinedGame)
+ .ToList();
+ ChooseAntags((uid, component), players, midround: true);
AssignPreSelectedSessions((uid, component));
}
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
{
- if (ent.Comp.PreSelectionsComplete)
- return;
-
foreach (var def in ent.Comp.Definitions)
{
ChooseAntags(ent, pool, def, midround: midround);
bool midround = false)
{
var playerPool = GetPlayerPool(ent, pool, def);
- var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
+ var existingAntagCount = ent.Comp.PreSelectedSessions.TryGetValue(def, out var existingAntags) ? existingAntags.Count : 0;
+ var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def) - existingAntagCount;
// if there is both a spawner and players getting picked, let it fall back to a spawner.
var noSpawner = def.SpawnerPrototype == null;
/// </summary>
public bool TryMakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true, bool onlyPreSelect = false)
{
+ _adminLogger.Add(LogType.AntagSelection, $"Start trying to make {session} become the antagonist: {ToPrettyString(ent)}");
+
if (checkPref && !HasPrimaryAntagPreference(session, def))
return false;
if (antagEnt is not { } player)
{
Log.Error($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
- if (session != null)
+ _adminLogger.Add(LogType.AntagSelection,$"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
+ if (session != null && ent.Comp.RemoveUponFailedSpawn)
{
ent.Comp.AssignedSessions.Remove(session);
ent.Comp.PreSelectedSessions[def].Remove(session);
if (!TryComp<GhostRoleAntagSpawnerComponent>(player, out var spawnerComp))
{
Log.Error($"Antag spawner {player} does not have a GhostRoleAntagSpawnerComponent.");
+ _adminLogger.Add(LogType.AntagSelection,$"Antag spawner {player} in gamerule {ToPrettyString(ent)} failed due to not having GhostRoleAntagSpawnerComponent.");
if (session != null)
{
ent.Comp.AssignedSessions.Remove(session);
if (!IsSessionValid(ent, session, def) || !IsEntityValid(session.AttachedEntity, def))
continue;
+ if (ent.Comp.PreSelectedSessions.TryGetValue(def, out var preSelected) && preSelected.Contains(session))
+ continue;
+
if (HasPrimaryAntagPreference(session, def))
{
preferredList.Add(session);
description: traitor-description
showInVote: false
rules:
- - Traitor
- - SubGamemodesRule
- - BasicStationEventScheduler
- - MeteorSwarmScheduler
- - SpaceTrafficControlEventScheduler
- - BasicRoundstartVariation
+ - DummyNonAntagChance
+ - Traitor
+ - SubGamemodesRule
+ - BasicStationEventScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
+ - BasicRoundstartVariation
- type: gamePreset
id: Deathmatch
description: nukeops-description
showInVote: false
rules:
- - Nukeops
- - SubGamemodesRule
- - BasicStationEventScheduler
- - MeteorSwarmScheduler
- - SpaceTrafficControlEventScheduler
- - BasicRoundstartVariation
+ - Nukeops
+ - DummyNonAntagChance
+ - SubGamemodesRule
+ - BasicStationEventScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
+ - BasicRoundstartVariation
- type: gamePreset
id: Revolutionary
description: rev-description
showInVote: false
rules:
- - Revolutionary
- - SubGamemodesRule
- - BasicStationEventScheduler
- - MeteorSwarmScheduler
- - SpaceTrafficControlEventScheduler
- - BasicRoundstartVariation
+ - DummyNonAntagChance
+ - Revolutionary
+ - SubGamemodesRule
+ - BasicStationEventScheduler
+ - MeteorSwarmScheduler
+ - SpaceTrafficControlEventScheduler
+ - BasicRoundstartVariation
- type: gamePreset
id: Wizard
showInVote: false
rules:
- Wizard
+ - DummyNonAntagChance
- SubGamemodesRuleNoWizard #No Dual Wizards at the start, midround is fine
- BasicStationEventScheduler
- MeteorSwarmScheduler