1
0

RevolutionaryRuleSystem.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Antag;
  3. using Content.Server.EUI;
  4. using Content.Server.Flash;
  5. using Content.Server.GameTicking.Rules.Components;
  6. using Content.Server.Mind;
  7. using Content.Server.Popups;
  8. using Content.Server.Revolutionary;
  9. using Content.Server.Revolutionary.Components;
  10. using Content.Server.Roles;
  11. using Content.Server.RoundEnd;
  12. using Content.Server.Shuttles.Systems;
  13. using Content.Server.Station.Systems;
  14. using Content.Shared.Database;
  15. using Content.Shared.GameTicking.Components;
  16. using Content.Shared.Humanoid;
  17. using Content.Shared.IdentityManagement;
  18. using Content.Shared.Mind.Components;
  19. using Content.Shared.Mindshield.Components;
  20. using Content.Shared.Mobs;
  21. using Content.Shared.Mobs.Components;
  22. using Content.Shared.Mobs.Systems;
  23. using Content.Shared.NPC.Prototypes;
  24. using Content.Shared.NPC.Systems;
  25. using Content.Shared.Revolutionary.Components;
  26. using Content.Shared.Stunnable;
  27. using Content.Shared.Zombies;
  28. using Robust.Shared.Prototypes;
  29. using Robust.Shared.Timing;
  30. using Content.Shared.Cuffs.Components;
  31. namespace Content.Server.GameTicking.Rules;
  32. /// <summary>
  33. /// Where all the main stuff for Revolutionaries happens (Assigning Head Revs, Command on station, and checking for the game to end.)
  34. /// </summary>
  35. public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
  36. {
  37. [Dependency] private readonly IAdminLogManager _adminLogManager = default!;
  38. [Dependency] private readonly AntagSelectionSystem _antag = default!;
  39. [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
  40. [Dependency] private readonly EuiManager _euiMan = default!;
  41. [Dependency] private readonly MindSystem _mind = default!;
  42. [Dependency] private readonly MobStateSystem _mobState = default!;
  43. [Dependency] private readonly NpcFactionSystem _npcFaction = default!;
  44. [Dependency] private readonly PopupSystem _popup = default!;
  45. [Dependency] private readonly RoleSystem _role = default!;
  46. [Dependency] private readonly SharedStunSystem _stun = default!;
  47. [Dependency] private readonly RoundEndSystem _roundEnd = default!;
  48. [Dependency] private readonly StationSystem _stationSystem = default!;
  49. [Dependency] private readonly IGameTiming _timing = default!;
  50. //Used in OnPostFlash, no reference to the rule component is available
  51. public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary";
  52. public readonly ProtoId<NpcFactionPrototype> RevPrototypeId = "Rev";
  53. public override void Initialize()
  54. {
  55. base.Initialize();
  56. SubscribeLocalEvent<CommandStaffComponent, MobStateChangedEvent>(OnCommandMobStateChanged);
  57. SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
  58. SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
  59. SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
  60. }
  61. protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
  62. {
  63. base.Started(uid, component, gameRule, args);
  64. component.CommandCheck = _timing.CurTime + component.TimerWait;
  65. }
  66. protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, float frameTime)
  67. {
  68. base.ActiveTick(uid, component, gameRule, frameTime);
  69. if (component.CommandCheck <= _timing.CurTime)
  70. {
  71. component.CommandCheck = _timing.CurTime + component.TimerWait;
  72. if (CheckCommandLose())
  73. {
  74. _roundEnd.DoRoundEndBehavior(RoundEndBehavior.ShuttleCall, component.ShuttleCallTime);
  75. GameTicker.EndGameRule(uid, gameRule);
  76. }
  77. }
  78. }
  79. protected override void AppendRoundEndText(EntityUid uid,
  80. RevolutionaryRuleComponent component,
  81. GameRuleComponent gameRule,
  82. ref RoundEndTextAppendEvent args)
  83. {
  84. base.AppendRoundEndText(uid, component, gameRule, ref args);
  85. var revsLost = CheckRevsLose();
  86. var commandLost = CheckCommandLose();
  87. // This is (revsLost, commandsLost) concatted together
  88. // (moony wrote this comment idk what it means)
  89. var index = (commandLost ? 1 : 0) | (revsLost ? 2 : 0);
  90. args.AddLine(Loc.GetString(Outcomes[index]));
  91. var sessionData = _antag.GetAntagIdentifiers(uid);
  92. args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count)));
  93. foreach (var (mind, data, name) in sessionData)
  94. {
  95. _role.MindHasRole<RevolutionaryRoleComponent>(mind, out var role);
  96. var count = CompOrNull<RevolutionaryRoleComponent>(role)?.ConvertedCount ?? 0;
  97. args.AddLine(Loc.GetString("rev-headrev-name-user",
  98. ("name", name),
  99. ("username", data.UserName),
  100. ("count", count)));
  101. // TODO: someone suggested listing all alive? revs maybe implement at some point
  102. }
  103. }
  104. private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args)
  105. {
  106. var ent = args.Mind.Comp.OwnedEntity;
  107. var head = HasComp<HeadRevolutionaryComponent>(ent);
  108. args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
  109. }
  110. /// <summary>
  111. /// Called when a Head Rev uses a flash in melee to convert somebody else.
  112. /// </summary>
  113. private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref AfterFlashedEvent ev)
  114. {
  115. var alwaysConvertible = HasComp<AlwaysRevolutionaryConvertibleComponent>(ev.Target);
  116. if (!_mind.TryGetMind(ev.Target, out var mindId, out var mind) && !alwaysConvertible)
  117. return;
  118. if (HasComp<RevolutionaryComponent>(ev.Target) ||
  119. HasComp<MindShieldComponent>(ev.Target) ||
  120. !HasComp<HumanoidAppearanceComponent>(ev.Target) &&
  121. !alwaysConvertible ||
  122. !_mobState.IsAlive(ev.Target) ||
  123. HasComp<ZombieComponent>(ev.Target))
  124. {
  125. return;
  126. }
  127. _npcFaction.AddFaction(ev.Target, RevolutionaryNpcFaction);
  128. var revComp = EnsureComp<RevolutionaryComponent>(ev.Target);
  129. if (ev.User != null)
  130. {
  131. _adminLogManager.Add(LogType.Mind,
  132. LogImpact.Medium,
  133. $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
  134. if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _))
  135. {
  136. if (_role.MindHasRole<RevolutionaryRoleComponent>(revMindId, out var role))
  137. role.Value.Comp2.ConvertedCount++;
  138. }
  139. }
  140. if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
  141. {
  142. _role.MindAddRole(mindId, "MindRoleRevolutionary");
  143. }
  144. if (mind?.Session != null)
  145. _antag.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, null);
  146. }
  147. //TODO: Enemies of the revolution
  148. private void OnCommandMobStateChanged(EntityUid uid, CommandStaffComponent comp, MobStateChangedEvent ev)
  149. {
  150. if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid)
  151. CheckCommandLose();
  152. }
  153. /// <summary>
  154. /// Checks if all of command is dead and if so will remove all sec and command jobs if there were any left.
  155. /// </summary>
  156. private bool CheckCommandLose()
  157. {
  158. var commandList = new List<EntityUid>();
  159. var heads = AllEntityQuery<CommandStaffComponent>();
  160. while (heads.MoveNext(out var id, out _))
  161. {
  162. commandList.Add(id);
  163. }
  164. return IsGroupDetainedOrDead(commandList, true, true, true);
  165. }
  166. private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev)
  167. {
  168. if (ev.NewMobState == MobState.Dead || ev.NewMobState == MobState.Invalid)
  169. CheckRevsLose();
  170. }
  171. /// <summary>
  172. /// Checks if all the Head Revs are dead and if so will deconvert all regular revs.
  173. /// </summary>
  174. private bool CheckRevsLose()
  175. {
  176. var stunTime = TimeSpan.FromSeconds(4);
  177. var headRevList = new List<EntityUid>();
  178. var headRevs = AllEntityQuery<HeadRevolutionaryComponent, MobStateComponent>();
  179. while (headRevs.MoveNext(out var uid, out _, out _))
  180. {
  181. headRevList.Add(uid);
  182. }
  183. // If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen
  184. // Cuffing Head Revs is not enough - they must be killed.
  185. if (IsGroupDetainedOrDead(headRevList, false, false, false))
  186. {
  187. var rev = AllEntityQuery<RevolutionaryComponent, MindContainerComponent>();
  188. while (rev.MoveNext(out var uid, out _, out var mc))
  189. {
  190. if (HasComp<HeadRevolutionaryComponent>(uid))
  191. continue;
  192. _npcFaction.RemoveFaction(uid, RevolutionaryNpcFaction);
  193. _stun.TryParalyze(uid, stunTime, true);
  194. RemCompDeferred<RevolutionaryComponent>(uid);
  195. _popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), uid);
  196. _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying.");
  197. if (!_mind.TryGetMind(uid, out var mindId, out _, mc))
  198. continue;
  199. // remove their antag role
  200. _role.MindTryRemoveRole<RevolutionaryRoleComponent>(mindId);
  201. // make it very obvious to the rev they've been deconverted since
  202. // they may not see the popup due to antag and/or new player tunnel vision
  203. if (_mind.TryGetSession(mindId, out var session))
  204. _euiMan.OpenEui(new DeconvertedEui(), session);
  205. }
  206. return true;
  207. }
  208. return false;
  209. }
  210. /// <summary>
  211. /// Will take a group of entities and check if these entities are alive, dead or cuffed.
  212. /// </summary>
  213. /// <param name="list">The list of the entities</param>
  214. /// <param name="checkOffStation">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)</param>
  215. /// <param name="countCuffed">Bool for if you don't want to count cuffed entities.</param>
  216. /// <param name="countRevolutionaries">Bool for if you want to count revolutionaries.</param>
  217. /// <returns></returns>
  218. private bool IsGroupDetainedOrDead(List<EntityUid> list, bool checkOffStation, bool countCuffed, bool countRevolutionaries)
  219. {
  220. var gone = 0;
  221. foreach (var entity in list)
  222. {
  223. if (TryComp<CuffableComponent>(entity, out var cuffed) && cuffed.CuffedHandCount > 0 && countCuffed)
  224. {
  225. gone++;
  226. continue;
  227. }
  228. if (TryComp<MobStateComponent>(entity, out var state))
  229. {
  230. if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid)
  231. {
  232. gone++;
  233. continue;
  234. }
  235. if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived)
  236. {
  237. gone++;
  238. continue;
  239. }
  240. }
  241. //If they don't have the MobStateComponent they might as well be dead.
  242. else
  243. {
  244. gone++;
  245. continue;
  246. }
  247. if ((HasComp<RevolutionaryComponent>(entity) || HasComp<HeadRevolutionaryComponent>(entity)) && countRevolutionaries)
  248. {
  249. gone++;
  250. continue;
  251. }
  252. }
  253. return gone == list.Count || list.Count == 0;
  254. }
  255. private static readonly string[] Outcomes =
  256. {
  257. // revs survived and heads survived... how
  258. "rev-reverse-stalemate",
  259. // revs won and heads died
  260. "rev-won",
  261. // revs lost and heads survived
  262. "rev-lost",
  263. // revs lost and heads died
  264. "rev-stalemate"
  265. };
  266. }