using Core; using Input; using Minigames.Airplane.Abilities; using Minigames.Airplane.Core; using TMPro; using UnityEngine; using UnityEngine.UI; namespace Minigames.Airplane.UI { /// /// UI button for activating airplane special abilities. /// Handles input, visual feedback, and cooldown display. /// Implements ITouchInputConsumer to properly handle hold/release for Jet ability. /// public class AirplaneAbilityButton : MonoBehaviour, ITouchInputConsumer { #region Inspector References [Header("UI Components")] [SerializeField] private Button button; [SerializeField] private Image abilityIcon; [SerializeField] private Image cooldownFill; [SerializeField] private TextMeshProUGUI cooldownText; [Header("Active State Visual")] [Tooltip("Visual element shown when ability is active (e.g., glowing border, pulsing overlay)")] [SerializeField] private GameObject activeStateVisual; [Header("Debug")] [SerializeField] private bool showDebugLogs; #endregion #region State private BaseAirplaneAbility currentAbility; private AirplaneController currentAirplane; private bool isHoldAbility; // Jet plane needs hold mechanic private bool isHolding; // Track if button is currently being held #endregion #region Lifecycle private void Awake() { if (button != null) { button.onClick.AddListener(OnButtonClick); } // Hide active state visual by default if (activeStateVisual != null) { activeStateVisual.SetActive(false); } // Hide by default gameObject.SetActive(false); } private void Update() { if (currentAbility == null) return; // Priority 1: Show active duration if ability is active if (currentAbility.IsActive && currentAbility.HasActiveDuration) { // NO fill during active duration - only text and active visual if (cooldownFill != null) { cooldownFill.fillAmount = 0f; } // Show remaining active time if (cooldownText != null) { cooldownText.text = $"{currentAbility.ActiveDurationRemaining:F1}s"; } } // Priority 2: Show cooldown if on cooldown else if (currentAbility.IsOnCooldown) { // Fill always starts at 1 (full) and reduces to 0, regardless of actual cooldown duration float fillAmount = currentAbility.InitialCooldownDuration > 0f ? currentAbility.CooldownRemaining / currentAbility.InitialCooldownDuration : 0f; if (cooldownFill != null) { cooldownFill.fillAmount = fillAmount; } // Show timer text if (cooldownText != null) { cooldownText.text = $"{currentAbility.CooldownRemaining:F1}s"; } } else { // Normal state - fill at 0, no text if (cooldownFill != null) cooldownFill.fillAmount = 0f; if (cooldownText != null) cooldownText.text = ""; } } private void OnDestroy() { // Unsubscribe from events if (currentAbility != null) { currentAbility.OnAbilityActivated -= HandleAbilityActivated; currentAbility.OnAbilityDeactivated -= HandleAbilityDeactivated; currentAbility.OnCooldownChanged -= HandleCooldownChanged; currentAbility.OnActiveDurationChanged -= HandleActiveDurationChanged; } // Unregister from input system if (InputManager.Instance != null) { InputManager.Instance.UnregisterOverrideConsumer(this); } } #endregion #region Public API /// /// Setup button with airplane and ability reference. /// public void Setup(AirplaneController airplane, BaseAirplaneAbility ability) { currentAirplane = airplane; currentAbility = ability; isHolding = false; // Set icon and show immediately if (abilityIcon != null && ability != null) { abilityIcon.sprite = ability.AbilityIcon; abilityIcon.enabled = true; } // Initialize cooldown display if (cooldownFill != null) { cooldownFill.fillAmount = 0f; } if (cooldownText != null) { cooldownText.text = ""; } // Check if this is a hold ability (Jet or Drop - abilities with active duration that can be interrupted) isHoldAbility = ability is JetAbility || ability is DropAbility; // Subscribe to ability events if (ability != null) { ability.OnAbilityActivated += HandleAbilityActivated; ability.OnAbilityDeactivated += HandleAbilityDeactivated; ability.OnCooldownChanged += HandleCooldownChanged; ability.OnActiveDurationChanged += HandleActiveDurationChanged; if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] Subscribed to ability events for: {ability.AbilityName}"); } } // Show UI gameObject.SetActive(true); if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] Setup complete with ability: {ability?.AbilityName ?? "None"}, Hold: {isHoldAbility}"); } } /// /// Hide and cleanup button. /// public void Hide() { if (currentAbility != null) { currentAbility.OnAbilityActivated -= HandleAbilityActivated; currentAbility.OnAbilityDeactivated -= HandleAbilityDeactivated; currentAbility.OnCooldownChanged -= HandleCooldownChanged; currentAbility.OnActiveDurationChanged -= HandleActiveDurationChanged; } // Unregister from input system if (InputManager.Instance != null) { InputManager.Instance.UnregisterOverrideConsumer(this); } // Hide active state visual HideActiveState(); currentAbility = null; currentAirplane = null; isHolding = false; gameObject.SetActive(false); } #endregion #region Input Handling private void OnButtonClick() { if (currentAirplane == null || currentAbility == null) return; if (!currentAbility.CanActivate) return; // Activate ability currentAirplane.ActivateAbility(); // For hold abilities (Jet), mark as holding and register for input if (isHoldAbility) { isHolding = true; // Register as override consumer to receive hold/release events if (InputManager.Instance != null) { InputManager.Instance.RegisterOverrideConsumer(this); } if (showDebugLogs) { Logging.Debug("[AirplaneAbilityButton] Started holding ability, registered for input"); } } // For non-hold abilities (Bobbing, Drop), this is all we need } #endregion #region Event Handlers private void HandleAbilityActivated(BaseAirplaneAbility ability) { // Show active state visual if ability has active duration if (ability.HasActiveDuration) { ShowActiveState(); } if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] Ability activated: {ability.AbilityName}"); } } private void HandleAbilityDeactivated(BaseAirplaneAbility ability) { // Hide active state visual HideActiveState(); if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] Ability deactivated: {ability.AbilityName}"); } } private void HandleCooldownChanged(float remaining, float total) { if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] OnCooldownChanged: remaining={remaining:F2}, total={total:F2}"); } // When cooldown starts (remaining == total), set fill to 1 if (remaining >= total - 0.01f && cooldownFill != null) { cooldownFill.fillAmount = 1f; if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] Cooldown started: {total}s, fill set to 1"); } } } private void HandleActiveDurationChanged(float remaining, float total) { if (showDebugLogs) { Logging.Debug($"[AirplaneAbilityButton] OnActiveDurationChanged: remaining={remaining:F2}, total={total:F2}"); } } #endregion #region ITouchInputConsumer Implementation public void OnTap(Vector2 position) { // If Jet ability is active (holding), next tap anywhere deactivates it if (isHoldAbility && isHolding) { isHolding = false; currentAirplane?.DeactivateAbility(); // Unregister from input system after tap if (InputManager.Instance != null) { InputManager.Instance.UnregisterOverrideConsumer(this); } if (showDebugLogs) { Logging.Debug("[AirplaneAbilityButton] Tap detected - deactivated Jet ability, unregistered"); } } // Handle as button click for non-hold abilities else if (!isHoldAbility) { OnButtonClick(); } } public void OnHoldStart(Vector2 position) { // Not used - button click handles activation, tap handles deactivation } public void OnHoldMove(Vector2 position) { // Not used } public void OnHoldEnd(Vector2 position) { // Not used - tap handles deactivation for Jet ability } #endregion #region Active State Visual API /// /// Show active state visual (placeholder API). /// Override or assign activeStateVisual GameObject in inspector for custom visuals. /// private void ShowActiveState() { if (activeStateVisual != null) { activeStateVisual.SetActive(true); if (showDebugLogs) { Logging.Debug("[AirplaneAbilityButton] Active state visual shown"); } } } /// /// Hide active state visual (placeholder API). /// private void HideActiveState() { if (activeStateVisual != null) { activeStateVisual.SetActive(false); if (showDebugLogs) { Logging.Debug("[AirplaneAbilityButton] Active state visual hidden"); } } } #endregion } }