Skip to content

Commit

Permalink
[Fix] Antags Refactor Fix (#14)
Browse files Browse the repository at this point in the history
* fix: fix loadouts not spawning items inhands

* Prevent SecretRule from picking invalid presets (#27456)

* Prevent SecretRule from picking invalid presets

* remove lonely semicolon

---------

Co-authored-by: Nemanja <[email protected]>

* fix: fix makethief adminverb icon

* 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 <[email protected]>
# Conflicts:
#	Content.Server/Antag/AntagSelectionSystem.API.cs
#	Content.Server/Antag/AntagSelectionSystem.cs
#	Content.Server/Preferences/Managers/ServerPreferencesManager.cs

* Fix under-selecting antags (#28327)

Fix under selecting antags

* fix: antag adminverbs now target actual target

* fix: test

---------

Co-authored-by: Leon Friedrich <[email protected]>
Co-authored-by: Nemanja <[email protected]>
Co-authored-by: deltanedas <[email protected]>
  • Loading branch information
4 people committed Aug 24, 2024
1 parent 2ea1619 commit 69f3452
Show file tree
Hide file tree
Showing 16 changed files with 391 additions and 128 deletions.
28 changes: 28 additions & 0 deletions Content.IntegrationTests/Pair/TestPair.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Preferences.Managers;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
Expand Down Expand Up @@ -128,4 +131,29 @@ public List<EntityPrototype> GetPrototypesWithComponent<T>(

return list;
}

/// <summary>
/// Helper method for enabling or disabling a antag role
/// </summary>
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
{
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();

var prefs = prefMan.GetPreferences(Client.User!.Value);
// what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable?
var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter;

Assert.That(profile.AntagPreferences.Any(preference => preference == id), Is.EqualTo(!value));
var newProfile = profile.WithAntagPreference(id, value);

await Server.WaitPost(() =>
{
prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait();
});

// And why the fuck does it always create a new preference and profile object instead of just reusing them?
var newPrefs = prefMan.GetPreferences(Client.User.Value);
var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter;
Assert.That(newProf.AntagPreferences.Any(preference => preference == id), Is.EqualTo(value));
}
}
4 changes: 2 additions & 2 deletions Content.IntegrationTests/PoolManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ public static partial class PoolManager

options.BeforeStart += () =>
{
// Server-only systems (i.e., systems that subscribe to events with server-only components)
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
entSysMan.LoadExtraSystemType<ResettingEntitySystemTests.TestRoundRestartCleanupEvent>();
entSysMan.LoadExtraSystemType<InteractionSystemTests.TestInteractionSystem>();
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
entSysMan.LoadExtraSystemType<TestDestructibleListenerSystem>();
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
IoCManager.Resolve<IConfigurationManager>()
.OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true);
Expand Down
76 changes: 76 additions & 0 deletions Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.Antag;
using Content.Server.Antag.Components;
using Content.Server.GameTicking;
using Content.Shared.GameTicking;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Random;

namespace Content.IntegrationTests.Tests.GameRules;

// Once upon a time, players in the lobby weren't ever considered eligible for antag roles.
// Lets not let that happen again.
[TestFixture]
public sealed class AntagPreferenceTest
{
[Test]
public async Task TestLobbyPlayersValid()
{
await using var pair = await PoolManager.GetServerClient(new PoolSettings
{
DummyTicker = false,
Connected = true,
InLobby = true
});

var server = pair.Server;
var client = pair.Client;
var ticker = server.System<GameTicker>();
var sys = server.System<AntagSelectionSystem>();

// Initially in the lobby
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
Assert.That(client.AttachedEntity, Is.Null);
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));

EntityUid uid = default;
await server.WaitPost(() => uid = server.EntMan.Spawn("Traitor"));
var rule = new Entity<AntagSelectionComponent>(uid, server.EntMan.GetComponent<AntagSelectionComponent>(uid));
var def = rule.Comp.Definitions.Single();

// IsSessionValid & IsEntityValid are preference agnostic and should always be true for players in the lobby.
// Though maybe that will change in the future, but then GetPlayerPool() needs to be updated to reflect that.
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);

// By default, traitor/antag preferences are disabled, so the pool should be empty.
var sessions = new List<ICommonSession>{pair.Player!};
var pool = sys.GetPlayerPool(rule, sessions, def);
Assert.That(pool.Count, Is.EqualTo(0));

// Opt into the traitor role.
await pair.SetAntagPref("Traitor", true);

Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
pool = sys.GetPlayerPool(rule, sessions, def);
Assert.That(pool.Count, Is.EqualTo(1));
pool.TryPickAndTake(pair.Server.ResolveDependency<IRobustRandom>(), out var picked);
Assert.That(picked, Is.EqualTo(pair.Player));
Assert.That(sessions.Count, Is.EqualTo(1));

// opt back out
await pair.SetAntagPref("Traitor", false);

Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
pool = sys.GetPlayerPool(rule, sessions, def);
Assert.That(pool.Count, Is.EqualTo(0));

await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
await pair.CleanReturnAsync();
}
}
4 changes: 4 additions & 0 deletions Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public async Task TryStopNukeOpsFromConstantlyFailing()
Assert.That(client.AttachedEntity, Is.Null);
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
// Opt into the nukies role.
await pair.SetAntagPref("NukeopsCommander", true);
// There are no grids or maps
Assert.That(entMan.Count<MapComponent>(), Is.Zero);
Assert.That(entMan.Count<MapGridComponent>(), Is.Zero);
Expand Down Expand Up @@ -201,6 +204,7 @@ public async Task TryStopNukeOpsFromConstantlyFailing()
//ticker.SetGamePreset((GamePresetPrototype?)null); WD edit
server.CfgMan.SetCVar(CCVars.GridFill, false);
await pair.SetAntagPref("NukeopsCommander", false);
await pair.CleanReturnAsync();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,6 @@ await server.WaitAssertion(() =>
await pair.CleanReturnAsync();
}

[Reflect(false)]
public sealed class TestInteractionSystem : EntitySystem
{
public EntityEventHandler<InteractUsingEvent>? InteractUsingEvent;
Expand Down
3 changes: 0 additions & 3 deletions Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace Content.IntegrationTests.Tests
[TestOf(typeof(RoundRestartCleanupEvent))]
public sealed class ResettingEntitySystemTests
{
[Reflect(false)]
public sealed class TestRoundRestartCleanupEvent : EntitySystem
{
public bool HasBeenReset { get; set; }
Expand Down Expand Up @@ -49,8 +48,6 @@ await server.WaitAssertion(() =>
system.HasBeenReset = false;
Assert.That(system.HasBeenReset, Is.False);
gameTicker.RestartRound();
Assert.That(system.HasBeenReset);
Expand Down
14 changes: 8 additions & 6 deletions Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,19 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun))
return;

if (!HasComp<MindContainerComponent>(args.Target))
if (!HasComp<MindContainerComponent>(args.Target) || !TryComp<ActorComponent>(args.Target, out var targetActor))
return;

var targetPlayer = targetActor.PlayerSession;

Verb traitor = new()
{
Text = Loc.GetString("admin-verb-text-make-traitor"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster5_contraband"),
Act = () =>
{
_antag.ForceMakeAntag<TraitorRuleComponent>(player, DefaultTraitorRule);
_antag.ForceMakeAntag<TraitorRuleComponent>(targetPlayer, DefaultTraitorRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-traitor"),
Expand Down Expand Up @@ -83,7 +85,7 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"),
Act = () =>
{
_antag.ForceMakeAntag<NukeopsRuleComponent>(player, DefaultNukeOpRule);
_antag.ForceMakeAntag<NukeopsRuleComponent>(targetPlayer, DefaultNukeOpRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-nuclear-operative"),
Expand Down Expand Up @@ -112,7 +114,7 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"),
Act = () =>
{
_antag.ForceMakeAntag<RevolutionaryRuleComponent>(player, DefaultRevsRule);
_antag.ForceMakeAntag<RevolutionaryRuleComponent>(targetPlayer, DefaultRevsRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-head-rev"),
Expand All @@ -123,10 +125,10 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
{
Text = Loc.GetString("admin-verb-text-make-thief"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"),
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/Color/black.rsi"), "icon"),
Act = () =>
{
_antag.ForceMakeAntag<ThiefRuleComponent>(player, DefaultThiefRule);
_antag.ForceMakeAntag<ThiefRuleComponent>(targetPlayer, DefaultThiefRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-thief"),
Expand Down
35 changes: 30 additions & 5 deletions Content.Server/Antag/AntagSelectionSystem.API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Content.Shared.Mind;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.Enums;
using Robust.Shared.Player;

namespace Content.Server.Antag;
Expand All @@ -26,6 +27,11 @@ public bool TryGetNextAvailableDefinition(Entity<AntagSelectionComponent> ent,
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);
Expand All @@ -46,12 +52,26 @@ public bool TryGetNextAvailableDefinition(Entity<AntagSelectionComponent> ent,
/// Gets the number of antagonists that should be present for a given rule based on the provided pool.
/// A null pool will simply use the player count.
/// </summary>
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, AntagSelectionPlayerPool? pool = null)
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, int? playerCount = null)
{
var count = 0;
foreach (var def in ent.Comp.Definitions)
{
count += GetTargetAntagCount(ent, pool, def);
count += GetTargetAntagCount(ent, playerCount, def);
}

return count;
}

public int GetTotalPlayerCount(IList<ICommonSession> pool)
{
var count = 0;
foreach (var session in pool)
{
if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie)
continue;

count++;
}

return count;
Expand All @@ -61,9 +81,14 @@ public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, AntagSelecti
/// Gets the number of antagonists that should be present for a given antag definition based on the provided pool.
/// A null pool will simply use the player count.
/// </summary>
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, AntagSelectionPlayerPool? pool, AntagSelectionDefinition def)
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, int? playerCount, AntagSelectionDefinition def)
{
var poolSize = pool?.Count ?? _playerManager.Sessions.Length;
// 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 = playerCount ?? GetTotalPlayerCount(_playerManager.Sessions);

// factor in other definitions' affect on the count.
var countOffset = 0;
foreach (var otherDef in ent.Comp.Definitions)
Expand Down Expand Up @@ -278,7 +303,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);
}

Expand Down
Loading

0 comments on commit 69f3452

Please sign in to comment.