using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Administration.Logs; using Content.Server.GameTicking.Rules.Components; using Content.Shared.GameTicking.Components; using Content.Shared.Random; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Shared.GameTicking.Prototypes; using Content.Shared.GameTicking.Rules; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Configuration; using Robust.Shared.Utility; namespace Content.Server.GameTicking.Rules; /// /// The handler for secret rules, rules that randomly pick from a set of subrules in secret. /// /// public sealed partial class SecretRuleSystem : GameRuleSystem { [Dependency] private IRobustRandom _random = default!; [Dependency] private IConfigurationManager _configurationManager = default!; [Dependency] private IAdminLogManager _adminLogger = default!; public override void Initialize() { base.Initialize(); } protected override void Added(Entity ent, ref GameRuleAddedEvent args) { base.Added(ent, ref args); var weights = _configurationManager.GetCVar(CCVars.SecretWeightPrototype); if (!TryPickPreset(weights, out var preset)) { Log.Error($"{ToPrettyString(ent.Owner)} failed to pick any preset. Removing rule."); Del(ent); return; } Log.Info($"Selected {preset.ID} as the secret preset."); _adminLogger.Add(LogType.EventStarted, $"Selected {preset.ID} as the secret preset."); foreach (var rule in preset.Rules) { if (GameTicker.IsIgnored(rule)) continue; Entity? ruleEnt; // if we're pre-round (i.e. will only be added) // then just add rules. if we're added in the middle of the round (or at any other point really) // then we want to start them as well if (GameTicker.RunLevel <= GameRunLevel.InRound) ruleEnt = GameTicker.AddGameRule(rule); else GameTicker.StartGameRule(rule, out ruleEnt); if (ruleEnt == null) continue; ent.Comp1.AdditionalGameRules.Add(ruleEnt.Value); } } // TODO: We PROBABLY SHOULD NOT BE DOING THIS as the only time Secret ends naturally is end of round which already cleans up these rules. // TODO: IN ADDITION We should end secret once it spawns its rules so we don't tick it :V or pause it? protected override void Ended(Entity rule, ref GameRuleEndedEvent args) { base.Ended(rule, ref args); foreach (var gameRule in rule.Comp.AdditionalGameRules) { GameTicker.EndGameRule(gameRule); } } private bool TryPickPreset(ProtoId weights, [NotNullWhen(true)] out GamePresetPrototype? preset) { var options = ProtoMan.Index(weights).Weights.ShallowClone(); var players = GameTicker.ReadyPlayerCount(); GamePresetPrototype? selectedPreset = null; var sum = options.Values.Sum(); while (options.Count > 0) { var accumulated = 0f; var rand = _random.NextFloat(sum); foreach (var (key, weight) in options) { accumulated += weight; if (accumulated < rand) continue; if (!ProtoMan.TryIndex(key, out selectedPreset)) Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {weights}"); options.Remove(key); sum -= weight; break; } if (CanPick(selectedPreset, players)) { preset = selectedPreset; return true; } if (selectedPreset != null) Log.Info($"Excluding {selectedPreset.ID} from secret preset selection."); } preset = null; return false; } public bool CanPickAny() { var secretPresetId = _configurationManager.GetCVar(CCVars.SecretWeightPrototype); return CanPickAny(secretPresetId); } /// /// Can any of the given presets be picked, taking into account the currently available player count? /// public bool CanPickAny(ProtoId weightedPresets) { var ids = ProtoMan.Index(weightedPresets).Weights.Keys .Select(x => new ProtoId(x)); return CanPickAny(ids); } /// /// Can any of the given presets be picked, taking into account the currently available player count? /// public bool CanPickAny(IEnumerable> protos) { var players = GameTicker.ReadyPlayerCount(); foreach (var id in protos) { if (!ProtoMan.TryIndex(id, out var selectedPreset)) Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {id}"); if (CanPick(selectedPreset, players)) return true; } return false; } /// /// Can the given preset be picked, taking into account the currently available player count? /// private bool CanPick([NotNullWhen(true)] GamePresetPrototype? selected, int players) { if (selected == null) return false; return players >= GameTicker.GetMinimumPlayerCount(selected); } }