UI/SkillTreePanel.razor
@using Sandbox.UI
@using System
@using System.Linq
@namespace Prototype
@inherits Panel
<root class="progress-tree">
<div class="tree-heading"><div class="tree-title"><label class="tree-eyebrow" style="font-size:13px; font-weight:500; letter-spacing:0px; color:#97b6c7;">DASHER / PERMANENT PROGRESSION</label><label class="tree-main-title" style="font-size:38px; font-weight:700; letter-spacing:0px; color:#f3f7fa;">Skill Tree</label><label class="tree-subtitle" style="font-size:18px; font-weight:400; letter-spacing:0px; color:#b8cbd7;">Build your strengths. Upgrades apply to your next run.</label></div><div class="tree-wallet"><label>YOUR COINS</label><label class="wallet-value">@GameProfile.Progress.Coins.ToString("N0")</label></div></div>
<div class="tree-workspace">
<div class="tree-branches">
@for(var b = 0; b < 3; b++)
{
var branch = b;
<div class="tree-branch branch-@branch">
<label class="branch-name">@(new[]{"SURVIVAL", "FIREPOWER", "MOBILITY"}[branch])</label>
@foreach(var skill in HeroCatalog.Skills.Where(x => x.Branch == branch).OrderBy(x => x.Tier))
{
var item = skill; var rank = GameProfile.SkillRank(item.Id); var locked = GameProfile.SkillRequirement(item).Length > 0;
@if(item.Tier > 0){<div class="tree-link @(locked ? "" : "unlocked")"></div>}
<button class="talent @(Selected.Id == item.Id ? "selected" : "") @(locked ? "locked" : "") @(rank == item.MaxRank ? "complete" : "")" onclick="@(() => Choose(item.Id))">
<div class="talent-icon"><MenuGlyph Kind="@item.Icon" /></div>
<div class="talent-text"><label class="talent-tier" style="font-size:13px; font-weight:400; letter-spacing:0px; color:#adc5d3;">LEVEL @(item.Tier+1) @(locked ? " / LOCKED" : "")</label><label class="talent-name" style="font-size:21px; font-weight:600; letter-spacing:0px; color:#f2f6fa;">@item.Name</label><div class="rank-marks">@for(var r=0;r<item.MaxRank;r++){<div class="rank-mark @(r < rank ? "filled" : "")"></div>}</div></div>
<label class="talent-rank" style="font-size:16px; font-weight:500; letter-spacing:0px; color:#d1e1ea;">@rank / @item.MaxRank</label>
</button>
}
</div>
}
</div>
<div class="talent-details">
<div class="detail-icon"><MenuGlyph Kind="@Selected.Icon" /></div>
<label class="tree-eyebrow" style="font-size:13px; font-weight:500; letter-spacing:0px; color:#97b6c7;">LEVEL @(Selected.Tier+1) UPGRADE</label>
<label class="detail-title" style="font-size:28px; font-weight:600; letter-spacing:0px; color:#f2f6fa;">@Selected.Name</label>
<label class="detail-description" style="font-size:20px; font-weight:400; letter-spacing:0px; color:#d5e4ec;">@Selected.Description</label>
<div class="detail-rank"><label style="font-size:15px; font-weight:400; letter-spacing:0px; color:#b8cbd7;">Current rank</label><label style="font-size:18px; font-weight:600; letter-spacing:0px; color:#f2f6fa;">@GameProfile.SkillRank(Selected.Id) / @Selected.MaxRank</label></div>
<label class="detail-requirement" style="font-size:16px; font-weight:400; letter-spacing:0px; color:#e5cf8b;">@(GameProfile.SkillRequirement(Selected).Length > 0 ? GameProfile.SkillRequirement(Selected) : "Ready to upgrade")</label>
<button class="talent-buy @(!CanBuy ? "unavailable" : "")" disabled="@(!CanBuy)" onclick="@Purchase"><label style="font-size:18px; font-weight:600; letter-spacing:0px; color:#ffffff;">@BuyLabel</label></button>
<label class="detail-note" style="font-size:16px; font-weight:400; letter-spacing:0px; color:#aebfcb;">Reach rank 2 to unlock the next skill in this branch. Each skill has 3 ranks.</label>
</div>
</div>
</root>
@code {
private string _selected = "tree_heart";
private SkillSpec Selected => HeroCatalog.Skills.First(x => x.Id == _selected);
private bool CanBuy => GameProfile.SkillRank(Selected.Id) < Selected.MaxRank && GameProfile.SkillRequirement(Selected).Length == 0 && GameProfile.Progress.Coins >= GameProfile.SkillCost(Selected);
private string BuyLabel => GameProfile.SkillRank(Selected.Id) >= Selected.MaxRank ? "Fully upgraded" : GameProfile.SkillRequirement(Selected).Length > 0 ? "Locked" : GameProfile.Progress.Coins < GameProfile.SkillCost(Selected) ? $"Need {GameProfile.SkillCost(Selected):N0} coins" : $"Upgrade - {GameProfile.SkillCost(Selected):N0} coins";
private void Choose(string id) { _selected = id; StateHasChanged(); }
private void Purchase() { GameProfile.BuySkill(Selected); StateHasChanged(); }
protected override int BuildHash() => HashCode.Combine(_selected, GameProfile.Progress.Coins, HeroCatalog.Skills.Sum(x => GameProfile.SkillRank(x.Id)));
}