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

Cherry-Pick Antag Refactor #734

Merged
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
2 changes: 1 addition & 1 deletion Content.Benchmarks/SpawnEquipDeleteBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ await _pair.Server.WaitPost(() =>
for (var i = 0; i < N; i++)
{
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
_spawnSys.EquipStartingGear(_entity, _gear, null);
_spawnSys.EquipStartingGear(_entity, _gear);
server.EntMan.DeleteEntity(_entity);
}
});
Expand Down
5 changes: 4 additions & 1 deletion Content.Client/Humanoid/HumanoidAppearanceSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,11 @@ private void SetLayerData(
/// This should not be used if the entity is owned by the server. The server will otherwise
/// override this with the appearance data it sends over.
/// </remarks>
public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null)
public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profile, HumanoidAppearanceComponent? humanoid = null)
{
if (profile == null)
return;

if (!Resolve(uid, ref humanoid))
{
return;
Expand Down
208 changes: 208 additions & 0 deletions Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/* WD edit

#nullable enable
using System.Linq;
using Content.Server.Body.Components;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Presets;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Server.NPC.Systems;
using Content.Server.Pinpointer;
using Content.Server.Roles;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Components;
using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.GameTicking;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
using Content.Shared.NukeOps;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;

namespace Content.IntegrationTests.Tests.GameRules;

[TestFixture]
public sealed class NukeOpsTest
{
/// <summary>
/// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded.
/// </summary>
[Test]
public async Task TryStopNukeOpsFromConstantlyFailing()
{
await using var pair = await PoolManager.GetServerClient(new PoolSettings
{
Dirty = true,
DummyTicker = false,
Connected = true,
InLobby = true
});

var server = pair.Server;
var client = pair.Client;
var entMan = server.EntMan;
var mapSys = server.System<MapSystem>();
var ticker = server.System<GameTicker>();
var mindSys = server.System<MindSystem>();
var roleSys = server.System<RoleSystem>();
var invSys = server.System<InventorySystem>();
var factionSys = server.System<NpcFactionSystem>();

Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False);
server.CfgMan.SetCVar(CCVars.GridFill, true);

// 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));

// There are no grids or maps
Assert.That(entMan.Count<MapComponent>(), Is.Zero);
Assert.That(entMan.Count<MapGridComponent>(), Is.Zero);
Assert.That(entMan.Count<StationMapComponent>(), Is.Zero);
Assert.That(entMan.Count<StationMemberComponent>(), Is.Zero);
Assert.That(entMan.Count<StationCentcommComponent>(), Is.Zero);

// And no nukie related components
Assert.That(entMan.Count<NukeopsRuleComponent>(), Is.Zero);
Assert.That(entMan.Count<NukeopsRoleComponent>(), Is.Zero);
Assert.That(entMan.Count<NukeOperativeComponent>(), Is.Zero);
Assert.That(entMan.Count<NukeOpsShuttleComponent>(), Is.Zero);
Assert.That(entMan.Count<NukeOperativeSpawnerComponent>(), Is.Zero);

// Ready up and start nukeops
await pair.WaitClientCommand("toggleready True");
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
await pair.WaitCommand("forcepreset Nukeops");
await pair.RunTicksSync(10);

// Game should have started
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame));
Assert.That(client.EntMan.EntityExists(client.AttachedEntity));
var player = pair.Player!.AttachedEntity!.Value;
Assert.That(entMan.EntityExists(player));

// Maps now exist
Assert.That(entMan.Count<MapComponent>(), Is.GreaterThan(0));
Assert.That(entMan.Count<MapGridComponent>(), Is.GreaterThan(0));
Assert.That(entMan.Count<StationDataComponent>(), Is.EqualTo(2)); // The main station & nukie station
Assert.That(entMan.Count<StationMemberComponent>(), Is.GreaterThan(3)); // Each station has at least 1 grid, plus some shuttles
Assert.That(entMan.Count<StationCentcommComponent>(), Is.EqualTo(1));

// And we now have nukie related components
Assert.That(entMan.Count<NukeopsRuleComponent>(), Is.EqualTo(1));
Assert.That(entMan.Count<NukeopsRoleComponent>(), Is.EqualTo(1));
Assert.That(entMan.Count<NukeOperativeComponent>(), Is.EqualTo(1));
Assert.That(entMan.Count<NukeOpsShuttleComponent>(), Is.EqualTo(1));

// The player entity should be the nukie commander
var mind = mindSys.GetMind(player)!.Value;
Assert.That(entMan.HasComponent<NukeOperativeComponent>(player));
Assert.That(roleSys.MindIsAntagonist(mind));
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));

Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);

var roles = roleSys.MindGetAllRoles(mind);
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
Assert.That(cmdRoles.Count(), Is.EqualTo(1));

// The game rule exists, and all the stations/shuttles/maps are properly initialized
var rule = entMan.AllComponents<NukeopsRuleComponent>().Single().Component;
var mapRule = entMan.AllComponents<LoadMapRuleComponent>().Single().Component;
foreach (var grid in mapRule.MapGrids)
{
Assert.That(entMan.EntityExists(grid));
Assert.That(entMan.HasComponent<MapGridComponent>(grid));
Assert.That(entMan.HasComponent<StationMemberComponent>(grid));
}
Assert.That(entMan.EntityExists(rule.TargetStation));

Assert.That(entMan.HasComponent<StationDataComponent>(rule.TargetStation));

var nukieShuttlEnt = entMan.AllComponents<NukeOpsShuttleComponent>().FirstOrDefault().Uid;
Assert.That(entMan.EntityExists(nukieShuttlEnt));

EntityUid? nukieStationEnt = null;
foreach (var grid in mapRule.MapGrids)
{
if (entMan.HasComponent<StationMemberComponent>(grid))
{
nukieStationEnt = grid;
break;
}
}

Assert.That(entMan.EntityExists(nukieStationEnt));
var nukieStation = entMan.GetComponent<StationMemberComponent>(nukieStationEnt!.Value);

Assert.That(entMan.EntityExists(nukieStation.Station));
Assert.That(nukieStation.Station, Is.Not.EqualTo(rule.TargetStation));

Assert.That(server.MapMan.MapExists(mapRule.Map));
var nukieMap = mapSys.GetMap(mapRule.Map!.Value);

var targetStation = entMan.GetComponent<StationDataComponent>(rule.TargetStation!.Value);
var targetGrid = targetStation.Grids.First();
var targetMap = entMan.GetComponent<TransformComponent>(targetGrid).MapUid!.Value;
Assert.That(targetMap, Is.Not.EqualTo(nukieMap));

Assert.That(entMan.GetComponent<TransformComponent>(player).MapUid, Is.EqualTo(nukieMap));
Assert.That(entMan.GetComponent<TransformComponent>(nukieStationEnt.Value).MapUid, Is.EqualTo(nukieMap));
Assert.That(entMan.GetComponent<TransformComponent>(nukieShuttlEnt).MapUid, Is.EqualTo(nukieMap));

// The maps are all map-initialized, including the player
// Yes, this is necessary as this has repeatedly been broken somehow.
Assert.That(mapSys.IsInitialized(nukieMap));
Assert.That(mapSys.IsInitialized(targetMap));
Assert.That(mapSys.IsPaused(nukieMap), Is.False);
Assert.That(mapSys.IsPaused(targetMap), Is.False);

EntityLifeStage LifeStage(EntityUid? uid) => entMan.GetComponent<MetaDataComponent>(uid!.Value).EntityLifeStage;
Assert.That(LifeStage(player), Is.GreaterThan(EntityLifeStage.Initialized));
Assert.That(LifeStage(nukieMap), Is.GreaterThan(EntityLifeStage.Initialized));
Assert.That(LifeStage(targetMap), Is.GreaterThan(EntityLifeStage.Initialized));
Assert.That(LifeStage(nukieStationEnt.Value), Is.GreaterThan(EntityLifeStage.Initialized));
Assert.That(LifeStage(nukieShuttlEnt), Is.GreaterThan(EntityLifeStage.Initialized));
Assert.That(LifeStage(rule.TargetStation), Is.GreaterThan(EntityLifeStage.Initialized));

// Make sure the player has hands. We've had fucking disarmed nukies before.
Assert.That(entMan.HasComponent<HandsComponent>(player));
Assert.That(entMan.GetComponent<HandsComponent>(player).Hands.Count, Is.GreaterThan(0));

// While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be
// likely to have in the future. But nukies should probably have at least 3 slots with something in them.
var enumerator = invSys.GetSlotEnumerator(player);
int total = 0;
while (enumerator.NextItem(out _))
{
total++;
}
Assert.That(total, Is.GreaterThan(3));

// Finally lets check the nukie commander passed basic training and figured out how to breathe.
var totalSeconds = 30;
var totalTicks = (int) Math.Ceiling(totalSeconds / server.Timing.TickPeriod.TotalSeconds);
int increment = 5;
var resp = entMan.GetComponent<RespiratorComponent>(player);
var damage = entMan.GetComponent<DamageableComponent>(player);
for (var tick = 0; tick < totalTicks; tick += increment)
{
await pair.RunTicksSync(increment);
Assert.That(resp.SuffocationCycles, Is.LessThanOrEqualTo(resp.SuffocationCycleThreshold));
Assert.That(damage.TotalDamage, Is.EqualTo(FixedPoint2.Zero));
}

//ticker.SetGamePreset((GamePresetPrototype?)null); WD edit
server.CfgMan.SetCVar(CCVars.GridFill, false);
await pair.CleanReturnAsync();
}
}

*/
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Content.Server.GameTicking;
using Content.Server.GameTicking.Commands;
using Content.Server.GameTicking.Components;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Shared.CCVar;
Expand Down
6 changes: 2 additions & 4 deletions Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public async Task TestSecretStarts()

var server = pair.Server;
await server.WaitIdleAsync();
var entMan = server.ResolveDependency<IEntityManager>();
var gameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();

await server.WaitAssertion(() =>
Expand All @@ -32,10 +33,7 @@ await server.WaitAssertion(() =>

await server.WaitAssertion(() =>
{
foreach (var rule in gameTicker.GetAddedGameRules())
{
Assert.That(gameTicker.GetActiveGameRules(), Does.Contain(rule));
}
Assert.That(gameTicker.GetAddedGameRules().Count(), Is.GreaterThan(1), $"No additional rules started by secret rule.");

// End all rules
gameTicker.ClearGameRules();
Expand Down
1 change: 1 addition & 0 deletions Content.Server/Administration/ServerApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Content.Server.Administration.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Components;
using Content.Server.GameTicking.Presets;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Maps;
Expand Down
42 changes: 27 additions & 15 deletions Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
using Content.Server.GameTicking.Rules;
using Content.Server.Administration.Commands;
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Zombies;
using Content.Shared.Administration;
using Content.Shared.Database;
using Content.Shared.Humanoid;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Verbs;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Server.Administration.Systems;

public sealed partial class AdminVerbSystem
{
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
[Dependency] private readonly ThiefRuleSystem _thief = default!;
[Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
[Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!;
[Dependency] private readonly PiratesRuleSystem _piratesRule = default!;
[Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!;

[ValidatePrototypeId<EntityPrototype>]
private const string DefaultTraitorRule = "Traitor";

[ValidatePrototypeId<EntityPrototype>]
private const string DefaultNukeOpRule = "LoneOpsSpawn";

[ValidatePrototypeId<EntityPrototype>]
private const string DefaultRevsRule = "Revolutionary";

[ValidatePrototypeId<EntityPrototype>]
private const string DefaultThiefRule = "Thief";

[ValidatePrototypeId<StartingGearPrototype>]
private const string PirateGearId = "PirateGear";

// All antag verbs have names so invokeverb works.
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Expand All @@ -40,9 +54,7 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster5_contraband"),
Act = () =>
{
// if its a monkey or mouse or something dont give uplink or objectives
var isHuman = HasComp<HumanoidAppearanceComponent>(args.Target);
_traitorRule.MakeTraitorAdmin(args.Target, giveUplink: isHuman, giveObjectives: isHuman);
_antag.ForceMakeAntag<TraitorRuleComponent>(player, DefaultTraitorRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-traitor"),
Expand Down Expand Up @@ -71,7 +83,7 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"),
Act = () =>
{
_nukeopsRule.MakeLoneNukie(args.Target);
_antag.ForceMakeAntag<NukeopsRuleComponent>(player, DefaultNukeOpRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-nuclear-operative"),
Expand All @@ -85,22 +97,22 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Hats/pirate.rsi"), "icon"),
Act = () =>
{
_piratesRule.MakePirate(args.Target);
// pirates just get an outfit because they don't really have logic associated with them
SetOutfitCommand.SetOutfit(args.Target, PirateGearId, EntityManager);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-pirate"),
};
args.Verbs.Add(pirate);

//todo come here at some point dear lort.
Verb headRev = new()
{
Text = Loc.GetString("admin-verb-text-make-head-rev"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"),
Act = () =>
{
_revolutionaryRule.OnHeadRevAdmin(args.Target);
_antag.ForceMakeAntag<RevolutionaryRuleComponent>(player, DefaultRevsRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-head-rev"),
Expand All @@ -114,7 +126,7 @@ private void AddAntagVerbs(GetVerbsEvent<Verb> args)
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"),
Act = () =>
{
_thief.AdminMakeThief(args.Target, false); //Midround add pacified is bad
_antag.ForceMakeAntag<ThiefRuleComponent>(player, DefaultThiefRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-thief"),
Expand Down
27 changes: 27 additions & 0 deletions Content.Server/Antag/AntagSelectionPlayerPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Player;
using Robust.Shared.Random;

namespace Content.Server.Antag;

public sealed class AntagSelectionPlayerPool (List<List<ICommonSession>> orderedPools)
{
public bool TryPickAndTake(IRobustRandom random, [NotNullWhen(true)] out ICommonSession? session)
{
session = null;

foreach (var pool in orderedPools)
{
if (pool.Count == 0)
continue;

session = random.PickAndTake(pool);
break;
}

return session != null;
}

public int Count => orderedPools.Sum(p => p.Count);
}
Loading
Loading