241 lines
8.4 KiB
C#
241 lines
8.4 KiB
C#
using UnityEngine;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
using UnityEngine.InputSystem;
|
|
#endif
|
|
using System;
|
|
|
|
namespace SpecialMoveCards
|
|
{
|
|
/// <summary>
|
|
/// Holds up to two cards, exposes UseCard(slot), integrates with Animation via SpecialAnimationBridge.
|
|
/// </summary>
|
|
public class PlayerPocket : MonoBehaviour
|
|
{
|
|
[Header("Animation")] public SpecialAnimationBridge animationBridge;
|
|
[Header("UI")] public UIController_Pocket pocketUI;
|
|
[Tooltip("Tag used to find the Pocket UI at runtime if not assigned in inspector.")]
|
|
[SerializeField] private string pocketUITag = "Pocket UI";
|
|
private Coroutine resolveUICoroutine;
|
|
|
|
[Header("Input (Optional)")]
|
|
#if ENABLE_INPUT_SYSTEM
|
|
public InputAction useSlot1Action; // bind to keyboard 1
|
|
public InputAction useSlot2Action; // bind to keyboard 2
|
|
[Tooltip("Input System action that triggers using the next available card in the pocket (left-most).")]
|
|
public InputActionReference specialMoveAction; // expected to be bound to e.g. gamepad X / keyboard E
|
|
#endif
|
|
[Tooltip("Allow using cards while holding only 1.")]
|
|
public bool allowUseWithSingleCard = true;
|
|
|
|
private CardData[] slots = new CardData[2];
|
|
public int Count { get { return (slots[0] != null ? 1 : 0) + (slots[1] != null ? 1 : 0); } }
|
|
|
|
private void Awake()
|
|
{
|
|
// Skip if card system is disabled globally
|
|
if (CardSpawner.CARD_SYSTEM_DISABLED)
|
|
{
|
|
Debug.Log("[PlayerPocket] Card System is DISABLED - disabling component");
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
if (animationBridge == null)
|
|
animationBridge = GetComponent<SpecialAnimationBridge>();
|
|
|
|
// Try to auto-resolve UI early (player is often spawned from prefab)
|
|
if (pocketUI == null)
|
|
TryResolvePocketUIOnce();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
// Create defaults if not wired in Inspector
|
|
if (useSlot1Action == null)
|
|
{
|
|
useSlot1Action = new InputAction(name: "UseCardSlot1", binding: "<Keyboard>/1");
|
|
}
|
|
if (useSlot2Action == null)
|
|
{
|
|
useSlot2Action = new InputAction(name: "UseCardSlot2", binding: "<Keyboard>/2");
|
|
}
|
|
|
|
if (useSlot1Action != null)
|
|
{
|
|
useSlot1Action.SafeEnable();
|
|
useSlot1Action.performed += OnUseSlot1;
|
|
}
|
|
if (useSlot2Action != null)
|
|
{
|
|
useSlot2Action.SafeEnable();
|
|
useSlot2Action.performed += OnUseSlot2;
|
|
}
|
|
|
|
// Hook SpecialMove input if provided
|
|
if (specialMoveAction != null && specialMoveAction.action != null)
|
|
{
|
|
specialMoveAction.action.SafeEnable();
|
|
specialMoveAction.action.performed += OnSpecialMovePerformed;
|
|
}
|
|
#endif
|
|
PushUI();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
if (useSlot1Action != null) useSlot1Action.performed -= OnUseSlot1;
|
|
if (useSlot2Action != null) useSlot2Action.performed -= OnUseSlot2;
|
|
if (useSlot1Action != null) useSlot1Action.Disable();
|
|
if (useSlot2Action != null) useSlot2Action.Disable();
|
|
|
|
if (specialMoveAction != null && specialMoveAction.action != null)
|
|
{
|
|
specialMoveAction.action.performed -= OnSpecialMovePerformed;
|
|
specialMoveAction.action.Disable();
|
|
}
|
|
#endif
|
|
if (resolveUICoroutine != null)
|
|
{
|
|
StopCoroutine(resolveUICoroutine);
|
|
resolveUICoroutine = null;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// Legacy fallback
|
|
if (Input.GetKeyDown(KeyCode.Alpha1)) UseCard(0);
|
|
if (Input.GetKeyDown(KeyCode.Alpha2)) UseCard(1);
|
|
}
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
private void OnUseSlot1(InputAction.CallbackContext ctx) { if (ctx.performed) UseCard(0); }
|
|
private void OnUseSlot2(InputAction.CallbackContext ctx) { if (ctx.performed) UseCard(1); }
|
|
private void OnSpecialMovePerformed(InputAction.CallbackContext ctx)
|
|
{
|
|
if (!ctx.performed) return;
|
|
// Always consume the left-most available card (sequential use)
|
|
// UseCard will compact and raise events/UI.
|
|
if (slots[0] != null) { UseCard(0); return; }
|
|
if (slots[1] != null) { UseCard(1); return; }
|
|
// no card -> ignore
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Try adding a card to the first empty slot. Returns true if added.
|
|
/// </summary>
|
|
public bool TryAddCard(CardData data)
|
|
{
|
|
if (data == null) return false;
|
|
if (slots[0] == null) { slots[0] = data; OnChanged(); GameEvents.RaiseCardPicked(data); return true; }
|
|
if (slots[1] == null) { slots[1] = data; OnChanged(); GameEvents.RaiseCardPicked(data); return true; }
|
|
return false; // full
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses a card at slot index (0 or 1). Consumes immediately and plays mapped animation.
|
|
/// </summary>
|
|
public void UseCard(int slotIndex)
|
|
{
|
|
if (slotIndex < 0 || slotIndex > 1) return;
|
|
var data = slots[slotIndex];
|
|
if (data == null)
|
|
{
|
|
// If only one card and user pressed empty slot, optionally redirect to occupied slot
|
|
if (allowUseWithSingleCard && Count == 1)
|
|
{
|
|
int other = slotIndex == 0 ? 1 : 0;
|
|
if (slots[other] != null) { UseCard(other); }
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Consume first
|
|
slots[slotIndex] = null;
|
|
CompactLeft();
|
|
OnChanged();
|
|
GameEvents.RaiseCardUsed(data, slotIndex);
|
|
|
|
// Play animation
|
|
if (animationBridge)
|
|
{
|
|
animationBridge.PlaySpecialMove(data.animationTriggerName);
|
|
}
|
|
|
|
// Optional particles could be spawned by listening to GameEvents.CardUsed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a snapshot of slots (length 2). Do not mutate the returned array.
|
|
/// </summary>
|
|
public CardData[] GetSnapshot()
|
|
{
|
|
var snap = new CardData[2];
|
|
snap[0] = slots[0];
|
|
snap[1] = slots[1];
|
|
return snap;
|
|
}
|
|
|
|
private void CompactLeft()
|
|
{
|
|
// Keep items packed to the left: [A,null] => [A,null], [null,B] => [B,null]
|
|
if (slots[0] == null && slots[1] != null)
|
|
{
|
|
slots[0] = slots[1];
|
|
slots[1] = null;
|
|
}
|
|
}
|
|
|
|
private void OnChanged()
|
|
{
|
|
PushUI();
|
|
GameEvents.RaisePocketChanged(GetSnapshot(), Count);
|
|
}
|
|
|
|
private void PushUI()
|
|
{
|
|
if (pocketUI == null)
|
|
{
|
|
// Attempt to resolve again at first use; if still missing, start a short retry loop
|
|
TryResolvePocketUIOnce();
|
|
if (pocketUI == null && resolveUICoroutine == null)
|
|
resolveUICoroutine = StartCoroutine(ResolvePocketUIRoutine());
|
|
}
|
|
|
|
if (pocketUI)
|
|
pocketUI.UpdateUI(GetSnapshot());
|
|
}
|
|
|
|
private void TryResolvePocketUIOnce()
|
|
{
|
|
if (string.IsNullOrEmpty(pocketUITag)) return;
|
|
var go = GameObject.FindWithTag(pocketUITag);
|
|
if (go == null) return;
|
|
var ui = go.GetComponent<UIController_Pocket>();
|
|
if (ui != null) pocketUI = ui;
|
|
}
|
|
|
|
private System.Collections.IEnumerator ResolvePocketUIRoutine()
|
|
{
|
|
// Try for a short window (e.g., next 1 second) in case UI spawns after the player
|
|
float timeout = 1.0f;
|
|
float t = 0f;
|
|
while (pocketUI == null && t < timeout)
|
|
{
|
|
TryResolvePocketUIOnce();
|
|
if (pocketUI != null) break;
|
|
t += Time.deltaTime;
|
|
yield return null;
|
|
}
|
|
resolveUICoroutine = null;
|
|
if (pocketUI)
|
|
pocketUI.UpdateUI(GetSnapshot());
|
|
else
|
|
Debug.LogWarning("PlayerPocket: Could not find Pocket UI by tag '" + pocketUITag + "' within 1s. Make sure the UI exists and is tagged.");
|
|
}
|
|
}
|
|
}
|