using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; using Content.Server.Station; using Content.Shared.Database; using Content.Shared.Station; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Player; using Robust.Shared.Random; namespace Content.Server.StationEvents.Events { public abstract class StationEvent { public const float WeightVeryLow = 0.0f; public const float WeightLow = 5.0f; public const float WeightNormal = 10.0f; public const float WeightHigh = 15.0f; public const float WeightVeryHigh = 20.0f; /// /// If the event has started and is currently running. /// public bool Running { get; set; } /// /// Human-readable name for the event. /// public abstract string Name { get; } /// /// The weight this event has in the random-selection process. /// public virtual float Weight => WeightNormal; /// /// What should be said in chat when the event starts (if anything). /// public virtual string? StartAnnouncement { get; set; } = null; /// /// What should be said in chat when the event ends (if anything). /// protected virtual string? EndAnnouncement { get; } = null; /// /// Starting audio of the event. /// public virtual string? StartAudio { get; set; } = null; /// /// Ending audio of the event. /// public virtual string? EndAudio { get; } = null; public virtual AudioParams AudioParams { get; } = AudioParams.Default.WithVolume(-10f); /// /// In minutes, when is the first round time this event can start /// public virtual int EarliestStart { get; } = 5; /// /// When in the lifetime to call Start(). /// protected virtual float StartAfter { get; } = 0.0f; /// /// When in the lifetime the event should end. /// protected virtual float EndAfter { get; set; } = 0.0f; /// /// How long has the event existed. Do not change this. /// private float Elapsed { get; set; } = 0.0f; /// /// How many players need to be present on station for the event to run /// /// /// To avoid running deadly events with low-pop /// public virtual int MinimumPlayers { get; } = 0; /// /// How many times this event has run this round /// public int Occurrences { get; set; } = 0; /// /// How many times this even can occur in a single round /// public virtual int? MaxOccurrences { get; } = null; /// /// Has the startup time elapsed? /// protected bool Started { get; set; } = false; /// /// Has this event commenced (announcement may or may not be used)? /// private bool Announced { get; set; } = false; /// /// Called once to setup the event after StartAfter has elapsed. /// public virtual void Startup() { Started = true; Occurrences += 1; EntitySystem.Get() .Add(LogType.EventStarted, LogImpact.High, $"Event startup: {Name}"); } /// /// Called once as soon as an event is active. /// Can also be used for some initial setup. /// public virtual void Announce() { EntitySystem.Get() .Add(LogType.EventAnnounced, $"Event announce: {Name}"); if (StartAnnouncement != null) { var chatManager = IoCManager.Resolve(); chatManager.DispatchStationAnnouncement(StartAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); } if (StartAudio != null) { SoundSystem.Play(Filter.Broadcast(), StartAudio, AudioParams); } Announced = true; Running = true; } /// /// Called once when the station event ends for any reason. /// public virtual void Shutdown() { EntitySystem.Get() .Add(LogType.EventStopped, $"Event shutdown: {Name}"); if (EndAnnouncement != null) { var chatManager = IoCManager.Resolve(); chatManager.DispatchStationAnnouncement(EndAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); } if (EndAudio != null) { SoundSystem.Play(Filter.Broadcast(), EndAudio, AudioParams); } Started = false; Announced = false; Elapsed = 0; } /// /// Called every tick when this event is running. /// /// public virtual void Update(float frameTime) { Elapsed += frameTime; if (!Started && Elapsed >= StartAfter) { Startup(); } if (EndAfter <= Elapsed) { Running = false; } } public static bool TryFindRandomTile(out Vector2i tile, out StationId targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords, IRobustRandom? robustRandom = null, IEntityManager? entityManager = null) { tile = default; robustRandom ??= IoCManager.Resolve(); entityManager ??= IoCManager.Resolve(); targetCoords = EntityCoordinates.Invalid; targetStation = robustRandom.Pick(entityManager.EntityQuery().ToArray()).Station; var t = targetStation; // thanks C# var possibleTargets = entityManager.EntityQuery() .Where(x => x.Station == t).ToArray(); targetGrid = robustRandom.Pick(possibleTargets).Owner; if (!entityManager.TryGetComponent(targetGrid!, out var gridComp)) return false; var grid = gridComp.Grid; var atmosphereSystem = EntitySystem.Get(); var found = false; var gridBounds = grid.WorldBounds; var gridPos = grid.WorldPosition; 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 - (int) gridPos.X, randomY - (int) gridPos.Y); if (atmosphereSystem.IsTileSpace(grid, tile) || atmosphereSystem.IsTileAirBlocked(grid, tile)) continue; found = true; targetCoords = grid.GridTileToLocal(tile); break; } if (!found) return false; return true; } } }