public override void Update(float frameTime)
        {
            _timeElapsed += frameTime;

            if (_pulsesRemaining == 0)
            {
                Running = false;
            }

            if (!Running)
            {
                return;
            }

            if (_timeElapsed < StartupTime)
            {
                return;
            }

            _timeUntilPulse -= frameTime;

            if (_timeUntilPulse <= 0.0f)
            {
                // TODO: Probably rate-limit this for small grids (e.g. no more than 25% covered)
                foreach (var grid in _mapManager.GetAllGrids())
                {
                    if (grid.IsDefaultGrid)
                    {
                        continue;
                    }
                    SpawnPulse(grid);
                }
            }
        }
        public override void Update(float frameTime)
        {
            _internalTimer += frameTime;
            var gridsWithGravity = new List <GridId>();

            foreach (var generator in ComponentManager.EntityQuery <GravityGeneratorComponent>())
            {
                if (generator.NeedsUpdate)
                {
                    generator.UpdateState();
                }

                if (generator.Status == GravityGeneratorStatus.On)
                {
                    gridsWithGravity.Add(generator.Owner.Transform.GridID);
                }
            }

            foreach (var grid in _mapManager.GetAllGrids())
            {
                if (grid.HasGravity && !gridsWithGravity.Contains(grid.Index))
                {
                    grid.HasGravity = false;
                    ScheduleGridToShake(grid.Index, ShakeTimes);
                }
                else if (!grid.HasGravity && gridsWithGravity.Contains(grid.Index))
                {
                    grid.HasGravity = true;
                    ScheduleGridToShake(grid.Index, ShakeTimes);
                }
            }

            if (_internalTimer > 0.2f)
            {
                ShakeGrids();
                _internalTimer = 0.0f;
            }
        }
        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 <GameTicker>().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 <PhysicsComponent>(meteor.Uid);
                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.
                meteor.GetComponent <ProjectileComponent>().TimeLeft = 120f;
            }
        }