using SoulHarvest.Common;
using SoulHarvest.Items;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
using System;
using System.Collections.Generic;
using System.Text;
using Terraria;
using Terraria.Audio;
using Terraria.GameContent;
using Terraria.GameContent.UI.Elements;
using Terraria.GameInput;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.UI;
namespace SoulHarvest.UI;
internal sealed class ReaperTreeNodeModel
{
public ReaperTreeNodeKind Kind { get; }
public ReaperFormId Form { get; }
public int Index { get; }
public ReaperCommonNode CommonNode { get; }
public Vector2 Position { get; }
public float Radius { get; }
public int CurrentLevel { get; private set; }
public int MaximumLevel { get; private set; } = 1;
public int ExpectedTarget { get; private set; }
public bool Completed { get; private set; }
public bool CanRequest { get; private set; }
public bool Affordable { get; private set; }
public bool Lit => Kind == ReaperTreeNodeKind.Stage ? Completed : CurrentLevel > 0;
public string Title { get; private set; } = string.Empty;
public string Category { get; private set; } = string.Empty;
public string Description { get; private set; } = string.Empty;
public string Status { get; private set; } = string.Empty;
public string Cost { get; private set; } = string.Empty;
private ReaperTreeNodeModel(ReaperTreeNodeKind kind, ReaperFormId form, int index, ReaperCommonNode commonNode, Vector2 position, float radius)
{
Kind = kind;
Form = form;
Index = index;
CommonNode = commonNode;
Position = position;
Radius = radius;
}
public static ReaperTreeNodeModel Base(Vector2 position) => new(ReaperTreeNodeKind.Base, ReaperFormId.Base, 0, default, position, 46f);
public static ReaperTreeNodeModel Death(Vector2 position) => new(ReaperTreeNodeKind.Death, ReaperFormId.Death, 0, default, position, 50f);
public static ReaperTreeNodeModel Stage(ReaperFormId form, int stage, Vector2 position) => new(ReaperTreeNodeKind.Stage, form, stage, default, position, 31f);
public static ReaperTreeNodeModel Skill(ReaperFormId form, int skill, Vector2 position) => new(ReaperTreeNodeKind.Skill, form, skill, default, position, 24f);
public static ReaperTreeNodeModel Common(ReaperCommonNode node, Vector2 position) => new(ReaperTreeNodeKind.Common, ReaperFormId.Base, 0, node, position, 23f);
public static ReaperTreeNodeModel DeathUpgrade(ReaperCommonNode node,
Vector2 position) => new(ReaperTreeNodeKind.DeathUpgrade,
ReaperFormId.Death, 0, node, position, 25f);
public void Refresh(Player player, ReaperProgressionState progression, int essenceCount, int souls)
{
CanRequest = false;
Affordable = false;
Cost = string.Empty;
switch (Kind)
{
case ReaperTreeNodeKind.Base:
CurrentLevel = MaximumLevel = 1;
Completed = true;
Title = ReaperUIData.GetFormName(ReaperFormId.Base);
Category = Text("CategoryBase");
Description = ReaperUIData.GetFormSummary(ReaperFormId.Base);
Status = Text("BaseAlwaysAvailable");
break;
case ReaperTreeNodeKind.Death:
RefreshDeath(progression);
break;
case ReaperTreeNodeKind.Stage:
RefreshStage(player, progression, essenceCount);
break;
case ReaperTreeNodeKind.Skill:
RefreshSkill(progression, souls);
break;
case ReaperTreeNodeKind.Common:
case ReaperTreeNodeKind.DeathUpgrade:
RefreshCommon(progression, essenceCount, souls);
break;
}
}
public void RequestUpgrade()
{
if (!CanRequest || !Affordable)
return;
switch (Kind)
{
case ReaperTreeNodeKind.Stage:
SoulHarvest.RequestReaperStageUpgrade(Form, (ReaperStage)ExpectedTarget);
break;
case ReaperTreeNodeKind.Skill:
SoulHarvest.RequestReaperSkillUpgrade(Form, Index, ExpectedTarget);
break;
case ReaperTreeNodeKind.Common:
case ReaperTreeNodeKind.DeathUpgrade:
SoulHarvest.RequestReaperCommonUpgrade(CommonNode, ExpectedTarget);
break;
}
}
private void RefreshDeath(ReaperProgressionState progression)
{
CurrentLevel = progression.DeathFormUnlocked ? 1 : 0;
MaximumLevel = 1;
Completed = progression.DeathFormUnlocked;
Title = ReaperUIData.GetFormName(ReaperFormId.Death);
Category = Text("CategoryFinal");
Description = $"{ReaperUIData.GetFormSummary(ReaperFormId.Death)}\n{Text("UltimateLabel", ReaperUIData.GetUltimateName(ReaperFormId.Death))}";
Status = Text(progression.DeathFormUnlocked ? "DeathUnlocked" : "DeathLocked");
int completedBranches = 0;
int masteredSkills = 0;
foreach (ReaperFormId form in ReaperDefinitions.BranchForms)
{
if (progression.GetStage(form) >= ReaperStage.StageIII)
completedBranches++;
for (int skill = 0; skill < ReaperDefinitions.SkillsPerBranch; skill++)
if (progression.GetSkillLevel(form, skill) >= ReaperDefinitions.MaximumSkillLevel)
masteredSkills++;
}
Cost = Text("DeathMasteryProgress", completedBranches, masteredSkills);
}
private void RefreshStage(Player player, ReaperProgressionState progression, int essenceCount)
{
int current = (int)progression.GetStage(Form);
// Each stage node is a binary unlock, not one shared three-rank node.
CurrentLevel = current >= Index ? 1 : 0;
MaximumLevel = 1;
ExpectedTarget = Index;
Completed = current >= Index;
CanRequest = current == Index - 1;
Title = $"{ReaperUIData.GetFormName(Form)} · {ReaperUIData.GetStageName(Index)}";
Category = Text("CategoryStage");
ReaperFormDefinition definition = ReaperDefinitions.GetForm(Form);
Description = Text("StageCombatStats", ReaperUIData.GetFormSummary(Form), definition.GetBaseDamage((ReaperStage)Index), definition.BaseUseTime);
if (Index == 3)
Description += $"\n{Text("UltimateLabel", ReaperUIData.GetUltimateName(Form))}";
int seals = progression.GetSoulSealAvailable((ReaperStage)Index);
int receiptCount = CountInventoryItem(player, ReaperSoulSealCatalog.GetItemType((ReaperStage)Index));
int essenceCost = ReaperDefinitions.GetStageEssenceCost((ReaperStage)Index);
bool worldReady = ReaperDefinitions.MeetsWorldGate((ReaperStage)Index);
Affordable = Completed || worldReady && seals > 0 && receiptCount > 0 && essenceCount >= essenceCost;
if (Completed)
{
Status = Text("StatusComplete");
Cost = Text("Completed");
}
else if (!CanRequest)
{
Status = Text("StatusLocked");
Cost = Text("StagePrerequisite", Index - 1);
}
else if (!worldReady)
{
Status = Text("StatusLocked");
Cost = Language.GetTextValue($"Mods.SoulHarvest.UI.ReaperTree.StageGates.Stage{Index}");
}
else if (seals <= 0)
{
Status = Text("StatusInsufficient");
Cost = Text("NoStageSeal");
}
else if (receiptCount <= 0)
{
Status = Text("StatusInsufficient");
Cost = Text("NoStageSealItem");
}
else
{
Status = Affordable ? Text("StatusReady") : Text("StatusInsufficient");
Cost = Text("StageCost", essenceCost, seals, receiptCount);
}
}
private void RefreshSkill(ReaperProgressionState progression, int souls)
{
int stage = (int)progression.GetStage(Form);
int level = progression.GetSkillLevel(Form, Index);
int requiredStage = Index + 1;
CurrentLevel = level;
MaximumLevel = ReaperDefinitions.MaximumSkillLevel;
ExpectedTarget = level + 1;
Completed = level >= MaximumLevel;
CanRequest = stage >= requiredStage && !Completed;
int soulCost = ReaperDefinitions.GetSkillSoulCost(level + 1);
Affordable = Completed || CanRequest && soulCost >= 0 && souls >= soulCost;
Title = ReaperUIData.GetSkillName(Form, Index);
Category = Text("CategorySkill", ReaperUIData.GetFormName(Form));
Description = ReaperUIData.GetSkillDescription(Form, Index);
if (stage < requiredStage)
{
Status = Text("StatusLocked");
Cost = Text("SkillStageLocked", requiredStage);
}
else if (Completed)
{
Status = Text("StatusComplete");
Cost = Text("CompletedLevel", level, MaximumLevel);
}
else
{
Status = Affordable ? Text("StatusReady") : Text("StatusInsufficient");
Cost = Text("SkillLevel", level, MaximumLevel, soulCost);
}
}
private void RefreshCommon(ReaperProgressionState progression, int essenceCount, int souls)
{
int level = progression.GetCommonNodeLevel(CommonNode);
int maximum = ReaperDefinitions.GetCommonNodeMaxLevel(CommonNode);
bool unlimited = ReaperDefinitions.IsUnlimitedCommonNode(CommonNode);
int targetLevel = level + 1;
ReaperStage requiredStage = ReaperDefinitions.GetCommonNodeRequiredStage(CommonNode, targetLevel);
ReaperProgressionCost? price = ReaperDefinitions.GetCommonNodeCost(CommonNode, targetLevel);
CurrentLevel = level;
MaximumLevel = maximum;
ExpectedTarget = targetLevel;
Completed = !unlimited && level >= maximum;
CanRequest = !Completed && progression.HasAnyBranchAtLeast(requiredStage);
if (ReaperDefinitions.IsDeathNode(CommonNode))
{
CanRequest &= progression.DeathFormUnlocked;
if (CommonNode == ReaperCommonNode.DeathRiftWidth)
{
CanRequest &= progression.GetCommonNodeLevel(
ReaperCommonNode.DeathSpaceBreak) > 0;
}
}
Affordable = Completed || CanRequest && price is ReaperProgressionCost cost
&& (cost.Currency == ReaperProgressionCurrency.Souls ? souls >= cost.Amount : essenceCount >= cost.Amount);
Title = ReaperUIData.GetCommonNodeName(CommonNode);
Category = Text(ReaperDefinitions.IsDeathNode(CommonNode)
? "CategoryDeathUpgrade" : "CategoryCommon");
Description = ReaperUIData.GetCommonNodeDescription(CommonNode);
if (Completed)
{
Status = Text("StatusComplete");
Cost = Text("CompletedLevel", level, maximum);
}
else if (!CanRequest)
{
Status = Text("StatusLocked");
Cost = ReaperDefinitions.IsDeathNode(CommonNode)
? Text("DeathUpgradeLocked")
: Text("CommonStageLocked", (int)requiredStage);
}
else if (price is ReaperProgressionCost commonCost)
{
Status = Affordable ? Text("StatusReady") : Text("StatusInsufficient");
Cost = unlimited
? Text("CommonUnlimitedLevel", level, FormatCost(commonCost))
: Text("CommonLevel", level, maximum, FormatCost(commonCost));
}
else
{
Status = Text("StatusLocked");
Cost = Text("Unavailable");
}
}
private static int CountInventoryItem(Player player, int itemType)
{
if (itemType <= ItemID.None)
return 0;
int count = 0;
int slots = Math.Min(58, player.inventory.Length);
for (int slot = 0; slot < slots; slot++)
{
Item item = player.inventory[slot];
if (item.type == itemType && item.stack > 0)
count += item.stack;
}
return count;
}
private static string FormatCost(ReaperProgressionCost cost) => Text(cost.Currency == ReaperProgressionCurrency.Souls ? "CostSouls" : "CostEssence", cost.Amount);
private static string Text(string key, params object[] args) => Language.GetTextValue($"Mods.SoulHarvest.UI.ReaperTree.{key}", args);
}
using SoulHarvest.Common;
using SoulHarvest.Items;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
using System;
using System.Collections.Generic;
using System.Text;
using Terraria;
using Terraria.Audio;
using Terraria.GameContent;
using Terraria.GameContent.UI.Elements;
using Terraria.GameInput;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.UI;
namespace SoulHarvest.UI;
internal sealed class ReaperTreeNodeModel
{
public ReaperTreeNodeKind Kind { get; }
public ReaperFormId Form { get; }
public int Index { get; }
public ReaperCommonNode CommonNode { get; }
public Vector2 Position { get; }
public float Radius { get; }
public int CurrentLevel { get; private set; }
public int MaximumLevel { get; private set; } = 1;
public int ExpectedTarget { get; private set; }
public bool Completed { get; private set; }
public bool CanRequest { get; private set; }
public bool Affordable { get; private set; }
public bool Lit => Kind == ReaperTreeNodeKind.Stage ? Completed : CurrentLevel > 0;
public string Title { get; private set; } = string.Empty;
public string Category { get; private set; } = string.Empty;
public string Description { get; private set; } = string.Empty;
public string Status { get; private set; } = string.Empty;
public string Cost { get; private set; } = string.Empty;
private ReaperTreeNodeModel(ReaperTreeNodeKind kind, ReaperFormId form, int index, ReaperCommonNode commonNode, Vector2 position, float radius)
{
Kind = kind;
Form = form;
Index = index;
CommonNode = commonNode;
Position = position;
Radius = radius;
}
public static ReaperTreeNodeModel Base(Vector2 position) => new(ReaperTreeNodeKind.Base, ReaperFormId.Base, 0, default, position, 46f);
public static ReaperTreeNodeModel Death(Vector2 position) => new(ReaperTreeNodeKind.Death, ReaperFormId.Death, 0, default, position, 50f);
public static ReaperTreeNodeModel Stage(ReaperFormId form, int stage, Vector2 position) => new(ReaperTreeNodeKind.Stage, form, stage, default, position, 31f);
public static ReaperTreeNodeModel Skill(ReaperFormId form, int skill, Vector2 position) => new(ReaperTreeNodeKind.Skill, form, skill, default, position, 24f);
public static ReaperTreeNodeModel Common(ReaperCommonNode node, Vector2 position) => new(ReaperTreeNodeKind.Common, ReaperFormId.Base, 0, node, position, 23f);
public static ReaperTreeNodeModel DeathUpgrade(ReaperCommonNode node,
Vector2 position) => new(ReaperTreeNodeKind.DeathUpgrade,
ReaperFormId.Death, 0, node, position, 25f);
public void Refresh(Player player, ReaperProgressionState progression, int essenceCount, int souls)
{
CanRequest = false;
Affordable = false;
Cost = string.Empty;
switch (Kind)
{
case ReaperTreeNodeKind.Base:
CurrentLevel = MaximumLevel = 1;
Completed = true;
Title = ReaperUIData.GetFormName(ReaperFormId.Base);
Category = Text("CategoryBase");
Description = ReaperUIData.GetFormSummary(ReaperFormId.Base);
Status = Text("BaseAlwaysAvailable");
break;
case ReaperTreeNodeKind.Death:
RefreshDeath(progression);
break;
case ReaperTreeNodeKind.Stage:
RefreshStage(player, progression, essenceCount);
break;
case ReaperTreeNodeKind.Skill:
RefreshSkill(progression, souls);
break;
case ReaperTreeNodeKind.Common:
case ReaperTreeNodeKind.DeathUpgrade:
RefreshCommon(progression, essenceCount, souls);
break;
}
}
public void RequestUpgrade()
{
if (!CanRequest || !Affordable)
return;
switch (Kind)
{
case ReaperTreeNodeKind.Stage:
SoulHarvest.RequestReaperStageUpgrade(Form, (ReaperStage)ExpectedTarget);
break;
case ReaperTreeNodeKind.Skill:
SoulHarvest.RequestReaperSkillUpgrade(Form, Index, ExpectedTarget);
break;
case ReaperTreeNodeKind.Common:
case ReaperTreeNodeKind.DeathUpgrade:
SoulHarvest.RequestReaperCommonUpgrade(CommonNode, ExpectedTarget);
break;
}
}
private void RefreshDeath(ReaperProgressionState progression)
{
CurrentLevel = progression.DeathFormUnlocked ? 1 : 0;
MaximumLevel = 1;
Completed = progression.DeathFormUnlocked;
Title = ReaperUIData.GetFormName(ReaperFormId.Death);
Category = Text("CategoryFinal");
Description = $"{ReaperUIData.GetFormSummary(ReaperFormId.Death)}\n{Text("UltimateLabel", ReaperUIData.GetUltimateName(ReaperFormId.Death))}";
Status = Text(progression.DeathFormUnlocked ? "DeathUnlocked" : "DeathLocked");
int completedBranches = 0;
int masteredSkills = 0;
foreach (ReaperFormId form in ReaperDefinitions.BranchForms)
{
if (progression.GetStage(form) >= ReaperStage.StageIII)
completedBranches++;
for (int skill = 0; skill < ReaperDefinitions.SkillsPerBranch; skill++)
if (progression.GetSkillLevel(form, skill) >= ReaperDefinitions.MaximumSkillLevel)
masteredSkills++;
}
Cost = Text("DeathMasteryProgress", completedBranches, masteredSkills);
}
private void RefreshStage(Player player, ReaperProgressionState progression, int essenceCount)
{
int current = (int)progression.GetStage(Form);
// Each stage node is a binary unlock, not one shared three-rank node.
CurrentLevel = current >= Index ? 1 : 0;
MaximumLevel = 1;
ExpectedTarget = Index;
Completed = current >= Index;
CanRequest = current == Index - 1;
Title = $"{ReaperUIData.GetFormName(Form)} · {ReaperUIData.GetStageName(Index)}";
Category = Text("CategoryStage");
ReaperFormDefinition definition = ReaperDefinitions.GetForm(Form);
Description = Text("StageCombatStats", ReaperUIData.GetFormSummary(Form), definition.GetBaseDamage((ReaperStage)Index), definition.BaseUseTime);
if (Index == 3)
Description += $"\n{Text("UltimateLabel", ReaperUIData.GetUltimateName(Form))}";
int seals = progression.GetSoulSealAvailable((ReaperStage)Index);
int receiptCount = CountInventoryItem(player, ReaperSoulSealCatalog.GetItemType((ReaperStage)Index));
int essenceCost = ReaperDefinitions.GetStageEssenceCost((ReaperStage)Index);
bool worldReady = ReaperDefinitions.MeetsWorldGate((ReaperStage)Index);
Affordable = Completed || worldReady && seals > 0 && receiptCount > 0 && essenceCount >= essenceCost;
if (Completed)
{
Status = Text("StatusComplete");
Cost = Text("Completed");
}
else if (!CanRequest)
{
Status = Text("StatusLocked");
Cost = Text("StagePrerequisite", Index - 1);
}
else if (!worldReady)
{
Status = Text("StatusLocked");
Cost = Language.GetTextValue($"Mods.SoulHarvest.UI.ReaperTree.StageGates.Stage{Index}");
}
else if (seals <= 0)
{
Status = Text("StatusInsufficient");
Cost = Text("NoStageSeal");
}
else if (receiptCount <= 0)
{
Status = Text("StatusInsufficient");
Cost = Text("NoStageSealItem");
}
else
{
Status = Affordable ? Text("StatusReady") : Text("StatusInsufficient");
Cost = Text("StageCost", essenceCost, seals, receiptCount);
}
}
private void RefreshSkill(ReaperProgressionState progression, int souls)
{
int stage = (int)progression.GetStage(Form);
int level = progression.GetSkillLevel(Form, Index);
int requiredStage = Index + 1;
CurrentLevel = level;
MaximumLevel = ReaperDefinitions.MaximumSkillLevel;
ExpectedTarget = level + 1;
Completed = level >= MaximumLevel;
CanRequest = stage >= requiredStage && !Completed;
int soulCost = ReaperDefinitions.GetSkillSoulCost(level + 1);
Affordable = Completed || CanRequest && soulCost >= 0 && souls >= soulCost;
Title = ReaperUIData.GetSkillName(Form, Index);
Category = Text("CategorySkill", ReaperUIData.GetFormName(Form));
Description = ReaperUIData.GetSkillDescription(Form, Index);
if (stage < requiredStage)
{
Status = Text("StatusLocked");
Cost = Text("SkillStageLocked", requiredStage);
}
else if (Completed)
{
Status = Text("StatusComplete");
Cost = Text("CompletedLevel", level, MaximumLevel);
}
else
{
Status = Affordable ? Text("StatusReady") : Text("StatusInsufficient");
Cost = Text("SkillLevel", level, MaximumLevel, soulCost);
}
}
private void RefreshCommon(ReaperProgressionState progression, int essenceCount, int souls)
{
int level = progression.GetCommonNodeLevel(CommonNode);
int maximum = ReaperDefinitions.GetCommonNodeMaxLevel(CommonNode);
bool unlimited = ReaperDefinitions.IsUnlimitedCommonNode(CommonNode);
int targetLevel = level + 1;
ReaperStage requiredStage = ReaperDefinitions.GetCommonNodeRequiredStage(CommonNode, targetLevel);
ReaperProgressionCost? price = ReaperDefinitions.GetCommonNodeCost(CommonNode, targetLevel);
CurrentLevel = level;
MaximumLevel = maximum;
ExpectedTarget = targetLevel;
Completed = !unlimited && level >= maximum;
CanRequest = !Completed && progression.HasAnyBranchAtLeast(requiredStage);
if (ReaperDefinitions.IsDeathNode(CommonNode))
{
CanRequest &= progression.DeathFormUnlocked;
if (CommonNode == ReaperCommonNode.DeathRiftWidth)
{
CanRequest &= progression.GetCommonNodeLevel(
ReaperCommonNode.DeathSpaceBreak) > 0;
}
}
Affordable = Completed || CanRequest && price is ReaperProgressionCost cost
&& (cost.Currency == ReaperProgressionCurrency.Souls ? souls >= cost.Amount : essenceCount >= cost.Amount);
Title = ReaperUIData.GetCommonNodeName(CommonNode);
Category = Text(ReaperDefinitions.IsDeathNode(CommonNode)
? "CategoryDeathUpgrade" : "CategoryCommon");
Description = ReaperUIData.GetCommonNodeDescription(CommonNode);
if (Completed)
{
Status = Text("StatusComplete");
Cost = Text("CompletedLevel", level, maximum);
}
else if (!CanRequest)
{
Status = Text("StatusLocked");
Cost = ReaperDefinitions.IsDeathNode(CommonNode)
? Text("DeathUpgradeLocked")
: Text("CommonStageLocked", (int)requiredStage);
}
else if (price is ReaperProgressionCost commonCost)
{
Status = Affordable ? Text("StatusReady") : Text("StatusInsufficient");
Cost = unlimited
? Text("CommonUnlimitedLevel", level, FormatCost(commonCost))
: Text("CommonLevel", level, maximum, FormatCost(commonCost));
}
else
{
Status = Text("StatusLocked");
Cost = Text("Unavailable");
}
}
private static int CountInventoryItem(Player player, int itemType)
{
if (itemType <= ItemID.None)
return 0;
int count = 0;
int slots = Math.Min(58, player.inventory.Length);
for (int slot = 0; slot < slots; slot++)
{
Item item = player.inventory[slot];
if (item.type == itemType && item.stack > 0)
count += item.stack;
}
return count;
}
private static string FormatCost(ReaperProgressionCost cost) => Text(cost.Currency == ReaperProgressionCurrency.Souls ? "CostSouls" : "CostEssence", cost.Amount);
private static string Text(string key, params object[] args) => Language.GetTextValue($"Mods.SoulHarvest.UI.ReaperTree.{key}", args);
}