XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
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;
    }
}