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

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
using SoulHarvest.Common;
using SoulHarvest.Tiles;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Terraria;
using Terraria.GameContent;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;

namespace SoulHarvest.Items;

[AutoloadEquip(EquipType.Body)]
public class DeathRobe : ModItem, IDeathAltarUpgradeable
{
    public const int MaxRobeLevel = 10;

    private static readonly int[] DefenseByLevel = [12, 20, 35, 55, 85, 130, 220, 400, 1000, 9999];
    private static readonly int[] MinionsByLevel = [1, 2, 3, 4, 6, 10, 16, 26, 42, 60, 80];
    private static readonly int[] TurretsByLevel = [1, 2, 3, 4, 5, 7, 9, 12, 15, 18, 20];
    private static readonly DeathAltarUpgradeType[] UpgradeTypes =
    [
        DeathAltarUpgradeType.RobeDefense,
        DeathAltarUpgradeType.RobeMaxLife,
        DeathAltarUpgradeType.RobeLifeRegen,
        DeathAltarUpgradeType.RobeMobility,
        DeathAltarUpgradeType.RobeMinions,
        DeathAltarUpgradeType.RobeTurrets,
        DeathAltarUpgradeType.RobePlacementRange,
        DeathAltarUpgradeType.RobeKnockbackImmunity,
        DeathAltarUpgradeType.RobeFallDamageImmunity,
        DeathAltarUpgradeType.RobeZeroMana,
        DeathAltarUpgradeType.RobeLavaImmunity,
        DeathAltarUpgradeType.RobeImmuneOnFire,
        DeathAltarUpgradeType.RobeImmunePoisoned,
        DeathAltarUpgradeType.RobeImmuneConfused,
        DeathAltarUpgradeType.RobeImmuneBleeding,
        DeathAltarUpgradeType.RobeImmuneSlow,
        DeathAltarUpgradeType.RobeImmuneWeak,
        DeathAltarUpgradeType.RobeImmuneSilenced,
        DeathAltarUpgradeType.RobeImmuneDarkness,
        DeathAltarUpgradeType.RobeImmuneCursed,
        DeathAltarUpgradeType.RobeImmuneFrostburn,
        DeathAltarUpgradeType.RobeImmuneFrozen,
        DeathAltarUpgradeType.RobeImmuneChaosState,
        DeathAltarUpgradeType.RobeImmuneWet,
        DeathAltarUpgradeType.RobeImmuneBrokenArmor,
        DeathAltarUpgradeType.RobeImmuneSuffocation
    ];

    public int DefenseLevel { get; private set; } = 1;
    public int MaxLifeLevel { get; private set; }
    public int LifeRegenLevel { get; private set; }
    public int MobilityLevel { get; private set; }
    public int MinionLevel { get; private set; }
    public int TurretLevel { get; private set; }
    public int PlacementRangeLevel { get; private set; }
    public bool KnockbackImmunityUnlocked { get; private set; }
    public bool FallDamageImmunityUnlocked { get; private set; }
    public bool ZeroManaUnlocked { get; private set; }
    public bool LavaImmunityUnlocked { get; private set; }
    public ushort DebuffImmunityMask { get; private set; }

    public int DefenseValue => DefenseByLevel[DefenseLevel - 1];
    public int MaxLifeBonus => MaxLifeLevel * 50;
    public int LifeRegenBonus => LifeRegenLevel * 5;
    public int MinionCapacity => MinionsByLevel[MinionLevel];
    public int TurretCapacity => TurretsByLevel[TurretLevel];
    public int PlacementRangeBonus => PlacementRangeLevel * 10;
    public IReadOnlyList<DeathAltarUpgradeType> AltarUpgradeTypes => UpgradeTypes;

    protected override bool CloneNewInstances => true;

    public override void PreReforge()
    {
        DeathAltarReforgePreservation.Capture(this);
    }

    public override void PostReforge()
    {
        DeathAltarReforgePreservation.Restore(this);
    }

    public override void SetStaticDefaults()
    {
        ArmorIDs.Body.Sets.HidesTopSkin[Item.bodySlot] = true;

        // These sprites came directly from the 1.3 mod. A 1.4 body equip normally treats
        // DeathRobe_Body as a 360x224 composite sheet, but this asset is the legacy 40x1120
        // format with separate arm frames. Keep the original framing and bind each legacy
        // layer explicitly so the robe uses the same player-relative positions as before.
        FieldInfo? framingField = typeof(ArmorIDs.Body.Sets).GetField(
            "UsesNewFramingCode",
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
        if (framingField?.GetValue(null) is bool[] usesNewFramingCode)
            usesNewFramingCode[Item.bodySlot] = false;
        if (!Main.dedServ)
        {
            var bodyTexture = ModContent.Request<Texture2D>($"{Texture}_{EquipType.Body}");
            TextureAssets.ArmorBody[Item.bodySlot] = bodyTexture;
            TextureAssets.FemaleBody[Item.bodySlot] = bodyTexture;
            TextureAssets.ArmorArm[Item.bodySlot] = ModContent.Request<Texture2D>($"{Texture}_Arms");
        }
    }

    public override void SetDefaults()
    {
        Item.width = 18;
        Item.height = 18;
        Item.value = Item.sellPrice(gold: 10);
        Item.rare = ItemRarityID.LightRed;
        ApplyDynamicStats();
    }

    public override void UpdateInventory(Player player) => ApplyDynamicStats();

    public override void UpdateEquip(Player player)
    {
        player.statLifeMax2 += MaxLifeBonus;
        player.lifeRegen += LifeRegenBonus;

        if (MobilityLevel > 0)
        {
            float progress = MobilityLevel / 10f;
            player.maxRunSpeed = Math.Max(player.maxRunSpeed, MathHelper.Lerp(3f, 4f, progress));
            player.runAcceleration = Math.Max(player.runAcceleration, MathHelper.Lerp(0.08f, 0.7f, progress));
            player.runSlowdown = Math.Max(player.runSlowdown, MathHelper.Lerp(0.08f, 1f, progress));
        }

        if (MinionLevel > 0)
            player.maxMinions = Math.Max(player.maxMinions, MinionCapacity);
        if (TurretLevel > 0)
            player.maxTurrets = Math.Max(player.maxTurrets, TurretCapacity);

        player.blockRange += PlacementRangeBonus;
        player.noKnockback |= KnockbackImmunityUnlocked;
        player.noFallDmg |= FallDamageImmunityUnlocked;
        if (ZeroManaUnlocked)
            player.manaCost = 0f;
        if (LavaImmunityUnlocked)
        {
            player.lavaImmune = true;
            player.fireWalk = true;
        }

        foreach (DeathAltarUpgradeType type in UpgradeTypes)
        {
            if (IsDebuffUpgrade(type) && IsDebuffImmune(type))
                player.buffImmune[GetDebuffId(type)] = true;
        }

        if (Main.rand.NextBool(4))
        {
            Dust dust = Dust.NewDustDirect(player.position, player.width, player.height, DustID.BatScepter, Alpha: 100, Scale: 1f);
            dust.noGravity = true;
        }
    }

    public override void ModifyTooltips(List<TooltipLine> tooltips)
    {
        string stats = Language.GetTextValue(
            "Mods.SoulHarvest.UI.RobeStats",
            DefenseValue,
            MaxLifeBonus,
            LifeRegenBonus,
            CountDebuffImmunities(),
            15,
            ToggleValue(LavaImmunityUnlocked));
        tooltips.Add(new TooltipLine(Mod, "DeathRobeStats", stats) { OverrideColor = new Color(170, 95, 230) });
        tooltips.Add(new TooltipLine(Mod, "SoulHarvestAltarHint", Language.GetTextValue("Mods.SoulHarvest.UI.AltarHint")));
    }

    public DeathAltarPrice GetUpgradePrice(DeathAltarUpgradeType upgradeType)
    {
        if (IsDebuffUpgrade(upgradeType))
            return IsDebuffImmune(upgradeType) ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(1);

        return upgradeType switch
        {
            DeathAltarUpgradeType.RobeDefense => DefenseLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPrice(DefenseLevel, 40),
            DeathAltarUpgradeType.RobeMaxLife => MaxLifeLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MaxLifeLevel, 30),
            DeathAltarUpgradeType.RobeLifeRegen => LifeRegenLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(LifeRegenLevel, 35),
            DeathAltarUpgradeType.RobeMobility => MobilityLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MobilityLevel, 30),
            DeathAltarUpgradeType.RobeMinions => MinionLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(MinionLevel, 50),
            DeathAltarUpgradeType.RobeTurrets => TurretLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(TurretLevel, 50),
            DeathAltarUpgradeType.RobePlacementRange => PlacementRangeLevel >= MaxRobeLevel ? DeathAltarPrice.Unavailable : LevelPriceFromZero(PlacementRangeLevel, 20),
            DeathAltarUpgradeType.RobeKnockbackImmunity => KnockbackImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(8),
            DeathAltarUpgradeType.RobeFallDamageImmunity => FallDamageImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(5),
            DeathAltarUpgradeType.RobeZeroMana => ZeroManaUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(15),
            DeathAltarUpgradeType.RobeLavaImmunity => LavaImmunityUnlocked ? DeathAltarPrice.Unavailable : DeathAltarPrice.Essence(8),
            _ => DeathAltarPrice.Unavailable
        };
    }

    public bool ApplyAltarUpgrade(Item item, DeathAltarUpgradeType upgradeType)
    {
        if (!GetUpgradePrice(upgradeType).IsAvailable)
            return false;

        if (IsDebuffUpgrade(upgradeType))
        {
            DebuffImmunityMask |= (ushort)(1 << GetDebuffBit(upgradeType));
            return true;
        }

        switch (upgradeType)
        {
            case DeathAltarUpgradeType.RobeDefense:
                DefenseLevel++;
                ApplyDynamicStats();
                break;
            case DeathAltarUpgradeType.RobeMaxLife:
                MaxLifeLevel++;
                break;
            case DeathAltarUpgradeType.RobeLifeRegen:
                LifeRegenLevel++;
                break;
            case DeathAltarUpgradeType.RobeMobility:
                MobilityLevel++;
                break;
            case DeathAltarUpgradeType.RobeMinions:
                MinionLevel++;
                break;
            case DeathAltarUpgradeType.RobeTurrets:
                TurretLevel++;
                break;
            case DeathAltarUpgradeType.RobePlacementRange:
                PlacementRangeLevel++;
                break;
            case DeathAltarUpgradeType.RobeKnockbackImmunity:
                KnockbackImmunityUnlocked = true;
                break;
            case DeathAltarUpgradeType.RobeFallDamageImmunity:
                FallDamageImmunityUnlocked = true;
                break;
            case DeathAltarUpgradeType.RobeZeroMana:
                ZeroManaUnlocked = true;
                break;
            case DeathAltarUpgradeType.RobeLavaImmunity:
                LavaImmunityUnlocked = true;
                break;
            default:
                return false;
        }
        return true;
    }

    public string GetCurrentUpgradeValue(DeathAltarUpgradeType upgradeType)
    {
        if (IsDebuffUpgrade(upgradeType))
            return Language.GetTextValue(IsDebuffImmune(upgradeType) ? "Mods.SoulHarvest.UI.Immune" : "Mods.SoulHarvest.UI.NotImmune");

        return upgradeType switch
        {
            DeathAltarUpgradeType.RobeDefense => DefenseValue.ToString(),
            DeathAltarUpgradeType.RobeMaxLife => $"+{MaxLifeBonus}",
            DeathAltarUpgradeType.RobeLifeRegen => $"+{LifeRegenBonus}",
            DeathAltarUpgradeType.RobeMobility => $"Lv.{MobilityLevel}",
            DeathAltarUpgradeType.RobeMinions => MinionCapacity.ToString(),
            DeathAltarUpgradeType.RobeTurrets => TurretCapacity.ToString(),
            DeathAltarUpgradeType.RobePlacementRange => $"+{PlacementRangeBonus}",
            DeathAltarUpgradeType.RobeKnockbackImmunity => ToggleValue(KnockbackImmunityUnlocked),
            DeathAltarUpgradeType.RobeFallDamageImmunity => ToggleValue(FallDamageImmunityUnlocked),
            DeathAltarUpgradeType.RobeZeroMana => ToggleValue(ZeroManaUnlocked),
            DeathAltarUpgradeType.RobeLavaImmunity => ToggleValue(LavaImmunityUnlocked),
            _ => string.Empty
        };
    }

    public string GetNextUpgradeValue(DeathAltarUpgradeType upgradeType)
    {
        if (IsDebuffUpgrade(upgradeType))
            return Language.GetTextValue("Mods.SoulHarvest.UI.Immune");

        return upgradeType switch
        {
            DeathAltarUpgradeType.RobeDefense => DefenseByLevel[Math.Min(DefenseLevel, MaxRobeLevel - 1)].ToString(),
            DeathAltarUpgradeType.RobeMaxLife => $"+{(MaxLifeLevel + 1) * 50}",
            DeathAltarUpgradeType.RobeLifeRegen => $"+{(LifeRegenLevel + 1) * 5}",
            DeathAltarUpgradeType.RobeMobility => $"Lv.{MobilityLevel + 1}",
            DeathAltarUpgradeType.RobeMinions => MinionsByLevel[Math.Min(MinionLevel + 1, MaxRobeLevel)].ToString(),
            DeathAltarUpgradeType.RobeTurrets => TurretsByLevel[Math.Min(TurretLevel + 1, MaxRobeLevel)].ToString(),
            DeathAltarUpgradeType.RobePlacementRange => $"+{(PlacementRangeLevel + 1) * 10}",
            DeathAltarUpgradeType.RobeKnockbackImmunity or DeathAltarUpgradeType.RobeFallDamageImmunity or DeathAltarUpgradeType.RobeZeroMana or DeathAltarUpgradeType.RobeLavaImmunity
                => Language.GetTextValue("Mods.SoulHarvest.UI.Enabled"),
            _ => string.Empty
        };
    }

    public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType upgradeType)
    {
        return DeathAltarUnavailableReason.Maxed;
    }

    public override void SaveData(TagCompound tag)
    {
        tag[nameof(DefenseLevel)] = DefenseLevel;
        tag[nameof(MaxLifeLevel)] = MaxLifeLevel;
        tag[nameof(LifeRegenLevel)] = LifeRegenLevel;
        tag[nameof(MobilityLevel)] = MobilityLevel;
        tag[nameof(MinionLevel)] = MinionLevel;
        tag[nameof(TurretLevel)] = TurretLevel;
        tag[nameof(PlacementRangeLevel)] = PlacementRangeLevel;
        tag[nameof(KnockbackImmunityUnlocked)] = KnockbackImmunityUnlocked;
        tag[nameof(FallDamageImmunityUnlocked)] = FallDamageImmunityUnlocked;
        tag[nameof(ZeroManaUnlocked)] = ZeroManaUnlocked;
        tag[nameof(LavaImmunityUnlocked)] = LavaImmunityUnlocked;
        tag[nameof(DebuffImmunityMask)] = (int)DebuffImmunityMask;
    }

    public override void LoadData(TagCompound tag)
    {
        DefenseLevel = Math.Clamp(tag.GetInt(nameof(DefenseLevel)), 1, MaxRobeLevel);
        MaxLifeLevel = Math.Clamp(tag.GetInt(nameof(MaxLifeLevel)), 0, MaxRobeLevel);
        LifeRegenLevel = Math.Clamp(tag.GetInt(nameof(LifeRegenLevel)), 0, MaxRobeLevel);
        MobilityLevel = Math.Clamp(tag.GetInt(nameof(MobilityLevel)), 0, MaxRobeLevel);
        MinionLevel = Math.Clamp(tag.GetInt(nameof(MinionLevel)), 0, MaxRobeLevel);
        TurretLevel = Math.Clamp(tag.GetInt(nameof(TurretLevel)), 0, MaxRobeLevel);
        PlacementRangeLevel = Math.Clamp(tag.GetInt(nameof(PlacementRangeLevel)), 0, MaxRobeLevel);
        KnockbackImmunityUnlocked = tag.GetBool(nameof(KnockbackImmunityUnlocked));
        FallDamageImmunityUnlocked = tag.GetBool(nameof(FallDamageImmunityUnlocked));
        ZeroManaUnlocked = tag.GetBool(nameof(ZeroManaUnlocked));
        LavaImmunityUnlocked = tag.GetBool(nameof(LavaImmunityUnlocked));
        DebuffImmunityMask = (ushort)Math.Clamp(tag.GetInt(nameof(DebuffImmunityMask)), 0, ushort.MaxValue);
        ApplyDynamicStats();
    }

    public override void NetSend(BinaryWriter writer)
    {
        writer.Write((byte)DefenseLevel);
        writer.Write((byte)MaxLifeLevel);
        writer.Write((byte)LifeRegenLevel);
        writer.Write((byte)MobilityLevel);
        writer.Write((byte)MinionLevel);
        writer.Write((byte)TurretLevel);
        writer.Write((byte)PlacementRangeLevel);
        writer.Write(KnockbackImmunityUnlocked);
        writer.Write(FallDamageImmunityUnlocked);
        writer.Write(ZeroManaUnlocked);
        writer.Write(LavaImmunityUnlocked);
        writer.Write(DebuffImmunityMask);
    }

    public override void NetReceive(BinaryReader reader)
    {
        DefenseLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxRobeLevel);
        MaxLifeLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
        LifeRegenLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
        MobilityLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
        MinionLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
        TurretLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
        PlacementRangeLevel = Math.Clamp((int)reader.ReadByte(), 0, MaxRobeLevel);
        KnockbackImmunityUnlocked = reader.ReadBoolean();
        FallDamageImmunityUnlocked = reader.ReadBoolean();
        ZeroManaUnlocked = reader.ReadBoolean();
        LavaImmunityUnlocked = reader.ReadBoolean();
        DebuffImmunityMask = reader.ReadUInt16();
        ApplyDynamicStats();
    }

    public override void AddRecipes()
    {
        CreateRecipe()
            .AddIngredient(ItemID.Silk, 20)
            .AddIngredient<Soul>()
            .AddTile(ModContent.TileType<DeathAltarTile>())
            .Register();
    }

    private void ApplyDynamicStats()
    {
        Item.defense = DefenseValue;
    }

    private bool IsDebuffImmune(DeathAltarUpgradeType type)
    {
        return (DebuffImmunityMask & (1 << GetDebuffBit(type))) != 0;
    }

    private int CountDebuffImmunities()
    {
        int count = 0;
        ushort mask = DebuffImmunityMask;
        while (mask != 0)
        {
            count += mask & 1;
            mask >>= 1;
        }
        return count;
    }

    private static bool IsDebuffUpgrade(DeathAltarUpgradeType type)
    {
        return type >= DeathAltarUpgradeType.RobeImmuneOnFire && type <= DeathAltarUpgradeType.RobeImmuneSuffocation;
    }

    private static int GetDebuffBit(DeathAltarUpgradeType type)
    {
        return (int)type - (int)DeathAltarUpgradeType.RobeImmuneOnFire;
    }

    private static int GetDebuffId(DeathAltarUpgradeType type)
    {
        return type switch
        {
            DeathAltarUpgradeType.RobeImmuneOnFire => BuffID.OnFire,
            DeathAltarUpgradeType.RobeImmunePoisoned => BuffID.Poisoned,
            DeathAltarUpgradeType.RobeImmuneConfused => BuffID.Confused,
            DeathAltarUpgradeType.RobeImmuneBleeding => BuffID.Bleeding,
            DeathAltarUpgradeType.RobeImmuneSlow => BuffID.Slow,
            DeathAltarUpgradeType.RobeImmuneWeak => BuffID.Weak,
            DeathAltarUpgradeType.RobeImmuneSilenced => BuffID.Silenced,
            DeathAltarUpgradeType.RobeImmuneDarkness => BuffID.Darkness,
            DeathAltarUpgradeType.RobeImmuneCursed => BuffID.Cursed,
            DeathAltarUpgradeType.RobeImmuneFrostburn => BuffID.Frostburn,
            DeathAltarUpgradeType.RobeImmuneFrozen => BuffID.Frozen,
            DeathAltarUpgradeType.RobeImmuneChaosState => BuffID.ChaosState,
            DeathAltarUpgradeType.RobeImmuneWet => BuffID.Wet,
            DeathAltarUpgradeType.RobeImmuneBrokenArmor => BuffID.BrokenArmor,
            DeathAltarUpgradeType.RobeImmuneSuffocation => BuffID.Suffocation,
            _ => 0
        };
    }

    private static DeathAltarPrice LevelPrice(int currentLevel, int soulMultiplier)
    {
        return currentLevel < 6
            ? DeathAltarPrice.Souls(currentLevel * soulMultiplier)
            : DeathAltarPrice.Essence(currentLevel * 2);
    }

    private static DeathAltarPrice LevelPriceFromZero(int currentLevel, int soulMultiplier)
    {
        return currentLevel < 5
            ? DeathAltarPrice.Souls((currentLevel + 1) * soulMultiplier)
            : DeathAltarPrice.Essence((currentLevel + 1) * 2);
    }

    private static string ToggleValue(bool enabled)
    {
        return Language.GetTextValue(enabled ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled");
    }
}