using System; using System.Collections.Generic; using OTGIntegrated.Core; using OTGIntegrated.Player; using OTGIntegrated.Stats; using UnityEngine; namespace OTGIntegrated.Abilities { public sealed class AbilityManager : MonoBehaviour { public List learnedAbilities = new List(); public Transform castOrigin; private readonly List cooldownRemaining = new List(); private PlayerStats playerStats; private PlayerController playerController; public event Action OnAbilitiesChanged; public IReadOnlyList LearnedAbilities => learnedAbilities; public void Initialize(PlayerStats stats) { playerStats = stats != null ? stats : GetComponent(); playerController = GetComponent(); if (castOrigin == null) { castOrigin = transform; } EnsureCooldownSlots(); OnAbilitiesChanged?.Invoke(); } private void Awake() { if (playerStats == null) { playerStats = GetComponent(); } playerController = GetComponent(); EnsureCooldownSlots(); } private void Update() { TickCooldowns(); if (playerController != null && !playerController.InputEnabled) { return; } if (Input.GetKeyDown(KeyCode.Alpha1)) TryUseAbility(0); if (Input.GetKeyDown(KeyCode.Alpha2)) TryUseAbility(1); if (Input.GetKeyDown(KeyCode.Alpha3)) TryUseAbility(2); } public bool TryUseAbility(int index) { EnsureCooldownSlots(); if (index < 0 || index >= learnedAbilities.Count) { return false; } AbilityData ability = learnedAbilities[index]; if (ability == null || playerStats == null) { return false; } if (cooldownRemaining[index] > 0f) { GameEvents.RaiseNotification("Способность на перезарядке"); return false; } if (!playerStats.SpendMana(ability.manaCost)) { return false; } if (ability.effectPrefab == null) { playerStats.RestoreMana(ability.manaCost); Debug.LogWarning($"Ability has no effect prefab: {ability.displayName}", ability); return false; } Vector3 forward = GetCastForward(); Vector3 origin = castOrigin != null ? castOrigin.position : transform.position + Vector3.up; GameObject effectObject = Instantiate(ability.effectPrefab, origin, Quaternion.LookRotation(forward, Vector3.up)); AbilityEffect effect = effectObject.GetComponent(); if (effect == null) { playerStats.RestoreMana(ability.manaCost); Destroy(effectObject); Debug.LogWarning($"Ability effect prefab misses AbilityEffect: {ability.effectPrefab.name}", ability.effectPrefab); return false; } effect.Activate(new AbilityContext(ability, transform, playerStats, origin, forward)); cooldownRemaining[index] = GetEffectiveCooldown(ability); GameEvents.RaiseAbilityUsed(ability); GameEvents.RaiseNotification($"Способность: {ability.displayName}"); OnAbilitiesChanged?.Invoke(); return true; } public float GetCooldownRemaining(int index) { EnsureCooldownSlots(); return index >= 0 && index < cooldownRemaining.Count ? cooldownRemaining[index] : 0f; } public float GetCooldownNormalized(int index) { if (index < 0 || index >= learnedAbilities.Count || learnedAbilities[index] == null) { return 0f; } float cooldown = GetEffectiveCooldown(learnedAbilities[index]); return cooldown <= 0f ? 0f : Mathf.Clamp01(GetCooldownRemaining(index) / cooldown); } private float GetEffectiveCooldown(AbilityData ability) { float reduction = playerStats != null ? playerStats.CooldownReduction : 0f; return Mathf.Max(0f, ability.cooldown * (1f - Mathf.Clamp(reduction, 0f, 0.9f))); } private void TickCooldowns() { EnsureCooldownSlots(); bool changed = false; for (int i = 0; i < cooldownRemaining.Count; i++) { if (cooldownRemaining[i] <= 0f) { continue; } cooldownRemaining[i] = Mathf.Max(0f, cooldownRemaining[i] - Time.deltaTime); changed = true; } if (changed) { OnAbilitiesChanged?.Invoke(); } } private void EnsureCooldownSlots() { while (cooldownRemaining.Count < learnedAbilities.Count) { cooldownRemaining.Add(0f); } while (cooldownRemaining.Count > learnedAbilities.Count) { cooldownRemaining.RemoveAt(cooldownRemaining.Count - 1); } } private Vector3 GetCastForward() { if (playerController != null) { return playerController.GetAimForward(); } Vector3 forward = transform.forward; forward.y = 0f; return forward.sqrMagnitude > 0.001f ? forward.normalized : Vector3.forward; } } }