MapEventManager.cs

Component that manages periodic map weather events. It starts random events (LowGravity, SlipperyIce, StrongWind) at intervals, updates their duration, and applies forces/velocity changes to CharacterController and Rigidbody components during FixedUpdate.

Native Interop
using Sandbox;
using System;
using System.Linq;

public enum WeatherType
{
    None,
    LowGravity,
    SlipperyIce,
    StrongWind
}

public sealed class MapEventManager : Component
{
    public static MapEventManager Instance { get; private set; }

    [Property] public float EventDuration { get; set; } = 12f;
    [Property] public float EventInterval { get; set; } = 25f;
    [Property] public float LowGravityForce { get; set; } = 450f;
    [Property] public float WindForce { get; set; } = 400f;

    public WeatherType CurrentEvent { get; private set; } = WeatherType.None;
    public Vector3 CurrentWindDirection { get; private set; } = Vector3.Right;
    public float TimeRemaining { get; private set; }

    private TimeUntil _nextEventTime;
    private TimeUntil _eventEndTime;

    protected override void OnAwake()
    {
        Instance = this;
        _nextEventTime = EventInterval;
    }

    protected override void OnUpdate()
    {
        if ( IsProxy ) return;

        if ( CurrentEvent == WeatherType.None && _nextEventTime )
        {
            StartRandomEvent();
        }
        else if ( CurrentEvent != WeatherType.None )
        {
            TimeRemaining = _eventEndTime;
            if ( _eventEndTime )
            {
                EndEvent();
            }
        }
    }

    protected override void OnFixedUpdate()
    {
        if ( CurrentEvent == WeatherType.None ) return;

        var players = Scene.GetAllComponents<CharacterController>();
        var rigidbodies = Scene.GetAllComponents<Rigidbody>();

        if ( CurrentEvent == WeatherType.LowGravity )
        {
            foreach ( var cc in players )
            {
                if ( cc.IsValid() )
                {
                    cc.Velocity += Vector3.Up * LowGravityForce * Time.Delta;
                }
            }
            foreach ( var rb in rigidbodies )
            {
                if ( rb.IsValid() && !rb.IsProxy )
                {
                    rb.ApplyImpulse( Vector3.Up * (rb.PhysicsBody.Mass * LowGravityForce * Time.Delta) );
                }
            }
        }
        else if ( CurrentEvent == WeatherType.StrongWind )
        {
            foreach ( var cc in players )
            {
                if ( cc.IsValid() )
                {
                    cc.Velocity += CurrentWindDirection * WindForce * Time.Delta;
                }
            }
            foreach ( var rb in rigidbodies )
            {
                if ( rb.IsValid() && !rb.IsProxy )
                {
                    rb.ApplyImpulse( CurrentWindDirection * WindForce * 15f * Time.Delta );
                }
            }
        }
        else if ( CurrentEvent == WeatherType.SlipperyIce )
        {
            foreach ( var cc in players )
            {
                if ( cc.IsValid() && cc.IsOnGround )
                {
                    var vel = cc.Velocity;
                    vel.x *= 1.015f;
                    vel.y *= 1.015f;
                    cc.Velocity = vel;
                }
            }
        }
    }

    private void StartRandomEvent()
    {
        var values = Enum.GetValues( typeof( WeatherType ) ).Cast<WeatherType>().Where( w => w != WeatherType.None ).ToArray();
        CurrentEvent = values[Random.Shared.Next( values.Length )];
        _eventEndTime = EventDuration;

        if ( CurrentEvent == WeatherType.StrongWind )
        {
            float angle = Random.Shared.NextSingle() * MathF.PI * 2f;
            CurrentWindDirection = new Vector3( MathF.Cos( angle ), MathF.Sin( angle ), 0f ).Normal;
        }

        Log.Info( $"[MapEventManager] Запущен погодный ивент: {CurrentEvent}" );
    }

    private void EndEvent()
    {
        Log.Info( $"[MapEventManager] Завершён ивент: {CurrentEvent}" );
        CurrentEvent = WeatherType.None;
        _nextEventTime = EventInterval;
    }
}