1
0

TraitorRuleSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Antag;
  3. using Content.Server.GameTicking.Rules.Components;
  4. using Content.Server.Mind;
  5. using Content.Server.Objectives;
  6. using Content.Server.PDA.Ringer;
  7. using Content.Server.Roles;
  8. using Content.Server.Traitor.Uplink;
  9. using Content.Shared.Database;
  10. using Content.Shared.FixedPoint;
  11. using Content.Shared.GameTicking.Components;
  12. using Content.Shared.Mind;
  13. using Content.Shared.NPC.Systems;
  14. using Content.Shared.PDA;
  15. using Content.Shared.Random.Helpers;
  16. using Content.Shared.Roles;
  17. using Content.Shared.Roles.Jobs;
  18. using Content.Shared.Roles.RoleCodeword;
  19. using Robust.Shared.Prototypes;
  20. using Robust.Shared.Random;
  21. using System.Linq;
  22. using System.Text;
  23. namespace Content.Server.GameTicking.Rules;
  24. public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
  25. {
  26. private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b");
  27. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  28. [Dependency] private readonly AntagSelectionSystem _antag = default!;
  29. [Dependency] private readonly SharedJobSystem _jobs = default!;
  30. [Dependency] private readonly MindSystem _mindSystem = default!;
  31. [Dependency] private readonly NpcFactionSystem _npcFaction = default!;
  32. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  33. [Dependency] private readonly IRobustRandom _random = default!;
  34. [Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!;
  35. [Dependency] private readonly SharedRoleSystem _roleSystem = default!;
  36. [Dependency] private readonly UplinkSystem _uplink = default!;
  37. public override void Initialize()
  38. {
  39. base.Initialize();
  40. Log.Level = LogLevel.Debug;
  41. SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
  42. SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
  43. }
  44. protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
  45. {
  46. base.Added(uid, component, gameRule, args);
  47. SetCodewords(component, args.RuleEntity);
  48. }
  49. private void AfterEntitySelected(Entity<TraitorRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
  50. {
  51. Log.Debug($"AfterAntagEntitySelected {ToPrettyString(ent)}");
  52. MakeTraitor(args.EntityUid, ent);
  53. }
  54. private void SetCodewords(TraitorRuleComponent component, EntityUid ruleEntity)
  55. {
  56. component.Codewords = GenerateTraitorCodewords(component);
  57. _adminLogger.Add(LogType.EventStarted, LogImpact.Low, $"Codewords generated for game rule {ToPrettyString(ruleEntity)}: {string.Join(", ", component.Codewords)}");
  58. }
  59. public string[] GenerateTraitorCodewords(TraitorRuleComponent component)
  60. {
  61. var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values;
  62. var verbs = _prototypeManager.Index(component.CodewordVerbs).Values;
  63. var codewordPool = adjectives.Concat(verbs).ToList();
  64. var finalCodewordCount = Math.Min(component.CodewordCount, codewordPool.Count);
  65. string[] codewords = new string[finalCodewordCount];
  66. for (var i = 0; i < finalCodewordCount; i++)
  67. {
  68. codewords[i] = Loc.GetString(_random.PickAndTake(codewordPool));
  69. }
  70. return codewords;
  71. }
  72. public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component)
  73. {
  74. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - start");
  75. //Grab the mind if it wasn't provided
  76. if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
  77. {
  78. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - failed, no Mind found");
  79. return false;
  80. }
  81. var briefing = "";
  82. if (component.GiveCodewords)
  83. {
  84. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - added codewords flufftext to briefing");
  85. briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
  86. }
  87. var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers));
  88. // Uplink code will go here if applicable, but we still need the variable if there aren't any
  89. Note[]? code = null;
  90. if (component.GiveUplink)
  91. {
  92. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink start");
  93. // Calculate the amount of currency on the uplink.
  94. var startingBalance = component.StartingBalance;
  95. if (_jobs.MindTryGetJob(mindId, out var prototype))
  96. {
  97. if (startingBalance < prototype.AntagAdvantage) // Can't use Math functions on FixedPoint2
  98. startingBalance = 0;
  99. else
  100. startingBalance = startingBalance - prototype.AntagAdvantage;
  101. }
  102. // Choose and generate an Uplink, and return the uplink code if applicable
  103. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink request start");
  104. var uplinkParams = RequestUplink(traitor, startingBalance, briefing);
  105. code = uplinkParams.Item1;
  106. briefing = uplinkParams.Item2;
  107. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink request completed");
  108. }
  109. string[]? codewords = null;
  110. if (component.GiveCodewords)
  111. {
  112. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - set codewords from component");
  113. codewords = component.Codewords;
  114. }
  115. if (component.GiveBriefing)
  116. {
  117. _antag.SendBriefing(traitor, GenerateBriefing(codewords, code, issuer), null, null);
  118. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Sent the Briefing");
  119. }
  120. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Adding TraitorMind");
  121. component.TraitorMinds.Add(mindId);
  122. // Assign briefing
  123. //Since this provides neither an antag/job prototype, nor antag status/roletype,
  124. //and is intrinsically related to the traitor role
  125. //it does not need to be a separate Mind Role Entity
  126. _roleSystem.MindHasRole<TraitorRoleComponent>(mindId, out var traitorRole);
  127. if (traitorRole is not null)
  128. {
  129. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Add traitor briefing components");
  130. AddComp<RoleBriefingComponent>(traitorRole.Value.Owner);
  131. Comp<RoleBriefingComponent>(traitorRole.Value.Owner).Briefing = briefing;
  132. }
  133. else
  134. {
  135. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - did not get traitor briefing");
  136. }
  137. // Send codewords to only the traitor client
  138. var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found
  139. RoleCodewordComponent codewordComp = EnsureComp<RoleCodewordComponent>(mindId);
  140. _roleCodewordSystem.SetRoleCodewords(codewordComp, "traitor", component.Codewords.ToList(), color);
  141. // Change the faction
  142. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Change faction");
  143. _npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false);
  144. _npcFaction.AddFaction(traitor, component.SyndicateFaction);
  145. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Finished");
  146. return true;
  147. }
  148. private (Note[]?, string) RequestUplink(EntityUid traitor, FixedPoint2 startingBalance, string briefing)
  149. {
  150. var pda = _uplink.FindUplinkTarget(traitor);
  151. Note[]? code = null;
  152. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink add");
  153. var uplinked = _uplink.AddUplink(traitor, startingBalance, pda, true);
  154. if (pda is not null && uplinked)
  155. {
  156. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink is PDA");
  157. // Codes are only generated if the uplink is a PDA
  158. code = EnsureComp<RingerUplinkComponent>(pda.Value).Code;
  159. // If giveUplink is false the uplink code part is omitted
  160. briefing = string.Format("{0}\n{1}",
  161. briefing,
  162. Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#"))));
  163. return (code, briefing);
  164. }
  165. else if (pda is null && uplinked)
  166. {
  167. Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Uplink is implant");
  168. briefing += "\n" + Loc.GetString("traitor-role-uplink-implant-short");
  169. }
  170. return (null, briefing);
  171. }
  172. // TODO: AntagCodewordsComponent
  173. private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args)
  174. {
  175. if (comp.GiveCodewords)
  176. args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));
  177. }
  178. // TODO: figure out how to handle this? add priority to briefing event?
  179. private string GenerateBriefing(string[]? codewords, Note[]? uplinkCode, string? objectiveIssuer = null)
  180. {
  181. var sb = new StringBuilder();
  182. sb.AppendLine(Loc.GetString("traitor-role-greeting", ("corporation", objectiveIssuer ?? Loc.GetString("objective-issuer-unknown"))));
  183. if (codewords != null)
  184. sb.AppendLine(Loc.GetString("traitor-role-codewords", ("codewords", string.Join(", ", codewords))));
  185. if (uplinkCode != null)
  186. sb.AppendLine(Loc.GetString("traitor-role-uplink-code", ("code", string.Join("-", uplinkCode).Replace("sharp", "#"))));
  187. else
  188. sb.AppendLine(Loc.GetString("traitor-role-uplink-implant"));
  189. return sb.ToString();
  190. }
  191. public List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind)
  192. {
  193. List<(EntityUid Id, MindComponent Mind)> allTraitors = new();
  194. var query = EntityQueryEnumerator<TraitorRuleComponent>();
  195. while (query.MoveNext(out var uid, out var traitor))
  196. {
  197. foreach (var role in GetOtherTraitorMindsAliveAndConnected(ourMind, (uid, traitor)))
  198. {
  199. if (!allTraitors.Contains(role))
  200. allTraitors.Add(role);
  201. }
  202. }
  203. return allTraitors;
  204. }
  205. private List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind, Entity<TraitorRuleComponent> rule)
  206. {
  207. var traitors = new List<(EntityUid Id, MindComponent Mind)>();
  208. foreach (var mind in _antag.GetAntagMinds(rule.Owner))
  209. {
  210. if (mind.Comp == ourMind)
  211. continue;
  212. traitors.Add((mind, mind));
  213. }
  214. return traitors;
  215. }
  216. }