using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; using Content.Shared.GameTicking.Components; using Robust.Server.GameObjects; 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!; // 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)) { 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); } } }