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.
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;
}
}