fix antag selection being evil (#28197)
* fix antag selection being evil * fix test * untroll the other tests * remove role timer troll * Allow tests to modify antag preferences * Fix antag selection * Misc test fixes * Add AntagPreferenceTest * Fix lazy mistakes * Test cleanup * Try stop players in lobbies from being assigned mid-round antags * ranting * I am going insane --------- Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
This commit is contained in:
@@ -27,6 +27,11 @@ public sealed partial class AntagSelectionSystem
|
||||
if (mindCount >= totalTargetCount)
|
||||
return false;
|
||||
|
||||
// TODO ANTAG fix this
|
||||
// If here are two definitions with 1/10 and 10/10 slots filled, this will always return the second definition
|
||||
// even though it has already met its target
|
||||
// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA I fucking hate game ticker code.
|
||||
// It needs to track selected minds for each definition independently.
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
var target = GetTargetAntagCount(ent, null, def);
|
||||
@@ -64,6 +69,10 @@ public sealed partial class AntagSelectionSystem
|
||||
/// </summary>
|
||||
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, AntagSelectionPlayerPool? pool, AntagSelectionDefinition def)
|
||||
{
|
||||
// TODO ANTAG
|
||||
// make pool non-nullable
|
||||
// Review uses and ensure that people are INTENTIONALLY including players in the lobby if this is a mid-round
|
||||
// antag selection.
|
||||
var poolSize = pool?.Count ?? _playerManager.Sessions
|
||||
.Count(s => s.State.Status is not SessionStatus.Disconnected and not SessionStatus.Zombie);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using Content.Server.Roles.Jobs;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Antag;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Players;
|
||||
@@ -25,6 +26,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Antag;
|
||||
|
||||
@@ -85,10 +87,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
continue;
|
||||
|
||||
if (comp.SelectionsComplete)
|
||||
return;
|
||||
continue;
|
||||
|
||||
ChooseAntags((uid, comp), pool);
|
||||
comp.SelectionsComplete = true;
|
||||
|
||||
foreach (var session in comp.SelectedSessions)
|
||||
{
|
||||
@@ -106,11 +107,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn)
|
||||
continue;
|
||||
|
||||
if (comp.SelectionsComplete)
|
||||
continue;
|
||||
|
||||
ChooseAntags((uid, comp));
|
||||
comp.SelectionsComplete = true;
|
||||
ChooseAntags((uid, comp), args.Players);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +123,18 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out var uid, out _, out var antag, out _))
|
||||
{
|
||||
// TODO ANTAG
|
||||
// what why aasdiuhasdopiuasdfhksad
|
||||
// stop this insanity please
|
||||
// probability of antag assignment shouldn't depend on the order in which rules are returned by the query.
|
||||
if (!RobustRandom.Prob(LateJoinRandomChance))
|
||||
continue;
|
||||
|
||||
if (!antag.Definitions.Any(p => p.LateJoinAdditional))
|
||||
continue;
|
||||
|
||||
DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn);
|
||||
|
||||
if (!TryGetNextAvailableDefinition((uid, antag), out var def))
|
||||
continue;
|
||||
|
||||
@@ -164,43 +167,40 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (component.SelectionsComplete)
|
||||
return;
|
||||
|
||||
// If the round has not yet started, we defer antag selection until roundstart
|
||||
if (GameTicker.RunLevel != GameRunLevel.InRound)
|
||||
return;
|
||||
|
||||
if (GameTicker.RunLevel == GameRunLevel.InRound && component.SelectionTime == AntagSelectionTime.PrePlayerSpawn)
|
||||
if (component.SelectionsComplete)
|
||||
return;
|
||||
|
||||
ChooseAntags((uid, component));
|
||||
component.SelectionsComplete = true;
|
||||
}
|
||||
var players = _playerManager.Sessions
|
||||
.Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame)
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the current selection of players
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent)
|
||||
{
|
||||
var sessions = _playerManager.Sessions.ToList();
|
||||
ChooseAntags(ent, sessions);
|
||||
ChooseAntags((uid, component), players);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the given selection of players
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, List<ICommonSession> pool)
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool)
|
||||
{
|
||||
if (ent.Comp.SelectionsComplete)
|
||||
return;
|
||||
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
ChooseAntags(ent, pool, def);
|
||||
}
|
||||
|
||||
ent.Comp.SelectionsComplete = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the given selection of players for the given antag definition.
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, List<ICommonSession> pool, AntagSelectionDefinition def)
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, AntagSelectionDefinition def)
|
||||
{
|
||||
var playerPool = GetPlayerPool(ent, pool, def);
|
||||
var count = GetTargetAntagCount(ent, playerPool, def);
|
||||
@@ -324,20 +324,15 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <summary>
|
||||
/// Gets an ordered player pool based on player preferences and the antagonist definition.
|
||||
/// </summary>
|
||||
public AntagSelectionPlayerPool GetPlayerPool(Entity<AntagSelectionComponent> ent, List<ICommonSession> sessions, AntagSelectionDefinition def)
|
||||
public AntagSelectionPlayerPool GetPlayerPool(Entity<AntagSelectionComponent> ent, IList<ICommonSession> sessions, AntagSelectionDefinition def)
|
||||
{
|
||||
var preferredList = new List<ICommonSession>();
|
||||
var fallbackList = new List<ICommonSession>();
|
||||
var unwantedList = new List<ICommonSession>();
|
||||
var invalidList = new List<ICommonSession>();
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
if (!IsSessionValid(ent, session, def) ||
|
||||
!IsEntityValid(session.AttachedEntity, def))
|
||||
{
|
||||
invalidList.Add(session);
|
||||
continue;
|
||||
}
|
||||
|
||||
var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter;
|
||||
if (def.PrefRoles.Count != 0 && pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p)))
|
||||
@@ -348,13 +343,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
fallbackList.Add(session);
|
||||
}
|
||||
else
|
||||
{
|
||||
unwantedList.Add(session);
|
||||
}
|
||||
}
|
||||
|
||||
return new AntagSelectionPlayerPool(new() { preferredList, fallbackList, unwantedList, invalidList });
|
||||
return new AntagSelectionPlayerPool(new() { preferredList, fallbackList });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -365,14 +356,18 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (session == null)
|
||||
return true;
|
||||
|
||||
mind ??= session.GetMind();
|
||||
|
||||
if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie)
|
||||
return false;
|
||||
|
||||
if (ent.Comp.SelectedSessions.Contains(session))
|
||||
return false;
|
||||
|
||||
mind ??= session.GetMind();
|
||||
|
||||
// If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity.
|
||||
if (mind == null)
|
||||
return true;
|
||||
|
||||
//todo: we need some way to check that we're not getting the same role twice. (double picking thieves or zombies through midrounds)
|
||||
|
||||
switch (def.MultiAntagSetting)
|
||||
@@ -401,10 +396,11 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <summary>
|
||||
/// Checks if a given entity (mind/session not included) is valid for a given antagonist.
|
||||
/// </summary>
|
||||
private bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def)
|
||||
public bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def)
|
||||
{
|
||||
// If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity.
|
||||
if (entity == null)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
if (HasComp<PendingClockInComponent>(entity))
|
||||
return false;
|
||||
|
||||
@@ -795,7 +795,7 @@ namespace Content.Server.GameTicking
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised after players were assigned jobs by the GameTicker.
|
||||
/// Event raised after players were assigned jobs by the GameTicker and have been spawned in.
|
||||
/// You can give on-station people special roles by listening to this event.
|
||||
/// </summary>
|
||||
public sealed class RulePlayerJobsAssignedEvent
|
||||
|
||||
@@ -20,5 +20,7 @@ namespace Content.Server.Preferences.Managers
|
||||
PlayerPreferences? GetPreferencesOrNull(NetUserId? userId);
|
||||
IEnumerable<KeyValuePair<NetUserId, ICharacterProfile>> GetSelectedProfilesForPlayers(List<NetUserId> userIds);
|
||||
bool HavePreferencesLoaded(ICommonSession session);
|
||||
|
||||
Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ namespace Content.Server.Preferences.Managers
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencies = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
// Cache player prefs on the server so we don't need as much async hell related to them.
|
||||
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
|
||||
new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots);
|
||||
|
||||
public void Init()
|
||||
@@ -42,6 +45,7 @@ namespace Content.Server.Preferences.Managers
|
||||
_netManager.RegisterNetMessage<MsgSelectCharacter>(HandleSelectCharacterMessage);
|
||||
_netManager.RegisterNetMessage<MsgUpdateCharacter>(HandleUpdateCharacterMessage);
|
||||
_netManager.RegisterNetMessage<MsgDeleteCharacter>(HandleDeleteCharacterMessage);
|
||||
_sawmill = _log.GetSawmill("prefs");
|
||||
}
|
||||
|
||||
private async void HandleSelectCharacterMessage(MsgSelectCharacter message)
|
||||
@@ -78,27 +82,25 @@ namespace Content.Server.Preferences.Managers
|
||||
|
||||
private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message)
|
||||
{
|
||||
var slot = message.Slot;
|
||||
var profile = message.Profile;
|
||||
var userId = message.MsgChannel.UserId;
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
Logger.WarningS("prefs",
|
||||
$"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {slot}.");
|
||||
return;
|
||||
}
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (message.Profile == null)
|
||||
_sawmill.Error($"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {message.Slot}.");
|
||||
else
|
||||
await SetProfile(userId, message.Slot, message.Profile);
|
||||
}
|
||||
|
||||
public async Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile)
|
||||
{
|
||||
if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded)
|
||||
{
|
||||
Logger.WarningS("prefs", $"User {userId} tried to modify preferences before they loaded.");
|
||||
_sawmill.Error($"Tried to modify user {userId} preferences before they loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= MaxCharacterSlots)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var curPrefs = prefsData.Prefs!;
|
||||
var session = _playerManager.GetSessionById(userId);
|
||||
@@ -112,10 +114,8 @@ namespace Content.Server.Preferences.Managers
|
||||
|
||||
prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor);
|
||||
|
||||
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||
{
|
||||
await _db.SaveCharacterSlotAsync(message.MsgChannel.UserId, message.Profile, message.Slot);
|
||||
}
|
||||
if (ShouldStorePrefs(session.Channel.AuthType))
|
||||
await _db.SaveCharacterSlotAsync(userId, profile, slot);
|
||||
}
|
||||
|
||||
private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message)
|
||||
|
||||
Reference in New Issue
Block a user