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);
}
}
}
}
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);
}
}
}
}