ArenaShrinker.cs

Component that manages arena shrinking over a round. It tracks round time, triggers a short warning period for outer tiles, highlights them, and then drops them to shrink the playable area.

using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;

public sealed class ArenaShrinker : Component
{
    [Property] public float RoundTime { get; set; } = 120f;
    [Property] public float ShrinkInterval { get; set; } = 15f;
    [Property] public float WarningDuration { get; set; } = 3f;

    public float TimeRemaining { get; private set; }

    private float nextShrinkTime;
    private bool isWarningActive = false;
    private List<ArenaTile> activeWarningTiles = new();

    protected override void OnStart()
    {
        StartRound();
    }

    public void StartRound()
    {
        TimeRemaining = RoundTime;
        nextShrinkTime = RoundTime - ShrinkInterval;
        isWarningActive = false;
        activeWarningTiles.Clear();

        var tiles = Scene.GetAllComponents<ArenaTile>();
        foreach ( var tile in tiles )
        {
            tile.ResetTile();
        }

        Log.Info( "[ArenaShrinker] Round started!" );
    }

    protected override void OnUpdate()
    {
        if ( TimeRemaining <= 0 )
        {
            if ( RoundManager.Instance != null && RoundManager.Instance.IsInRound )
            {
                _ = RoundManager.Instance.EndRound();
            }
            return;
        }

        TimeRemaining -= Time.Delta;

        if ( !isWarningActive && TimeRemaining <= (nextShrinkTime + WarningDuration) )
        {
            TriggerWarning();
        }

        if ( isWarningActive && TimeRemaining <= nextShrinkTime )
        {
            DropWarnedTiles();
        }
    }

    private void TriggerWarning()
    {
        isWarningActive = true;
        activeWarningTiles.Clear();

        var availableTiles = Scene.GetAllComponents<ArenaTile>()
            .Where( t => !t.IsFalling )
            .ToList();

        if ( availableTiles.Count <= 2 ) return;

        Vector3 center = Vector3.Zero;
        foreach ( var tile in availableTiles )
        {
            center += tile.WorldPosition;
        }
        center /= availableTiles.Count;

        var sortedTiles = availableTiles
            .OrderByDescending( t => Vector3.DistanceBetween( t.WorldPosition, center ) )
            .ToList();

        int tilesToDropCount = Math.Max( 1, (int)(sortedTiles.Count * 0.25f) );

        if ( sortedTiles.Count - tilesToDropCount < 2 )
        {
            tilesToDropCount = Math.Max( 0, sortedTiles.Count - 2 );
        }

        if ( tilesToDropCount <= 0 ) return;

        activeWarningTiles = sortedTiles.Take( tilesToDropCount ).ToList();

        foreach ( var tile in activeWarningTiles )
        {
            tile.HighlightWarning( true );
        }

        Log.Info( $"[ArenaShrinker] Warning started for {activeWarningTiles.Count} outer tiles!" );
    }

    private void DropWarnedTiles()
    {
        foreach ( var tile in activeWarningTiles )
        {
            tile.HighlightWarning( false );
            tile.Drop();
        }

        activeWarningTiles.Clear();
        isWarningActive = false;
        nextShrinkTime -= ShrinkInterval;

        Log.Info( "[ArenaShrinker] Outer ring dropped successfully!" );
    }
}