using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; using Content.Server.Jobs; using Content.Server.Preferences.Managers; using Content.Server.Revolutionary.Components; using Content.Shared.GameTicking.Components; using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server.GameTicking.Rules; public abstract partial class GameRuleSystem : EntitySystem where T : IComponent { [Dependency] protected readonly IRobustRandom RobustRandom = default!; [Dependency] protected readonly IChatManager ChatManager = default!; [Dependency] protected readonly GameTicker GameTicker = default!; [Dependency] protected readonly IGameTiming Timing = default!; [Dependency] protected readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IComponentFactory _componentFactory = default!; // Not protected, just to be used in utility methods [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly MapSystem _map = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartAttempt); SubscribeLocalEvent(OnGameRuleAdded); SubscribeLocalEvent(OnGameRuleStarted); SubscribeLocalEvent(OnGameRuleEnded); SubscribeLocalEvent(OnRoundEndTextAppend); } private void OnStartAttempt(RoundStartAttemptEvent args) { if (args.Forced || args.Cancelled) return; var query = QueryAllRules(); while (query.MoveNext(out var uid, out _, out var gameRule)) { // Sunrise-Start if (gameRule.MinCommandStaff > 0) { var availableHeads = new List(); foreach (var playerSession in args.Players) { var userId = playerSession.UserId; var preferencesManager = IoCManager.Resolve(); var prefs = preferencesManager.GetPreferences(userId); var profile = prefs.SelectedCharacter as HumanoidCharacterProfile; if (profile == null) continue; foreach (var profileJobPriority in profile.JobPriorities) { if (profileJobPriority.Value == JobPriority.Never) continue; if (!_prototype.TryIndex(profileJobPriority.Key.Id, out var job)) continue; foreach (var special in job.Special) { if (special is not AddComponentSpecial componentSpecial) continue; foreach (var componentSpecialComponent in componentSpecial.Components) { var copy = _componentFactory.GetComponent(componentSpecialComponent.Value); if (copy is CommandStaffComponent) { if (availableHeads.Contains(profileJobPriority.Key)) continue; availableHeads.Add(profileJobPriority.Key); } } } } } if (gameRule.CancelPresetOnTooFewPlayers && availableHeads.Count < gameRule.MinCommandStaff) { ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-command-staff", ("readyCommandStaffCount", args.Players.Length), ("minimumCommandStaff", gameRule.MinCommandStaff), ("presetName", ToPrettyString(uid)))); args.Cancel(); } } // Sunrise-Edit var minPlayers = gameRule.MinPlayers; var name = ToPrettyString(uid); if (args.Players.Length >= minPlayers) continue; if (gameRule.CancelPresetOnTooFewPlayers) { ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players", ("readyPlayersCount", args.Players.Length), ("minimumPlayers", minPlayers), ("presetName", name))); args.Cancel(); //TODO remove this once announcements are logged Log.Info($"Rule '{name}' requires {minPlayers} players, but only {args.Players.Length} are ready."); } else { ForceEndSelf(uid, gameRule); } } } private void OnGameRuleAdded(EntityUid uid, T component, ref GameRuleAddedEvent args) { if (!TryComp(uid, out var ruleData)) return; Added(uid, component, ruleData, args); } private void OnGameRuleStarted(EntityUid uid, T component, ref GameRuleStartedEvent args) { if (!TryComp(uid, out var ruleData)) return; Started(uid, component, ruleData, args); } private void OnGameRuleEnded(EntityUid uid, T component, ref GameRuleEndedEvent args) { if (!TryComp(uid, out var ruleData)) return; Ended(uid, component, ruleData, args); } private void OnRoundEndTextAppend(RoundEndTextAppendEvent ev) { var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var comp)) { if (!TryComp(uid, out var ruleData)) continue; AppendRoundEndText(uid, comp, ruleData, ref ev); } } /// /// Called when the gamerule is added /// protected virtual void Added(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleAddedEvent args) { } /// /// Called when the gamerule begins /// protected virtual void Started(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleStartedEvent args) { } /// /// Called when the gamerule ends /// protected virtual void Ended(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleEndedEvent args) { } /// /// Called at the end of a round when text needs to be added for a game rule. /// protected virtual void AppendRoundEndText(EntityUid uid, T component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { } /// /// Called on an active gamerule entity in the Update function /// protected virtual void ActiveTick(EntityUid uid, T component, GameRuleComponent gameRule, float frameTime) { } public override void Update(float frameTime) { base.Update(frameTime); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp1, out var comp2)) { if (!GameTicker.IsGameRuleActive(uid, comp2)) continue; ActiveTick(uid, comp1, comp2, frameTime); } } }