AI/Guests/Guest.Persistence.cs
using HC3.Persistence;
using System.Collections.Immutable;
using System.Text.Json.Nodes;

namespace HC3;

partial class Guest
{
	public sealed record SaveData( string? FullName, Vector3 Position, float Rotation, JsonObject Metadata = null );

	public SaveData GetSaveData()
	{
		return new( FullName, WorldPosition, WorldRotation.Yaw(), GetPersistentMetadata() );
	}

	public void SetSaveData( SaveData saveData )
	{
		FullName = saveData.FullName ?? FullName;

		WorldPosition = saveData.Position;
		WorldRotation = Rotation.FromYaw( saveData.Rotation );

		Transform.ClearInterpolation();

		SetPersistentMetadata( saveData.Metadata );
	}

	protected virtual JsonObject GetPersistentMetadata()
	{
		var obj = new JsonObject();
		obj["Needs"] = Needs.ToJson();
		obj["Identity"] = Identity.ToJson();
		obj["Delinquency"] = Delinquency;

		if ( Building.IsValid() )
		{
			obj["Building"] = Building.GetStateJson( this );
		}

		if ( Thoughts.IsValid() && Thoughts.GetAll().Any() )
		{
			obj["Thoughts"] = Thoughts.ToJson();
		}

		return obj;
	}

	protected virtual void SetPersistentMetadata( JsonObject obj )
	{
		if ( obj == null )
			return;

		if ( obj.TryGetPropertyValue( "Delinquency", out var delinquency ) )
		{
			Delinquency = delinquency.GetValue<float>();
		}

		if ( obj.TryGetPropertyValue( "Needs", out var needs ) )
		{
			Needs.LoadFromJson( needs );
		}

		if ( obj.TryGetPropertyValue( "Identity", out var identity ) )
		{
			Identity = Identity.FromJson( identity );
		}

		if ( obj.TryGetPropertyValue( "Building", out var state ) )
		{
			InitializeFromState( state );
		}

		if ( obj.TryGetPropertyValue( "Thoughts", out var thoughts ) )
		{
			Thoughts?.FromJson( thoughts );
		}
	}


	/// <summary>
	/// Initializes a guest from a saved building state - right now we just load them into the building they were on.
	/// In the future it could be smarter, saved slot index, shit like that.
	/// </summary>
	/// <param name="node"></param>
	async void InitializeFromState( JsonNode node )
	{
		// Wait for everything else to load first
		await PersistenceManager.WaitForLoad();

		var gridPosition = Vector3.Parse( node["Position"]?.ToString() );
		var terrain = GridNavigation.Instance.Terrain;
		var worldTileIndex = terrain.GetTileIndex( gridPosition );
		var cell = GridManager.Instance.GetCell( new Vector2Int( worldTileIndex ) );

		if ( cell is null )
		{
			Log.Warning( $"When trying to initialize a guest from saved state, we couldn't find a cell." );
			return;
		}

		var building = cell.GetComponent<Building>();
		if ( !building.IsValid() ) return;

		// Load us in!
		if ( !building.LoadIn( this ) )
		{
			Log.Warning( $"When trying to load a guest into saved building, the building reported a failure.\nThis could happen if the slots changed between game updates." );
			GameObject.Destroy();
			return;
		}
		else
		{
			// Bit of a hack, but it works
			var needsAction = ActionController.GetComponentInChildren<SatisfyNeedsAction>();
			needsAction.Force( building );
		}
	}
}

partial class GuestManager : ISaveDataProperty<ImmutableArray<Guest.SaveData>>
{
	string ISaveDataProperty.PropertyName => "Guests";
	int ISaveDataProperty.PropertyOrder => 1_000;

	ImmutableArray<Guest.SaveData> ISaveDataProperty<ImmutableArray<Guest.SaveData>>.WriteValue( Scene scene )
	{
		return Scene.GetAll<Guest>()
			.Select( x => x.GetSaveData() )
			.ToImmutableArray();
	}

	void ISaveDataProperty<ImmutableArray<Guest.SaveData>>.ReadValue( Scene scene, ImmutableArray<Guest.SaveData> array )
	{
		foreach ( var guest in Scene.GetAll<Guest>().ToArray() )
		{
			guest.GameObject.Destroy();
		}

		foreach ( var saveData in array )
		{
			CreateGuestCore( guest => guest.SetSaveData( saveData ) );
		}
	}
}