Terrain/TerrainScenery.cs
using HC3.Persistence;
using System;

namespace HC3.Terrain;

#nullable enable

public class SceneryGenerationOptions
{
	[Range( 0f, 1f )]
	public float ClusterDensity { get; set; } = 0f;

	[Range( 0f, 1f )]
	public float ClusterScale { get; set; }

	public RangedFloat AltitudeRange { get; set; } = new( 10f, 20f );

	public Curve AltitudeCurve { get; set; } = new Curve( new Curve.Frame( 0f, 1f ), new Curve.Frame( 1f, 1f ) );

	[Property]
	public int MaxGradient { get; set; }
}

public sealed class TerrainScenery : Component, ISceneMetadata, IPrefabSourceComponent<Vector4>
{
	[RequireComponent] public GridObject? GridObject { get; private set; }

	[Property] public Vector2Int Size { get; set; } = 1;

	[Property] public int DestructionCost { get; set; } = 2_500;

	[Property]
	public SceneryGenerationOptions GenerationOptions { get; set; } = new();

	/// <summary>
	/// Hack so we don't forget which prefab this came from when it spawns.
	/// </summary>
	public string? PrefabSource { get; private set; }

	Dictionary<string, string> ISceneMetadata.GetMetadata()
	{
		return new() {
			{ "Type", "Scenery" },
			{ nameof(GenerationOptions), Json.Serialize( GenerationOptions ) }
		};
	}

	protected override void OnValidate()
	{
		GridObject ??= GetOrAddComponent<GridObject>();
		GridObject.LocalBounds = new RectInt( -Size / 2, Size );
		GridObject.GridFlags = GridObjectFlags.BlocksConstruction;
	}

	protected override void OnAwake()
	{
		if ( GameObject.IsPrefabInstanceRoot )
		{
			PrefabSource = GameObject.PrefabInstanceSource;
		}
	}

	protected override void DrawGizmos()
	{
		if ( !Gizmo.IsSelected ) return;

		var min = -Size / 2f;
		var max = min + Size;

		Gizmo.Draw.Color = Color.Red;
		Gizmo.Draw.LineBBox( new BBox( min * GridManager.GridSize, max * GridManager.GridSize ) );

		Gizmo.Draw.Color = Color.Red.WithAlpha( 0.25f );
		Gizmo.Draw.SolidBox( new BBox( min * GridManager.GridSize, max * GridManager.GridSize ) );
	}

	public static IEnumerable<(GameObject Prefab, SceneryGenerationOptions Options)> AllPrefabs
	{
		get
		{
			return ResourceLibrary.GetAll<PrefabFile>()
				.Where( x => x.GetMetadata( "Type", "" ).Equals( "Scenery" ) )
				.ToList()
				.Select( x => (GameObject.GetPrefab( x.ResourcePath ),
					Json.Deserialize<SceneryGenerationOptions>( x.GetMetadata( nameof( GenerationOptions ), "{}" ) )) )
				.Where( x => x.Item1 is not null );
		}
	}

	Vector4 IPrefabSourceComponent<Vector4>.GetSaveData() =>
		new( GridManager.Instance.Terrain.WorldToGrid( WorldPosition ), WorldRotation.Yaw() );

	void IPrefabSourceComponent<Vector4>.Spawn( Vector4 saveData )
	{
		var gridManager = GridManager.Instance;

		WorldPosition = gridManager.Terrain.GridToWorld( saveData );
		WorldRotation = Rotation.FromYaw( saveData.w );

		if ( !Scene.IsEditor )
		{
			GameObject.NetworkSpawn();
		}
		else
		{
			GameObject.Flags |= GameObjectFlags.NotSaved;
		}
	}
}

public sealed class RandomizeTransform : Component, Component.ExecuteInEditor
{
	[Property]
	public Curve Scale { get; set; } = 1f;

	protected override void OnEnabled()
	{
		GameObject.WorldRotation = Rotation.FromYaw( Random.Shared.NextSingle() * 360f );
		GameObject.WorldScale = Scale.Evaluate( Random.Shared.NextSingle() );
	}
}

file sealed class ScenerySaveData : SpawnedPrefabSaveData<TerrainScenery, Vector4>
{
	public override string PropertyName => "Scenery";
}