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.DataStructures;
using Terraria.ID;
using Terraria.ModLoader;

namespace SoulHarvest.Projectiles;


/// <summary>
/// Zero-damage, server-authored presentation cue for a Void Reaper direct hit.
/// It only transports the authoritative action snapshot and impact location;
/// clients own all drawing and the cue can never create damage or move an NPC.
/// </summary>
public sealed class ReaperVoidHitCueProjectile : ModProjectile
{
    private const int SeedSalt = 0x564F4944;
    private const int MaximumActiveCues = 96;

    private int ownerIndex = -1;
    private ReaperStage stage;
    private int actionId;
    private int targetIndex = -1;
    private Vector2 origin;
    private Vector2 direction;
    private int seed;
    private bool configured;
    private bool presented;

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

    public static int Spawn(
        IEntitySource source,
        int owner,
        ReaperStage stage,
        int actionId,
        int targetIndex,
        Vector2 origin,
        Vector2 direction)
    {
        if (Main.netMode == NetmodeID.MultiplayerClient
            || owner < 0 || owner >= Main.maxPlayers
            || stage is < ReaperStage.StageI or > ReaperStage.StageIII
            || targetIndex is < -1 || targetIndex >= Main.maxNPCs
            || !IsFinite(origin)
            || !IsFinite(direction))
        {
            return -1;
        }

        direction = direction.SafeNormalize(Vector2.UnitX);
        int projectileType = ModContent.ProjectileType<ReaperVoidHitCueProjectile>();
        if (!HasTransportCapacity(projectileType)
            || !ReaperVoidHitCueReservationSystem.TryReserve(owner, actionId, targetIndex))
        {
            return -1;
        }

        // The server owns the transport projectile. Using the releasing player's
        // owner slot here would let the server's local identity collide with a
        // different player-owned projectile identity on that player's client.
        int transportOwner = Main.netMode == NetmodeID.Server ? Main.maxPlayers : owner;
        int projectileIndex = Projectile.NewProjectile(source, origin, Vector2.Zero,
            projectileType, 0, 0f, transportOwner);
        if (projectileIndex < 0 || projectileIndex >= Main.maxProjectiles)
        {
            ReaperVoidHitCueReservationSystem.Release(owner, actionId, targetIndex);
            return -1;
        }

        Projectile projectile = Main.projectile[projectileIndex];
        if (projectile.ModProjectile is not ReaperVoidHitCueProjectile cue)
        {
            ReaperVoidHitCueReservationSystem.Release(owner, actionId, targetIndex);
            projectile.Kill();
            return -1;
        }

        cue.ownerIndex = owner;
        cue.stage = stage;
        cue.actionId = actionId;
        cue.targetIndex = targetIndex;
        cue.origin = origin;
        cue.direction = direction;
        cue.seed = ReaperSkillCueProjectile.StableSeed(
            owner, actionId, targetIndex, (int)stage, SeedSalt);
        cue.configured = true;
        projectile.Center = origin;
        projectile.timeLeft = 30;
        ReaperProjectileHelper.SyncNewProjectile(projectile);
        return projectileIndex;
    }

    public override void SetDefaults()
    {
        Projectile.width = 2;
        Projectile.height = 2;
        Projectile.friendly = false;
        Projectile.hostile = false;
        Projectile.tileCollide = false;
        Projectile.ignoreWater = true;
        Projectile.penetrate = -1;
        Projectile.timeLeft = 30;
        Projectile.hide = true;
        Projectile.netImportant = true;
    }

    public override bool ShouldUpdatePosition() => false;

    public override bool? CanDamage() => false;

    public override void AI()
    {
        if (!configured || presented || Main.netMode == NetmodeID.Server)
            return;

        presented = true;
        ReaperVfxDirector.Submit(new ReaperVisualEvent(
            ownerIndex,
            ReaperFormId.Void,
            stage,
            ReaperVisualActionKind.NormalAttack,
            Skill: -1,
            SkillLevel: 0,
            actionId,
            Target: targetIndex,
            Origin: origin,
            Direction: direction,
            Seed: seed,
            TimelinePhase: 0,
            NormalizedTime: 0f));
    }

    public override bool PreDraw(ref Color lightColor) => false;

    public override void SendExtraAI(BinaryWriter writer)
    {
        writer.Write(configured);
        if (!configured)
            return;

        writer.Write((byte)ownerIndex);
        writer.Write((byte)stage);
        writer.Write(actionId);
        writer.Write(targetIndex);
        writer.WriteVector2(origin);
        writer.WriteVector2(direction);
        writer.Write(seed);
    }

    public override void ReceiveExtraAI(BinaryReader reader)
    {
        bool incomingConfigured = reader.ReadBoolean();
        if (!incomingConfigured)
        {
            if (Main.netMode != NetmodeID.Server)
                configured = false;
            return;
        }

        int incomingOwner = reader.ReadByte();
        ReaperStage incomingStage = (ReaperStage)reader.ReadByte();
        int incomingActionId = reader.ReadInt32();
        int incomingTargetIndex = reader.ReadInt32();
        Vector2 incomingOrigin = reader.ReadVector2();
        Vector2 incomingDirection = reader.ReadVector2();
        int incomingSeed = reader.ReadInt32();
        if (Main.netMode == NetmodeID.Server)
            return;

        if (incomingOwner < 0 || incomingOwner >= Main.maxPlayers
            || incomingStage is < ReaperStage.StageI or > ReaperStage.StageIII
            || incomingTargetIndex is < -1 || incomingTargetIndex >= Main.maxNPCs
            || !IsFinite(incomingOrigin)
            || !IsFinite(incomingDirection))
        {
            configured = false;
            return;
        }

        ownerIndex = incomingOwner;
        stage = incomingStage;
        actionId = incomingActionId;
        targetIndex = incomingTargetIndex;
        origin = incomingOrigin;
        direction = incomingDirection.SafeNormalize(Vector2.UnitX);
        seed = incomingSeed;
        configured = true;
        Projectile.Center = origin;
    }

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

    private static bool HasTransportCapacity(int projectileType)
    {
        bool hasFreeSlot = false;
        int activeCues = 0;
        for (int index = 0; index < Main.maxProjectiles; index++)
        {
            Projectile projectile = Main.projectile[index];
            if (!projectile.active)
            {
                hasFreeSlot = true;
                continue;
            }

            if (projectile.type == projectileType && ++activeCues >= MaximumActiveCues)
                return false;
        }
        return hasFreeSlot;
    }
}