using System.Linq; using Content.Server.Administration.Logs; using Content.Server.EUI; using Content.Server.Mind; using Content.Server.Popups; using Content.Server.Revolutionary; using Content.Server.Revolutionary.Components; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Systems; using Content.Server.Station.Systems; using Content.Shared.Antag; using Content.Shared.Cuffs.Components; using Content.Shared.Database; using Content.Shared.Flash; using Content.Shared.GameTicking; using Content.Shared.GameTicking.Components; using Content.Shared.GameTicking.Rules; using Content.Shared.GameTicking.Rules.Components; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; using Content.Shared.Mind.Components; using Content.Shared.Mindshield; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.Revolutionary; using Content.Shared.Revolutionary.Components; using Content.Shared.Roles.Components; using Content.Shared.RoundEnd; using Content.Shared.Stunnable; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Server.GameTicking.Rules; public sealed partial class ServerRevolutionaryRuleSystem : RevolutionaryRuleSystem { [Dependency] private AntagSelectionSystem _antag = default!; [Dependency] private EmergencyShuttleSystem _emergencyShuttle = default!; [Dependency] private EuiManager _euiMan = default!; [Dependency] private IAdminLogManager _adminLogManager = default!; [Dependency] private ISharedPlayerManager _player = default!; [Dependency] private MindSystem _mind = default!; [Dependency] private MobStateSystem _mobState = default!; [Dependency] private NpcFactionSystem _npcFaction = default!; [Dependency] private PopupSystem _popup = default!; [Dependency] private RoleSystem _role = default!; [Dependency] private RoundEndSystem _roundEnd = default!; [Dependency] private SharedStunSystem _stun = default!; [Dependency] private ServerStationSystem _stationSystem = default!; [Dependency] private MindShieldSystem _mindShield = default!; //Used in OnPostFlash, no reference to the rule component is available public readonly ProtoId RevolutionaryNpcFaction = "Revolutionary"; public readonly ProtoId RevPrototypeId = "Rev"; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnCommandMobStateChanged); SubscribeLocalEvent(OnPostFlash); SubscribeLocalEvent(OnHeadRevMobStateChanged); } protected override void Started(Entity entity, ref GameRuleStartedEvent args) { base.Started(entity, ref args); entity.Comp1.CommandCheck = Timing.CurTime + entity.Comp1.TimerWait; } protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, float frameTime) { base.ActiveTick(uid, component, gameRule, frameTime); if (component.CommandCheck <= Timing.CurTime) { component.CommandCheck = Timing.CurTime + component.TimerWait; if (CheckCommandLose()) { _roundEnd.DoRoundEndBehavior(RoundEndBehavior.ShuttleCall, component.ShuttleCallTime); GameTicker.EndGameRule((uid, gameRule)); } } } protected override void AppendRoundEndText(Entity rule, ref RoundEndTextAppendEvent args) { base.AppendRoundEndText(rule, ref args); var revsLost = CheckRevsLose(); var commandLost = CheckCommandLose(); // This is (revsLost, commandsLost) concatted together // (moony wrote this comment idk what it means) var index = (commandLost ? 1 : 0) | (revsLost ? 2 : 0); args.AddLine(Loc.GetString(Outcomes[index])); var sessionData = _antag.GetAntagIdentifiers(rule.Owner).ToList(); args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count))); foreach (var (mind, data, name) in sessionData) { _role.MindHasRole(mind, out var role); var count = CompOrNull(role)?.ConvertedCount ?? 0; args.AddLine(Loc.GetString("rev-headrev-name-user", ("name", name), ("username", data.UserName), ("count", count))); // TODO: someone suggested listing all alive? revs maybe implement at some point } args.AddLine(""); } /// /// Called when a Head Rev uses a flash in melee to convert somebody else. /// private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev) { if (uid != ev.User || !ev.Melee) return; var alwaysConvertible = HasComp(ev.Target); if (!_mind.TryGetMind(ev.Target, out var mindId, out var mind) && !alwaysConvertible) return; if (!HasComp(ev.Target) && !alwaysConvertible || !_mobState.IsAlive(ev.Target) || !HasComp(ev.Used)) { return; } var attemptConvertEv = new AttemptConvertRevolutionaryEvent(); RaiseLocalEvent(ev.Target, ref attemptConvertEv); _mindShield.GetMindshieldStatus(ev.Target, out var isMindshielded, out _); if (attemptConvertEv.Cancelled || isMindshielded) return; _npcFaction.AddFaction(ev.Target, RevolutionaryNpcFaction); var revComp = EnsureComp(ev.Target); if (ev.User != null) { _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary"); if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _)) { if (_role.MindHasRole(revMindId, out var role)) { role.Value.Comp2.ConvertedCount++; Dirty(role.Value.Owner, role.Value.Comp2); } } } if (mindId == default || !_role.MindHasRole(mindId)) { _role.MindAddRole(mindId, "MindRoleRevolutionary"); } if (mind is { UserId: not null } && _player.TryGetSessionById(mind.UserId, out var session)) _antag.SendBriefing(session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound); } //TODO: Enemies of the revolution private void OnCommandMobStateChanged(EntityUid uid, CommandStaffComponent comp, MobStateChangedEvent ev) { if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid) CheckCommandLose(); } /// /// Checks if all of command is dead and if so will remove all sec and command jobs if there were any left. /// private bool CheckCommandLose() { var commandList = new List(); var heads = AllEntityQuery(); while (heads.MoveNext(out var id, out _)) { commandList.Add(id); } return IsGroupDetainedOrDead(commandList, true, true, true); } private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev) { if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid) CheckRevsLose(); } /// /// Checks if all the Head Revs are dead and if so will deconvert all regular revs. /// private bool CheckRevsLose() { var stunTime = TimeSpan.FromSeconds(4); var headRevList = new List(); var headRevs = AllEntityQuery(); while (headRevs.MoveNext(out var uid, out _, out _)) { headRevList.Add(uid); } // If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen // Cuffing Head Revs is not enough - they must be killed. if (IsGroupDetainedOrDead(headRevList, false, false, false)) { var rev = AllEntityQuery(); while (rev.MoveNext(out var uid, out _, out var mc)) { if (HasComp(uid)) continue; _npcFaction.RemoveFaction(uid, RevolutionaryNpcFaction); _stun.TryUpdateParalyzeDuration(uid, stunTime); RemCompDeferred(uid); _popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), uid); _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying."); if (!_mind.TryGetMind(uid, out var mindId, out var mind, mc)) continue; // remove their antag role _role.MindRemoveRole(mindId); // make it very obvious to the rev they've been deconverted since // they may not see the popup due to antag and/or new player tunnel vision if (_player.TryGetSessionById(mind.UserId, out var session)) _euiMan.OpenEui(new DeconvertedEui(), session); } return true; } return false; } /// /// Will take a group of entities and check if these entities are alive, dead or cuffed. /// /// The list of the entities /// Bool for if you want to check if someone is in space and consider them missing in action. (Won't check when emergency shuttle arrives just in case) /// Bool for if you don't want to count cuffed entities. /// Bool for if you want to count revolutionaries. /// private bool IsGroupDetainedOrDead(List list, bool checkOffStation, bool countCuffed, bool countRevolutionaries) { var gone = 0; foreach (var entity in list) { if (TryComp(entity, out var cuffed) && cuffed.CuffedHandCount > 0 && countCuffed) { gone++; continue; } if (TryComp(entity, out var state)) { if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid) { gone++; continue; } if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived) { gone++; continue; } } //If they don't have the MobStateComponent they might as well be dead. else { gone++; continue; } if ((HasComp(entity) || HasComp(entity)) && countRevolutionaries) { gone++; continue; } } return gone == list.Count || list.Count == 0; } private static readonly string[] Outcomes = { // revs survived and heads survived... how "rev-reverse-stalemate", // revs won and heads died "rev-won", // revs lost and heads survived "rev-lost", // revs lost and heads died "rev-stalemate" }; }