XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
UTF-8
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;

namespace SoulHarvest.Common;

internal sealed class DeathDomainLootAttractionSystem : ModSystem
{
    private const float ArrivalDistance = 44f;
    private const float MinimumPullStep = 18f;
    private const float MaximumPullStep = 160f;
    private const int NetworkSyncInterval = 5;
    private static readonly List<Player> ActiveCollectors = [];

    public override void OnWorldUnload()
    {
        ActiveCollectors.Clear();
    }

    public override void PostUpdateWorld()
    {
        // World item positions are authoritative on the server. Clients only replay
        // the synchronized flight so a domain cannot collect an item client-side.
        if (Main.netMode == NetmodeID.MultiplayerClient)
            return;

        RefreshActiveCollectors();
        if (ActiveCollectors.Count == 0)
            return;

        for (int itemIndex = 0; itemIndex < Main.maxItems; itemIndex++)
        {
            Item item = Main.item[itemIndex];
            if (!item.active || item.IsAir || item.stack <= 0)
                continue;

            Player? collector = FindNearestDomainOwner(item.Center);
            if (collector is null)
                continue;
            int absorbAllLevel = collector.GetModPlayer<MyPlayer>()
                .GetEquippedDeathNecklace()?.AbsorbAllLevel ?? 1;
            float pullScale = 0.65f + absorbAllLevel * 0.08f;

            Vector2 toCollector = collector.MountedCenter - item.Center;
            float distance = toCollector.Length();
            if (distance <= 0.001f)
                continue;

            item.noGrabDelay = 0;
            Vector2 direction = toCollector / distance;
            bool arrived = distance <= ArrivalDistance;
            if (arrived)
            {
                item.Center = collector.MountedCenter;
                item.velocity = Vector2.Zero;
            }
            else
            {
                // Move the world entity itself so the global pull is not stopped by
                // terrain. Velocity is retained for smooth interpolation on clients.
                float step = MathHelper.Clamp(distance * 0.075f * pullScale,
                    MinimumPullStep * pullScale, MaximumPullStep * pullScale);
                item.position += direction * Math.Min(distance - ArrivalDistance, step);
                item.velocity = Vector2.Lerp(item.velocity,
                    direction * MathHelper.Clamp(step * 0.22f, 6f, 32f * pullScale), 0.58f);
            }

            if (Main.netMode == NetmodeID.Server
                && (Main.GameUpdateCount + (ulong)itemIndex)
                    % NetworkSyncInterval == 0)
            {
                NetMessage.SendData(MessageID.SyncItem, -1, -1, null, itemIndex);
            }
        }
    }

    private static Player? FindNearestDomainOwner(Vector2 itemCenter)
    {
        Player? nearest = null;
        float nearestDistanceSquared = float.MaxValue;
        foreach (Player player in ActiveCollectors)
        {
            float distanceSquared = Vector2.DistanceSquared(itemCenter,
                player.MountedCenter);
            if (distanceSquared >= nearestDistanceSquared)
                continue;

            nearestDistanceSquared = distanceSquared;
            nearest = player;
        }

        return nearest;
    }

    private static void RefreshActiveCollectors()
    {
        ActiveCollectors.Clear();
        for (int playerIndex = 0; playerIndex < Main.maxPlayers; playerIndex++)
        {
            Player player = Main.player[playerIndex];
            if (!player.active || player.dead)
                continue;

            MyPlayer modPlayer = player.GetModPlayer<MyPlayer>();
            if (modPlayer.DeathDomainEnabled
                && modPlayer.GetEquippedDeathNecklace() is not null)
            {
                ActiveCollectors.Add(player);
            }
        }
    }
}