Resources/SpawnDefinition.cs
using System;
using System.Collections.Generic;

namespace Sandbox.Spawns;

public enum ScaleMode
{
	Fixed, //use min value for static scale
	FitnessRandom,//use fitness value for dynamic scale and * by min max
	Random // multiply by min max
}

public enum SpawnObjectType
{
	GameObject,
	Clutter
}

[Serializable]
public class SpawnEntry
{
	
	[Hide] private bool IsClutter => ObjectType == SpawnObjectType .Clutter;
	
	[Property] public SpawnObjectType ObjectType { get; set; }
	[Property,  ShowIf( nameof( IsClutter ), false)] public GameObject Prefab       { get; set; }
	[Property,  ShowIf( nameof( IsClutter ), true)] public Model      ClutterModel { get; set; }
	[Property, Range( 0f, 10f )] public float Weight { get; set; } = 1f;

	public override string ToString() =>
		Prefab       != null ? Prefab.Name            :
		ClutterModel != null ? ClutterModel.ResourceName :
		"Empty";
}

[AssetType( Name = "SpawnDefinition", Extension = "sd", Category = "Apex World" )]
public class SpawnDefinition: GameResource
{
	[Hide] private bool ShowRandomScale => SpawnScale != ScaleMode.Fixed;
	

	[Property] public List<SpawnEntry>  Entries    { get; set; } = new();
	
	
	[Property, Range( -100f, 0f )] public float MinYOffset { get; set; } = 0f;

	[Property, Range(0f, 200f)]  public float MaxYOffset { get; set; } = 0f;
	
	[Property] public ScaleMode SpawnScale { get; set; }
	

	
	/// <summary>
	/// add a percentage to existing scale
	/// </summary>
	[Property, Range(0f, 100f), ShowIf( nameof( ShowRandomScale ), true )]  public float WidthRandomPercent { get; set; } = 0;
	
	[Property, Range( 0f, 10f ), ShowIf( nameof( ShowRandomScale ), true )] public float WidthMinScale { get; set; } = 1f;

	[Property, Range(0f, 10f)]  public float WidthMaxScale { get; set; } = 1f;
	
	/// <summary>
	///add a percentage to existing scale
	/// </summary>
	[Property, Range(0f, 100f), ShowIf( nameof( ShowRandomScale ), true )]  public float HeightRandomPercent { get; set; } = 0;
	
	[Property, Range( 0f, 10f), ShowIf( nameof( ShowRandomScale ), true )] public float HeightMinScale { get; set; } = 1f;

	[Property, Range(0f, 10f)]  public float HeightMaxScale { get; set; } = 1f;
	
	
	/// <summary>
	/// Rotates the object so its up vector matches terrain normal.
	/// Useful for rocks and props on uneven terrain.
	/// </summary>
	[Property]
	public bool AlignToSlope { get; set; }

	/// <summary>
	/// Rotates the forward direction toward the terrain slope direction.
	/// Useful for fallen logs, directional props, etc.
	/// </summary>
	[Property]
	public bool ForwardToSlope { get; set; }

	/// <summary>
	/// Minimum random rotation offset applied after alignment.
	/// </summary>
	[Property]
	public Angles MinRotationOffset { get; set; } = Angles.Zero;

	/// <summary>
	/// Maximum random rotation offset applied after alignment.
	/// </summary>
	[Property]
	public Angles MaxRotationOffset { get; set; } = Angles.Zero;
	
	public SpawnEntry PickEntry( Random rng )
	{
		if ( Entries == null || Entries.Count == 0 ) return null;
		if ( Entries.Count == 1 ) return Entries[0];

		float total = 0f;
		foreach ( var e in Entries ) total += e.Weight;
		if ( total <= 0f ) return Entries[0];

		float roll = (float)rng.NextDouble() * total;
		float acc  = 0f;
		foreach ( var e in Entries )
		{
			acc += e.Weight;
			if ( roll <= acc ) return e;
		}
		return Entries[^1];
	}

}