diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs
index 3701cfa3..84f03721 100644
--- a/Assets/Scripts/Core/GameManager.cs
+++ b/Assets/Scripts/Core/GameManager.cs
@@ -50,7 +50,7 @@ public class GameManager : MonoBehaviour
public float ManualMoveSmooth => gameSettings != null ? gameSettings.manualMoveSmooth : 8f;
public float ThresholdFar => gameSettings != null ? gameSettings.thresholdFar : 2.5f;
public float ThresholdNear => gameSettings != null ? gameSettings.thresholdNear : 0.5f;
- public float StopThreshold => gameSettings != null ? gameSettings.stopThreshold : 0.1f;
+ public float StopThreshold => gameSettings != null ? gameSettings.stopThreshold : 0.5f;
public float MoveSpeed => gameSettings != null ? gameSettings.moveSpeed : 5f;
public float StopDistance => gameSettings != null ? gameSettings.stopDistance : 0.1f;
public bool UseRigidbody => gameSettings != null ? gameSettings.useRigidbody : true;
diff --git a/Assets/Scripts/Data.meta b/Assets/Scripts/Data.meta
new file mode 100644
index 00000000..85e71fb9
--- /dev/null
+++ b/Assets/Scripts/Data.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e0b15b90103942c3b0e630462ecc5de1
+timeCreated: 1757518020
\ No newline at end of file
diff --git a/Assets/Scripts/Data/ItemCombinationManager.cs b/Assets/Scripts/Data/ItemCombinationManager.cs
new file mode 100644
index 00000000..e02abfc9
--- /dev/null
+++ b/Assets/Scripts/Data/ItemCombinationManager.cs
@@ -0,0 +1 @@
+
diff --git a/Assets/Scripts/Data/ItemCombinationManager.cs.meta b/Assets/Scripts/Data/ItemCombinationManager.cs.meta
new file mode 100644
index 00000000..8d07764a
--- /dev/null
+++ b/Assets/Scripts/Data/ItemCombinationManager.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a54fc1a3945f46c580a83e74f9436526
+timeCreated: 1757518020
\ No newline at end of file
diff --git a/Assets/Scripts/Interactions/CombineWithBehavior.cs b/Assets/Scripts/Interactions/CombineWithBehavior.cs
index 70638d52..8aa1247c 100644
--- a/Assets/Scripts/Interactions/CombineWithBehavior.cs
+++ b/Assets/Scripts/Interactions/CombineWithBehavior.cs
@@ -13,7 +13,7 @@ public class CombineWithBehavior : InteractionRequirementBase
/// True if the combination was successful, false otherwise.
public override bool TryInteract(FollowerController follower)
{
- var heldItem = follower.CurrentlyHeldItem;
+ var heldItem = follower.GetHeldPickupObject();
var pickup = GetComponent();
if (heldItem == null)
{
@@ -27,36 +27,17 @@ public class CombineWithBehavior : InteractionRequirementBase
OnFailure?.Invoke();
return false;
}
- var rule = GameManager.Instance.GetCombinationRule(heldItem, pickup.itemData);
- if (rule != null && rule.resultPrefab != null)
+ var combinedItem = InteractionOrchestrator.Instance.CombineItems(heldItem, pickup.gameObject);
+ if (combinedItem != null)
{
- // Instantiate the result prefab at the pickup's position
- var resultObj = GameObject.Instantiate(rule.resultPrefab, pickup.transform.position, Quaternion.identity);
- var resultPickup = resultObj.GetComponent();
- if (resultPickup != null)
- {
- // Hide and parent to follower
- resultObj.SetActive(false);
- resultObj.transform.SetParent(follower.transform);
- // Set held item and icon from the spawned prefab
- follower.SetHeldItem(resultPickup.itemData, resultPickup.iconRenderer);
- // Cache the spawned object as the held item
- follower.CacheHeldPickupObject(resultObj);
- follower.justCombined = true;
- OnSuccess?.Invoke();
- return true;
- }
- else
- {
- Debug.LogWarning("Result prefab does not have a Pickup component.");
- GameObject.Destroy(resultObj);
- OnFailure?.Invoke();
- return false;
- }
+ InteractionOrchestrator.Instance.PickupItem(follower, combinedItem);
+ follower.justCombined = true;
+ OnSuccess?.Invoke();
+ return true;
}
else
{
- // DebugUIMessage.Show($"Cannot combine {heldItem.itemName ?? \"an item\"} with {pickup.itemData.itemName ?? \"target item\"}.");
+ // DebugUIMessage.Show($"Cannot combine {follower.CurrentlyHeldItem?.itemName ?? "an item"} with {pickup.itemData.itemName ?? "target item"}.");
OnFailure?.Invoke();
return true;
}
diff --git a/Assets/Scripts/Interactions/InteractionOrchestrator.cs b/Assets/Scripts/Interactions/InteractionOrchestrator.cs
index 3621e793..8899092e 100644
--- a/Assets/Scripts/Interactions/InteractionOrchestrator.cs
+++ b/Assets/Scripts/Interactions/InteractionOrchestrator.cs
@@ -151,4 +151,68 @@ public class InteractionOrchestrator : MonoBehaviour
interactable.CompleteInteraction(false);
}
}
+
+ // --- ITEM MANAGEMENT API ---
+ public void PickupItem(FollowerController follower, GameObject item)
+ {
+ if (item == null || follower == null) return;
+ item.SetActive(false);
+ item.transform.SetParent(null);
+ follower.SetHeldItemFromObject(item);
+ // Optionally: fire event, update UI, etc.
+ }
+
+ public void DropItem(FollowerController follower, Vector3 position)
+ {
+ var item = follower.GetHeldPickupObject();
+ if (item == null) return;
+ item.transform.position = position;
+ item.transform.SetParent(null);
+ item.SetActive(true);
+ follower.ClearHeldItem();
+ // Optionally: fire event, update UI, etc.
+ }
+
+ public void SlotItem(SlotItemBehavior slot, GameObject item)
+ {
+ if (slot == null || item == null) return;
+ item.SetActive(false);
+ item.transform.SetParent(null);
+ slot.SetSlottedObject(item);
+ // Optionally: update visuals, fire event, etc.
+ }
+
+ public void SwapItems(FollowerController follower, SlotItemBehavior slot)
+ {
+ var heldObj = follower.GetHeldPickupObject();
+ var slotObj = slot.GetSlottedObject();
+ // 1. Slot the follower's held object
+ SlotItem(slot, heldObj);
+ // 2. Give the slot's object to the follower
+ PickupItem(follower, slotObj);
+ }
+
+ public GameObject CombineItems(GameObject itemA, GameObject itemB)
+ {
+ if (itemA == null || itemB == null) return null;
+ var pickupA = itemA.GetComponent();
+ var pickupB = itemB.GetComponent();
+ if (pickupA == null || pickupB == null) {
+ if (itemA != null) Destroy(itemA);
+ if (itemB != null) Destroy(itemB);
+ return null;
+ }
+ var rule = GameManager.Instance.GetCombinationRule(pickupA.itemData, pickupB.itemData);
+ Vector3 spawnPos = itemA.transform.position;
+ if (rule != null && rule.resultPrefab != null) {
+ var newItem = Instantiate(rule.resultPrefab, spawnPos, Quaternion.identity);
+ Destroy(itemA);
+ Destroy(itemB);
+ return newItem;
+ }
+ // If no combination found, just destroy both
+ Destroy(itemA);
+ Destroy(itemB);
+ return null;
+ }
}
diff --git a/Assets/Scripts/Interactions/SlotItemBehavior.cs b/Assets/Scripts/Interactions/SlotItemBehavior.cs
index e45b3012..9189e40d 100644
--- a/Assets/Scripts/Interactions/SlotItemBehavior.cs
+++ b/Assets/Scripts/Interactions/SlotItemBehavior.cs
@@ -21,8 +21,12 @@ public class SlotItemBehavior : InteractionRequirementBase
private GameObject _cachedSlottedObject = null;
- // Helper for slotting an object, with option to skip destruction (for swap)
- private void SetSlottedObject(GameObject obj)
+ public GameObject GetSlottedObject()
+ {
+ return _cachedSlottedObject;
+ }
+
+ public void SetSlottedObject(GameObject obj)
{
_cachedSlottedObject = obj;
if (_cachedSlottedObject != null)
@@ -31,32 +35,6 @@ public class SlotItemBehavior : InteractionRequirementBase
}
}
- private void RemoveSlottedObject()
- {
- if (_cachedSlottedObject != null)
- {
- Destroy(_cachedSlottedObject);
- _cachedSlottedObject = null;
- }
- }
-
- private void CacheSlottedObject(GameObject obj)
- {
- // Only destroy if not swapping
- RemoveSlottedObject();
- SetSlottedObject(obj);
- }
-
- private void RestoreSlottedObject()
- {
- if (_cachedSlottedObject != null)
- {
- _cachedSlottedObject.transform.SetParent(null);
- // Do NOT activate or move the object here; it stays hidden until dropped
- // Activation is handled by the drop logic elsewhere
- }
- }
-
///
/// Attempts to interact with the slot, handling slotting, swapping, or picking up items.
///
@@ -75,9 +53,7 @@ public class SlotItemBehavior : InteractionRequirementBase
// CASE 1: No held item, slot has item -> pick up slotted item
if (heldItem == null && _cachedSlottedObject != null)
{
- // Give the hidden slotted object to the follower (do NOT activate or move it)
- RestoreSlottedObject();
- follower.SetHeldItemFromObject(_cachedSlottedObject);
+ InteractionOrchestrator.Instance.PickupItem(follower, _cachedSlottedObject);
_cachedSlottedObject = null;
currentlySlottedItem = null;
UpdateSlottedSprite();
@@ -86,20 +62,9 @@ public class SlotItemBehavior : InteractionRequirementBase
// CASE 2: Held item, slot has item -> swap
if (heldItem != null && _cachedSlottedObject != null)
{
- var followerHeldObj = heldObj;
- var followerHeldItem = heldItem;
- var slotObj = _cachedSlottedObject;
- var slotItemData = currentlySlottedItem;
-
- // 1. Slot the follower's held object (do NOT destroy the old one)
- SetSlottedObject(followerHeldObj);
- currentlySlottedItem = followerHeldItem;
+ InteractionOrchestrator.Instance.SwapItems(follower, this);
+ currentlySlottedItem = heldItem;
UpdateSlottedSprite();
-
- // 2. Give the slot's object to the follower (do NOT activate or move it)
- RestoreSlottedObject();
- follower.SetHeldItemFromObject(slotObj);
-
return true;
}
// CASE 3: Held item, slot empty -> slot the held item
@@ -110,7 +75,7 @@ public class SlotItemBehavior : InteractionRequirementBase
DebugUIMessage.Show("Can't place that here.");
return false;
}
- CacheSlottedObject(heldObj);
+ InteractionOrchestrator.Instance.SlotItem(this, heldObj);
currentlySlottedItem = heldItem;
UpdateSlottedSprite();
follower.ClearHeldItem();
diff --git a/Assets/Scripts/Movement/FollowerController.cs b/Assets/Scripts/Movement/FollowerController.cs
index 3c081ae9..f194bb81 100644
--- a/Assets/Scripts/Movement/FollowerController.cs
+++ b/Assets/Scripts/Movement/FollowerController.cs
@@ -325,7 +325,7 @@ public class FollowerController : Character
if (pickup != null)
{
SetHeldItem(pickup.itemData, pickup.iconRenderer);
- CacheHeldPickupObject(obj);
+ _cachedPickupObject = obj;
}
else
{
@@ -335,11 +335,7 @@ public class FollowerController : Character
public void ClearHeldItem()
{
- if (_cachedPickupObject != null)
- {
- // Destroy(_cachedPickupObject);
- _cachedPickupObject = null;
- }
+ _cachedPickupObject = null;
_currentlyHeldItem = null;
if (heldObjectRenderer != null)
{
@@ -348,6 +344,11 @@ public class FollowerController : Character
}
}
+ public void DropHeldItemAt(Vector3 position)
+ {
+ InteractionOrchestrator.Instance.DropItem(this, position);
+ }
+
private System.Collections.IEnumerator PickupSequence(Vector2 itemPosition, Transform playerTransform)
{
_isManualFollowing = false;
@@ -376,26 +377,21 @@ public class FollowerController : Character
var slotBehavior = pickup.GetComponent();
if (slotBehavior != null)
{
- // Slot item: do not destroy or swap, just return to player
+ // Slot item: orchestrator handles slotting
break;
}
if (justCombined)
{
- GameObject.Destroy(pickup.gameObject);
+ InteractionOrchestrator.Instance.CombineItems(pickup.gameObject, _cachedPickupObject);
justCombined = false;
break;
}
// Swap logic: if holding an item, drop it here
if (_currentlyHeldItem != null && _cachedPickupObject != null)
{
- // Drop the cached object at the pickup's position
- _cachedPickupObject.transform.position = pickup.transform.position;
- _cachedPickupObject.transform.SetParent(null);
- _cachedPickupObject.SetActive(true);
- _cachedPickupObject = null;
+ InteractionOrchestrator.Instance.DropItem(this, pickup.transform.position);
}
- SetHeldItem(pickup.itemData, pickup.iconRenderer);
- CacheHeldPickupObject(pickup.gameObject);
+ InteractionOrchestrator.Instance.PickupItem(this, pickup.gameObject);
break;
}
}
@@ -425,27 +421,6 @@ public class FollowerController : Character
_pickupCoroutine = null;
}
- ///
- /// Drop the held item at the specified position, unparenting and activating it.
- ///
- /// The world position to drop the item at.
- public void DropHeldItemAt(Vector3 position)
- {
- if (_cachedPickupObject != null)
- {
- _cachedPickupObject.transform.position = position;
- _cachedPickupObject.transform.SetParent(null);
- _cachedPickupObject.SetActive(true);
- _cachedPickupObject = null;
- _currentlyHeldItem = null;
- if (heldObjectRenderer != null)
- {
- heldObjectRenderer.sprite = null;
- heldObjectRenderer.enabled = false;
- }
- }
- }
-
void OnDrawGizmos()
{
if (debugDrawTarget && Application.isPlaying)