Refactor antag rule code (#23445)

* Initial Pass, Rev, Thief

* Zombie initial pass

* Rebase, Traitor

* Nukeops, More overloads

* Revert RevolutionaryRuleComponent

* Use TryRoundStartAttempt, Rewrite nukie spawning

* Comments, Add task scheduler to GameRuleSystem

* Zombie initial testing done

* Sort methods, rework GameRuleTask

* Add CCVar, Initial testing continues

* Might as well get rid of the obsolete logging

* Oops, i dont know how to log apparently

* Suggested formatting fixes

* Suggested changes

* Fix merge issues

* Minor optimisation

* Allowed thief to choose other antags

* Review changes

* Spawn items on floor first, then inserting

* minor tweaks

* Shift as much as possible to ProtoId<>

* Remove unneeded

* Add exclusive antag attribute

* Fix merge issues

* Minor formatting fix

* Convert to struct

* Cleanup

* Review cleanup (need to test a lot)

* Some fixes, (mostly) tested

* oop

* Pass tests (for real)

---------

Co-authored-by: Rainfall <rainfey0+git@gmail.com>
Co-authored-by: AJCM <AJCM@tutanota.com>
This commit is contained in:
Rainfey
2024-02-29 06:25:10 +00:00
committed by GitHub
parent 3966a65c65
commit 4e6c59cfe5
53 changed files with 22454 additions and 22396 deletions

View File

@@ -1,7 +1,5 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Antag;
using Content.Server.Chat.Managers;
using Content.Server.EUI;
using Content.Server.Flash;
using Content.Server.GameTicking.Rules.Components;
@@ -13,10 +11,12 @@ using Content.Server.Revolutionary;
using Content.Server.Revolutionary.Components;
using Content.Server.Roles;
using Content.Server.RoundEnd;
using Content.Shared.Chat;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Database;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components;
@@ -27,9 +27,9 @@ using Content.Shared.Revolutionary.Components;
using Content.Shared.Roles;
using Content.Shared.Stunnable;
using Content.Shared.Zombies;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using System.Linq;
namespace Content.Server.GameTicking.Rules;
@@ -39,7 +39,6 @@ namespace Content.Server.GameTicking.Rules;
public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
{
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AntagSelectionSystem _antagSelection = default!;
[Dependency] private readonly EuiManager _euiMan = default!;
@@ -50,12 +49,13 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[ValidatePrototypeId<NpcFactionPrototype>]
public const string RevolutionaryNpcFaction = "Revolutionary";
[ValidatePrototypeId<AntagPrototype>]
public const string RevolutionaryAntagRole = "Rev";
//Used in OnPostFlash, no reference to the rule component is available
public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary";
public readonly ProtoId<NpcFactionPrototype> RevPrototypeId = "Rev";
public override void Initialize()
{
@@ -69,15 +69,20 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
}
//Set miniumum players
protected override void Added(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
gameRule.MinPlayers = component.MinPlayers;
}
protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
component.CommandCheck = _timing.CurTime + component.TimerWait;
}
/// <summary>
/// Checks if the round should end and also checks who has a mindshield.
/// </summary>
protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, float frameTime)
{
base.ActiveTick(uid, component, gameRule, frameTime);
@@ -139,63 +144,63 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
}
//Check for enough players to start rule
private void OnStartAttempt(RoundStartAttemptEvent ev)
{
var query = AllEntityQuery<RevolutionaryRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out var comp, out var gameRule))
{
_antagSelection.AttemptStartGameRule(ev, uid, comp.MinPlayers, gameRule);
}
TryRoundStartAttempt(ev, Loc.GetString("roles-antag-rev-name"));
}
private void OnPlayerJobAssigned(RulePlayerJobsAssignedEvent ev)
{
var query = QueryActiveRules();
while (query.MoveNext(out _, out var comp, out _))
while (query.MoveNext(out var uid, out var activeGameRule, out var comp, out var gameRule))
{
_antagSelection.EligiblePlayers(comp.HeadRevPrototypeId, comp.MaxHeadRevs, comp.PlayersPerHeadRev, comp.HeadRevStartSound,
"head-rev-role-greeting", "#5e9cff", out var chosen);
if (chosen.Any())
GiveHeadRev(chosen, comp.HeadRevPrototypeId, comp);
else
{
_chatManager.SendAdminAnnouncement(Loc.GetString("rev-no-heads"));
}
var eligiblePlayers = _antagSelection.GetEligiblePlayers(ev.Players, comp.HeadRevPrototypeId);
if (eligiblePlayers.Count == 0)
continue;
var headRevCount = _antagSelection.CalculateAntagCount(ev.Players.Length, comp.PlayersPerHeadRev, comp.MaxHeadRevs);
var headRevs = _antagSelection.ChooseAntags(headRevCount, eligiblePlayers);
GiveHeadRev(headRevs, comp.HeadRevPrototypeId, comp);
}
}
private void GiveHeadRev(List<EntityUid> chosen, string antagProto, RevolutionaryRuleComponent comp)
private void GiveHeadRev(IEnumerable<EntityUid> chosen, ProtoId<AntagPrototype> antagProto, RevolutionaryRuleComponent comp)
{
foreach (var headRev in chosen)
GiveHeadRev(headRev, antagProto, comp);
}
private void GiveHeadRev(EntityUid chosen, ProtoId<AntagPrototype> antagProto, RevolutionaryRuleComponent comp)
{
RemComp<CommandStaffComponent>(chosen);
var inCharacterName = MetaData(chosen).EntityName;
if (!_mind.TryGetMind(chosen, out var mind, out _))
return;
if (!_role.MindHasRole<RevolutionaryRoleComponent>(mind))
{
RemComp<CommandStaffComponent>(headRev);
var inCharacterName = MetaData(headRev).EntityName;
if (_mind.TryGetMind(headRev, out var mindId, out var mind))
{
if (!_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
{
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = antagProto });
}
if (mind.Session != null)
{
comp.HeadRevs.Add(inCharacterName, mindId);
}
}
_antagSelection.GiveAntagBagGear(headRev, comp.StartingGear);
EnsureComp<RevolutionaryComponent>(headRev);
EnsureComp<HeadRevolutionaryComponent>(headRev);
_role.MindAddRole(mind, new RevolutionaryRoleComponent { PrototypeId = antagProto }, silent: true);
}
comp.HeadRevs.Add(inCharacterName, mind);
_inventory.SpawnItemsOnEntity(chosen, comp.StartingGear);
var revComp = EnsureComp<RevolutionaryComponent>(chosen);
EnsureComp<HeadRevolutionaryComponent>(chosen);
_antagSelection.SendBriefing(chosen, Loc.GetString("head-rev-role-greeting"), Color.CornflowerBlue, revComp.RevStartSound);
}
/// <summary>
/// Called when a Head Rev uses a flash in melee to convert somebody else.
/// </summary>
public void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev)
private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev)
{
TryComp<AlwaysRevolutionaryConvertibleComponent>(ev.Target, out var alwaysConvertibleComp);
var alwaysConvertible = alwaysConvertibleComp != null;
var alwaysConvertible = HasComp<AlwaysRevolutionaryConvertibleComponent>(ev.Target);
if (!_mind.TryGetMind(ev.Target, out var mindId, out var mind) && !alwaysConvertible)
return;
@@ -211,8 +216,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
}
_npcFaction.AddFaction(ev.Target, RevolutionaryNpcFaction);
EnsureComp<RevolutionaryComponent>(ev.Target);
var revComp = EnsureComp<RevolutionaryComponent>(ev.Target);
_stun.TryParalyze(ev.Target, comp.StunTime, true);
if (ev.User != null)
{
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
@@ -223,20 +229,16 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
{
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevolutionaryAntagRole });
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId });
}
if (mind?.Session != null)
{
var message = Loc.GetString("rev-role-greeting");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.Channel, Color.Red);
_audioSystem.PlayGlobal("/Audio/Ambience/Antag/headrev_start.ogg", ev.Target);
}
_antagSelection.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound);
}
public void OnHeadRevAdmin(EntityUid mindId, MindComponent? mind = null)
public void OnHeadRevAdmin(EntityUid entity)
{
if (!Resolve(mindId, ref mind))
if (HasComp<HeadRevolutionaryComponent>(entity))
return;
var revRule = EntityQuery<RevolutionaryRuleComponent>().FirstOrDefault();
@@ -246,24 +248,10 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
revRule = Comp<RevolutionaryRuleComponent>(ruleEnt);
}
if (!HasComp<HeadRevolutionaryComponent>(mind.OwnedEntity))
{
if (mind.OwnedEntity != null)
{
var player = new List<EntityUid>
{
mind.OwnedEntity.Value
};
GiveHeadRev(player, RevolutionaryAntagRole, revRule);
}
if (mind.Session != null)
{
var message = Loc.GetString("head-rev-role-greeting");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, mind.Session.Channel, Color.FromHex("#5e9cff"));
}
}
GiveHeadRev(entity, revRule.HeadRevPrototypeId, revRule);
}
//TODO: Enemies of the revolution
private void OnCommandMobStateChanged(EntityUid uid, CommandStaffComponent comp, MobStateChangedEvent ev)
{
if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid)
@@ -283,7 +271,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
commandList.Add(id);
}
return _antagSelection.IsGroupDead(commandList, true);
return IsGroupDead(commandList, true);
}
private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev)
@@ -307,7 +295,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
}
// If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen
if (_antagSelection.IsGroupDead(headRevList, false))
if (IsGroupDead(headRevList, false))
{
var rev = AllEntityQuery<RevolutionaryComponent, MindContainerComponent>();
while (rev.MoveNext(out var uid, out _, out var mc))
@@ -338,6 +326,38 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
return false;
}
/// <summary>
/// Will take a group of entities and check if they are all alive or dead
/// </summary>
/// <param name="list">The list of the entities</param>
/// <param name="checkOffStation">Bool for if you want to check if someone is in space and consider them dead. (Won't check when emergency shuttle arrives just in case)</param>
/// <returns></returns>
private bool IsGroupDead(List<EntityUid> list, bool checkOffStation)
{
var dead = 0;
foreach (var entity in list)
{
if (TryComp<MobStateComponent>(entity, out var state))
{
if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid)
{
dead++;
}
else if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived)
{
dead++;
}
}
//If they don't have the MobStateComponent they might as well be dead.
else
{
dead++;
}
}
return dead == list.Count || list.Count == 0;
}
private static readonly string[] Outcomes =
{
// revs survived and heads survived... how