Park/StartingArea.cs
using System;
using HC3.Terrain;

namespace HC3;

/// <summary>
/// Defines a starting area for the park -- including park entrance, and paths along the entranceway.
/// </summary>
class StartingArea : Component, Component.ExecuteInEditor, ITerrainEvent
{
	[Property] public GameObject ParkEntrance { get; set; }
	[Property] public GameObject Fence { get; set; }

	public int AreaSize { get; set; } = 64;

	void ITerrainEvent.Generated( ParkTerrain terrain, int seed )
	{
		// Only the host can spawn the entrance path
		if ( !Networking.IsHost )
			return;

		var entrancePos = SpawnEntrance( terrain );
		SpawnPath( terrain, entrancePos, seed );
	}

	void ITerrainEvent.Loaded( ParkTerrain terrain )
	{
		SpawnEntrance( terrain );
	}

	private Vector2Int SpawnEntrance( ParkTerrain terrain )
	{
		// Kill any old procedural
		foreach ( var obj in GameObject.Children.Where( x => (x.Flags & GameObjectFlags.NotSaved) == GameObjectFlags.NotSaved ).ToArray() )
		{
			obj.DestroyImmediate();
		}

		var entranceGridPos = new Vector2Int( terrain.Bounds.Left + 16, 0 );
		var entranceHeight = terrain[entranceGridPos].MaxHeight;

		// Spawn park entrance
		var entrance = ParkEntrance.Clone( new CloneConfig() { Name = "Park Entrance", Parent = GameObject } );
		entrance.WorldPosition = terrain.GridToWorld( new Vector3( entranceGridPos + 0.5f, entranceHeight ) );
		entrance.Flags |= GameObjectFlags.NotSaved;
		entrance.Enabled = true;

		terrain.SetTiles( new RectInt( entranceGridPos - new Vector2Int( 1, 3 ), new Vector2Int( 3, 7 ) ), entranceHeight );

		ITerrainEvent.Post( x => x.EntranceMoved( new Vector3Int( entranceGridPos.x, entranceGridPos.y, entranceHeight ) ) );

		return entranceGridPos;
	}

	private void SpawnPath( ParkTerrain terrain, Vector2Int entranceGridPos, int seed )
	{
		// Spawn entrance path
		var random = new Random( seed );

		// Clear all existing paths
		PathBuilder.Instance?.Clear();

		SpawnPath( terrain, PathBuilder.Instance, entranceGridPos, terrain.Bounds.Position.x - entranceGridPos.x - 1, random );
		SpawnPath( terrain, PathBuilder.Instance, entranceGridPos, random.Next( 6, 12 ), random );
	}

	private void ClearCell( Vector2Int pos )
	{
		var scenery = GridManager.Instance?
			.GetCell( pos )?
			.GetComponents<TerrainScenery>()
			.ToArray();

		if ( scenery is null ) return;

		foreach ( var terrainScenery in scenery )
		{
			terrainScenery.GameObject.DestroyImmediate();
		}
	}

	private void SpawnPath( ParkTerrain terrain, PathBuilder pathBuilder, Vector2Int start, int xLength, Random random )
	{
		var height = terrain[start].MaxHeight;
		var pathY = start.y;
		var sinceTurn = 0;

		for ( var i = 0; i < Math.Abs( xLength ); ++i )
		{
			var pos = new Vector2Int( start.x + i * Math.Sign( xLength ), pathY );

			terrain[pos] = TerrainTile.LevelGround( height );

			ClearCell( pos );
			pathBuilder.AddPath( pos );

			if ( sinceTurn > 3 && random.NextSingle() < 0.25f )
			{
				var length = random.Next( 1, 5 );
				var sign = random.Next( 0, 2 ) * 2 - 1;

				for ( var j = 0; j < length; ++j )
				{
					pathY += sign;
					pos = pos with { y = pathY };

					ClearCell( pos );
					terrain[pos] = TerrainTile.LevelGround( height );
					pathBuilder.AddPath( pos );
				}

				sinceTurn = 0;
			}
			else
			{
				sinceTurn++;
			}
		}
	}
}