1
0

SecretRuleSystem.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Server.Administration.Logs;
  4. using Content.Server.Chat.Managers;
  5. using Content.Server.GameTicking.Presets;
  6. using Content.Server.GameTicking.Rules.Components;
  7. using Content.Shared.GameTicking.Components;
  8. using Content.Shared.Random;
  9. using Content.Shared.CCVar;
  10. using Content.Shared.Database;
  11. using Robust.Shared.Prototypes;
  12. using Robust.Shared.Random;
  13. using Robust.Shared.Configuration;
  14. using Robust.Shared.Utility;
  15. namespace Content.Server.GameTicking.Rules;
  16. public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
  17. {
  18. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  19. [Dependency] private readonly IRobustRandom _random = default!;
  20. [Dependency] private readonly IConfigurationManager _configurationManager = default!;
  21. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  22. [Dependency] private readonly IComponentFactory _compFact = default!;
  23. private string _ruleCompName = default!;
  24. public override void Initialize()
  25. {
  26. base.Initialize();
  27. _ruleCompName = _compFact.GetComponentName(typeof(GameRuleComponent));
  28. }
  29. protected override void Added(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
  30. {
  31. base.Added(uid, component, gameRule, args);
  32. var weights = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
  33. if (!TryPickPreset(weights, out var preset))
  34. {
  35. Log.Error($"{ToPrettyString(uid)} failed to pick any preset. Removing rule.");
  36. Del(uid);
  37. return;
  38. }
  39. Log.Info($"Selected {preset.ID} as the secret preset.");
  40. _adminLogger.Add(LogType.EventStarted, $"Selected {preset.ID} as the secret preset.");
  41. foreach (var rule in preset.Rules)
  42. {
  43. EntityUid ruleEnt;
  44. // if we're pre-round (i.e. will only be added)
  45. // then just add rules. if we're added in the middle of the round (or at any other point really)
  46. // then we want to start them as well
  47. if (GameTicker.RunLevel <= GameRunLevel.InRound)
  48. ruleEnt = GameTicker.AddGameRule(rule);
  49. else
  50. GameTicker.StartGameRule(rule, out ruleEnt);
  51. component.AdditionalGameRules.Add(ruleEnt);
  52. }
  53. }
  54. protected override void Ended(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
  55. {
  56. base.Ended(uid, component, gameRule, args);
  57. foreach (var rule in component.AdditionalGameRules)
  58. {
  59. GameTicker.EndGameRule(rule);
  60. }
  61. }
  62. private bool TryPickPreset(ProtoId<WeightedRandomPrototype> weights, [NotNullWhen(true)] out GamePresetPrototype? preset)
  63. {
  64. var options = _prototypeManager.Index(weights).Weights.ShallowClone();
  65. var players = GameTicker.ReadyPlayerCount();
  66. GamePresetPrototype? selectedPreset = null;
  67. var sum = options.Values.Sum();
  68. while (options.Count > 0)
  69. {
  70. var accumulated = 0f;
  71. var rand = _random.NextFloat(sum);
  72. foreach (var (key, weight) in options)
  73. {
  74. accumulated += weight;
  75. if (accumulated < rand)
  76. continue;
  77. if (!_prototypeManager.TryIndex(key, out selectedPreset))
  78. Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {weights}");
  79. options.Remove(key);
  80. sum -= weight;
  81. break;
  82. }
  83. if (CanPick(selectedPreset, players))
  84. {
  85. preset = selectedPreset;
  86. return true;
  87. }
  88. if (selectedPreset != null)
  89. Log.Info($"Excluding {selectedPreset.ID} from secret preset selection.");
  90. }
  91. preset = null;
  92. return false;
  93. }
  94. public bool CanPickAny()
  95. {
  96. var secretPresetId = _configurationManager.GetCVar(CCVars.SecretWeightPrototype);
  97. return CanPickAny(secretPresetId);
  98. }
  99. /// <summary>
  100. /// Can any of the given presets be picked, taking into account the currently available player count?
  101. /// </summary>
  102. public bool CanPickAny(ProtoId<WeightedRandomPrototype> weightedPresets)
  103. {
  104. var ids = _prototypeManager.Index(weightedPresets).Weights.Keys
  105. .Select(x => new ProtoId<GamePresetPrototype>(x));
  106. return CanPickAny(ids);
  107. }
  108. /// <summary>
  109. /// Can any of the given presets be picked, taking into account the currently available player count?
  110. /// </summary>
  111. public bool CanPickAny(IEnumerable<ProtoId<GamePresetPrototype>> protos)
  112. {
  113. var players = GameTicker.ReadyPlayerCount();
  114. foreach (var id in protos)
  115. {
  116. if (!_prototypeManager.TryIndex(id, out var selectedPreset))
  117. Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {id}");
  118. if (CanPick(selectedPreset, players))
  119. return true;
  120. }
  121. return false;
  122. }
  123. /// <summary>
  124. /// Can the given preset be picked, taking into account the currently available player count?
  125. /// </summary>
  126. private bool CanPick([NotNullWhen(true)] GamePresetPrototype? selected, int players)
  127. {
  128. if (selected == null)
  129. return false;
  130. foreach (var ruleId in selected.Rules)
  131. {
  132. if (!_prototypeManager.TryIndex(ruleId, out EntityPrototype? rule)
  133. || !rule.TryGetComponent(_ruleCompName, out GameRuleComponent? ruleComp))
  134. {
  135. Log.Error($"Encountered invalid rule {ruleId} in preset {selected.ID}");
  136. return false;
  137. }
  138. if (ruleComp.MinPlayers > players && ruleComp.CancelPresetOnTooFewPlayers)
  139. return false;
  140. }
  141. return true;
  142. }
  143. }