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

namespace SoulHarvest.Projectiles;

/// <summary>
/// A tiny, zero-damage server-authored presentation event.  Skill mechanics stay
/// authoritative on the server while every receiving client gets the same skill,
/// level, target point and deterministic seed for its local storyboard.
/// </summary>
public sealed class ReaperSkillCueProjectile : ModProjectile
{
    private ReaperFormId form;
    private ReaperStage stage;
    private byte skillIndex;
    private byte skillLevel;
    private byte variant;
    private int actionId;
    private int seed;
    private Vector2 direction;
    private bool configured;
    private bool presented;

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

    public static int Spawn(
        IEntitySource source,
        int owner,
        in SickleCombatSnapshot snapshot,
        int skillIndex,
        Vector2 center,
        Vector2 direction,
        int variant = 0,
        int actionId = -1)
    {
        return Spawn(source, owner, snapshot.Form, snapshot.Stage, skillIndex,
            snapshot.GetSkillLevel(skillIndex), center, direction, variant, actionId);
    }

    public static int Spawn(
        IEntitySource source,
        int owner,
        ReaperFormId form,
        ReaperStage stage,
        int skillIndex,
        int skillLevel,
        Vector2 center,
        Vector2 direction,
        int variant = 0,
        int actionId = -1)
    {
        if (Main.netMode == NetmodeID.MultiplayerClient
            || owner < 0 || owner >= Main.maxPlayers
            || !ReaperDefinitions.IsBranchForm(form)
            || skillIndex is < 0 or > 2
            || skillLevel <= 0)
        {
            return -1;
        }

        direction = direction.SafeNormalize(Vector2.UnitY * -1f);
        int projectileIndex = Projectile.NewProjectile(source, center, direction,
            ModContent.ProjectileType<ReaperSkillCueProjectile>(), 0, 0f, owner);
        if (projectileIndex < 0 || projectileIndex >= Main.maxProjectiles)
            return -1;

        Projectile projectile = Main.projectile[projectileIndex];
        if (projectile.ModProjectile is not ReaperSkillCueProjectile cue)
        {
            projectile.Kill();
            return -1;
        }

        cue.form = form;
        cue.stage = stage;
        cue.skillIndex = (byte)skillIndex;
        cue.skillLevel = (byte)Math.Clamp(skillLevel, 1, 3);
        cue.variant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
        cue.actionId = actionId >= 0 ? actionId : projectile.identity;
        cue.seed = StableSeed(owner, projectile.identity, cue.actionId, (int)form, skillIndex);
        cue.direction = direction;
        cue.configured = true;
        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(
            Projectile.owner,
            form,
            stage,
            ReaperVisualActionKind.Skill,
            skillIndex,
            skillLevel,
            actionId,
            Target: -1,
            Origin: Projectile.Center,
            Direction: direction,
            Seed: seed,
            TimelinePhase: variant,
            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)form);
        writer.Write((byte)stage);
        writer.Write(skillIndex);
        writer.Write(skillLevel);
        writer.Write(variant);
        writer.Write(actionId);
        writer.Write(seed);
        writer.WriteVector2(direction);
    }

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

        ReaperFormId incomingForm = (ReaperFormId)reader.ReadByte();
        ReaperStage incomingStage = (ReaperStage)reader.ReadByte();
        byte incomingSkillIndex = reader.ReadByte();
        byte incomingSkillLevel = reader.ReadByte();
        byte incomingVariant = reader.ReadByte();
        int incomingActionId = reader.ReadInt32();
        int incomingSeed = reader.ReadInt32();
        Vector2 incomingDirection = reader.ReadVector2().SafeNormalize(Vector2.UnitY * -1f);
        if (Main.netMode == NetmodeID.Server)
            return;

        if (!ReaperDefinitions.IsBranchForm(incomingForm)
            || incomingStage is < ReaperStage.StageI or > ReaperStage.StageIII
            || incomingSkillIndex > 2
            || incomingSkillLevel is < 1 or > 3)
        {
            configured = false;
            return;
        }

        form = incomingForm;
        stage = incomingStage;
        skillIndex = incomingSkillIndex;
        skillLevel = incomingSkillLevel;
        variant = incomingVariant;
        actionId = incomingActionId;
        seed = incomingSeed;
        direction = incomingDirection;
        configured = true;
    }

    internal static int StableSeed(int first, int second, int third, int fourth, int fifth = 0)
    {
        unchecked
        {
            uint hash = 2166136261u;
            hash = (hash ^ (uint)first) * 16777619u;
            hash = (hash ^ (uint)second) * 16777619u;
            hash = (hash ^ (uint)third) * 16777619u;
            hash = (hash ^ (uint)fourth) * 16777619u;
            hash = (hash ^ (uint)fifth) * 16777619u;
            return (int)hash;
        }
    }
}