Scripts/TreeSkills/TreeSkillState.cs
using System;
using Milaine.Interfaces;
namespace Milaine.TreeSkills;
public sealed class TreeSkillState : IManager {
public static TreeSkillState Instance { get; private set; } = new();
public List<SkillNodeData> Nodes { get; private set; } = [];
public HashSet<string> Unlocked { get; private set; } = [];
public Func<int> GetMoney { get; set; } = () => 0;
public Func<int> GetSkillPoints { get; set; } = () => 0;
public Action<int> SpendMoney { get; set; } = amount => { };
public Action SpendPoint { get; set; } = () => { };
public Action OnChanged { get; set; }
public Action<SkillNodeData> OnUnlocked;
public TreeSkillState() {
BuildDefaultTree();
}
private void BuildDefaultTree() {
Nodes = [
// Root Node
new() {
Id = "core",
Name = "milaine.skill.dollars",
Description = "milaine.skill.dollars.desc",
KeyDescription = "DollarCategoryValue",
Cost = 1,
Category = SkillCategory.Dollars,
Point = 1,
Row = 3,
Column = 3,
Icon = "🐦🔥",
IsRoot = true
},
// branch to top (vertical)
new() {
Id = "skill_wallet_1",
Name = "milaine.skill.wallet",
Description = "milaine.skill.wallet.desc",
KeyDescription = "WalletCategoryValue",
Cost = 600,
Category = SkillCategory.Wallet,
Point = 1,
Row = 2,
Column = 3,
Icon = "✊"
},
new() {
Id = "skill_wallet_2",
Name = "milaine.skill.wallet-v2",
Description = "milaine.skill.wallet-v2.desc",
KeyDescription = "WalletCategoryV2Value",
Cost = 1_000,
Category = SkillCategory.Wallet,
Point = 2,
Row = 1,
Column = 3,
Icon = "✊"
},
// branch to bottom (vertical)
new() {
Id = "skill_dollar_1",
Name = "milaine.skill.dollars-v2",
Description = "milaine.skill.dollars-v2.desc",
KeyDescription = "DollarCategoryV2Value",
Cost = 20,
Category = SkillCategory.Dollars,
Point = 2,
Row = 4,
Column = 3,
Icon = "🫁"
},
new() {
Id = "skill_dollar_2",
Name = "milaine.skill.dollars-v3",
Description = "milaine.skill.dollars-v3.desc",
KeyDescription = "DollarCategoryV3Value",
Cost = 400,
Category = SkillCategory.Dollars,
Point = 6,
Row = 5,
Column = 3,
Icon = "🫁"
},
// branch to right (horizontal)
new() {
Id = "skill_dividends_1",
Name = "milaine.skill.dividends",
Description = "milaine.skill.dividends.desc",
KeyDescription = "DividendsCategoryValue",
Cost = 1_000,
Category = SkillCategory.Dividends,
Point = 0.02f,
Row = 3,
Column = 4,
Icon = "☕"
},
new() {
Id = "skill_dividends_2",
Name = "milaine.skill.dividends-v2",
Description = "milaine.skill.dividends-v2.desc",
KeyDescription = "DividendsCategoryV2Value",
Cost = 2_000,
Category = SkillCategory.Dividends,
Point = 0.03f,
Row = 3,
Column = 5,
Icon = "☕"
},
// branch to left (horizontal)
new() {
Id = "skill_investment_1",
Name = "milaine.skill.investment",
Description = "milaine.skill.investment.desc",
KeyDescription = "InvestmentCategoryValue",
Cost = 600,
Category = SkillCategory.Investment,
Point = 1,
Row = 3,
Column = 2,
Icon = "🖐️"
},
new() {
Id = "skill_investment_2",
Name = "milaine.skill.investment-v2",
Description = "milaine.skill.investment-v2.desc",
KeyDescription = "InvestmentCategoryV2Value",
Cost = 1_000,
Category = SkillCategory.Investment,
Point = 2,
Row = 3,
Column = 1,
Icon = "🖐️"
},
// branch to diagonal (arriba-derecha)
new() {
Id = "skill_walletplus_1",
Name = "milaine.skill.wallet_plus",
Description = "milaine.skill.wallet_plus.desc",
KeyDescription = "WalletPlusCategoryValue",
Cost = 600,
Category = SkillCategory.WalletPlus,
Point = 2,
Row = 2,
Column = 4,
Icon = "🍀"
},
new() {
Id = "skill_walletplus_2",
Name = "milaine.skill.wallet_plus-v2",
Description = "milaine.skill.wallet_plus-v2.desc",
KeyDescription = "WalletPlusCategoryV2Value",
Cost = 1_000,
Category = SkillCategory.WalletPlus,
Point = 2.5f,
Row = 1,
Column = 5,
Icon = "🍀"
},
// branch to diagonal (abajo-izquierda) — special node like example
new() {
Id = "skill_dollars_magic_1",
Name = "milaine.skill.magic_dollar",
Description = "milaine.skill.magic_dollar.desc",
KeyDescription = "MagicDollarCategoryValue",
Cost = 20,
Category = SkillCategory.DollarAutomatic,
Point = 1,
Row = 4,
Column = 2,
Icon = "⚡"
},
new() {
Id = "skill_dollars_magic_2",
Name = "milaine.skill.magic_dollar-v2",
Description = "milaine.skill.magic_dollar-v2.desc",
KeyDescription = "MagicDollarCategoryV2Value",
Cost = 100,
Category = SkillCategory.DollarAutomatic,
Point = 2,
Row = 5,
Column = 1,
Icon = "⚡"
},
new() {
Id = "skill_dollars_magic_3",
Name = "milaine.skill.magic_dollar-v3",
Description = "milaine.skill.magic_dollar-v3.desc",
KeyDescription = "MagicDollarCategoryV3Value",
Cost = 600,
Category = SkillCategory.DollarAutomatic,
Point = 4,
Row = 6,
Column = 0,
Icon = "⚡"
},
new() {
Id = "skill_dollars_magic_4",
Name = "milaine.skill.magic_dollar-v4",
Description = "milaine.skill.magic_dollar-v4.desc",
KeyDescription = "MagicDollarCategoryV4Value",
Cost = 1_000,
Category = SkillCategory.DollarAutomatic,
Point = 10,
Row = 7,
Column = -1,
Icon = "⚡"
},
new() {
Id = "skill_dollars_magic_5",
Name = "milaine.skill.magic_dollar-v5",
Description = "milaine.skill.magic_dollar-v5.desc",
KeyDescription = "MagicDollarCategoryV5Value",
Cost = 1_500,
Category = SkillCategory.DollarAutomatic,
Point = 20,
Row = 8,
Column = -2,
Icon = "⚡"
},
];
}
public SkillNodeData GetNodeAt(int row, int column) => Nodes.FirstOrDefault(n => n.Row == row && n.Column == column);
public bool IsUnlocked(SkillNodeData node) => node != null && Unlocked.Contains(node.Id);
public bool CanUnlock(SkillNodeData node) {
if (node == null || IsUnlocked(node)) {
return false;
}
if (node.IsRoot) {
return true;
}
for (int dr = -1; dr <= 1; dr++) {
for (int dc = -1; dc <= 1; dc++) {
if (dr == 0 && dc == 0) {
continue;
}
var neighbor = GetNodeAt(node.Row + dr, node.Column + dc);
if (neighbor != null && IsUnlocked(neighbor)) {
return true;
}
}
}
return false;
}
public bool TryUnlock(SkillNodeData node) {
if (!CanUnlock(node)) {
return false;
}
if (GetMoney() < node.Cost && GetSkillPoints() <= 0) {
return false;
}
if (GetSkillPoints() > 0) {
SpendPoint();
} else {
SpendMoney(node.Cost);
}
Unlocked.Add(node.Id);
OnUnlocked?.Invoke(node);
OnChanged?.Invoke();
return true;
}
public IEnumerable<SkillNodeData> GetExistingNeighbors(SkillNodeData node) {
for (int dr = -1; dr <= 1; dr++) {
for (int dc = -1; dc <= 1; dc++) {
if (dr == 0 && dc == 0) {
continue;
}
var neighbor = GetNodeAt(node.Row + dr, node.Column + dc);
if (neighbor != null) {
yield return neighbor;
}
}
}
}
public bool CanPurchase(SkillNodeData node) {
return GetMoney() >= node.Cost || GetSkillPoints() > 0;
}
public void Reset() {
Unlocked.Clear();
BuildDefaultTree();
OnChanged?.Invoke();
}
public void Restart() {
Reset();
}
}