Park/Areas/AreaTile.cs
using System;
using HC3.Persistence;
using HC3.Terrain;
using System.Collections.Immutable;
namespace HC3;
public partial class AreaTile : Component, Component.ExecuteInEditor, IGridObjectEvent, IPathConnector
{
public sealed record SaveData( Vector3Int Position, AreaType Type, PathMask Mask );
public SaveData GetSaveData()
{
return new( TilePosition, AreaType, Mask );
}
[RequireComponent]
public GridObject GridObject { get; private set; }
public bool GhostPreview { get; set; }
[Property, Hide] public PathMask Mask { get; private set; }
[Property, Hide] public GameObject Tile { get; private set; }
[Property] public AreaType AreaType { get; set; }
public virtual int Hash => HashCode.Combine( AreaType, Mask );
private int prevHash;
public Vector3Int TilePosition
{
get
{
var pos2d = GridManager.WorldToGridPosition( WorldPosition );
return new Vector3Int( pos2d.x, pos2d.y, (int)MathF.Round( WorldPosition.z / GridManager.HeightStep ) );
}
}
protected virtual void UpdateConnections()
{
}
protected override void OnStart()
{
// Add a collider to the tile so it can be clicked
var collider = GetOrAddComponent<BoxCollider>();
collider.Center = new Vector3( 0f, 0f, GridManager.HeightStep / 2f );
collider.Scale = new Vector3( GridManager.GridSize, GridManager.GridSize, GridManager.HeightStep );
collider.IsTrigger = true;
}
void IGridObjectEvent.NeighborsChanged( GridCell cell )
{
UpdateConnections();
UpdateTile();
}
protected override void OnEnabled()
{
UpdateConnections();
UpdateTile();
}
private void UpdateMask()
{
var terrain = GridManager.Instance?.Terrain;
var pos = TilePosition;
var pos2d = new Vector2Int( pos.x, pos.y );
Mask = 0;
// Only check connections for the same AreaType
foreach ( var edge in GridManager.AllEdges )
{
var neighborPos = pos2d + edge.GetDirection();
var cell = GridManager.Instance?.GetCell( neighborPos );
if ( cell == null )
continue;
// Check if there's a matching AreaTile at this position
var neighborArea = cell.GetComponents<AreaTile>( pos.z )
.FirstOrDefault( x => !x.GhostPreview && x.AreaType == this.AreaType );
if ( neighborArea != null )
{
Mask |= edge.ToPathMask();
}
}
}
protected void UpdateTile()
{
var tilemap = AreaBuilder.Instance.EnclosureTileMap;
UpdateMask();
if ( !GhostPreview && prevHash == Hash )
return;
prevHash = Hash;
Tile?.DestroyImmediate();
var (tilePrefab, direction) = tilemap.GetTile( Mask, Vector2Int.Zero, false );
if ( tilePrefab is null ) return;
Tile = tilePrefab.Clone( new CloneConfig { Parent = GameObject, StartEnabled = true } );
Tile.LocalRotation = Rotation.LookAt( direction );
Tile.Flags |= GameObjectFlags.NotSaved;
if ( GhostPreview )
{
var pos = TilePosition;
var pos2d = new Vector2Int( pos.x, pos.y );
foreach ( var renderer in Tile.GetComponentsInChildren<ModelRenderer>() )
{
renderer.SceneObject.Attributes.Set( "Ghost", AreaBuilder.Instance.CanPlace( pos2d ) ? 1 : 2 );
renderer.SceneObject.Batchable = false;
}
}
OnTileUpdated();
}
protected virtual void OnTileUpdated() { }
public virtual bool CanConnectTile( Vector3Int gridPos, TileEdge edge )
{
if ( GhostPreview )
return false;
var targetPos = gridPos + edge.GetDirection();
var cell = GridManager.Instance?.GetCell( new Vector2Int( targetPos.x, targetPos.y ) );
if ( cell == null )
return false;
// Only connect to same AreaType at same height
return cell.GetComponents<AreaTile>( targetPos.z )
.Any( x => !x.GhostPreview && x.AreaType == this.AreaType );
}
protected override void OnUpdate()
{
if ( DebugMode.Enabled )
{
DebugOverlay.Text( WorldPosition + Vector3.Up * 8f, Mask.ToString(), 10f );
}
}
}
file sealed class AreaTileSaveData : ISaveDataProperty<ImmutableArray<AreaTile.SaveData>>
{
string ISaveDataProperty.PropertyName => "Areas";
ImmutableArray<AreaTile.SaveData> ISaveDataProperty<ImmutableArray<AreaTile.SaveData>>.WriteValue( Scene scene ) =>
scene.GetAllComponents<AreaTile>()
.Select( x => x.GetSaveData() )
.ToImmutableArray();
void ISaveDataProperty<ImmutableArray<AreaTile.SaveData>>.ReadValue( Scene scene, ImmutableArray<AreaTile.SaveData> model )
{
var builder = AreaBuilder.Instance;
builder.Clear();
Dictionary<AreaTile, AreaTile.SaveData> paths = new();
foreach ( var data in model )
{
var path = builder.AddArea( new( data.Position.x, data.Position.y ), data.Type );
if ( path is null ) continue;
paths.Add( path, data );
}
}
}