Files

409 lines
10 KiB
C#
Raw Permalink Normal View History

2026-06-04 22:14:21 +03:00
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<TalentData> allTalents = new List<TalentData>();
public PlayerStats playerStats;
[Header("Runtime Progress")]
public HashSet<string> learnedTalentIds = new HashSet<string>();
public int availableTalentPoints = 1;
public int startingTalentPoints = 1;
[Header("Feedback")]
public NotificationUI notificationUI;
private readonly Dictionary<string, TalentData> talentLookup = new Dictionary<string, TalentData>();
public event Action OnTalentStateChanged;
public event Action<string> OnNotificationRequested;
private void Awake()
{
if (playerStats == null)
{
playerStats = FindObjectOfType<PlayerStats>();
}
if (notificationUI == null)
{
notificationUI = FindObjectOfType<NotificationUI>();
}
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<TalentData> 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<TalentSaveData>(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);
}
}
}