using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Systems; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Shared.Database; using Content.Shared.Physics; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.Collections; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server._White.AspectsSystem.Base { /// /// Base class for aspect systems. /// /// The type of component to which the system is applied. public abstract class AspectSystem : GameRuleSystem where T : Component { [Dependency] private readonly IAdminLogManager _adminLogManager = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly AtmosphereSystem _atmosphere = default!; [Dependency] private readonly TransformSystem _transform = default!; protected ISawmill Sawmill = default!; public override void Initialize() { base.Initialize(); Sawmill = Logger.GetSawmill("aspects"); } /// /// Called every tick when this aspect is running. /// public override void Update(float frameTime) { base.Update(frameTime); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var aspect, out var ruleData)) { if (!GameTicker.IsGameRuleAdded(uid, ruleData)) continue; if (!GameTicker.IsGameRuleActive(uid, ruleData) && _timing.CurTime >= aspect.StartTime) { GameTicker.StartGameRule(uid, ruleData); } } } /// /// Called when an aspect is added to an entity. /// protected override void Added(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleAddedEvent args) { base.Added(uid, component, gameRule, args); if (!TryComp(uid, out var aspect)) return; _adminLogManager.Add(LogType.AspectAnnounced, $"Aspect added {ToPrettyString(uid)}"); if (aspect is { Description: not null, IsHidden: false }) { _chatSystem.DispatchGlobalAnnouncement(aspect.Description, playSound: false, colorOverride: Color.Aquamarine); } _audio.PlayGlobal(aspect.StartAudio, Filter.Broadcast(), true); aspect.StartTime = _timing.CurTime + aspect.StartDelay; } /// /// Called when an aspect is started. /// protected override void Started( EntityUid uid, T component, GameRuleComponent gameRule, GameRuleStartedEvent args) { base.Started(uid, component, gameRule, args); if (!TryComp(uid, out _)) return; _adminLogManager.Add(LogType.AspectStarted, LogImpact.High, $"Aspect started: {ToPrettyString(uid)}"); } /// /// Called when an aspect is ended. /// protected override void Ended(EntityUid uid, T component, GameRuleComponent gameRule, GameRuleEndedEvent args) { base.Ended(uid, component, gameRule, args); if (!TryComp(uid, out var aspect)) return; _adminLogManager.Add(LogType.AspectStopped, $"Aspect ended: {ToPrettyString(uid)}"); if (aspect is { Name: not null, IsHidden: false }) { _chatSystem.DispatchGlobalAnnouncement($"Именем аспекта являлось: {aspect.Name}", playSound: false, colorOverride: Color.Aquamarine); } _audio.PlayGlobal(aspect.EndAudio, Filter.Broadcast(), true); } #region Helpers /// /// Forces this aspect to end prematurely. /// /// The entity UID on which the aspect is being performed. /// The game rule component associated with this aspect (optional). protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null) { GameTicker.EndGameRule(uid, component); } protected bool TryGetRandomStation( [NotNullWhen(true)] out EntityUid? station, Func? filter = null) { var stations = new ValueList(); if (filter == null) { var stationCount = Count(); if (stationCount > 0) { stations.EnsureCapacity(stationCount); } } filter ??= _ => true; var query = AllEntityQuery(); while (query.MoveNext(out var uid, out _)) { if (!filter(uid)) continue; stations.Add(uid); } if (stations.Count == 0) { station = null; return false; } station = stations[_robustRandom.Next(stations.Count)]; return true; } protected bool TryGetStationGrids( [NotNullWhen(true)] out EntityUid? targetStation, [NotNullWhen(true)] out HashSet? grids) { if (!TryGetRandomStation(out targetStation)) { targetStation = EntityUid.Invalid; grids = null; return false; } grids = Comp(targetStation.Value).Grids; return grids.Count > 0; } protected bool TryFindRandomTile( out Vector2i tile, [NotNullWhen(true)] out EntityUid? targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords) { tile = default; targetCoords = EntityCoordinates.Invalid; if (!TryGetStationGrids(out targetStation, out var possibleTargets)) { targetGrid = EntityUid.Invalid; return false; } targetGrid = _robustRandom.Pick(possibleTargets); foreach (var target in possibleTargets.Where(HasComp)) { targetGrid = target; break; } if (!TryComp(targetGrid, out var gridComp)) return false; var found = false; var gridBounds = gridComp.LocalAABB.Scale(0.6f); for (var i = 0; i < 10; i++) { var randomX = _robustRandom.Next((int) gridBounds.Left, (int) gridBounds.Right); var randomY = _robustRandom.Next((int) gridBounds.Bottom, (int) gridBounds.Top); tile = new Vector2i(randomX, randomY); if (_atmosphere.IsTileSpace(targetGrid, Transform(targetGrid).MapUid, tile, mapGridComp: gridComp) || _atmosphere.IsTileAirBlocked(targetGrid, tile, mapGridComp: gridComp)) { continue; } var physQuery = GetEntityQuery(); var valid = true; foreach (var ent in gridComp.GetAnchoredEntities(tile)) { if (!physQuery.TryGetComponent(ent, out var body)) continue; if (body.BodyType != BodyType.Static || !body.Hard || (body.CollisionLayer & (int) CollisionGroup.Impassable) == 0) continue; valid = false; break; } if (!valid) continue; found = true; targetCoords = gridComp.GridTileToLocal(tile); break; } return found; } #endregion } }