using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Content.Server._Sunrise.Boss.Components; using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Chat.Systems; using Content.Server.Construction; using Content.Server.RoundEnd; using Content.Server.Shuttles.Systems; using Content.Server.Sunrise.Paws; using Content.Shared._Sunrise.Boss.Components; using Content.Shared._Sunrise.Boss.Systems; using Content.Shared.Body.Events; using Content.Shared.Construction; using Content.Shared.Damage.Components; using Content.Shared.Damage.ForceSay; using Content.Shared.GameTicking; using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Interaction.Components; using Content.Shared.Mech.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Silicons.Borgs.Components; using Content.Shared.SSDIndicator; using Content.Shared.Strip.Components; using Robust.Server.GameObjects; using Robust.Shared.Console; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Server._Sunrise.Boss.Systems; /// public sealed class HellSpawnArenaSystem : SharedHellSpawnArenaSystem { [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IConsoleHost _console = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly ILogManager _log = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly MapSystem _mapSystem = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; [Dependency] private readonly ShuttleSystem _shuttles = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; [Dependency] private readonly ConstructionSystem _construction = default!; [Dependency] private readonly SharedConstructionSystem _shConstruction = default!; private ISawmill _sawmill = default!; /// public override void Initialize() { base.Initialize(); _sawmill = _log.GetSawmill("arena"); SubscribeLocalEvent(OnRoundEnd); SubscribeLocalEvent(OnTravelButtonPressed); SubscribeLocalEvent(OnFighterMobStateChanged); SubscribeLocalEvent(OnCultistMobStateChanged); SubscribeLocalEvent(OnSpawnMobStateChanged); SubscribeLocalEvent(OnCultistShutdown); SubscribeLocalEvent(OnCultistGib); SubscribeLocalEvent(OnConsoleInit); } private void OnConsoleInit(EntityUid uid, HellSpawnConsoleComponent comp, ComponentInit args) { UpdateArenaUi(); } private async void OnFighterMobStateChanged(EntityUid uid, HellSpawnFighterComponent component, MobStateChangedEvent args) { if (args is not { OldMobState: MobState.Alive, NewMobState: MobState.Dead or MobState.Critical }) return; var flag = true; var query = EntityQuery(); foreach (var fighter in query) { if (!_mobState.IsAlive(fighter.Owner)) continue; flag = false; } if (flag) { TeleportFightersBack(); Status = HellSpawnBossStatus.Idle; UpdateArenaUi(); await Task.Delay(1000); if (ArenaMap != null) _mapManager.DeleteMap(ArenaMap.Value); } } private async void OnCultistMobStateChanged(EntityUid uid, HellSpawnCultistComponent component, MobStateChangedEvent args) { if (args is not { OldMobState: MobState.Alive, NewMobState: MobState.Dead or MobState.Critical }) return; if (!SpawnHellSpawn(out var entityUid)) return; Status = HellSpawnBossStatus.InProgress; UpdateArenaUi(); if (TryComp(entityUid, out var hellSpawnComponent)) hellSpawnComponent.ConsoleUid = component.ConsoleUid; QueueDel(uid); } public bool SpawnHellSpawn( [NotNullWhen(true)] out EntityUid? entityUid) { if (Arena == null) { entityUid = null; return false; } entityUid = Spawn("MobHellspawnBoss", new EntityCoordinates(Arena.Value, 0.5f, 0.5f)); var fighters = EntityQuery().ToList().Count; return true; } private async void OnCultistShutdown(EntityUid uid, HellSpawnCultistComponent component, ComponentShutdown args) { if (Status == HellSpawnBossStatus.InProgress) return; TeleportFightersBack(); await Task.Delay(1000); if (ArenaMap != null && _mapManager.MapExists(ArenaMap)) _mapManager.DeleteMap(ArenaMap.Value); } private async void OnCultistGib(EntityUid uid, HellSpawnCultistComponent component, BeingGibbedEvent args) { if (Status == HellSpawnBossStatus.InProgress) return; TeleportFightersBack(); await Task.Delay(1000); if (ArenaMap != null) _mapManager.DeleteMap(ArenaMap.Value); } private async void OnSpawnMobStateChanged(EntityUid uid, HellSpawnComponent component, MobStateChangedEvent args) { if (args is not { OldMobState: MobState.Alive, NewMobState: MobState.Dead or MobState.Critical }) return; TeleportFightersBack(); Status = HellSpawnBossStatus.Idle; UpdateArenaUi(); if (AllEntityQuery() .MoveNext(out var consoleUid, out _, out _) && consoleUid != null) // В идеальном случае в мире только один алтарь призыва _transform.SetCoordinates(uid, Transform(consoleUid).Coordinates); await Task.Delay(1000); if (ArenaMap != null) _mapManager.DeleteMap(ArenaMap.Value); } /// /// Телепортируем туда, откуда прилетели /// public void TeleportFightersBack() { var consoleQuery = AllEntityQuery(); if (!consoleQuery.MoveNext(out var consoleEntity, out _)) return; var cnsXform = Transform(consoleEntity); // Для тех, кто тепнулся по-нормальному { var query = EntityQuery(); foreach (var fighter in query) { if (fighter.TeleportedFromCoordinates == null) { if (consoleEntity != null) _transform.SetCoordinates(fighter.Owner, cnsXform.Coordinates); continue; } _transform.SetCoordinates(fighter.Owner, fighter.TeleportedFromCoordinates.Value); if (HasComp(fighter.Owner)) RemComp(fighter.Owner); } } // Для заглянувших на огонек гостов { var query = AllEntityQuery(); while (query.MoveNext(out var ghostEntity, out _)) { if (ArenaMap != null && _transform.GetMapCoordinates(ghostEntity).MapId == ArenaMap.Value) { _transform.SetCoordinates(ghostEntity, cnsXform.Coordinates); } } } // Для ААбуз заспавненных существ { var query = AllEntityQuery(); while (query.MoveNext(out var flammableEntity, out _)) { if (ArenaMap != null && _transform.GetMapCoordinates(flammableEntity).MapId == ArenaMap.Value) { _transform.SetCoordinates(flammableEntity, cnsXform.Coordinates); } } } MarkedTargets.Clear(); } /// /// Cleanup после того как раунд закончился /// private void OnRoundEnd(RoundRestartCleanupEvent args) { Status = HellSpawnBossStatus.Idle; ArenaMap = null; Arena = null; Shuttles = new List(); MarkedTargets = new HashSet(); } private void OnTravelButtonPressed(TravelButtonPressedMessage args) { if (Status == HellSpawnBossStatus.InProgress) return; var actorUid = args.Actor; var ownerUid = GetEntity(args.Owner); if (!TryComp(ownerUid, out var consoleComponent)) return; consoleComponent.ActivationTime = _timing.CurTime + CooldownLength; var ev = new HellSpawnArenaConsoleUiState { CurrentStatus = Status, ActivationTime = consoleComponent.ActivationTime, }; _ui.SetUiState(ownerUid, HellSpawnArenaUiKey.Key, ev); UpdateArenaUi(); // _sawmill.Debug($"OnTravelButtonPressed: owner: {ownerUid} actor: {actorUid}"); Cooldown(ownerUid); } /// /// Проходим по всем консолям и обновляем у них интерфейс. /// public void UpdateArenaUi() { var query = EntityQuery(); foreach (var cons in query) { var consoleEv = new HellSpawnArenaConsoleUiState { CurrentStatus = Status, ActivationTime = cons.ActivationTime, }; _ui.SetUiState(cons.Owner, HellSpawnArenaUiKey.Key, consoleEv); } } /// /// Запускаем кд на отправку парней по ту сторону. /// /// Консоль, которая хочет отправить туда public async void Cooldown(Entity console) { if (!Resolve(console.Owner, ref console.Comp)) return; if (Status == HellSpawnBossStatus.Idle) { var mapId = CreateMap(); if (mapId == null) return; _console.ExecuteCommand($"mapinit {mapId}"); Timer.Spawn(TimeSpan.FromMilliseconds(100), () => OnSpawnCultists()); Status = HellSpawnBossStatus.Cooldown; } await Task.Delay(CooldownLength); var xform = Transform(console); // Серия выборов целей для телепорта foreach (var ent in _lookup.GetEntitiesInRange(xform.Coordinates, 2f)) { MarkedTargets.Add(ent); } foreach (var ent in _lookup.GetEntitiesInRange(xform.Coordinates, 2f)) { MarkedTargets.Add(ent); } foreach (var ent in _lookup.GetEntitiesInRange(xform.Coordinates, 2f)) { MarkedTargets.Add(ent); } foreach (var ent in _lookup.GetEntitiesInRange(xform.Coordinates, 2f)) { MarkedTargets.Add(ent); } if (Arena == null) return; foreach (var markedTarget in MarkedTargets) { var fighterComponent = EnsureComp(markedTarget); var markedXform = Transform(markedTarget); fighterComponent.TeleportedFromCoordinates = markedXform.Coordinates; _transform.SetCoordinates(markedTarget, new EntityCoordinates(Arena.Value, 0.5f, -2.5f)); } } /// /// Заспавнить культистов(а). Сделано для упрощения изменения поведения босса. /// private async void OnSpawnCultists() { if (Arena == null) return; var uid = Spawn("HellSpawnCultist", new EntityCoordinates(Arena.Value, 0.5f, 0.5f)); ApplyProtection(uid); } /// /// Серия защит от дурачков, ломающих скрипт. /// /// Сущность, которую защищать public void ApplyProtection(EntityUid uid) { if (HasComp(uid)) RemComp(uid); if (HasComp(uid)) RemComp(uid); if (HasComp(uid)) RemComp(uid); if (HasComp(uid)) RemComp(uid); if (HasComp(uid)) RemComp(uid); if (HasComp(uid)) RemComp(uid); if (HasComp(uid)) RemComp(uid); // Не надо ебать культиста // При мердже на ласт раскомментировать // if (HasComp(uid)) // RemComp(uid); EnsureComp(uid); } public MapId? CreateMap() { var mapInt = 2000; while (_mapManager.MapExists(new MapId(mapInt))) { mapInt += 1; } MapId map = new(mapInt); _loader.TryLoadMapWithId(map, ShuttlePath, out var mapComponent, out var shuttleUids); if (shuttleUids is null) return null; var shuttleId = shuttleUids.FirstOrNull(); if (shuttleId == null) return null; var arenaComp = EnsureComp(shuttleId.Value.Owner); Arena = shuttleId; ArenaMap = map; return map; } }