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

[Fix] Antags Refactor Fix #14

Merged
merged 7 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading