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

namespace SoulHarvest.Projectiles;

public sealed class ReaperFrostShardProjectile : ModProjectile
{
    private readonly HashSet<int> hitRoots = [];
    private SickleCombatSnapshot snapshot;
    private bool configured;
    private float maximumTravel;
    private float traveled;
    private int retargetsRemaining;
    private bool serverAuthorized;
    private int authoritativeDamage;
    private Vector2 authoritativeCenter;
    private Vector2 authoritativeVelocity;

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

    public static void Spawn(Terraria.DataStructures.IEntitySource source, int owner,
        in SickleCombatSnapshot snapshot, Vector2 origin, Vector2 direction)
    {
        if (Main.netMode == NetmodeID.MultiplayerClient)
            return;
        int level = Math.Max(1, (int)snapshot.Skill2);
        float factor = level switch { 2 => 0.90f, >= 3 => 1.05f, _ => 0.75f };
        int index = Projectile.NewProjectile(source, origin, direction.SafeNormalize(Vector2.UnitX) * 11f,
            ModContent.ProjectileType<ReaperFrostShardProjectile>(),
            Math.Max(1, (int)Math.Round(snapshot.Damage * factor)), snapshot.Knockback, owner);
        if (index < 0 || index >= Main.maxProjectiles)
            return;
        Projectile projectile = Main.projectile[index];
        projectile.originalDamage = snapshot.Damage;
        if (projectile.ModProjectile is ReaperFrostShardProjectile shard)
            shard.Configure(snapshot);
        projectile.GetGlobalProjectile<MyGlobalProjectile>().ConfigureReaperProjectile(
            projectile, snapshot, ReaperHitKind.PrimaryDerived, 2, direction.ToRotation());
        ReaperProjectileHelper.SyncNewProjectile(projectile);
    }

    public override void SetStaticDefaults()
    {
        ProjectileID.Sets.TrailCacheLength[Type] = 12;
        ProjectileID.Sets.TrailingMode[Type] = 2;
    }

    public override void SetDefaults()
    {
        Projectile.width = 30;
        Projectile.height = 30;
        Projectile.friendly = true;
        Projectile.tileCollide = true;
        Projectile.ignoreWater = true;
        Projectile.penetrate = -1;
        Projectile.timeLeft = 90;
        Projectile.DamageType = DamageClass.Melee;
        Projectile.usesLocalNPCImmunity = true;
        Projectile.localNPCHitCooldown = 10;
    }

    public override bool ShouldUpdatePosition() => false;

    public override void AI()
    {
        if (Main.netMode == NetmodeID.Server)
        {
            if (!serverAuthorized)
            {
                Projectile.Kill();
                return;
            }
            Projectile.damage = authoritativeDamage;
            Projectile.Center = authoritativeCenter;
            Projectile.velocity = authoritativeVelocity;
        }
        if (!configured)
            return;
        traveled += Projectile.velocity.Length();
        if (traveled >= maximumTravel)
        {
            Projectile.Kill();
            return;
        }
        NPC? target = FindTarget(maximumTravel - traveled);
        if (target is not null)
        {
            Vector2 desired = (target.Center - Projectile.Center).SafeNormalize(Projectile.velocity.SafeNormalize(Vector2.UnitX)) * 11f;
            Projectile.velocity = Vector2.Lerp(Projectile.velocity, desired, 0.11f);
        }
        Projectile.rotation += 0.28f * Math.Sign(Projectile.velocity.X == 0f ? 1f : Projectile.velocity.X);
        if (Main.netMode != NetmodeID.Server)
        {
            Lighting.AddLight(Projectile.Center, new Vector3(0.12f, 0.45f, 0.72f));
            if (Main.rand.NextBool(2))
            {
                Dust dust = Dust.NewDustPerfect(Projectile.Center, DustID.IceTorch, -Projectile.velocity * 0.08f,
                    60, new Color(125, 210, 255), 0.95f);
                dust.noGravity = true;
            }
        }
        MoveOneStep();
    }

    public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone)
    {
        hitRoots.Add(target.realLife >= 0 ? target.realLife : target.whoAmI);
        if (retargetsRemaining-- <= 0)
        {
            Projectile.Kill();
            return;
        }
        NPC? next = FindTarget(maximumTravel - traveled);
        if (next is null)
        {
            Projectile.Kill();
            return;
        }
        Projectile.velocity = (next.Center - Projectile.Center).SafeNormalize(Projectile.velocity.SafeNormalize(Vector2.UnitX)) * 11f;
        if (Main.netMode == NetmodeID.Server)
        {
            authoritativeCenter = Projectile.Center;
            authoritativeVelocity = Projectile.velocity;
        }
        Projectile.netUpdate = true;
    }

    public override bool OnTileCollide(Vector2 oldVelocity) => true;

    public override bool PreDraw(ref Color lightColor) => ReaperProjectileHelper.DrawTrail(
        Projectile, new Color(105, 205, 255), scaleMultiplier: 1f, copies: 10);

    public override void SendExtraAI(BinaryWriter writer)
    {
        writer.Write(configured);
        if (!configured)
            return;
        snapshot.Write(writer);
        writer.Write(maximumTravel);
        writer.Write(traveled);
        writer.Write((byte)Math.Clamp(retargetsRemaining, 0, byte.MaxValue));
    }

    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);
        float incomingMaximumTravel = reader.ReadSingle();
        float incomingTraveled = reader.ReadSingle();
        int incomingRetargets = reader.ReadByte();
        if (Main.netMode == NetmodeID.Server)
            return;
        configured = true;
        snapshot = incomingSnapshot;
        maximumTravel = incomingMaximumTravel;
        traveled = incomingTraveled;
        retargetsRemaining = incomingRetargets;
    }

    private void Configure(in SickleCombatSnapshot value)
    {
        snapshot = value;
        int level = Math.Max(1, (int)snapshot.Skill2);
        maximumTravel = (level switch { 2 => 12f, >= 3 => 14f, _ => 10f }) * 16f;
        retargetsRemaining = level >= 2 ? 2 : 1;
        traveled = 0f;
        hitRoots.Clear();
        configured = true;
        serverAuthorized = Main.netMode != NetmodeID.MultiplayerClient;
        authoritativeDamage = Math.Max(1, Projectile.damage);
        authoritativeCenter = Projectile.Center;
        authoritativeVelocity = Projectile.velocity;
        Projectile.timeLeft = Math.Max(20, (int)Math.Ceiling(maximumTravel / 8f));
    }

    private void MoveOneStep()
    {
        Vector2 movement = Collision.TileCollision(Projectile.position, Projectile.velocity,
            Projectile.width, Projectile.height, fallThrough: false, fall2: false);
        if (movement != Projectile.velocity)
        {
            Projectile.Kill();
            return;
        }
        Projectile.position += movement;
        if (Main.netMode == NetmodeID.Server)
        {
            authoritativeCenter = Projectile.Center;
            authoritativeVelocity = Projectile.velocity;
        }
    }

    private NPC? FindTarget(float maximumDistance)
    {
        NPC? result = null;
        foreach (NPC npc in Main.ActiveNPCs)
        {
            int root = npc.realLife >= 0 ? npc.realLife : npc.whoAmI;
            if (hitRoots.Contains(root) || !npc.CanBeChasedBy(Projectile))
                continue;
            float distance = Vector2.Distance(Projectile.Center, npc.Center);
            if (distance < maximumDistance)
            {
                maximumDistance = distance;
                result = npc;
            }
        }
        return result;
    }
}