260 lines
9.2 KiB
C#
260 lines
9.2 KiB
C#
using UnityEngine;
|
||
using System;
|
||
using System.Linq;
|
||
using Core;
|
||
|
||
namespace Interactions
|
||
{
|
||
/// <summary>
|
||
/// Saveable data for Pickup state
|
||
/// </summary>
|
||
[Serializable]
|
||
public class PickupSaveData
|
||
{
|
||
public bool isPickedUp;
|
||
public bool wasHeldByFollower;
|
||
public bool wasInSlot; // NEW: Was this pickup in a slot?
|
||
public string slotSaveId; // NEW: Which slot held this pickup?
|
||
public Vector3 worldPosition;
|
||
public Quaternion worldRotation;
|
||
public bool isActive;
|
||
}
|
||
|
||
public class Pickup : SaveableInteractable
|
||
{
|
||
public PickupItemData itemData;
|
||
public SpriteRenderer iconRenderer;
|
||
public bool IsPickedUp { get; internal set; }
|
||
|
||
// Track which slot owns this pickup (for bilateral restoration)
|
||
internal ItemSlot OwningSlot { get; set; }
|
||
|
||
public event Action<PickupItemData> OnItemPickedUp;
|
||
public event Action<PickupItemData, PickupItemData, PickupItemData> OnItemsCombined;
|
||
|
||
protected override void OnManagedAwake()
|
||
{
|
||
base.OnManagedAwake(); // Register with save system
|
||
|
||
if (iconRenderer == null)
|
||
iconRenderer = GetComponent<SpriteRenderer>();
|
||
|
||
ApplyItemData();
|
||
}
|
||
|
||
// Always register with ItemManager, even if picked up
|
||
// This allows the save/load system to find held items when restoring state
|
||
protected override void OnManagedStart()
|
||
{
|
||
base.OnManagedStart();
|
||
ItemManager.Instance?.RegisterPickup(this);
|
||
}
|
||
|
||
protected override void OnDestroy()
|
||
{
|
||
base.OnDestroy();
|
||
|
||
// Unregister from ItemManager
|
||
ItemManager.Instance?.UnregisterPickup(this);
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
/// <summary>
|
||
/// Unity OnValidate callback. Ensures icon and data are up to date in editor.
|
||
/// </summary>
|
||
void OnValidate()
|
||
{
|
||
if (iconRenderer == null)
|
||
iconRenderer = GetComponent<SpriteRenderer>();
|
||
ApplyItemData();
|
||
}
|
||
#endif
|
||
|
||
|
||
/// <summary>
|
||
/// Applies the item data to the pickup (icon, name, etc).
|
||
/// </summary>
|
||
public void ApplyItemData()
|
||
{
|
||
if (itemData != null)
|
||
{
|
||
if (iconRenderer != null && itemData.mapSprite != null)
|
||
{
|
||
iconRenderer.sprite = itemData.mapSprite;
|
||
}
|
||
|
||
gameObject.name = itemData.itemName;
|
||
}
|
||
}
|
||
|
||
#region Interaction Logic
|
||
|
||
/// <summary>
|
||
/// Main interaction logic: Try combination, then try pickup.
|
||
/// </summary>
|
||
protected override bool DoInteraction()
|
||
{
|
||
Logging.Debug("[Pickup] DoInteraction");
|
||
|
||
// IMPORTANT: Capture held item data BEFORE combination
|
||
// TryCombineItems destroys the original items, so we need this data for the event
|
||
var heldItemObject = FollowerController?.GetHeldPickupObject();
|
||
var heldItemData = heldItemObject?.GetComponent<Pickup>()?.itemData;
|
||
|
||
// Try combination first
|
||
var combinationResult = FollowerController.TryCombineItems(this, out var resultItem);
|
||
|
||
if (combinationResult == FollowerController.CombinationResult.Successful)
|
||
{
|
||
// Mark this pickup as picked up (consumed in combination) to prevent restoration
|
||
IsPickedUp = true;
|
||
|
||
// Combination succeeded - original items destroyed, result picked up by TryCombineItems
|
||
FireCombinationEvent(resultItem, heldItemData);
|
||
return true;
|
||
}
|
||
|
||
// No combination (or unsuccessful) - do regular pickup
|
||
FollowerController?.TryPickupItem(gameObject, itemData);
|
||
IsPickedUp = true;
|
||
OnItemPickedUp?.Invoke(itemData);
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Helper method to fire the combination event with correct item data.
|
||
/// </summary>
|
||
/// <param name="resultItem">The spawned result item</param>
|
||
/// <param name="originalHeldItemData">The ORIGINAL held item data (before destruction)</param>
|
||
private void FireCombinationEvent(GameObject resultItem, PickupItemData originalHeldItemData)
|
||
{
|
||
var resultPickup = resultItem?.GetComponent<Pickup>();
|
||
|
||
// Verify we have all required data
|
||
if (resultPickup?.itemData != null && originalHeldItemData != null && itemData != null)
|
||
{
|
||
OnItemsCombined?.Invoke(itemData, originalHeldItemData, resultPickup.itemData);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Save/Load Implementation
|
||
|
||
protected override object GetSerializableState()
|
||
{
|
||
// Check if this pickup is currently held by the follower
|
||
bool isHeldByFollower = IsPickedUp && !gameObject.activeSelf && transform.parent != null;
|
||
|
||
// Check if this pickup is in a slot
|
||
bool isInSlot = OwningSlot != null;
|
||
string slotId = isInSlot && OwningSlot is SaveableInteractable saveableSlot ? saveableSlot.SaveId : "";
|
||
|
||
return new PickupSaveData
|
||
{
|
||
isPickedUp = this.IsPickedUp,
|
||
wasHeldByFollower = isHeldByFollower,
|
||
wasInSlot = isInSlot,
|
||
slotSaveId = slotId,
|
||
worldPosition = transform.position,
|
||
worldRotation = transform.rotation,
|
||
isActive = gameObject.activeSelf
|
||
};
|
||
}
|
||
|
||
protected override void ApplySerializableState(string serializedData)
|
||
{
|
||
PickupSaveData data = JsonUtility.FromJson<PickupSaveData>(serializedData);
|
||
if (data == null)
|
||
{
|
||
Logging.Warning($"[Pickup] Failed to deserialize save data for {gameObject.name}");
|
||
return;
|
||
}
|
||
|
||
// Restore picked up state
|
||
IsPickedUp = data.isPickedUp;
|
||
|
||
if (IsPickedUp)
|
||
{
|
||
// Hide the pickup if it was already picked up
|
||
gameObject.SetActive(false);
|
||
|
||
// If this was held by the follower, try bilateral restoration
|
||
if (data.wasHeldByFollower)
|
||
{
|
||
// Try to give this pickup to the follower
|
||
// This might succeed or fail depending on timing
|
||
var follower = FollowerController.FindInstance();
|
||
if (follower != null)
|
||
{
|
||
follower.TryClaimHeldItem(this);
|
||
}
|
||
}
|
||
// If this was in a slot, try bilateral restoration with the slot
|
||
else if (data.wasInSlot && !string.IsNullOrEmpty(data.slotSaveId))
|
||
{
|
||
// Try to give this pickup to the slot
|
||
var slot = FindSlotBySaveId(data.slotSaveId);
|
||
if (slot != null)
|
||
{
|
||
slot.TryClaimSlottedItem(this);
|
||
}
|
||
else
|
||
{
|
||
Logging.Warning($"[Pickup] Could not find slot with SaveId: {data.slotSaveId}");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Restore position for items that haven't been picked up (they may have moved)
|
||
transform.position = data.worldPosition;
|
||
transform.rotation = data.worldRotation;
|
||
gameObject.SetActive(data.isActive);
|
||
}
|
||
|
||
// Note: We do NOT fire OnItemPickedUp event during restoration
|
||
// This prevents duplicate logic execution
|
||
}
|
||
|
||
/// <summary>
|
||
/// Find an ItemSlot by its SaveId (for bilateral restoration).
|
||
/// </summary>
|
||
private ItemSlot FindSlotBySaveId(string slotSaveId)
|
||
{
|
||
if (string.IsNullOrEmpty(slotSaveId)) return null;
|
||
|
||
// Get all ItemSlots from ItemManager
|
||
var allSlots = ItemManager.Instance?.GetAllItemSlots();
|
||
if (allSlots == null) return null;
|
||
|
||
foreach (var slot in allSlots)
|
||
{
|
||
if (slot is SaveableInteractable saveable && saveable.SaveId == slotSaveId)
|
||
{
|
||
return slot;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Resets the pickup state when the item is dropped back into the world.
|
||
/// Called by FollowerController when swapping items.
|
||
/// </summary>
|
||
public void ResetPickupState()
|
||
{
|
||
IsPickedUp = false;
|
||
gameObject.SetActive(true);
|
||
|
||
// Re-register with ItemManager if not already registered
|
||
if (ItemManager.Instance != null && !ItemManager.Instance.GetAllPickups().Contains(this))
|
||
{
|
||
ItemManager.Instance.RegisterPickup(this);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |