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 System;
using System.Collections.Generic;
using System.IO;
using Terraria;
using Terraria.DataStructures;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.IO;

namespace SoulHarvest.Items;

[AutoloadEquip(EquipType.Wings)]
public class DeathWings : ModItem, IDeathAltarUpgradeable
{
    public const int MaxWingLevel = 10;

    private static readonly DeathAltarUpgradeType[] UpgradeTypes =
    [
        DeathAltarUpgradeType.WingFlightTime,
        DeathAltarUpgradeType.WingHorizontalSpeed,
        DeathAltarUpgradeType.WingAcceleration,
        DeathAltarUpgradeType.WingVerticalPower,
        DeathAltarUpgradeType.WingInfiniteFlight
    ];

    public int FlightTimeLevel { get; private set; } = 1;
    public int HorizontalSpeedLevel { get; private set; } = 1;
    public int AccelerationLevel { get; private set; } = 1;
    public int VerticalPowerLevel { get; private set; } = 1;
    public bool InfiniteFlightUnlocked { get; private set; }

    public int FlightTimeTicks => 60 + (FlightTimeLevel - 1) * 120 / 9;
    public float HorizontalSpeed => MathHelper.Lerp(6f, 25f, LevelProgress(HorizontalSpeedLevel));
    public float HorizontalAcceleration => MathHelper.Lerp(0.15f, 1.2f, LevelProgress(AccelerationLevel));
    public float VerticalPower => MathHelper.Lerp(1f, 8f, LevelProgress(VerticalPowerLevel));
    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.Wing.Sets.Stats[Item.wingSlot] = new WingStats(60, 6f, 1f);
    }

    public override void SetDefaults()
    {
        Item.width = 22;
        Item.height = 22;
        Item.accessory = true;
        Item.rare = ItemRarityID.Red;
        Item.value = Item.sellPrice(gold: 10);
    }

    public override void UpdateAccessory(Player player, bool hideVisual)
    {
        player.wingTimeMax = Math.Max(player.wingTimeMax, FlightTimeTicks);
    }

    public override void VerticalWingSpeeds(
        Player player,
        ref float ascentWhenFalling,
        ref float ascentWhenRising,
        ref float maxCanAscendMultiplier,
        ref float maxAscentMultiplier,
        ref float constantAscend)
    {
        DeathWings equippedWings = FindEquippedWings(player, player.wingsLogic) ?? this;
        if (equippedWings.InfiniteFlightUnlocked)
            player.wingTime = player.wingTimeMax;

        float progress = LevelProgress(equippedWings.VerticalPowerLevel);
        ascentWhenFalling = MathHelper.Lerp(0.85f, 8f, progress);
        ascentWhenRising = MathHelper.Lerp(0.15f, 8f, progress);
        maxAscentMultiplier = equippedWings.VerticalPower;
    }

    public override void HorizontalWingSpeeds(Player player, ref float speed, ref float acceleration)
    {
        DeathWings equippedWings = FindEquippedWings(player, player.wingsLogic) ?? this;
        if (equippedWings.InfiniteFlightUnlocked)
            player.wingTime = player.wingTimeMax;

        speed = Math.Max(speed, equippedWings.HorizontalSpeed);
        acceleration = Math.Max(acceleration, equippedWings.HorizontalAcceleration);
    }

    public override bool WingUpdate(Player player, bool inUse)
    {
        if (!player.active || player.dead)
            return false;

        DeathWings visibleWings = FindEquippedWings(player, player.wings) ?? this;
        float mastery = visibleWings.VisualMastery;
        MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
        bool gliding = !inUse && player.controlJump && Math.Abs(player.velocity.Y) > 0.2f;
        bool justStartedFlying = inUse
            && !modPlayer.DeathWingsActiveLastTick
            && !modPlayer.DeathWingsActiveThisTick;
        modPlayer.DeathWingsActiveThisTick = inUse || gliding;

        float flightSpeed = player.velocity.Length();
        if ((inUse || gliding)
            && Main.netMode != NetmodeID.MultiplayerClient)
        {
            modPlayer.RecordDeathWingDomainTrail(mastery);
        }
        if (Main.netMode == NetmodeID.Server)
            return false;

        if ((inUse || gliding) && flightSpeed > 1.25f)
        {
            // Let Terraria render the entire character at its recorded shadow
            // positions. The body-width cut remains behind these silhouettes, so
            // rapid movement reads as one layered deathly afterimage instead of
            // a detached wing-tip effect.
            player.armorEffectDrawShadow = true;
            if (visibleWings.InfiniteFlightUnlocked && flightSpeed > 10f)
                player.armorEffectDrawShadowLokis = true;
        }

        if (justStartedFlying)
            SpawnTakeoffBurst(player, mastery);

        if (inUse)
        {
            GetWingTips(player, mastery, out Vector2 upperTip, out Vector2 lowerTip, out _);
            modPlayer.RecordDeathWingTrail(upperTip, lowerTip, mastery);
            SpawnFlightEffects(player, mastery, visibleWings.InfiniteFlightUnlocked);
        }
        else if (gliding)
        {
            GetWingTips(player, mastery, out Vector2 upperTip, out Vector2 lowerTip, out _);
            modPlayer.RecordDeathWingTrail(upperTip, lowerTip, mastery);
            SpawnGlideEffects(player, mastery);
        }

        return false;
    }

    public override void ModifyTooltips(List<TooltipLine> tooltips)
    {
        string stats = Language.GetTextValue(
            "Mods.SoulHarvest.UI.WingStats",
            FlightTimeTicks,
            HorizontalSpeed.ToString("0.00"),
            HorizontalAcceleration.ToString("0.00"),
            VerticalPower.ToString("0.00"),
            Language.GetTextValue(InfiniteFlightUnlocked ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled"));
        tooltips.Add(new TooltipLine(Mod, "DeathWingStats", stats) { OverrideColor = new Color(235, 65, 85) });
        tooltips.Add(new TooltipLine(Mod, "SoulHarvestAltarHint", Language.GetTextValue("Mods.SoulHarvest.UI.AltarHint")));
    }

    public DeathAltarPrice GetUpgradePrice(DeathAltarUpgradeType upgradeType)
    {
        return upgradeType switch
        {
            DeathAltarUpgradeType.WingFlightTime => LevelPrice(FlightTimeLevel, 30),
            DeathAltarUpgradeType.WingHorizontalSpeed => LevelPrice(HorizontalSpeedLevel, 40),
            DeathAltarUpgradeType.WingAcceleration => LevelPrice(AccelerationLevel, 35),
            DeathAltarUpgradeType.WingVerticalPower => LevelPrice(VerticalPowerLevel, 45),
            DeathAltarUpgradeType.WingInfiniteFlight => InfiniteFlightUnlocked || !AllFlightStatsMaxed
                ? DeathAltarPrice.Unavailable
                : DeathAltarPrice.Essence(HalvedCost(25)),
            _ => DeathAltarPrice.Unavailable
        };
    }

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

        switch (upgradeType)
        {
            case DeathAltarUpgradeType.WingFlightTime:
                FlightTimeLevel++;
                break;
            case DeathAltarUpgradeType.WingHorizontalSpeed:
                HorizontalSpeedLevel++;
                break;
            case DeathAltarUpgradeType.WingAcceleration:
                AccelerationLevel++;
                break;
            case DeathAltarUpgradeType.WingVerticalPower:
                VerticalPowerLevel++;
                break;
            case DeathAltarUpgradeType.WingInfiniteFlight:
                InfiniteFlightUnlocked = true;
                break;
            default:
                return false;
        }
        return true;
    }

    public string GetCurrentUpgradeValue(DeathAltarUpgradeType upgradeType)
    {
        return upgradeType switch
        {
            DeathAltarUpgradeType.WingFlightTime => Language.GetTextValue("Mods.SoulHarvest.UI.UpgradeValues.Frames", FlightTimeTicks),
            DeathAltarUpgradeType.WingHorizontalSpeed => HorizontalSpeed.ToString("0.00"),
            DeathAltarUpgradeType.WingAcceleration => HorizontalAcceleration.ToString("0.00"),
            DeathAltarUpgradeType.WingVerticalPower => VerticalPower.ToString("0.00"),
            DeathAltarUpgradeType.WingInfiniteFlight => Language.GetTextValue(InfiniteFlightUnlocked ? "Mods.SoulHarvest.UI.Enabled" : "Mods.SoulHarvest.UI.Disabled"),
            _ => string.Empty
        };
    }

    public string GetNextUpgradeValue(DeathAltarUpgradeType upgradeType)
    {
        return upgradeType switch
        {
            DeathAltarUpgradeType.WingFlightTime => Language.GetTextValue("Mods.SoulHarvest.UI.UpgradeValues.Frames", 60 + FlightTimeLevel * 120 / 9),
            DeathAltarUpgradeType.WingHorizontalSpeed => MathHelper.Lerp(6f, 25f, LevelProgress(HorizontalSpeedLevel + 1)).ToString("0.00"),
            DeathAltarUpgradeType.WingAcceleration => MathHelper.Lerp(0.15f, 1.2f, LevelProgress(AccelerationLevel + 1)).ToString("0.00"),
            DeathAltarUpgradeType.WingVerticalPower => MathHelper.Lerp(1f, 8f, LevelProgress(VerticalPowerLevel + 1)).ToString("0.00"),
            DeathAltarUpgradeType.WingInfiniteFlight => Language.GetTextValue("Mods.SoulHarvest.UI.Enabled"),
            _ => string.Empty
        };
    }

    public DeathAltarUnavailableReason GetUnavailableReason(DeathAltarUpgradeType upgradeType)
    {
        return upgradeType == DeathAltarUpgradeType.WingInfiniteFlight && !InfiniteFlightUnlocked && !AllFlightStatsMaxed
            ? DeathAltarUnavailableReason.RequiresWingMastery
            : DeathAltarUnavailableReason.Maxed;
    }

    public override void SaveData(TagCompound tag)
    {
        tag[nameof(FlightTimeLevel)] = FlightTimeLevel;
        tag[nameof(HorizontalSpeedLevel)] = HorizontalSpeedLevel;
        tag[nameof(AccelerationLevel)] = AccelerationLevel;
        tag[nameof(VerticalPowerLevel)] = VerticalPowerLevel;
        tag[nameof(InfiniteFlightUnlocked)] = InfiniteFlightUnlocked;
    }

    public override void LoadData(TagCompound tag)
    {
        FlightTimeLevel = Math.Clamp(tag.GetInt(nameof(FlightTimeLevel)), 1, MaxWingLevel);
        HorizontalSpeedLevel = Math.Clamp(tag.GetInt(nameof(HorizontalSpeedLevel)), 1, MaxWingLevel);
        AccelerationLevel = Math.Clamp(tag.GetInt(nameof(AccelerationLevel)), 1, MaxWingLevel);
        VerticalPowerLevel = Math.Clamp(tag.GetInt(nameof(VerticalPowerLevel)), 1, MaxWingLevel);
        InfiniteFlightUnlocked = tag.GetBool(nameof(InfiniteFlightUnlocked));
    }

    public override void NetSend(BinaryWriter writer)
    {
        writer.Write((byte)FlightTimeLevel);
        writer.Write((byte)HorizontalSpeedLevel);
        writer.Write((byte)AccelerationLevel);
        writer.Write((byte)VerticalPowerLevel);
        writer.Write(InfiniteFlightUnlocked);
    }

    public override void NetReceive(BinaryReader reader)
    {
        FlightTimeLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxWingLevel);
        HorizontalSpeedLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxWingLevel);
        AccelerationLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxWingLevel);
        VerticalPowerLevel = Math.Clamp((int)reader.ReadByte(), 1, MaxWingLevel);
        InfiniteFlightUnlocked = reader.ReadBoolean();
    }

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

    private bool AllFlightStatsMaxed => FlightTimeLevel >= MaxWingLevel
        && HorizontalSpeedLevel >= MaxWingLevel
        && AccelerationLevel >= MaxWingLevel
        && VerticalPowerLevel >= MaxWingLevel;

    private float VisualMastery => MathHelper.Clamp(
        (LevelProgress(FlightTimeLevel)
            + LevelProgress(HorizontalSpeedLevel)
            + LevelProgress(AccelerationLevel)
            + LevelProgress(VerticalPowerLevel)) / 4f
        + (InfiniteFlightUnlocked ? 0.15f : 0f),
        0f,
        1f);

    private static DeathAltarPrice LevelPrice(int level, int soulMultiplier)
    {
        if (level >= MaxWingLevel)
            return DeathAltarPrice.Unavailable;
        return level < 6
            ? DeathAltarPrice.Souls(HalvedCost(level * soulMultiplier))
            : DeathAltarPrice.Essence(HalvedCost(level));
    }

    private static int HalvedCost(int cost) => Math.Max(1, (cost + 1) / 2);

    private static float LevelProgress(int level)
    {
        return MathHelper.Clamp((level - 1) / 9f, 0f, 1f);
    }

    private static DeathWings? FindEquippedWings(Player player, int wingSlot)
    {
        DeathWings? bestMatch = null;
        float bestMastery = -1f;
        foreach (Item item in player.armor)
        {
            if (item.wingSlot != wingSlot || item.ModItem is not DeathWings candidate)
                continue;

            float mastery = candidate.VisualMastery;
            if (mastery > bestMastery)
            {
                bestMatch = candidate;
                bestMastery = mastery;
            }
        }

        return bestMatch;
    }

    private static void SpawnFlightEffects(Player player, float mastery, bool infiniteFlight)
    {
        GetWingTips(player, mastery, out Vector2 upperTip, out Vector2 lowerTip, out Vector2 wingRoot);
        Color emberColor = Color.Lerp(new Color(82, 3, 16), new Color(235, 24, 92), mastery);
        Color soulColor = Color.Lerp(new Color(48, 4, 68), new Color(218, 92, 255), mastery);
        int interval = mastery >= 0.35f ? 1 : 2;

        if ((Main.GameUpdateCount + (ulong)player.whoAmI) % (ulong)interval == 0)
        {
            SpawnWingTipEmber(player, upperTip, emberColor, mastery);
            SpawnWingTipEmber(player, lowerTip, emberColor, mastery);
        }

        if (Main.rand.NextFloat() < 0.16f + mastery * 0.34f)
        {
            Vector2 position = Vector2.Lerp(upperTip, lowerTip, Main.rand.NextFloat()) + Main.rand.NextVector2Circular(3f, 3f);
            Vector2 velocity = -player.velocity * 0.12f + Main.rand.NextVector2Circular(0.55f, 0.55f);
            Dust soul = Dust.NewDustPerfect(position, DustID.Smoke, velocity, 65, soulColor, 0.55f + mastery * 0.55f);
            soul.noGravity = true;
            soul.fadeIn = 0.75f + mastery * 0.25f;
        }

        float speed = player.velocity.Length();
        if (mastery >= 0.25f && speed > 6f && (Main.GameUpdateCount + (ulong)player.whoAmI) % 2UL == 0)
        {
            Vector2 movementDirection = player.velocity.SafeNormalize(Vector2.UnitY);
            int streakCount = mastery >= 0.8f ? 2 : 1;
            for (int index = 0; index < streakCount; index++)
            {
                Vector2 position = player.Center
                    - movementDirection * Main.rand.NextFloat(14f, 30f + speed * 1.5f)
                    + Main.rand.NextVector2Circular(8f, 10f);
                int dustType = mastery >= 0.45f
                    ? DustID.RainbowMk2
                    : DustID.Shadowflame;
                Dust streak = Dust.NewDustPerfect(position, dustType,
                    -player.velocity * 0.06f, 70,
                    Color.Lerp(emberColor, soulColor, mastery * 0.38f),
                    0.4f + mastery * 0.35f);
                streak.noGravity = true;
                streak.fadeIn = 0.65f;
            }
        }

        if (mastery >= 0.7f && Main.rand.NextBool(18))
        {
            Dust bat = Dust.NewDustPerfect(
                wingRoot + Main.rand.NextVector2Circular(7f, 10f),
                DustID.BatScepter,
                -player.velocity * 0.08f + Main.rand.NextVector2Circular(1.4f, 1.1f),
                85,
                emberColor,
                0.75f + mastery * 0.3f);
            bat.noGravity = true;
        }

        if (infiniteFlight && (Main.GameUpdateCount + (ulong)player.whoAmI) % 3UL == 0)
        {
            float angle = Main.GlobalTimeWrappedHourly * 5.5f + player.whoAmI * 0.73f;
            Vector2 orbit = new Vector2((float)Math.Cos(angle) * 20f, (float)Math.Sin(angle) * 11f);
            Dust orbitingSoul = Dust.NewDustPerfect(wingRoot + orbit, DustID.DungeonSpirit, -player.velocity * 0.05f, 50, soulColor, 0.75f);
            orbitingSoul.noGravity = true;
        }

        Lighting.AddLight(wingRoot, new Vector3(0.55f + mastery * 0.35f, 0.035f + mastery * 0.04f, 0.08f + mastery * 0.18f));
    }

    private static void SpawnGlideEffects(Player player, float mastery)
    {
        if ((Main.GameUpdateCount + (ulong)player.whoAmI) % 4UL != 0)
            return;

        GetWingTips(player, mastery, out Vector2 upperTip, out Vector2 lowerTip, out _);
        Color color = Color.Lerp(new Color(70, 2, 14), new Color(205, 20, 78), mastery);
        Vector2 position = Main.rand.NextBool() ? upperTip : lowerTip;
        Dust ember = Dust.NewDustPerfect(
            position + Main.rand.NextVector2Circular(2f, 2f),
            DustID.Smoke,
            -player.velocity * 0.09f + Main.rand.NextVector2Circular(0.35f, 0.35f),
            100,
            color,
            0.55f + mastery * 0.25f);
        ember.noGravity = true;
    }

    private static void SpawnTakeoffBurst(Player player, float mastery)
    {
        GetWingTips(player, mastery, out _, out _, out Vector2 wingRoot);
        Color color = Color.Lerp(new Color(215, 20, 35), new Color(255, 90, 165), mastery);
        int particleCount = 8 + (int)(mastery * 10f);
        for (int index = 0; index < particleCount; index++)
        {
            Vector2 velocity = Main.rand.NextVector2CircularEdge(2.2f + mastery * 2.1f, 1.7f + mastery * 1.4f)
                - player.velocity * 0.08f;
            int dustType = index % 4 == 0 ? DustID.Shadowflame : DustID.RainbowMk2;
            Dust burst = Dust.NewDustPerfect(
                wingRoot + Main.rand.NextVector2Circular(7f, 10f),
                dustType,
                velocity,
                65,
                color,
                0.65f + Main.rand.NextFloat(0.25f) + mastery * 0.25f);
            burst.noGravity = true;
            burst.fadeIn = 0.7f;
        }
    }

    private static void SpawnWingTipEmber(Player player, Vector2 position, Color color, float mastery)
    {
        Vector2 velocity = -player.velocity * MathHelper.Lerp(0.08f, 0.18f, mastery)
            + new Vector2(-player.direction * Main.rand.NextFloat(0.15f, 0.8f), Main.rand.NextFloat(-0.35f, 0.35f));
        int dustType = mastery >= 0.55f && Main.rand.NextBool(4)
            ? DustID.RainbowMk2
            : DustID.Smoke;
        Dust ember = Dust.NewDustPerfect(
            position + Main.rand.NextVector2Circular(2f + mastery * 1.5f, 2f + mastery * 1.5f),
            dustType,
            velocity,
            65,
            color,
            0.55f + mastery * 0.45f);
        ember.noGravity = true;
        ember.fadeIn = 0.7f + mastery * 0.25f;
    }

    private static void GetWingTips(Player player, float mastery, out Vector2 upperTip, out Vector2 lowerTip, out Vector2 wingRoot)
    {
        float flap = (float)Math.Sin(Main.GlobalTimeWrappedHourly * 13f + player.whoAmI * 0.61f);
        float reach = 15f + mastery * 6f;
        wingRoot = player.Center + new Vector2(-player.direction * 7f, -2f);
        upperTip = wingRoot + new Vector2(-player.direction * (reach + flap * 2.5f), -12f - flap * 3.5f);
        lowerTip = wingRoot + new Vector2(-player.direction * (reach - flap * 1.8f), 12f + flap * 3f);
    }
}