// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com> // SPDX-FileCopyrightText: 2025 Aidenkrz // SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com> // SPDX-FileCopyrightText: 2025 Ilya246 <57039557+Ilya246@users.noreply.github.com> // SPDX-FileCopyrightText: 2025 Misandry // SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com> // SPDX-FileCopyrightText: 2025 gus // // SPDX-License-Identifier: AGPL-3.0-or-later using System.Linq; using Content.Server._Shitcode.Wizard.Components; using Content.Server.Administration.Logs; using Content.Server.Antag; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.GameTicking.Rules; using Content.Server.Mind; using Content.Server.Roles; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Shared._Shitcode.Wizard; using Content.Shared._Shitcode.Wizard.BindSoul; using Content.Shared.Atmos; using Content.Shared.Chat; using Content.Shared.Cloning; using Content.Shared.Database; using Content.Shared.GameTicking.Components; using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.Parallax; using Robust.Server.Audio; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Server._Shitcode.Wizard.Systems; public sealed class WizardRuleSystem : GameRuleSystem { [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly RoleSystem _role = default!; [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly AtmosphereSystem _atmos = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly NpcFactionSystem _faction = default!; [Dependency] private readonly IAdminLogManager _log = default!; [Dependency] private readonly IChatManager _chatManager = default!; public static readonly ProtoId Faction = "Wizard"; public static readonly EntProtoId Role = "MindRoleWizard"; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAfterAntagSelected); SubscribeLocalEvent(OnWizardGetBriefing); SubscribeLocalEvent(OnApprenticeGetBriefing); SubscribeLocalEvent(OnStateChanged); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnWizardClone); SubscribeLocalEvent(OnStateChanged); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnApprenticeClone); SubscribeLocalEvent(OnRemove); SubscribeLocalEvent(OnParentChanged); SubscribeLocalEvent(OnDimensionShift); } private void OnApprenticeClone(Entity ent, ref CloningEvent args) { EnsureComp(args.Target); RemCompDeferred(ent); _faction.ClearFactions(args.Target, false); _faction.AddFaction(args.Target, Faction); } private void OnWizardClone(Entity ent, ref CloningEvent args) { EnsureComp(args.Target); RemCompDeferred(ent); _faction.ClearFactions(args.Target, false); _faction.AddFaction(args.Target, Faction); } public EntityUid? GetTargetMap() { var rule = GameTicker.GetActiveGameRules().Where(HasComp).FirstOrNull(); EntityUid? map; if (rule != null) { var ruleComp = Comp(rule.Value); if (ruleComp.TargetStation == null) map = GetRandomTargetMap(); else { var stationGrid = _station.GetLargestGrid(Comp(ruleComp.TargetStation.Value)); map = stationGrid == null ? GetRandomTargetMap() : Transform(stationGrid.Value).MapUid; } } else map = GetRandomTargetMap(); return map; } private EntityUid? GetRandomTargetMap() { var grid = GetWizardTargetRandomStationGrid(); return grid == null ? null : Transform(grid.Value).MapUid; } private void OnDimensionShift(DimensionShiftEvent ev) { var map = GetTargetMap(); if (map == null) return; if (ev.Parallax != null) { var parallax = EnsureComp(map.Value); parallax.Parallax = ev.Parallax; Dirty(map.Value, parallax); } var moles = new float[Atmospherics.AdjustedNumberOfGases]; moles[(int) Gas.Oxygen] = ev.OxygenMoles; moles[(int) Gas.Nitrogen] = ev.NitrogenMoles; moles[(int) Gas.CarbonDioxide] = ev.CarbonDioxideMoles; var mixture = new GasMixture(moles, ev.Temperature); _atmos.SetMapAtmosphere(map.Value, false, mixture); var message = Loc.GetString("dimension-shift-message"); var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); _chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, Color.Red); _audio.PlayGlobal(ev.Sound, Filter.Broadcast(), true); _log.Add(LogType.EventRan, LogImpact.Extreme, $"Station map changed via wizard spellbook dimension shift."); return; } private void OnParentChanged(ref EntParentChangedMessage args) { if (args.OldMapId != args.Transform.MapUid && RecursivePhylacteryCheck(args.Entity, args.Transform)) CheckRoundShouldEnd(); } private bool RecursivePhylacteryCheck(EntityUid entity, TransformComponent? xform = null) { if (HasComp(entity)) return true; if (!Resolve(entity, ref xform, false)) return false; var enumerator = xform.ChildEnumerator; while (enumerator.MoveNext(out var child)) { if (RecursivePhylacteryCheck(child)) return true; } return false; } private void OnRemove(EntityUid uid, Component component, ComponentRemove args) { CheckRoundShouldEnd(); } private void OnStateChanged(EntityUid uid, Component component, MobStateChangedEvent args) { if (args.NewMobState == MobState.Dead) CheckRoundShouldEnd(); } private void CheckRoundShouldEnd() { if (!_gameTicker.IsGameRuleActive() || !_gameTicker.IsGameRuleActive()) return; var endRound = false; var query = EntityQueryEnumerator(); while (query.MoveNext(out var mind, out var mindComp)) { if (!_role.MindHasRole(mind) && !_role.MindHasRole(mind)) continue; if (!_mind.IsCharacterDeadIc(mindComp)) return; if (TryComp(mindComp.OwnedEntity, out MobStateComponent? mobState) && mobState.CurrentState != MobState.Dead) return; if (TryComp(mind, out SoulBoundComponent? soulBound) && Exists(soulBound.Item) && HasComp(soulBound.Item.Value) && TryComp(soulBound.Item.Value, out TransformComponent? xform) && xform.MapUid != null && xform.MapUid == soulBound.MapId) return; endRound = true; } if (!endRound) return; var endQuery = EntityQueryEnumerator(); while (endQuery.MoveNext(out var uid, out var ruleOnDeath, out var gameRule)) { _gameTicker.EndGameRule(uid, gameRule); _gameTicker.StartGameRule(ruleOnDeath.Rule); } } public IEnumerable> GetWizardTargetStations() { var query = EntityQueryEnumerator(); while (query.MoveNext(out var station, out _, out var data)) { yield return (station, data); } } public IEnumerable GetWizardTargetStationGrids() { return GetWizardTargetStations().Select(station => _station.GetLargestGrid(station.Comp)); } public EntityUid? GetWizardTargetRandomStationGrid() { var grids = GetWizardTargetStationGrids().Where(grid => grid != null).ToList(); return grids.Count == 0 ? null : _random.Pick(grids); } protected override void Started(EntityUid uid, WizardRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { var stations = GetWizardTargetStations().ToList(); if (stations.Count == 0) return; component.TargetStation = _random.Pick(stations); } private void OnWizardGetBriefing(Entity ent, ref GetBriefingEvent args) { args.Append(Loc.GetString("wizard-role-briefing")); } private void OnApprenticeGetBriefing(Entity ent, ref GetBriefingEvent args) { args.Append(Loc.GetString("apprentice-role-briefing")); } private void OnAfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { MakeWizard(args.EntityUid, ent.Comp); } public bool MakeWizard(EntityUid target, WizardRuleComponent rule) { var station = (rule.TargetStation is not null) ? Name(rule.TargetStation.Value) : "the station"; _antag.SendBriefing(target, Loc.GetString("wizard-role-greeting", ("station", station)), Color.Cyan, null); if (!TryComp(target, out HumanoidAppearanceComponent? humanoid) || humanoid.Age >= 60) return true; // Wizards are old humanoid.Age = _random.Next(60, 121); Dirty(target, humanoid); return true; } }