Skip to content

Commit

Permalink
Cherry-Pick Antag Refactor (#734)
Browse files Browse the repository at this point in the history
This cherry-pick's Wizden's Antag Refactor, which is needed for all
future antag updates, as well as for me to cherry-pick over Cultists(Who
are going to need some editing to fit the antag refactor), Changelings,
Heretics, and Wizards. I actually selected the White-Dream-Public
version of the Antag Refactor, due to it having commits made tailored to
our repository, so it comes prepackaged with all the changes necessary
for our repo-specific content.

https://github.com/frosty-dev/ss14-wwdp/pull/10

Signed-off-by: Timemaster99 <[email protected]>
Co-authored-by: ThereDrD <[email protected]>
Co-authored-by: Jeff <[email protected]>
Co-authored-by: Timemaster99 <[email protected]>
Co-authored-by: Timemaster99 <[email protected]>
Co-authored-by: [email protected] <[email protected]>
Co-authored-by: Danger Revolution! <[email protected]>
Co-authored-by: Azzy <[email protected]>
  • Loading branch information
8 people authored Aug 15, 2024
1 parent f93b8dc commit 08822e3
Show file tree
Hide file tree
Showing 121 changed files with 4,544 additions and 4,777 deletions.
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

0 comments on commit 08822e3

Please sign in to comment.