using System;
using System.Collections.Generic;
using Terraria;
namespace SoulHarvest.Common;
public static class ReaperDefinitions
{
public const int SkillsPerBranch = 3;
public const int MaximumSkillLevel = 3;
public const int MaximumSoulSealsPerStage = 6;
public const float AttackSpeedBonusPerLevel = 0.10f;
public const int AssemblyFragmentCount = 16;
public const int AssemblyPortalExpandFrames = 15;
public const int AssemblyFragmentTravelFrames = 10;
public const int AssemblyPortalCloseFrames = 36;
public const int AssemblyFragmentReturnFrames = 28;
public const int AssemblyCancelFrames = AssemblyFragmentReturnFrames
+ AssemblyPortalCloseFrames;
public const int AssemblyMaximumGapFrames = 8;
public const int AssemblyMaximumLevelFrames = 30;
public const int AssemblyMaximumLevelPortalCloseFrames = 5;
private static readonly ReaperFormId[] BranchFormArray =
[
ReaperFormId.Bone,
ReaperFormId.Blood,
ReaperFormId.Infernal,
ReaperFormId.Frost,
ReaperFormId.Soul,
ReaperFormId.Void
];
private static readonly IReadOnlyList<ReaperFormId> ReadOnlyBranchForms = Array.AsReadOnly(BranchFormArray);
private static readonly ReaperFormDefinition[] FormDefinitions =
[
new(ReaperFormId.Base, 14, 14, 14, 25),
new(ReaperFormId.Bone, 40, 85, 155, 24),
new(ReaperFormId.Blood, 38, 81, 148, 22),
new(ReaperFormId.Infernal, 36, 77, 140, 20),
new(ReaperFormId.Frost, 40, 85, 155, 22),
new(ReaperFormId.Soul, 35, 75, 136, 22),
new(ReaperFormId.Void, 48, 102, 186, 27),
new(ReaperFormId.Death, 214, 214, 214, 24)
];
private static readonly ReaperNodeDefinition[] NodeDefinitions =
[
new(ReaperNodeId.BoneFractureEcho, ReaperFormId.Bone, 0, null, ReaperStage.StageI),
new(ReaperNodeId.BoneSpectralRestraint, ReaperFormId.Bone, 1, null, ReaperStage.StageII),
new(ReaperNodeId.BoneDeadChorus, ReaperFormId.Bone, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.BloodDrink, ReaperFormId.Blood, 0, null, ReaperStage.StageI),
new(ReaperNodeId.BloodFrenzy, ReaperFormId.Blood, 1, null, ReaperStage.StageII),
new(ReaperNodeId.BloodCoagulation, ReaperFormId.Blood, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.InfernalWildfireStep, ReaperFormId.Infernal, 0, null, ReaperStage.StageI),
new(ReaperNodeId.InfernalSearingBone, ReaperFormId.Infernal, 1, null, ReaperStage.StageII),
new(ReaperNodeId.InfernalOverburnCircuit, ReaperFormId.Infernal, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.FrostEtchedMark, ReaperFormId.Frost, 0, null, ReaperStage.StageI),
new(ReaperNodeId.FrostRefractedSoul, ReaperFormId.Frost, 1, null, ReaperStage.StageII),
new(ReaperNodeId.FrostZeroExecution, ReaperFormId.Frost, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.SoulEchoChorus, ReaperFormId.Soul, 0, null, ReaperStage.StageI),
new(ReaperNodeId.SoulFamiliarPact, ReaperFormId.Soul, 1, null, ReaperStage.StageII),
new(ReaperNodeId.SoulFatedInscription, ReaperFormId.Soul, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.VoidBoundaryEdge, ReaperFormId.Void, 0, null, ReaperStage.StageI),
new(ReaperNodeId.VoidDisplacedAftercut, ReaperFormId.Void, 1, null, ReaperStage.StageII),
new(ReaperNodeId.VoidTerminalCoordinate, ReaperFormId.Void, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.CommonDamage, ReaperFormId.Base, -1, ReaperCommonNode.Damage, ReaperStage.StageI),
new(ReaperNodeId.CommonAttackSpeed, ReaperFormId.Base, -1, ReaperCommonNode.AttackSpeed, ReaperStage.StageI),
new(ReaperNodeId.CommonCrit, ReaperFormId.Base, -1, ReaperCommonNode.Crit, ReaperStage.StageI),
new(ReaperNodeId.CommonArmorPenetration, ReaperFormId.Base, -1, ReaperCommonNode.ArmorPenetration, ReaperStage.StageI),
new(ReaperNodeId.CommonSpecialCooldown, ReaperFormId.Base, -1, ReaperCommonNode.SpecialCooldown, ReaperStage.StageI),
new(ReaperNodeId.CommonEnergyGain, ReaperFormId.Base, -1, ReaperCommonNode.EnergyGain, ReaperStage.StageI),
new(ReaperNodeId.CommonAssemblySpeed, ReaperFormId.Base, -1, ReaperCommonNode.AssemblySpeed, ReaperStage.StageI),
new(ReaperNodeId.DeathDamage, ReaperFormId.Death, -1, ReaperCommonNode.DeathDamage, ReaperStage.StageIII),
new(ReaperNodeId.DeathInvocationSlashes, ReaperFormId.Death, -1, ReaperCommonNode.DeathInvocationSlashes, ReaperStage.StageIII),
new(ReaperNodeId.DeathTempoCap, ReaperFormId.Death, -1, ReaperCommonNode.DeathTempoCap, ReaperStage.StageIII),
new(ReaperNodeId.DeathRange, ReaperFormId.Death, -1, ReaperCommonNode.DeathRange, ReaperStage.StageIII),
new(ReaperNodeId.DeathSpaceBreak, ReaperFormId.Death, -1, ReaperCommonNode.DeathSpaceBreak, ReaperStage.StageIII),
new(ReaperNodeId.DeathRiftWidth, ReaperFormId.Death, -1, ReaperCommonNode.DeathRiftWidth, ReaperStage.StageIII)
];
private static readonly IReadOnlyList<ReaperNodeDefinition> ReadOnlyNodeDefinitions = Array.AsReadOnly(NodeDefinitions);
public static IReadOnlyList<ReaperFormId> BranchForms => ReadOnlyBranchForms;
public static IReadOnlyList<ReaperNodeDefinition> Nodes => ReadOnlyNodeDefinitions;
public static bool IsBranchForm(ReaperFormId form)
{
return form is >= ReaperFormId.Bone and <= ReaperFormId.Void;
}
public static ReaperFormDefinition GetForm(ReaperFormId form)
{
int index = (int)form;
return index >= 0 && index < FormDefinitions.Length
? FormDefinitions[index]
: FormDefinitions[(int)ReaperFormId.Base];
}
public static bool TryGetNode(ReaperNodeId id, out ReaperNodeDefinition definition)
{
foreach (ReaperNodeDefinition candidate in NodeDefinitions)
{
if (candidate.Id == id)
{
definition = candidate;
return true;
}
}
definition = null!;
return false;
}
public static bool TryGetSkillNode(ReaperFormId form, int skillIndex, out ReaperNodeId id)
{
foreach (ReaperNodeDefinition definition in NodeDefinitions)
{
if (!definition.IsCommon && definition.Form == form && definition.SkillIndex == skillIndex)
{
id = definition.Id;
return true;
}
}
id = default;
return false;
}
public static bool TryGetCommonNode(ReaperCommonNode commonNode, out ReaperNodeId id)
{
foreach (ReaperNodeDefinition definition in NodeDefinitions)
{
if (definition.CommonNode == commonNode)
{
id = definition.Id;
return true;
}
}
id = default;
return false;
}
public static int GetStageEssenceCost(ReaperStage stage)
{
return stage switch
{
ReaperStage.StageI => 1,
ReaperStage.StageII => 2,
ReaperStage.StageIII => 3,
_ => -1
};
}
public static int GetSkillSoulCost(int targetLevel)
{
return targetLevel switch
{
2 => 50,
3 => 100,
_ => -1
};
}
public static int GetCommonNodeMaxLevel(ReaperCommonNode node)
{
return node switch
{
ReaperCommonNode.Damage => 10,
ReaperCommonNode.DeathDamage => int.MaxValue,
ReaperCommonNode.DeathInvocationSlashes => 5,
ReaperCommonNode.DeathTempoCap => 5,
ReaperCommonNode.DeathRange => 5,
ReaperCommonNode.DeathSpaceBreak => 1,
ReaperCommonNode.DeathRiftWidth => 5,
_ => IsCommonNode(node) ? 5 : 0
};
}
public static float GetAttackSpeedMultiplier(int level)
=> 1f + Math.Max(0, level) * AttackSpeedBonusPerLevel;
/// <summary>
/// Converts the common assembly upgrade into one shared timeline. Levels
/// below the cap retain the readable head-to-tail sequence; the final level
/// is a qualitative payoff where all rifts and fragments converge together
/// in roughly half a second.
/// </summary>
public static int GetAssemblyFrames(int level)
{
int maximum = GetCommonNodeMaxLevel(ReaperCommonNode.AssemblySpeed);
int clampedLevel = Math.Clamp(level, 0, maximum);
if (clampedLevel >= maximum)
return AssemblyMaximumLevelFrames;
float progress = clampedLevel / (float)maximum;
int gap = (int)Math.Round(AssemblyMaximumGapFrames
* (1f - progress));
int portalInterval = AssemblyPortalExpandFrames + gap;
return AssemblyFragmentCount * portalInterval
+ AssemblyFragmentTravelFrames + AssemblyPortalCloseFrames;
}
public static bool UsesSimultaneousAssembly(int totalFrames)
=> totalFrames == AssemblyMaximumLevelFrames;
public static int GetAssemblyPortalCloseFrames(int totalFrames)
=> UsesSimultaneousAssembly(totalFrames)
? AssemblyMaximumLevelPortalCloseFrames
: AssemblyPortalCloseFrames;
public static bool IsUnlimitedCommonNode(ReaperCommonNode node)
=> node == ReaperCommonNode.DeathDamage;
public static bool IsDeathNode(ReaperCommonNode node)
=> node is >= ReaperCommonNode.DeathDamage and <= ReaperCommonNode.DeathRiftWidth;
public static int GetDeathInvocationSlashCount(int level)
=> 1 + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathInvocationSlashes));
public static float GetDeathTempoCap(int level)
=> 1.5f + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathTempoCap)) * 0.3f;
public static float GetDeathPrimaryTempoCap(int level)
=> 1.5f + Math.Clamp(level, 0,
GetCommonNodeMaxLevel(ReaperCommonNode.DeathTempoCap)) * 0.7f;
public static float GetDeathRangeMultiplier(int level)
=> 0.5f + Math.Clamp(level, 0,
GetCommonNodeMaxLevel(ReaperCommonNode.DeathRange)) * 0.10f;
public static float GetDeathRiftWidth(int level)
=> 60f + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathRiftWidth)) * 12f;
public static ReaperProgressionCost? GetCommonNodeCost(ReaperCommonNode node, int targetLevel)
{
if (!IsCommonNode(node) || targetLevel < 1 || targetLevel > GetCommonNodeMaxLevel(node))
return null;
if (node is ReaperCommonNode.Damage or ReaperCommonNode.DeathDamage)
{
return targetLevel switch
{
1 => ReaperProgressionCost.Souls(50),
2 => ReaperProgressionCost.Souls(75),
3 => ReaperProgressionCost.Souls(100),
4 => ReaperProgressionCost.Souls(125),
5 => ReaperProgressionCost.Souls(150),
6 or 7 => ReaperProgressionCost.Essence(1),
8 or 9 => ReaperProgressionCost.Essence(2),
10 => ReaperProgressionCost.Essence(3),
_ when node == ReaperCommonNode.DeathDamage
=> ReaperProgressionCost.Essence(Math.Min(999, 3 + (targetLevel - 11) / 5)),
_ => null
};
}
if (IsDeathNode(node))
{
return targetLevel switch
{
1 => ReaperProgressionCost.Essence(3),
2 => ReaperProgressionCost.Essence(5),
3 => ReaperProgressionCost.Essence(8),
4 => ReaperProgressionCost.Essence(12),
5 => ReaperProgressionCost.Essence(18),
_ => null
};
}
return targetLevel switch
{
1 => ReaperProgressionCost.Souls(50),
2 => ReaperProgressionCost.Souls(100),
3 => ReaperProgressionCost.Essence(1),
4 => ReaperProgressionCost.Essence(2),
5 => ReaperProgressionCost.Essence(3),
_ => null
};
}
public static ReaperStage GetCommonNodeRequiredStage(ReaperCommonNode node, int targetLevel)
{
if (!IsCommonNode(node) || targetLevel <= 0)
return ReaperStage.Locked;
if (IsDeathNode(node))
return ReaperStage.StageIII;
if (node == ReaperCommonNode.Damage)
{
if (targetLevel <= 3)
return ReaperStage.Locked;
if (targetLevel <= 7)
return ReaperStage.StageII;
return ReaperStage.StageIII;
}
if (targetLevel <= 2)
return ReaperStage.Locked;
if (targetLevel <= 4)
return ReaperStage.StageII;
return ReaperStage.StageIII;
}
public static bool MeetsWorldGate(ReaperStage stage)
{
return stage switch
{
ReaperStage.StageI => NPC.downedBoss3,
ReaperStage.StageII => Main.hardMode,
ReaperStage.StageIII => NPC.downedMechBoss1 && NPC.downedMechBoss2 && NPC.downedMechBoss3,
_ => false
};
}
public static bool IsCommonNode(ReaperCommonNode node)
{
return node is >= ReaperCommonNode.Damage and <= ReaperCommonNode.AssemblySpeed;
}
}
using System;
using System.Collections.Generic;
using Terraria;
namespace SoulHarvest.Common;
public static class ReaperDefinitions
{
public const int SkillsPerBranch = 3;
public const int MaximumSkillLevel = 3;
public const int MaximumSoulSealsPerStage = 6;
public const float AttackSpeedBonusPerLevel = 0.10f;
public const int AssemblyFragmentCount = 16;
public const int AssemblyPortalExpandFrames = 15;
public const int AssemblyFragmentTravelFrames = 10;
public const int AssemblyPortalCloseFrames = 36;
public const int AssemblyFragmentReturnFrames = 28;
public const int AssemblyCancelFrames = AssemblyFragmentReturnFrames
+ AssemblyPortalCloseFrames;
public const int AssemblyMaximumGapFrames = 8;
public const int AssemblyMaximumLevelFrames = 30;
public const int AssemblyMaximumLevelPortalCloseFrames = 5;
private static readonly ReaperFormId[] BranchFormArray =
[
ReaperFormId.Bone,
ReaperFormId.Blood,
ReaperFormId.Infernal,
ReaperFormId.Frost,
ReaperFormId.Soul,
ReaperFormId.Void
];
private static readonly IReadOnlyList<ReaperFormId> ReadOnlyBranchForms = Array.AsReadOnly(BranchFormArray);
private static readonly ReaperFormDefinition[] FormDefinitions =
[
new(ReaperFormId.Base, 14, 14, 14, 25),
new(ReaperFormId.Bone, 40, 85, 155, 24),
new(ReaperFormId.Blood, 38, 81, 148, 22),
new(ReaperFormId.Infernal, 36, 77, 140, 20),
new(ReaperFormId.Frost, 40, 85, 155, 22),
new(ReaperFormId.Soul, 35, 75, 136, 22),
new(ReaperFormId.Void, 48, 102, 186, 27),
new(ReaperFormId.Death, 214, 214, 214, 24)
];
private static readonly ReaperNodeDefinition[] NodeDefinitions =
[
new(ReaperNodeId.BoneFractureEcho, ReaperFormId.Bone, 0, null, ReaperStage.StageI),
new(ReaperNodeId.BoneSpectralRestraint, ReaperFormId.Bone, 1, null, ReaperStage.StageII),
new(ReaperNodeId.BoneDeadChorus, ReaperFormId.Bone, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.BloodDrink, ReaperFormId.Blood, 0, null, ReaperStage.StageI),
new(ReaperNodeId.BloodFrenzy, ReaperFormId.Blood, 1, null, ReaperStage.StageII),
new(ReaperNodeId.BloodCoagulation, ReaperFormId.Blood, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.InfernalWildfireStep, ReaperFormId.Infernal, 0, null, ReaperStage.StageI),
new(ReaperNodeId.InfernalSearingBone, ReaperFormId.Infernal, 1, null, ReaperStage.StageII),
new(ReaperNodeId.InfernalOverburnCircuit, ReaperFormId.Infernal, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.FrostEtchedMark, ReaperFormId.Frost, 0, null, ReaperStage.StageI),
new(ReaperNodeId.FrostRefractedSoul, ReaperFormId.Frost, 1, null, ReaperStage.StageII),
new(ReaperNodeId.FrostZeroExecution, ReaperFormId.Frost, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.SoulEchoChorus, ReaperFormId.Soul, 0, null, ReaperStage.StageI),
new(ReaperNodeId.SoulFamiliarPact, ReaperFormId.Soul, 1, null, ReaperStage.StageII),
new(ReaperNodeId.SoulFatedInscription, ReaperFormId.Soul, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.VoidBoundaryEdge, ReaperFormId.Void, 0, null, ReaperStage.StageI),
new(ReaperNodeId.VoidDisplacedAftercut, ReaperFormId.Void, 1, null, ReaperStage.StageII),
new(ReaperNodeId.VoidTerminalCoordinate, ReaperFormId.Void, 2, null, ReaperStage.StageIII),
new(ReaperNodeId.CommonDamage, ReaperFormId.Base, -1, ReaperCommonNode.Damage, ReaperStage.StageI),
new(ReaperNodeId.CommonAttackSpeed, ReaperFormId.Base, -1, ReaperCommonNode.AttackSpeed, ReaperStage.StageI),
new(ReaperNodeId.CommonCrit, ReaperFormId.Base, -1, ReaperCommonNode.Crit, ReaperStage.StageI),
new(ReaperNodeId.CommonArmorPenetration, ReaperFormId.Base, -1, ReaperCommonNode.ArmorPenetration, ReaperStage.StageI),
new(ReaperNodeId.CommonSpecialCooldown, ReaperFormId.Base, -1, ReaperCommonNode.SpecialCooldown, ReaperStage.StageI),
new(ReaperNodeId.CommonEnergyGain, ReaperFormId.Base, -1, ReaperCommonNode.EnergyGain, ReaperStage.StageI),
new(ReaperNodeId.CommonAssemblySpeed, ReaperFormId.Base, -1, ReaperCommonNode.AssemblySpeed, ReaperStage.StageI),
new(ReaperNodeId.DeathDamage, ReaperFormId.Death, -1, ReaperCommonNode.DeathDamage, ReaperStage.StageIII),
new(ReaperNodeId.DeathInvocationSlashes, ReaperFormId.Death, -1, ReaperCommonNode.DeathInvocationSlashes, ReaperStage.StageIII),
new(ReaperNodeId.DeathTempoCap, ReaperFormId.Death, -1, ReaperCommonNode.DeathTempoCap, ReaperStage.StageIII),
new(ReaperNodeId.DeathRange, ReaperFormId.Death, -1, ReaperCommonNode.DeathRange, ReaperStage.StageIII),
new(ReaperNodeId.DeathSpaceBreak, ReaperFormId.Death, -1, ReaperCommonNode.DeathSpaceBreak, ReaperStage.StageIII),
new(ReaperNodeId.DeathRiftWidth, ReaperFormId.Death, -1, ReaperCommonNode.DeathRiftWidth, ReaperStage.StageIII)
];
private static readonly IReadOnlyList<ReaperNodeDefinition> ReadOnlyNodeDefinitions = Array.AsReadOnly(NodeDefinitions);
public static IReadOnlyList<ReaperFormId> BranchForms => ReadOnlyBranchForms;
public static IReadOnlyList<ReaperNodeDefinition> Nodes => ReadOnlyNodeDefinitions;
public static bool IsBranchForm(ReaperFormId form)
{
return form is >= ReaperFormId.Bone and <= ReaperFormId.Void;
}
public static ReaperFormDefinition GetForm(ReaperFormId form)
{
int index = (int)form;
return index >= 0 && index < FormDefinitions.Length
? FormDefinitions[index]
: FormDefinitions[(int)ReaperFormId.Base];
}
public static bool TryGetNode(ReaperNodeId id, out ReaperNodeDefinition definition)
{
foreach (ReaperNodeDefinition candidate in NodeDefinitions)
{
if (candidate.Id == id)
{
definition = candidate;
return true;
}
}
definition = null!;
return false;
}
public static bool TryGetSkillNode(ReaperFormId form, int skillIndex, out ReaperNodeId id)
{
foreach (ReaperNodeDefinition definition in NodeDefinitions)
{
if (!definition.IsCommon && definition.Form == form && definition.SkillIndex == skillIndex)
{
id = definition.Id;
return true;
}
}
id = default;
return false;
}
public static bool TryGetCommonNode(ReaperCommonNode commonNode, out ReaperNodeId id)
{
foreach (ReaperNodeDefinition definition in NodeDefinitions)
{
if (definition.CommonNode == commonNode)
{
id = definition.Id;
return true;
}
}
id = default;
return false;
}
public static int GetStageEssenceCost(ReaperStage stage)
{
return stage switch
{
ReaperStage.StageI => 1,
ReaperStage.StageII => 2,
ReaperStage.StageIII => 3,
_ => -1
};
}
public static int GetSkillSoulCost(int targetLevel)
{
return targetLevel switch
{
2 => 50,
3 => 100,
_ => -1
};
}
public static int GetCommonNodeMaxLevel(ReaperCommonNode node)
{
return node switch
{
ReaperCommonNode.Damage => 10,
ReaperCommonNode.DeathDamage => int.MaxValue,
ReaperCommonNode.DeathInvocationSlashes => 5,
ReaperCommonNode.DeathTempoCap => 5,
ReaperCommonNode.DeathRange => 5,
ReaperCommonNode.DeathSpaceBreak => 1,
ReaperCommonNode.DeathRiftWidth => 5,
_ => IsCommonNode(node) ? 5 : 0
};
}
public static float GetAttackSpeedMultiplier(int level)
=> 1f + Math.Max(0, level) * AttackSpeedBonusPerLevel;
/// <summary>
/// Converts the common assembly upgrade into one shared timeline. Levels
/// below the cap retain the readable head-to-tail sequence; the final level
/// is a qualitative payoff where all rifts and fragments converge together
/// in roughly half a second.
/// </summary>
public static int GetAssemblyFrames(int level)
{
int maximum = GetCommonNodeMaxLevel(ReaperCommonNode.AssemblySpeed);
int clampedLevel = Math.Clamp(level, 0, maximum);
if (clampedLevel >= maximum)
return AssemblyMaximumLevelFrames;
float progress = clampedLevel / (float)maximum;
int gap = (int)Math.Round(AssemblyMaximumGapFrames
* (1f - progress));
int portalInterval = AssemblyPortalExpandFrames + gap;
return AssemblyFragmentCount * portalInterval
+ AssemblyFragmentTravelFrames + AssemblyPortalCloseFrames;
}
public static bool UsesSimultaneousAssembly(int totalFrames)
=> totalFrames == AssemblyMaximumLevelFrames;
public static int GetAssemblyPortalCloseFrames(int totalFrames)
=> UsesSimultaneousAssembly(totalFrames)
? AssemblyMaximumLevelPortalCloseFrames
: AssemblyPortalCloseFrames;
public static bool IsUnlimitedCommonNode(ReaperCommonNode node)
=> node == ReaperCommonNode.DeathDamage;
public static bool IsDeathNode(ReaperCommonNode node)
=> node is >= ReaperCommonNode.DeathDamage and <= ReaperCommonNode.DeathRiftWidth;
public static int GetDeathInvocationSlashCount(int level)
=> 1 + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathInvocationSlashes));
public static float GetDeathTempoCap(int level)
=> 1.5f + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathTempoCap)) * 0.3f;
public static float GetDeathPrimaryTempoCap(int level)
=> 1.5f + Math.Clamp(level, 0,
GetCommonNodeMaxLevel(ReaperCommonNode.DeathTempoCap)) * 0.7f;
public static float GetDeathRangeMultiplier(int level)
=> 0.5f + Math.Clamp(level, 0,
GetCommonNodeMaxLevel(ReaperCommonNode.DeathRange)) * 0.10f;
public static float GetDeathRiftWidth(int level)
=> 60f + Math.Clamp(level, 0, GetCommonNodeMaxLevel(ReaperCommonNode.DeathRiftWidth)) * 12f;
public static ReaperProgressionCost? GetCommonNodeCost(ReaperCommonNode node, int targetLevel)
{
if (!IsCommonNode(node) || targetLevel < 1 || targetLevel > GetCommonNodeMaxLevel(node))
return null;
if (node is ReaperCommonNode.Damage or ReaperCommonNode.DeathDamage)
{
return targetLevel switch
{
1 => ReaperProgressionCost.Souls(50),
2 => ReaperProgressionCost.Souls(75),
3 => ReaperProgressionCost.Souls(100),
4 => ReaperProgressionCost.Souls(125),
5 => ReaperProgressionCost.Souls(150),
6 or 7 => ReaperProgressionCost.Essence(1),
8 or 9 => ReaperProgressionCost.Essence(2),
10 => ReaperProgressionCost.Essence(3),
_ when node == ReaperCommonNode.DeathDamage
=> ReaperProgressionCost.Essence(Math.Min(999, 3 + (targetLevel - 11) / 5)),
_ => null
};
}
if (IsDeathNode(node))
{
return targetLevel switch
{
1 => ReaperProgressionCost.Essence(3),
2 => ReaperProgressionCost.Essence(5),
3 => ReaperProgressionCost.Essence(8),
4 => ReaperProgressionCost.Essence(12),
5 => ReaperProgressionCost.Essence(18),
_ => null
};
}
return targetLevel switch
{
1 => ReaperProgressionCost.Souls(50),
2 => ReaperProgressionCost.Souls(100),
3 => ReaperProgressionCost.Essence(1),
4 => ReaperProgressionCost.Essence(2),
5 => ReaperProgressionCost.Essence(3),
_ => null
};
}
public static ReaperStage GetCommonNodeRequiredStage(ReaperCommonNode node, int targetLevel)
{
if (!IsCommonNode(node) || targetLevel <= 0)
return ReaperStage.Locked;
if (IsDeathNode(node))
return ReaperStage.StageIII;
if (node == ReaperCommonNode.Damage)
{
if (targetLevel <= 3)
return ReaperStage.Locked;
if (targetLevel <= 7)
return ReaperStage.StageII;
return ReaperStage.StageIII;
}
if (targetLevel <= 2)
return ReaperStage.Locked;
if (targetLevel <= 4)
return ReaperStage.StageII;
return ReaperStage.StageIII;
}
public static bool MeetsWorldGate(ReaperStage stage)
{
return stage switch
{
ReaperStage.StageI => NPC.downedBoss3,
ReaperStage.StageII => Main.hardMode,
ReaperStage.StageIII => NPC.downedMechBoss1 && NPC.downedMechBoss2 && NPC.downedMechBoss3,
_ => false
};
}
public static bool IsCommonNode(ReaperCommonNode node)
{
return node is >= ReaperCommonNode.Damage and <= ReaperCommonNode.AssemblySpeed;
}
}