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;

public sealed class ReaperSoulFamiliarProjectile : ModProjectile
{
    private SickleCombatSnapshot snapshot;
    private bool configured;
    private int timer;
    private int attacksMade;
    private bool serverAuthorized;
    private Vector2 authoritativeCenter;

    public override string Texture => "SoulHarvest/Projectiles/SoulSickleProjectile";

    public static void Spawn(Terraria.DataStructures.IEntitySource source, int owner,
        in SickleCombatSnapshot snapshot, Vector2 origin)
    {
        if (Main.netMode == NetmodeID.MultiplayerClient)
            return;
        int type = ModContent.ProjectileType<ReaperSoulFamiliarProjectile>();
        for (int index = 0; index < Main.maxProjectiles; index++)
        {
            Projectile existing = Main.projectile[index];
            if (existing.active && existing.owner == owner && existing.type == type)
                existing.Kill();
        }
        int projectileIndex = Projectile.NewProjectile(source, origin, Vector2.Zero, type, 0, 0f, owner);
        if (projectileIndex < 0 || projectileIndex >= Main.maxProjectiles)
            return;
        Projectile projectile = Main.projectile[projectileIndex];
        if (projectile.ModProjectile is ReaperSoulFamiliarProjectile familiar)
            familiar.Configure(snapshot);
        ReaperProjectileHelper.SyncNewProjectile(projectile);
        if (snapshot.Skill2 > 0)
        {
            ReaperSkillCueProjectile.Spawn(source, owner, snapshot, 1, origin,
                Vector2.UnitY * -1f, actionId: projectile.identity);
        }
    }

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

    public override bool? CanDamage() => false;
    public override bool ShouldUpdatePosition() => false;

    public override void AI()
    {
        if (Main.netMode == NetmodeID.Server && !serverAuthorized)
        {
            Projectile.Kill();
            return;
        }
        if (!configured || Projectile.owner < 0 || Projectile.owner >= Main.maxPlayers)
            return;
        if (Main.netMode == NetmodeID.Server)
            Projectile.Center = authoritativeCenter;
        Player owner = Main.player[Projectile.owner];
        if (!owner.active || owner.dead)
        {
            Projectile.Kill();
            return;
        }
        timer++;
        float angle = timer * 0.045f + Projectile.identity * 0.31f;
        Vector2 desired = owner.MountedCenter + new Vector2(owner.direction * -46f, -38f) + angle.ToRotationVector2() * 12f;
        Projectile.Center = Vector2.Lerp(Projectile.Center, desired, 0.16f);
        Projectile.velocity = Vector2.Zero;
        if (Main.netMode == NetmodeID.Server)
            authoritativeCenter = Projectile.Center;
        Projectile.rotation += 0.08f;

        int maximumAttacks = snapshot.Skill2 switch { 2 => 3, >= 3 => 4, _ => 2 };
        int duration = (snapshot.Skill2 switch { 2 => 4, >= 3 => 5, _ => 3 }) * 60;
        int interval = Math.Max(22, duration / (maximumAttacks + 1));
        if (timer % interval == 0 && attacksMade < maximumAttacks && Main.netMode != NetmodeID.MultiplayerClient)
        {
            NPC? target = FindTarget(640f);
            if (target is not null)
            {
                float factor = snapshot.Skill2 switch { 2 => 0.45f, >= 3 => 0.5f, _ => 0.4f };
                Vector2 direction = (target.Center - Projectile.Center).SafeNormalize(Vector2.UnitX * owner.direction);
                ReaperStrikeProjectile.Spawn(Projectile.GetSource_FromThis(), Projectile.owner, snapshot,
                    ReaperHitKind.SoulFamiliar, attacksMade, Projectile.Center, direction,
                    ReaperStrikeShape.Line, Math.Min(340f, Vector2.Distance(Projectile.Center, target.Center) + 36f), 19f, factor);
                attacksMade++;
            }
        }
        if (timer >= duration)
            Projectile.Kill();

        if (Main.netMode != NetmodeID.Server && Main.rand.NextBool(2))
        {
            Dust dust = Dust.NewDustPerfect(Projectile.Center + Main.rand.NextVector2Circular(12f, 12f),
                DustID.AncientLight, -Projectile.velocity * 0.1f, 60,
                Main.rand.NextBool() ? new Color(85, 225, 255) : new Color(175, 85, 255), 0.85f);
            dust.noGravity = true;
        }
    }

    public override void SendExtraAI(BinaryWriter writer)
    {
        writer.Write(configured);
        if (configured)
        {
            snapshot.Write(writer);
            writer.Write((short)timer);
            writer.Write((byte)attacksMade);
        }
    }

    public override void ReceiveExtraAI(BinaryReader reader)
    {
        bool incomingConfigured = reader.ReadBoolean();
        if (!incomingConfigured)
        {
            if (Main.netMode != NetmodeID.Server)
                configured = false;
            return;
        }
        SickleCombatSnapshot incomingSnapshot = SickleCombatSnapshot.Read(reader);
        int incomingTimer = reader.ReadInt16();
        int incomingAttacksMade = reader.ReadByte();
        if (Main.netMode == NetmodeID.Server)
            return;
        configured = true;
        snapshot = incomingSnapshot;
        timer = incomingTimer;
        attacksMade = incomingAttacksMade;
    }

    private void Configure(in SickleCombatSnapshot value)
    {
        snapshot = value;
        configured = true;
        serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
        Projectile.GetGlobalProjectile<MyGlobalProjectile>().AuthorizeServerReaperProjectile(Projectile);
        authoritativeCenter = Projectile.Center;
        timer = 0;
        attacksMade = 0;
        Projectile.timeLeft = (snapshot.Skill2 switch { 2 => 4, >= 3 => 5, _ => 3 }) * 60 + 10;
    }

    private NPC? FindTarget(float maximumDistance)
    {
        NPC? result = null;
        foreach (NPC npc in Main.ActiveNPCs)
        {
            if (!npc.CanBeChasedBy(Projectile))
                continue;
            float distance = Vector2.Distance(Projectile.Center, npc.Center);
            if (distance < maximumDistance && Collision.CanHitLine(Projectile.Center, 1, 1, npc.Center, 1, 1))
            {
                maximumDistance = distance;
                result = npc;
            }
        }
        return result;
    }
}