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 Microsoft.Xna.Framework;
using System;
using System.IO;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace SoulHarvest.Projectiles;

/// <summary>Target-locked shatter worth exactly the stored cut-and-domain total.</summary>
public sealed class ReaperDeathShatterProjectile : ModProjectile
{
    private SickleCombatSnapshot snapshot;
    private int targetIndex = -1;
    private int actionId;
    private int age;
    private bool configured;
    private Vector2 visualCenter;
    private bool drawCenterVisual;
    private bool serverAuthorized;
    private bool serverDamageTriggered;

    internal Vector2 VisualCenter => visualCenter;
    internal bool DrawsCenterVisual => drawCenterVisual;

    public override string Texture => "Terraria/Images/Projectile_0";

    internal static int Spawn(Terraria.DataStructures.IEntitySource source, int owner,
        in SickleCombatSnapshot snapshot, NPC target, int accumulatedDamage,
        int actionId, Vector2 visualCenter, bool drawCenterVisual)
    {
        if (Main.netMode == NetmodeID.MultiplayerClient || accumulatedDamage <= 0)
            return -1;
        int damage = Math.Max(1, Math.Min(int.MaxValue / 4, accumulatedDamage));
        int index = Projectile.NewProjectile(source, target.Center, Vector2.Zero,
            ModContent.ProjectileType<ReaperDeathShatterProjectile>(), damage,
            snapshot.Knockback * 0.2f, owner);
        if (index < 0 || index >= Main.maxProjectiles)
            return -1;
        Projectile projectile = Main.projectile[index];
        projectile.originalDamage = damage;
        projectile.ArmorPenetration = Math.Max(0, target.defense);
        if (projectile.ModProjectile is ReaperDeathShatterProjectile shatter)
            shatter.Configure(snapshot, target.whoAmI, actionId,
                visualCenter, drawCenterVisual);
        projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
            projectile, snapshot, ReaperHitKind.UltimateDerived, 63, 0f, actionId);
        ReaperProjectileHelper.SyncNewProjectile(projectile);
        return index;
    }

    public override void SetDefaults()
    {
        Projectile.width = 8;
        Projectile.height = 8;
        Projectile.friendly = true;
        Projectile.hostile = false;
        Projectile.tileCollide = false;
        Projectile.ignoreWater = true;
        Projectile.penetrate = 1;
        Projectile.timeLeft = 12;
        Projectile.netImportant = true;
        Projectile.DamageType = DamageClass.Melee;
    }

    public override bool ShouldUpdatePosition() => false;
    public override bool? CanDamage()
    {
        if (Main.netMode == NetmodeID.MultiplayerClient)
            return false;
        return configured && age is >= 2 and <= 4 ? null : false;
    }
    public override bool? CanHitNPC(NPC target)
        => configured && target.whoAmI == targetIndex ? null : false;

    public override void AI()
    {
        if (Main.netMode == NetmodeID.Server && !serverAuthorized)
        {
            Projectile.Kill();
            return;
        }
        if (!configured)
            return;
        age++;
        if (targetIndex >= 0 && targetIndex < Main.maxNPCs && Main.npc[targetIndex].active)
            Projectile.Center = Main.npc[targetIndex].Center;
        if (Main.netMode == NetmodeID.Server && !serverDamageTriggered
            && age >= 2)
        {
            serverDamageTriggered = true;
            ReaperProjectileHelper.DamageOnDedicatedServer(Projectile);
        }
    }

    public override bool? Colliding(Rectangle projHitbox, Rectangle targetHitbox)
        => configured && targetIndex >= 0 && targetIndex < Main.maxNPCs
            && Main.npc[targetIndex].Hitbox.Intersects(targetHitbox);

    public override bool PreDraw(ref Color lightColor)
    {
        if (!configured || !drawCenterVisual || Main.dedServ)
            return false;
        float opacity = 1f - MathHelper.Clamp(age / 11f, 0f, 1f);
        for (int shard = 0; shard < 12; shard++)
        {
            float angle = shard * MathHelper.TwoPi / 12f + actionId * 0.013f;
            Vector2 direction = angle.ToRotationVector2();
            Vector2 start = visualCenter + direction * (8f + age * 1.2f);
            Vector2 end = start + direction * (30f + age * 5f);
            DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
                new Color(225, 22, 62, 0) * opacity, 4.5f * opacity + 0.8f);
            DeathDomainPrimitiveTextureSystem.DrawSmoothWorldLine(start, end,
                Color.White * (opacity * 0.78f), 1.1f);
        }
        return false;
    }

    public override void SendExtraAI(BinaryWriter writer)
    {
        writer.Write(configured);
        if (!configured)
            return;
        snapshot.Write(writer);
        writer.Write((short)targetIndex);
        writer.Write(actionId);
        writer.WriteVector2(visualCenter);
        writer.Write(drawCenterVisual);
        writer.Write((byte)Math.Clamp(age, 0, byte.MaxValue));
    }

    public override void ReceiveExtraAI(BinaryReader reader)
    {
        bool incoming = reader.ReadBoolean();
        if (!incoming)
        {
            if (Main.netMode != NetmodeID.Server)
                configured = false;
            return;
        }
        SickleCombatSnapshot incomingSnapshot = SickleCombatSnapshot.Read(reader);
        int incomingTarget = reader.ReadInt16();
        int incomingAction = reader.ReadInt32();
        Vector2 incomingVisualCenter = reader.ReadVector2();
        bool incomingDrawCenterVisual = reader.ReadBoolean();
        int incomingAge = reader.ReadByte();
        if (Main.netMode == NetmodeID.Server)
            return;
        snapshot = incomingSnapshot;
        targetIndex = incomingTarget;
        actionId = incomingAction;
        visualCenter = IsFinite(incomingVisualCenter)
            ? incomingVisualCenter
            : Projectile.Center;
        drawCenterVisual = incomingDrawCenterVisual;
        age = incomingAge;
        configured = true;
    }

    private void Configure(in SickleCombatSnapshot value, int target,
        int parentActionId, Vector2 center, bool rendersCenterVisual)
    {
        snapshot = value;
        targetIndex = target;
        actionId = parentActionId;
        visualCenter = IsFinite(center) ? center : Projectile.Center;
        drawCenterVisual = rendersCenterVisual;
        age = 0;
        configured = true;
        serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
        serverDamageTriggered = false;
    }

    private static bool IsFinite(Vector2 value)
        => float.IsFinite(value.X) && float.IsFinite(value.Y);
}