Park/Events/EventManager.cs
using System;
using HC3;
using System.Text.Json.Serialization;
public sealed class EventManager : Component
{
public static EventManager Instance { get; private set; }
/// <summary>
/// The probability of an event happening. This is a value between 0 and 1.
/// </summary>
[Property, Range( 0, 1 )] float EventProbability { get; set; } = 0.1f;
/// <summary>
/// The time it takes for the event to happen.
/// </summary>
[Property] RangedFloat EventTime { get; set; } = new( 0.0f, 10.0f );
[Property, ReadOnly, JsonIgnore]
private float nextEventDelay;
[Property, ReadOnly, JsonIgnore]
private TimeSince timeSinceLastEvent;
[Property, ReadOnly, JsonIgnore]
public List<GameObject> ActiveEvents { get; set; } = new();
public static IEnumerable<GameObject> AllPrefabs
{
get
{
return ResourceLibrary.GetAll<PrefabFile>()
.Where( x => x.GetMetadata( "Type", "" ).Equals( "Events" ) )
.ToList()
.Select( x => GameObject.GetPrefab( x.ResourcePath ) );
}
}
protected override void OnAwake()
{
Instance = this;
}
protected override void OnStart()
{
timeSinceLastEvent = 0;
SetNextEventDelay();
}
protected override void OnUpdate()
{
// Don't trigger events if we aren't the host.
if ( !Networking.IsHost )
return;
if ( timeSinceLastEvent < nextEventDelay )
return;
timeSinceLastEvent = 0;
SetNextEventDelay();
if ( ShouldEventHappen() )
{
TriggerRandomEvent();
}
}
private void SetNextEventDelay()
{
nextEventDelay = Random.Shared.Float( EventTime.Min, EventTime.Max );
}
private bool ShouldEventHappen()
{
var randomValue = Random.Shared.Float( 0.0f, 1.0f );
return randomValue <= EventProbability;
}
private void TriggerRandomEvent()
{
var validEvents = AllPrefabs
.Select( prefab => new
{
Prefab = prefab,
EventComponent = prefab.GetComponent<BaseEvent>()
} )
.Where( x => x.Prefab.IsValid() && x.EventComponent.IsValid() )
// Only keep events that aren't already active (match by Title)
.Where( x => !ActiveEvents.Any( active =>
{
var activeEvent = active.GetComponent<BaseEvent>();
return activeEvent != null && activeEvent.Title == x.EventComponent.Title;
} ) )
.ToList();
if ( validEvents.Count == 0 )
{
return;
}
// Calculate total weight from all event probabilities
float totalWeight = validEvents.Sum( x => x.EventComponent.Probability );
if ( totalWeight <= 0 )
{
Log.Warning( "[EventManager] All events have 0 probability." );
return;
}
// Pick a random event based on weighted probabilities
float randomValue = Random.Shared.Float( 0, totalWeight );
float cumulative = 0;
foreach ( var entry in validEvents )
{
cumulative += entry.EventComponent.Probability;
if ( randomValue <= cumulative )
{
var newEvent = entry.Prefab.Clone();
newEvent.Parent = GameObject;
var instance = newEvent.GetComponent<BaseEvent>();
newEvent.NetworkSpawn();
NotificationPanel.Instance?.Broadcast( $"{instance.Title} has started!", "event" );
Log.Info( $"[EventManager] Triggered event: {instance.Title}" );
Stats.Increment( "event.triggered" );
Stats.Increment( $"event.triggered.{instance.Title.ToIdentifier()}" );
return;
}
}
Log.Error( "[EventManager] Failed to select an event by probability." );
}
}