using System; using System.Collections.Generic; using System.Linq; using UnityEngine; public class TalentManager : MonoBehaviour { private const string SaveKey = "Lab07_TalentProgress"; private const string TalentSourcePrefix = "Talent:"; [Header("Data")] public List allTalents = new List(); public PlayerStats playerStats; [Header("Runtime Progress")] public HashSet learnedTalentIds = new HashSet(); public int availableTalentPoints = 1; public int startingTalentPoints = 1; [Header("Feedback")] public NotificationUI notificationUI; private readonly Dictionary talentLookup = new Dictionary(); public event Action OnTalentStateChanged; public event Action OnNotificationRequested; private void Awake() { if (playerStats == null) { playerStats = FindObjectOfType(); } if (notificationUI == null) { notificationUI = FindObjectOfType(); } BuildTalentLookup(); } private void Start() { LoadProgress(false); } private void Update() { if (Input.GetKeyDown(KeyCode.T)) { AddTalentPoints(1); } if (Input.GetKeyDown(KeyCode.F5)) { SaveProgress(); } if (Input.GetKeyDown(KeyCode.F9)) { LoadProgress(); } if (Input.GetKeyDown(KeyCode.Backspace)) { ResetProgress(); } } public bool IsLearned(TalentData talent) { return IsValidTalent(talent) && learnedTalentIds.Contains(talent.talentId); } public bool CanLearn(TalentData talent) { return IsValidTalent(talent) && !IsLearned(talent) && availableTalentPoints >= Mathf.Max(0, talent.cost) && ArePrerequisitesLearned(talent); } public bool ArePrerequisitesLearned(TalentData talent) { if (!IsValidTalent(talent)) { return false; } if (talent.prerequisites == null || talent.prerequisites.Length == 0) { return true; } for (int i = 0; i < talent.prerequisites.Length; i++) { TalentData prerequisite = talent.prerequisites[i]; if (!IsValidTalent(prerequisite) || !IsLearned(prerequisite)) { return false; } } return true; } public void LearnTalent(TalentData talent) { if (!IsValidTalent(talent)) { Notify("Некорректный талант"); return; } if (IsLearned(talent)) { Notify($"Талант уже изучен: {talent.displayName}"); return; } if (!ArePrerequisitesLearned(talent)) { Notify(GetBlockingReason(talent)); return; } int cost = Mathf.Max(0, talent.cost); if (availableTalentPoints < cost) { Notify("Недостаточно очков талантов"); return; } availableTalentPoints -= cost; learnedTalentIds.Add(talent.talentId); ApplyTalentModifiers(talent); SaveProgress(false); Notify($"Изучен талант: {talent.displayName}"); RaiseTalentStateChanged(); } public void AddTalentPoints(int amount) { if (amount <= 0) { return; } availableTalentPoints = Mathf.Max(0, availableTalentPoints + amount); Notify(amount == 1 ? "Получено очко талантов" : $"Получено очков талантов: {amount}"); RaiseTalentStateChanged(); } public void ApplyTalentModifiers(TalentData talent) { if (!IsValidTalent(talent) || playerStats == null || talent.modifiers == null) { return; } string sourceId = GetModifierSourceId(talent); playerStats.RemoveModifiersBySource(sourceId); for (int i = 0; i < talent.modifiers.Length; i++) { StatModifier modifier = talent.modifiers[i]; modifier.sourceId = sourceId; playerStats.AddModifier(modifier); } } public void RebuildAllModifiers() { if (playerStats == null) { return; } playerStats.RemoveModifiersBySourcePrefix(TalentSourcePrefix); for (int i = 0; i < allTalents.Count; i++) { TalentData talent = allTalents[i]; if (IsLearned(talent)) { ApplyTalentModifiers(talent); } } playerStats.RecalculateStats(); RaiseTalentStateChanged(); } public void SaveProgress() { SaveProgress(true); } public void LoadProgress() { LoadProgress(true); } public void ResetProgress() { PlayerPrefs.DeleteKey(SaveKey); ResetRuntimeToDefaults(); RebuildAllModifiers(); Notify("Прогресс сброшен"); RaiseTalentStateChanged(); } public int GetAvailableTalentPoints() { return availableTalentPoints; } public List GetLearnedTalents() { BuildTalentLookup(); return allTalents.Where(IsLearned).ToList(); } public string GetBlockingReason(TalentData talent) { if (!IsValidTalent(talent)) { return "Некорректные данные таланта"; } if (IsLearned(talent)) { return "Талант уже изучен"; } if (talent.prerequisites != null) { for (int i = 0; i < talent.prerequisites.Length; i++) { TalentData prerequisite = talent.prerequisites[i]; if (!IsValidTalent(prerequisite)) { return "Некорректное требование таланта"; } if (!IsLearned(prerequisite)) { return $"Требуется талант: {prerequisite.displayName}"; } } } if (availableTalentPoints < Mathf.Max(0, talent.cost)) { return "Недостаточно очков талантов"; } return "Доступно"; } public TalentData GetTalentById(string talentId) { BuildTalentLookup(); if (string.IsNullOrWhiteSpace(talentId)) { return null; } talentLookup.TryGetValue(talentId, out TalentData talent); return talent; } private void SaveProgress(bool notify) { TalentSaveData saveData = new TalentSaveData { availableTalentPoints = Mathf.Max(0, availableTalentPoints), learnedTalentIds = learnedTalentIds .Where(id => !string.IsNullOrWhiteSpace(id)) .OrderBy(id => id, StringComparer.Ordinal) .ToList() }; PlayerPrefs.SetString(SaveKey, JsonUtility.ToJson(saveData)); PlayerPrefs.Save(); if (notify) { Notify("Прогресс сохранён"); } } private void LoadProgress(bool notify) { BuildTalentLookup(); learnedTalentIds.Clear(); if (PlayerPrefs.HasKey(SaveKey)) { try { TalentSaveData saveData = JsonUtility.FromJson(PlayerPrefs.GetString(SaveKey)); availableTalentPoints = Mathf.Max(0, saveData != null ? saveData.availableTalentPoints : startingTalentPoints); if (saveData != null && saveData.learnedTalentIds != null) { for (int i = 0; i < saveData.learnedTalentIds.Count; i++) { string talentId = saveData.learnedTalentIds[i]; if (talentLookup.ContainsKey(talentId)) { learnedTalentIds.Add(talentId); } } } } catch (Exception exception) { Debug.LogWarning($"Talent progress was corrupted and will be reset. {exception.Message}"); ResetRuntimeToDefaults(); } } else { ResetRuntimeToDefaults(); } EnsureDefaultTalentsLearned(); RebuildAllModifiers(); if (notify) { Notify("Прогресс загружен"); } RaiseTalentStateChanged(); } private void ResetRuntimeToDefaults() { learnedTalentIds.Clear(); availableTalentPoints = Mathf.Max(0, startingTalentPoints); EnsureDefaultTalentsLearned(); } private void EnsureDefaultTalentsLearned() { for (int i = 0; i < allTalents.Count; i++) { TalentData talent = allTalents[i]; if (IsValidTalent(talent) && talent.unlockedByDefault) { learnedTalentIds.Add(talent.talentId); } } } private void BuildTalentLookup() { talentLookup.Clear(); for (int i = 0; i < allTalents.Count; i++) { TalentData talent = allTalents[i]; if (!IsValidTalent(talent)) { continue; } if (talentLookup.ContainsKey(talent.talentId)) { Debug.LogWarning($"Duplicate talent id ignored: {talent.talentId}"); continue; } talentLookup.Add(talent.talentId, talent); } } private bool IsValidTalent(TalentData talent) { return talent != null && !string.IsNullOrWhiteSpace(talent.talentId); } private string GetModifierSourceId(TalentData talent) { return TalentSourcePrefix + talent.talentId; } private void RaiseTalentStateChanged() { OnTalentStateChanged?.Invoke(); } private void Notify(string message) { if (string.IsNullOrWhiteSpace(message)) { return; } OnNotificationRequested?.Invoke(message); if (notificationUI != null) { notificationUI.Show(message); } } }