chunk 1: core gameplay scripts scenes runtime assets

This commit is contained in:
2026-04-06 11:02:34 +03:00
parent fa0388bc79
commit 0d11a097d8
703 changed files with 2292651 additions and 0 deletions

View File

@@ -0,0 +1,240 @@
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.");
}
}
}