using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Shared.Database; using Content.Shared.Sound; using Robust.Shared.Audio; using Robust.Shared.Map; 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; } /// /// The time when this event last ran. /// public TimeSpan LastRun { get; set; } = TimeSpan.Zero; /// /// 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 SoundSpecifier? StartAudio { get; set; } = new SoundPathSpecifier("/Audio/Announcements/attention.ogg"); /// /// Ending audio of the event. /// public virtual SoundSpecifier? 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; /// /// In minutes, the amount of time before the same event can occur again /// public virtual int ReoccurrenceDelay { get; } = 30; /// /// 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; LastRun = EntitySystem.Get().RoundDuration(); IoCManager.Resolve() .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() { IoCManager.Resolve() .Add(LogType.EventAnnounced, $"Event announce: {Name}"); if (StartAnnouncement != null) { var chatSystem = IoCManager.Resolve().GetEntitySystem(); chatSystem.DispatchGlobalStationAnnouncement(StartAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); } if (StartAudio != null) { SoundSystem.Play(Filter.Broadcast(), StartAudio.GetSound(), AudioParams); } Announced = true; Running = true; } /// /// Called once when the station event ends for any reason. /// public virtual void Shutdown() { IoCManager.Resolve() .Add(LogType.EventStopped, $"Event shutdown: {Name}"); if (EndAnnouncement != null) { var chatSystem = IoCManager.Resolve().GetEntitySystem(); chatSystem.DispatchGlobalStationAnnouncement(EndAnnouncement, playDefaultSound: false, colorOverride: Color.Gold); } if (EndAudio != null) { SoundSystem.Play(Filter.Broadcast(), EndAudio.GetSound(), 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 EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords, IRobustRandom? robustRandom = null, IEntityManager? entityManager = null, IMapManager? mapManager = null, StationSystem? stationSystem = null) { tile = default; IoCManager.Resolve(ref robustRandom, ref entityManager, ref mapManager); entityManager.EntitySysManager.Resolve(ref stationSystem); targetCoords = EntityCoordinates.Invalid; targetStation = robustRandom.Pick(stationSystem.Stations); var possibleTargets = entityManager.GetComponent(targetStation).Grids; if (possibleTargets.Count == 0) { targetGrid = EntityUid.Invalid; return false; } targetGrid = robustRandom.Pick(possibleTargets); if (!entityManager.TryGetComponent(targetGrid, out var gridComp)) return false; var grid = gridComp.Grid; var atmosphereSystem = EntitySystem.Get(); var found = false; var gridBounds = grid.WorldAABB; 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; } } }