using System; using System.Linq; using Content.Server.GameTicking; using Content.Server.Projectiles.Components; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server.StationEvents.Events { public sealed class MeteorSwarm : StationEvent { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; public override string Name => "MeteorSwarm"; public override int EarliestStart => 30; public override float Weight => WeightLow; public override int? MaxOccurrences => 2; public override int MinimumPlayers => 20; public override string StartAnnouncement => Loc.GetString("station-event-meteor-swarm-start-announcement"); protected override string EndAnnouncement => Loc.GetString("station-event-meteor-swarm-ebd-announcement"); public override string? StartAudio => "/Audio/Announcements/bloblarm.ogg"; protected override float StartAfter => 30f; protected override float EndAfter => float.MaxValue; private float _cooldown; /// /// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer. /// private int _waveCounter; private const int MinimumWaves = 3; private const int MaximumWaves = 8; private const float MinimumCooldown = 10f; private const float MaximumCooldown = 60f; private const int MeteorsPerWave = 5; private const float MeteorVelocity = 10f; private const float MaxAngularVelocity = 0.25f; private const float MinAngularVelocity = -0.25f; public override void Startup() { base.Startup(); var robustRandom = IoCManager.Resolve(); _waveCounter = robustRandom.Next(MinimumWaves, MaximumWaves); } public override void Shutdown() { base.Shutdown(); _waveCounter = 0; _cooldown = 0f; EndAfter = float.MaxValue; } public override void Update(float frameTime) { base.Update(frameTime); if (!Started) return; if (_waveCounter <= 0) { Running = false; return; } _cooldown -= frameTime; if (_cooldown > 0f) return; _waveCounter--; _cooldown += (MaximumCooldown - MinimumCooldown) * _robustRandom.NextFloat() + MinimumCooldown; Box2? playableArea = null; var mapId = EntitySystem.Get().DefaultMap; foreach (var grid in _mapManager.GetAllGrids()) { if (grid.ParentMapId != mapId || !_entityManager.TryGetComponent(grid.GridEntityId, out PhysicsComponent? gridBody)) continue; var aabb = gridBody.GetWorldAABB(); playableArea = playableArea?.Union(aabb) ?? aabb; } if (playableArea == null) { EndAfter = float.MinValue; return; } var minimumDistance = (playableArea.Value.TopRight - playableArea.Value.Center).Length + 50f; var maximumDistance = minimumDistance + 100f; var center = playableArea.Value.Center; for (var i = 0; i < MeteorsPerWave; i++) { var angle = new Angle(_robustRandom.NextFloat() * MathF.Tau); var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * _robustRandom.NextFloat() + minimumDistance, 0)); var spawnPosition = new MapCoordinates(center + offset, mapId); var meteor = _entityManager.SpawnEntity("MeteorLarge", spawnPosition); var physics = _entityManager.GetComponent(meteor); physics.BodyStatus = BodyStatus.InAir; physics.LinearDamping = 0f; physics.AngularDamping = 0f; physics.ApplyLinearImpulse(-offset.Normalized * MeteorVelocity * physics.Mass); physics.ApplyAngularImpulse( // Get a random angular velocity. physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * _robustRandom.NextFloat() + MinAngularVelocity)); // TODO: God this disgusts me but projectile needs a refactor. IoCManager.Resolve().GetComponent(meteor).TimeLeft = 120f; } } } }