Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Choose Antags Before Job Selection #1059

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c1356c6
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Nov 30, 2024
576961a
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 1, 2024
6f039d8
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 7, 2024
6a7aca4
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 8, 2024
b2e7a3e
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 10, 2024
4ba5c21
first steps
formlessnameless Dec 10, 2024
63680c2
Merge branch 'dev' of https://github.com/Gh0ulcaller/imp-station-14 i…
formlessnameless Dec 10, 2024
3b56abe
asdfg
formlessnameless Dec 11, 2024
f9c8174
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 13, 2024
bb42319
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 13, 2024
fac9c67
intermission to test a different thing
formlessnameless Dec 13, 2024
a119930
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 14, 2024
bb404af
this technically works
formlessnameless Dec 18, 2024
b00a545
i think this works
formlessnameless Dec 18, 2024
465a1bf
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 18, 2024
3b2ae5f
this doesn't need to be there
formlessnameless Dec 18, 2024
0cb710c
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 20, 2024
6afee5a
sure ill commit like two spacing changes
formlessnameless Dec 23, 2024
a1e2416
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Dec 23, 2024
a1d0474
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Jan 5, 2025
b22eee3
still running tests but
formlessnameless Jan 12, 2025
fbddda3
Merge branch 'master' of https://github.com/impstation/imp-station-14…
formlessnameless Jan 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Content.Server/Antag/AntagSelectionSystem.API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ public void ForceMakeAntag<T>(ICommonSession? player, string defaultRule) where

if (!TryGetNextAvailableDefinition(rule, out var def))
def = rule.Comp.Definitions.Last();
MakeAntag(rule, player, def.Value, null);
MakeAntag(rule, player, def.Value);
}

/// <summary>
Expand Down
94 changes: 72 additions & 22 deletions Content.Server/Antag/AntagSelectionSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
Expand All @@ -50,6 +51,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
// arbitrary random number to give late joining some mild interest.
public const float LateJoinRandomChance = 0.5f;

public Dictionary<NetUserId, (ICommonSession, AntagSelectionDefinition, Entity<AntagSelectionComponent>)> QueuedAntags = [];

/// <inheritdoc/>
public override void Initialize()
{
Expand Down Expand Up @@ -77,7 +80,7 @@ private void OnTakeGhostRole(Entity<GhostRoleAntagSpawnerComponent> ent, ref Tak
if (!Exists(rule) || !TryComp<AntagSelectionComponent>(rule, out var select))
return;

MakeAntag((rule, select), args.Player, def, null, ignoreSpawner: true);
MakeAntag((rule, select), args.Player, def, ignoreSpawner: true);
args.TookRole = true;
_ghostRole.UnregisterGhostRole((ent, Comp<GhostRoleComponent>(ent)));
}
Expand All @@ -86,18 +89,21 @@ private void OnPlayerSpawning(RulePlayerSpawningEvent args)
{
var pool = args.PlayerPool;

var query = QueryActiveRules();
while (query.MoveNext(out var uid, out _, out var comp, out _))
var query = QueryAllRules();
while (query.MoveNext(out var uid, out var comp, out _))
{
if (comp.SelectionTime != AntagSelectionTime.PrePlayerSpawn)
continue;

if (comp.SelectionsComplete)
continue;

ChooseAntags((uid, comp), pool);

foreach (var session in comp.SelectedSessions)
if (comp.SelectionTime != AntagSelectionTime.PrePlayerSpawn)
{
continue;
}

foreach (var session in comp.ProcessedSessions)
{
args.PlayerPool.Remove(session);
GameTicker.PlayerJoinGame(session);
Expand All @@ -113,7 +119,15 @@ private void OnJobsAssigned(RulePlayerJobsAssignedEvent args)
if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn)
continue;

ChooseAntags((uid, comp), args.Players);
if (!comp.SelectionsComplete) // Should never happen I think?
ChooseAntags((uid, comp), args.Players);

foreach ((var _, var antagData) in QueuedAntags)
{
if (antagData.Item3.Comp == comp)
MakeAntag(antagData.Item3, antagData.Item1, antagData.Item2);
}

}
}

Expand Down Expand Up @@ -181,8 +195,33 @@ protected override void Started(EntityUid uid, AntagSelectionComponent component
if (GameTicker.RunLevel != GameRunLevel.InRound)
return;

if (component.SelectionsComplete)
return;
if (component.SelectionsComplete) // Imp edit start
{
if (QueuedAntags.Count != 0)
Copy link

@ruddygreat ruddygreat Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be > 0 for sanity, even if it never intentionally goes negative?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might as well yeah

{
foreach ((var _, var antagData) in QueuedAntags)
{
if (antagData.Item3.Comp == component)
MakeAntag(antagData.Item3, antagData.Item1, antagData.Item2);

}
// Checking if antag counts meet expectations and choosing additional antags if not
var existingAntags = GetAntagMinds((uid, component)).Count;
var targetCount = GetTargetAntagCount((uid, component), null);
if (existingAntags < targetCount)
{
var playerPool = _playerManager.Sessions
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) && status == PlayerGameStatus.JoinedGame)
.ToList();
if (TryGetNextAvailableDefinition((uid, component), out var def)) // Given how we're getting here this should never be false but I'm wrapping it like this anyway Because
{
ChooseAntags((uid, component), playerPool, (AntagSelectionDefinition)def, midround: true, targetCount - existingAntags);
}
}
}
else
return;
} // Imp edit end

var players = _playerManager.Sessions
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) && status == PlayerGameStatus.JoinedGame)
Expand Down Expand Up @@ -217,13 +256,17 @@ public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSessi
/// <param name="pool">The players to choose from</param>
/// <param name="def">The antagonist selection parameters and criteria</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
/// <param name="number">Override to choose a number of additional antags if there are not enough at the start of the gamerule. </param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent,
IList<ICommonSession> pool,
AntagSelectionDefinition def,
bool midround = false)
bool midround = false,
int number = 0)
{
var playerPool = GetPlayerPool(ent, pool, def);
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
var count = number;
if (count <= 0)
count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);

// if there is both a spawner and players getting picked, let it fall back to a spawner.
var noSpawner = def.SpawnerPrototype == null;
Expand All @@ -248,14 +291,19 @@ public void ChooseAntags(Entity<AntagSelectionComponent> ent,
break;
}

if (session != null && ent.Comp.SelectedSessions.Contains(session))
if (session != null && QueuedAntags.ContainsKey(session.UserId))
{
Log.Warning($"Somehow picked {session} for an antag when this rule already selected them previously");
Log.Warning($"Somehow picked {session} for an antag when another rule already selected them previously");
continue;
}
}

MakeAntag(ent, session, def, playerPool);
if (!midround && ent.Comp.SelectionTime != AntagSelectionTime.PrePlayerSpawn && session != null) //Midround rule additions, ghost roles, and prespawn activations should never be queued
{
ent.Comp.SelectedSessions.Add(session);
QueuedAntags[session.UserId] = (session, def, ent);
}
else
MakeAntag(ent, session, def);
}
}

Expand All @@ -270,21 +318,23 @@ public bool TryMakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? se
if (!IsSessionValid(ent, session, def) || !IsEntityValid(session?.AttachedEntity, def))
return false;

MakeAntag(ent, session, def, null, ignoreSpawner);
MakeAntag(ent, session, def, ignoreSpawner);
return true;
}

/// <summary>
/// Makes a given player into the specified antagonist.
/// </summary>
public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, AntagSelectionPlayerPool? pool, bool ignoreSpawner = false)
public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false)
{
EntityUid? antagEnt = null;
var isSpawner = false;

if (session != null)
{
ent.Comp.SelectedSessions.Add(session);
ent.Comp.SelectedSessions.Remove(session);
QueuedAntags.Remove(session.UserId);
ent.Comp.ProcessedSessions.Add(session);

// we shouldn't be blocking the entity if they're just a ghost or smth.
if (!HasComp<GhostComponent>(session.AttachedEntity))
Expand All @@ -307,7 +357,7 @@ public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? sessi
{
Log.Error($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
if (session != null)
ent.Comp.SelectedSessions.Remove(session);
ent.Comp.ProcessedSessions.Remove(session);
return;
}

Expand Down Expand Up @@ -337,7 +387,7 @@ public void MakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? sessi
return;
}

var prereqEv = new AntagPrereqSetupEvent(session, ent, def, pool);
var prereqEv = new AntagPrereqSetupEvent(session, ent, def);
RaiseLocalEvent(ent, ref prereqEv, true);

// The following is where we apply components, equipment, and other changes to our antagonist entity.
Expand Down Expand Up @@ -413,7 +463,7 @@ public bool IsSessionValid(Entity<AntagSelectionComponent> ent, ICommonSession?
if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie)
return false;

if (ent.Comp.SelectedSessions.Contains(session))
if (ent.Comp.ProcessedSessions.Contains(session))
return false;

mind ??= session.GetMind();
Expand Down Expand Up @@ -519,7 +569,7 @@ public record struct AntagSelectLocationEvent(ICommonSession? Session, Entity<An
/// Used for applying additional more complex setup logic.
/// </summary>
[ByRefEvent]
public readonly record struct AntagPrereqSetupEvent(ICommonSession? Session, Entity<AntagSelectionComponent> GameRule, AntagSelectionDefinition Def, AntagSelectionPlayerPool? Pool);
public readonly record struct AntagPrereqSetupEvent(ICommonSession? Session, Entity<AntagSelectionComponent> GameRule, AntagSelectionDefinition Def);

/// <summary>
/// Event raised on a game rule entity after the setup logic for an antag is complete.
Expand Down
6 changes: 6 additions & 0 deletions Content.Server/Antag/Components/AntagSelectionComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public sealed partial class AntagSelectionComponent : Component
/// </summary>
public HashSet<ICommonSession> SelectedSessions = new();

/// <summary>
/// Cached sessions of players who are chosen. Used so we don't have to rebuild the pool multiple times in a tick.
/// Is not serialized.
/// </summary>
public HashSet<ICommonSession> ProcessedSessions = new();

/// <summary>
/// Locale id for the name of the antag.
/// If this is set then the antag is listed in the round-end summary.
Expand Down
14 changes: 13 additions & 1 deletion Content.Server/GameTicking/Rules/TraitorRuleSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Content.Server.Traitor.Uplink;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.GameTicking;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mind;
using Content.Shared.NPC.Systems;
Expand All @@ -17,6 +18,7 @@
using Content.Shared.Roles.RoleCodeword;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Server.Player;
using System.Linq;
using System.Text;

Expand All @@ -31,6 +33,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
[Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!;
Expand Down Expand Up @@ -59,8 +62,17 @@ protected override void Added(EntityUid uid, TraitorRuleComponent component, Gam

private void AdditionalSetup(Entity<TraitorRuleComponent> ent, ref AntagPrereqSetupEvent args)
{
CurrentAntagPool = args.Pool;
ForceAllPossible = args.Def.ForceAllPossible;
if (args.Def.ForceAllPossible)
{
CurrentAntagPool = _antag.GetPlayerPool( // Get player pool of potential antags. Used for assigning objective targets
args.GameRule,
_playerManager.Sessions
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) && status == PlayerGameStatus.JoinedGame)
.ToList(),
args.Def
);
}
}

private void AfterEntitySelected(Entity<TraitorRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using Content.Server.Antag;
using Content.Server.Administration.Managers;
using Content.Server.Players.PlayTimeTracking;
using Content.Server.Station.Components;
Expand All @@ -17,6 +18,7 @@ public sealed partial class StationJobsSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IBanManager _banManager = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;

private Dictionary<int, HashSet<string>> _jobsByWeight = default!;
private List<int> _orderedWeights = default!;
Expand Down Expand Up @@ -361,6 +363,9 @@ private Dictionary<NetUserId, List<string>> GetPlayersJobCandidates(int? weight,
if (!_prototypeManager.TryIndex(jobId, out var job))
continue;

if (!job.CanBeAntag && _antag.QueuedAntags.ContainsKey(player))
continue;
Copy link

@ruddygreat ruddygreat Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any protection for if a player only has antagimmune jobs enabled for their chosen character? or can they sometimes be dumped into passenger / held in the lobby by this

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty sure they will just get put out into the lobby, but I also think this is handled when the antag pool is selected initially. I'll go back and verify before merging though. I vaguely remember testing this when I first wrote the initial portion a month ago, but I'd like to see it Actually Work.

Copy link

@Sha-Seng Sha-Seng Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to, if they're selected as an antag, just...make them spawn as a passenger? missing out on the job slot i want because i only queue command/sec but don't have antags disabled sounds rough. and as we've always discussed, more people might play sec if they could still get lucky and roll antag passenger as a surprise

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more people might play sec if they could still get lucky and roll antag passenger as a surprise

the natural solution to this feels like just putting passenger on 'low'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh... oh my god you're right it's that easy. for 600 hours I've only set jobs as high or never. yay!!


if (weight is not null && job.Weight != weight.Value)
continue;

Expand Down
4 changes: 2 additions & 2 deletions Resources/Prototypes/_Impstation/GameRules/roundstart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
- type: GameRule
minPlayers: 35
delay:
min: 2
max: 4
min: 240
max: 420
- type: AntagObjectives
objectives:
- KillRandomTraitorSvSObjective
Expand Down
Loading