Items/WorldObject.cs
using System;
using Clover.Data;
using Clover.Inventory;
using Clover.Persistence;
using Clover.Player;
using Sandbox.Diagnostics;

namespace Clover.Items;

[Category( "Clover/Items" )]
[Icon( "outlet" )]
[Description(
	"Has to be added to items placed not on the grid like physics objects, otherwise they will not be saved." )]
public class WorldObject : Component, IPickupable
{
	[RequireComponent] public WorldLayerObject WorldLayerObject { get; set; }

	private string _prefab;

	[Property, ResourceType( "prefab" )]
	public string Prefab
	{
		get { return !string.IsNullOrEmpty( _prefab ) ? _prefab : GameObject.PrefabInstanceSource; }
		set { _prefab = value; }
	}

	[Property] public ObjectData ObjectData { get; set; }

	public bool IsBeingPickedUp { get; set; }

	[Property] public Func<bool> CanPickupFunc { get; set; }

	public bool CanPickup( PlayerCharacter player )
	{
		if ( CanPickupFunc != null )
		{
			return CanPickupFunc();
		}

		return ObjectData.CanPickup;
	}

	public void OnPickup( PlayerCharacter player )
	{
		// player.Inventory.PickUpItem( this );

		Assert.NotNull( ObjectData, "ObjectData is null" );
		Assert.NotNull( ObjectData.PickupData, "ObjectData.PickupData is null" );

		var item = OnObjectSave().Item;
		item.ItemId = ObjectData.PickupData.GetIdentifier();

		if ( player.Inventory.PickUpItem( item ) )
		{
			RemoveFromWorld();
		}
	}

	public string GetPickupName()
	{
		return ObjectData?.Name ?? "Object";
	}

	public string GetName() => GetPickupName();

	protected override void OnFixedUpdate()
	{
		if ( Networking.IsHost )
		{
			if ( WorldPosition.z < -10000f )
			{
				Log.Warning( $"{GetName()} ({Id}, {GameObject.Name}) fell out of the world at {WorldPosition}." );
				RemoveFromWorld();
			}
		}
	}

	public delegate void OnObjectSaveActionEvent( PersistentItem item );

	[Property] public OnObjectSaveActionEvent OnObjectSaveAction { get; set; }

	public delegate void OnObjectLoadActionEvent( PersistentItem item );

	[Property] public OnObjectLoadActionEvent OnObjectLoadAction { get; set; }

	public PersistentWorldObject OnObjectSave()
	{
		var persistence = PersistentItem.Create( GameObject );

		Assert.NotNull( ObjectData, "ObjectData is null" );
		Assert.NotNull( GameObject, "GameObject is null" );
		Assert.NotNull( WorldLayerObject, "WorldLayerObject is null" );

		return new PersistentWorldObject()
		{
			Position = GetRelativeWorldPosition(),
			Rotation = GetRelativeWorldRotation(),
			PrefabPath = Prefab,
			ObjectId = ObjectData.ResourceName,
			Item = persistence,
		};
	}

	public void OnObjectLoad( PersistentWorldObject worldObject )
	{
		ObjectData = ObjectData.Get( worldObject.ObjectId );
		Prefab = worldObject.PrefabPath;

		WorldPosition = worldObject.Position + WorldLayerObject.World.WorldPosition;
		WorldRotation = worldObject.Rotation;

		/*foreach ( var persistent in Components.GetAll<IPersistent>( FindMode.EverythingInSelfAndAncestors ) )
		{
			persistent.OnLoad( worldObject.Item );
		}*/

		OnObjectLoadAction?.Invoke( worldObject.Item );

		GameObject.Name = ObjectData.Name;
	}

	public Vector3 GetRelativeWorldPosition()
	{
		if ( !WorldLayerObject.IsValid() || !WorldLayerObject.World.IsValid() )
		{
			Log.Error( $"WorldLayerObject or World is not valid for {this}" );
			return WorldPosition;
		}

		return WorldLayerObject.World.GetRelativePosition( WorldPosition );
	}

	public Angles GetRelativeWorldRotation()
	{
		if ( !WorldLayerObject.IsValid() || !WorldLayerObject.World.IsValid() )
		{
			Log.Error( $"WorldLayerObject or World is not valid for {this}" );
			return WorldRotation;
		}

		return WorldLayerObject.World.GetRelativeRotation( WorldRotation );
	}

	public void RemoveFromWorld()
	{
		if ( WorldLayerObject.IsValid() && WorldLayerObject.World.IsValid() )
		{
			WorldLayerObject.World.WorldObjects.Remove( this );
		}

		GameObject.Destroy();
	}

	protected override void OnDestroy()
	{
		if ( WorldLayerObject.IsValid() && WorldLayerObject.World.IsValid() &&
		     WorldLayerObject.World.WorldObjects.Contains( this ) )
		{
			Log.Warning( $"WorldObject {this} was not removed from world before destruction. Removing now." );
			WorldLayerObject.World.WorldObjects.Remove( this );
		}
	}
}