Items/WorldItem.cs
using System;
using System.Text.Json.Serialization;
using Clover.Components;
using Clover.Data;
using Clover.Interactable;
using Clover.Inventory;
using Clover.Persistence;
using Clover.Player;
namespace Clover.Items;
[Category( "Clover/Items" )]
[Icon( "outlet" )]
[Description( "Has to be added to items placed on the world grid, otherwise they will not be saved." )]
public class WorldItem : Component, IPickupable
{
[RequireComponent] public WorldLayerObject WorldLayerObject { get; set; }
[RequireComponent] public ItemHighlight ItemHighlight { get; set; }
// public WorldNodeLink NodeLink => !Scene.IsEditor ? WorldLayerObject.World?.GetNodeLink( GameObject ) : null;
// public Vector2Int GridPosition => NodeLink?.GridPosition ?? Vector2Int.Zero;
// public Vector2Int Size =>
// !Scene.IsEditor ? NodeLink.Size : new Vector2Int( ItemData?.Width ?? 1, ItemData?.Height ?? 1 );
// public World.ItemPlacement GridPlacement => !Scene.IsEditor ? NodeLink.GridPlacement : World.ItemPlacement.Floor;
// public World.ItemPlacementType GridPlacementType => NodeLink?.PlacementType ?? World.ItemPlacementType.Placed;
// public World.ItemRotation GridRotation => NodeLink?.GridRotation ?? World.ItemRotation.North;
public Vector2Int GridPosition { get; set; }
public Vector2Int Size { get; set; }
public World.ItemPlacementType ItemPlacement { get; set; }
public World.ItemRotation ItemRotation { get; set; }
private string _prefab;
[Property, ResourceType( "prefab" )]
public string Prefab
{
get { return !string.IsNullOrEmpty( _prefab ) ? _prefab : GameObject.PrefabInstanceSource; }
set { _prefab = value; }
}
[Property] public ItemData ItemData { get; set; }
[Property] public Vector3 PlaceModeOffset { get; set; }
protected override void OnAwake()
{
if ( Scene.IsEditor ) return;
GameObject.Transform.OnTransformChanged += GameObjectTransformChanged;
base.OnEnabled();
BackupPrefabSource();
/*
if ( Transform.Position != Vector3.Zero )
{
// Log.Warning( $"WorldObject {GameObject.Name} has no position set" );
SetPosition( Transform.Position );
}*/
}
private void BackupPrefabSource()
{
// Log.Info( $"WorldItem {GameObject.Name} has prefab source {GameObject.PrefabInstanceSource}, backup is {Prefab}" );
if ( !string.IsNullOrEmpty( GameObject.PrefabInstanceSource ) && GameObject.PrefabInstanceSource != Prefab )
{
Log.Error(
$"WorldItem {GameObject.Name} has prefab source {GameObject.PrefabInstanceSource}, backup is {Prefab}" );
Prefab = GameObject.PrefabInstanceSource;
}
}
private void GameObjectTransformChanged()
{
// Log.Info( $"WorldItem {GameObject.Name} transform changed" );
}
/*public delegate void OnObjectSave( WorldNodeLink nodeLink );
[Property] public OnObjectSave OnItemSave { get; set; }
public delegate void OnObjectLoad( WorldNodeLink nodeLink );
[Property] public OnObjectLoad OnItemLoad { get; set; }*/
protected override void DrawGizmos()
{
base.DrawGizmos();
if ( !Gizmo.Settings.GizmosEnabled ) return;
if ( Gizmo.Camera == null ) return;
if ( ItemData == null ) return;
Gizmo.Transform = global::Transform.Zero;
if ( Size == Vector2Int.Zero )
{
Gizmo.Draw.Text( "No size", new Transform( WorldPosition + Vector3.Up * 32 ) );
return;
}
if ( Scene != GameObject )
{
Gizmo.Draw.Color = Color.Red;
Gizmo.Draw.Text( "NOT AT ROOT", new Transform( WorldPosition ), "Roboto", 24f );
Gizmo.Draw.Color = Color.White;
}
var gridSize = World.GridSize;
/*var mins = new Vector3( GridPosition.x * gridSize, GridPosition.y * gridSize, gridSize );
var maxs = new Vector3( (GridPosition.x + Size.x) * gridSize, (GridPosition.y + Size.y) * gridSize, 0 );
var bbox = new BBox( mins, maxs );*/
var bbox = BBox.FromPositionAndSize( WorldPosition + (Vector3.Up * (gridSize / 2f)),
new Vector3( gridSize * Size.x, gridSize * Size.y, 32 ) );
Gizmo.Draw.LineBBox( bbox );
Gizmo.Draw.Arrow( WorldPosition + Vector3.Up * 64f, WorldPosition + Vector3.Forward * 64 + Vector3.Up * 64f,
8f );
Gizmo.Draw.Text( $"NORTH", new Transform( WorldPosition + Vector3.Up * 64f + Vector3.Forward * 72 ) );
}
/*protected override void OnUpdate()
{
base.OnUpdate();
DrawGizmos();
}*/
/*protected override void OnUpdate()
{
base.OnUpdate();
if ( Gizmo.Camera == null ) return;
Gizmo.Draw.Text( WorldPosition.ToString(), new Transform( WorldPosition ) );
}*/
protected override void OnFixedUpdate()
{
// ItemHighlight.Enabled = false;
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( Gizmo.Camera == null || !DebugWorldItem ) return;
/*if ( NodeLink.IsValid() && NodeLink.PlacementType == World.ItemPlacementType.Dropped )
{
Gizmo.Draw.Text( $"Dropped: {NodeLink.GetName()}", new Transform( WorldPosition + Vector3.Up * 20 ) );
}
if ( NodeLink.IsValid() )
{
var placeableNodes = NodeLink.GetPlaceableNodes().ToList();
foreach ( var placeableNode in placeableNodes )
{
Gizmo.Draw.Color = placeableNode.PlacedNodeLink == null ? Color.Green : Color.Red;
Gizmo.Draw.Text( placeableNode.GameObject.Name, new Transform( placeableNode.WorldPosition ) );
Gizmo.Draw.Color = Color.White;
}
}*/
}
[ConVar( "clover_debug_worlditem" )] public static bool DebugWorldItem { get; set; }
[Property] public bool CanPickupSimple { get; set; }
public bool IsBeingPickedUp { get; set; }
[Property, TargetType( typeof(PersistentItem) )]
public Type PersistentItemType { get; set; }
public bool CanPickup( PlayerCharacter player )
{
return CanPickupSimple &&
!WorldLayerObject.World.Data.DisableItemPlacement; // TODO: make a bool for picking up on world
}
/*[Obsolete]
public bool HasItemOnTop()
{
if ( NodeLink == null ) return false;
var gridPositions = NodeLink.GetGridPositions( true );
foreach ( var pos in gridPositions )
{
// if this item has an item on top of it then it can't be picked up
if ( WorldLayerObject.World.GetItems( pos ).Any( x => x.GridPlacement == World.ItemPlacement.OnTop ) &&
GridPlacement == World.ItemPlacement.Floor )
{
// Log.Warning( $"An item is on top of this item" );
return true;
}
}
return false;
}*/
/// <summary>
/// Called when the player picks up this item, mostly so you don't have to add IPickupable to every item.
/// This will just call the Inventory.PickUpItem method on the node link.
/// ALL IPickupable items will be iterated through when trying to pick up something.
/// </summary>
/// <param name="player"></param>
public void OnPickup( PlayerCharacter player )
{
player.Inventory.PickUpItem( this );
}
public string GetPickupName()
{
return ItemData?.Name ?? "Item";
}
[Button( "Add simple collider (32)" )]
private void AddSimpleCollider32()
{
var collider = GameObject.AddComponent<BoxCollider>();
collider.Scale = new Vector3( 32, 32, 32 );
collider.Center = new Vector3( 0, 0, 16 );
}
[Button( "Add simple collider (16)" )]
private void AddSimpleCollider16()
{
var collider = GameObject.AddComponent<BoxCollider>();
collider.Scale = new Vector3( 16, 16, 16 );
collider.Center = new Vector3( 0, 0, 8 );
}
/*public void OnTriggerEnter( Collider other )
{
ToggleHighlight( other, true );
}
public void OnTriggerExit( Collider other )
{
ToggleHighlight( other, false );
}
private void ToggleHighlight( Collider otherCollider, bool shouldEnable )
{
if ( otherCollider.GetComponentInParent<PlayerCharacter>() == null )
{
return;
}
ItemHighlight.Enabled = shouldEnable;
}*/
public void Hide()
{
Tags.Add( "invisible" );
}
public void Show()
{
Tags.Remove( "invisible" );
}
public string GetName()
{
return ItemData.IsValid() ? ItemData.Name : GameObject.Name;
}
public bool ShouldBeSaved()
{
return true; // TODO: add option to not save
}
public void SavePersistence( PersistentItem item )
{
var components = GetComponents<Component>();
var persistentInterfaceComponents = GetComponents<IPersistent>().ToList();
// foreach ( var persistentInterfaceComponent in persistentInterfaceComponents )
// {
// // XLog.Debug( this, $"Calling OnPreSave on {persistentInterfaceComponent}" );
// persistentInterfaceComponent.OnPreSave( item );
// }
var keys = new List<string>();
foreach ( var component in components )
{
var properties = TypeLibrary.GetPropertyDescriptions( component );
// XLog.Debug( this, $"Saving persistence for {component} on {this}" );
foreach ( var property in properties )
{
var saveDataAttribute = property.GetCustomAttribute<SaveDataAttribute>();
if ( saveDataAttribute == null )
{
// XLog.Debug( this, $"No save data attribute on {property.Name} on {component}" );
continue;
}
var keyName = !string.IsNullOrEmpty( saveDataAttribute.Key ) ? saveDataAttribute.Key : property.Name;
/*// clear the save data if the item is picked up and the attribute says to reset on pickup
if ( pickedUp )
{
if ( saveDataAttribute.ResetOnPickup )
{
// XLog.Debug( this, $"Resetting {property.Name} on {component}" );
item.ClearSaveData( keyName );
continue;
}
}*/
if ( keys.Contains( keyName ) )
{
Log.Error( $"Duplicate arbitrary data key {keyName} on {component}" );
continue;
}
var type = property.PropertyType;
var value = property.GetValue( component );
// XLog.Info( this,
// $"Saving arbitrary data {keyName} = {value}, type {type}/{value?.GetType()}" );
// XLog.Debug( this, $"Saving '{keyName}' = '{value}' on {component}" );
item.SetSaveData( keyName, value, type );
// XLog.Info( this, $"Saving arbitrary data {keyName} = {value}" );
keys.Add( keyName );
// XLog.Debug( this, $"Saved '{keyName}' = '{value}' on {component}" );
}
}
// XLog.Debug( this,
// $"Saved {keys.Count} arbitrary data keys, {item.SaveData.Count} total. Calling OnSave on IPersistent components on {this}" );
// XLog.Debug( this, $"Found {persistentInterfaceComponents.Count()} IPersistent components on {this}" );
foreach ( var persistentInterfaceComponent in persistentInterfaceComponents )
{
// XLog.Debug( this, $"Calling OnSave on {persistentInterfaceComponent}" );
persistentInterfaceComponent.OnSave( item );
}
// XLog.Debug( this, $"Calling SaveExtraPersistence on {this}" );
// SaveExtraPersistence( item );
}
public void LoadPersistence( PersistentItem item )
{
var components = GetComponents<Component>();
var persistentInterfaceComponents = GetComponents<IPersistent>().ToList();
// foreach ( var persistentInterfaceComponent in persistentInterfaceComponents )
// {
// XLog.Debug( this, $"Calling OnPreLoad on {persistentInterfaceComponent}" );
// persistentInterfaceComponent.OnPreLoad( item );
// }
foreach ( var component in components )
{
var properties = TypeLibrary.GetPropertyDescriptions( component );
foreach ( var property in properties )
{
/*if ( property.HasAttribute<SaveDataAttribute>() )
{
var value = item.GetArbitraryData( property.PropertyType, property.Name );
property.SetValue( component, value );
// XLog.Info( this, $"Set {property.Name} to {value} on {component}" );
}*/
var saveDataAttribute = property.GetCustomAttribute<SaveDataAttribute>();
if ( saveDataAttribute == null )
{
continue;
}
// Log.Info( $"{this} {property.Name} identity = {property.Identity.ToString( "X" )}" );
if ( !string.IsNullOrEmpty( saveDataAttribute.OnLoadMethodName ) )
{
/*var method = component.GetType().GetMethod( saveDataAttribute.OnLoadMethodName );
if ( method != null )
{
method.Invoke( component,
new object[] { item.GetArbitraryData( property.PropertyType, property.Name ) } );
continue;
}
*/
}
if ( !string.IsNullOrEmpty( saveDataAttribute.Key ) )
{
// first try using the key from the attribute
var keyData = item.GetSaveData( property.PropertyType, saveDataAttribute.Key );
if ( keyData != null )
{
property.SetValue( component, keyData );
continue;
}
}
// if that fails, try using the property name
// we do this to maintain backwards compatibility with old saves that don't have the key set
var propertyData = item.GetSaveData( property.PropertyType, property.Name );
if ( propertyData != null )
{
property.SetValue( component, propertyData );
}
}
}
// if ( persistentInterfaceComponents.Count > 0 )
// XLog.Info( this, $"Found {persistentInterfaceComponents.Count()} IPersistent components on {this.GameObject.Name}" );
foreach ( var persistentInterfaceComponent in persistentInterfaceComponents )
{
// XLog.Info( this, $"Calling persistence OnLoad on {persistentInterfaceComponent} on {this.GameObject.Name}" );
persistentInterfaceComponent.OnLoad( item );
}
// LoadExtraPersistence( item, source );
if ( ItemData == null )
{
Log.Info( "Overriding item data with persistent item data" );
ItemData = ItemData.Get( item.ItemId );
}
}
public void SetItemData( ItemData itemData )
{
ItemData = itemData;
}
public void CalculateSize()
{
}
public void RemoveFromWorld()
{
if ( WorldLayerObject.IsValid() && WorldLayerObject.World.IsValid() )
{
WorldLayerObject.World.WorldItems.Remove( this );
}
GameObject.Destroy();
}
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 );
}
protected override void OnDestroy()
{
if ( WorldLayerObject.IsValid() && WorldLayerObject.World.IsValid() &&
WorldLayerObject.World.WorldItems.Contains( this ) )
{
Log.Warning( $"WorldItem {this} was not removed from world before destruction. Removing now." );
WorldLayerObject.World.WorldItems.Remove( this );
}
}
}