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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
using Microsoft.Xna.Framework;
using System;
using System.IO;
using Terraria;
using Terraria.ID;

namespace SoulHarvest.Common;


/// <summary>
/// Immutable character combat data copied into every attack. Delayed attacks keep
/// this data even when the owner changes form or equipment.
/// </summary>
public readonly record struct SickleCombatSnapshot(
    ReaperFormId Form,
    ReaperStage Stage,
    byte Skill1,
    byte Skill2,
    byte Skill3,
    byte ArmorPenetrationLevel,
    byte AttackSpeedLevel,
    byte AssemblySpeedLevel,
    byte DeathInvocationSlashLevel,
    byte DeathTempoCapLevel,
    byte DeathRangeLevel,
    byte DeathSpaceBreakLevel,
    byte DeathRiftWidthLevel,
    float AttackSpeedMultiplier,
    byte CritChance,
    int Damage,
    float Knockback)
{
    public int StageNumber => Form == ReaperFormId.Base ? 0 : Form == ReaperFormId.Death ? 3 : (int)Stage;

    // Frenzy is folded into the captured attack-speed multiplier.  Reading it back
    // from the immutable snapshot keeps a finisher's passive state stable even if
    // the owner heals above the frenzy threshold or changes form before release.
    public bool BloodFrenzyActive => Form == ReaperFormId.Blood
        && Skill2 > 0
        && AttackSpeedMultiplier > ReaperDefinitions.GetAttackSpeedMultiplier(AttackSpeedLevel) + 0.001f;

    public int DeathInvocationSlashCount
        => ReaperDefinitions.GetDeathInvocationSlashCount(DeathInvocationSlashLevel);
    public float DeathMaximumTempo
        => ReaperDefinitions.GetDeathTempoCap(DeathTempoCapLevel);
    public float DeathPrimaryMaximumTempo
        => ReaperDefinitions.GetDeathPrimaryTempoCap(DeathTempoCapLevel);
    public float DeathRangeMultiplier
        => ReaperDefinitions.GetDeathRangeMultiplier(DeathRangeLevel);
    public bool DeathSpaceBreakUnlocked => DeathSpaceBreakLevel > 0;
    public float DeathRiftWidth
        => ReaperDefinitions.GetDeathRiftWidth(DeathRiftWidthLevel);
    public int AssemblyFrames
        => ReaperDefinitions.GetAssemblyFrames(AssemblySpeedLevel);

    public int GetSkillLevel(int index) => index switch
    {
        0 => Skill1,
        1 => Skill2,
        2 => Skill3,
        _ => 0
    };

    public void Write(BinaryWriter writer)
    {
        writer.Write((byte)Form);
        writer.Write((byte)Stage);
        writer.Write(Skill1);
        writer.Write(Skill2);
        writer.Write(Skill3);
        writer.Write(ArmorPenetrationLevel);
        writer.Write(AttackSpeedLevel);
        writer.Write(AssemblySpeedLevel);
        writer.Write(DeathInvocationSlashLevel);
        writer.Write(DeathTempoCapLevel);
        writer.Write(DeathRangeLevel);
        writer.Write(DeathSpaceBreakLevel);
        writer.Write(DeathRiftWidthLevel);
        writer.Write(AttackSpeedMultiplier);
        writer.Write(CritChance);
        writer.Write(Damage);
        writer.Write(Knockback);
    }

    public static SickleCombatSnapshot Read(BinaryReader reader)
    {
        return new SickleCombatSnapshot(
            (ReaperFormId)reader.ReadByte(),
            (ReaperStage)reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            reader.ReadByte(),
            MathHelper.Clamp(reader.ReadSingle(), 0.1f, 6f),
            reader.ReadByte(),
            Math.Max(1, reader.ReadInt32()),
            Math.Max(0f, reader.ReadSingle()));
    }

    public static SickleCombatSnapshot Capture(Player player, int damage, float knockback)
    {
        MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
        ReaperProgressionState progression = modPlayer.ReaperProgression;
        ReaperFormId form = ReaperCombatRegistry.ResolveUsableForm(progression);
        ReaperStage stage = ReaperDefinitions.IsBranchForm(form)
            ? progression.GetStage(form)
            : ReaperStage.StageIII;
        int attackSpeedLevel = progression.GetCommonNodeLevel(ReaperCommonNode.AttackSpeed);
        float attackSpeedMultiplier = ReaperDefinitions.GetAttackSpeedMultiplier(attackSpeedLevel);
        if (form == ReaperFormId.Blood && player.GetModPlayer<ReaperCombatPlayer>().BloodFrenzyActive)
        {
            int frenzyLevel = progression.GetSkillLevel(ReaperFormId.Blood, 1);
            attackSpeedMultiplier *= 1f + (frenzyLevel switch
            {
                2 => 0.08f,
                >= 3 => 0.11f,
                _ => frenzyLevel > 0 ? 0.05f : 0f
            });
        }

        return new SickleCombatSnapshot(
            form,
            stage,
            (byte)progression.GetSkillLevel(form, 0),
            (byte)progression.GetSkillLevel(form, 1),
            (byte)progression.GetSkillLevel(form, 2),
            (byte)progression.GetCommonNodeLevel(ReaperCommonNode.ArmorPenetration),
            (byte)attackSpeedLevel,
            (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.AssemblySpeed), 0, byte.MaxValue),
            (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathInvocationSlashes), 0, byte.MaxValue),
            (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathTempoCap), 0, byte.MaxValue),
            (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathRange), 0, byte.MaxValue),
            (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathSpaceBreak), 0, byte.MaxValue),
            (byte)Math.Clamp(progression.GetCommonNodeLevel(ReaperCommonNode.DeathRiftWidth), 0, byte.MaxValue),
            attackSpeedMultiplier,
            (byte)Math.Clamp(player.GetWeaponCrit(player.HeldItem), 0, 100),
            Math.Max(1, damage),
            Math.Max(0f, knockback));
    }
}