particle stuff

This commit is contained in:
sylvessa 2026-03-23 20:55:27 -05:00
parent a91abed6a1
commit 32f058d078
5 changed files with 316 additions and 0 deletions

View file

@ -373,6 +373,238 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender
NativeBridge.SetFoodLevel?.Invoke(getEntityId(), value);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
public void spawnParticle(Particle particle, Location location, int count)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
public void spawnParticle(Particle particle, double x, double y, double z, int count)
{
spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, Location location, int count, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, double x, double y, double z, int count, T? data)
{
spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T? data)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, extra, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, extra, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T? data)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
}
private void spawnParticleInternal(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, object? data)
{
if (NativeBridge.SpawnParticle == null)
return;
int particleId = (int)particle;
if (data is ItemStack itemStack &&
(particle == Particle.ITEM_CRACK || particle == Particle.BLOCK_CRACK))
{
int id = itemStack.getTypeId();
int aux = itemStack.getDurability();
particleId = (int)particle | ((id & 0x0FFF) << 8) | (aux & 0xFF);
}
NativeBridge.SpawnParticle(getEntityId(), particleId,
(float)x, (float)y, (float)z,
(float)offsetX, (float)offsetY, (float)offsetZ,
(float)extra, count);
}
// INTERNAL
internal void SetSaturationInternal(float saturation) => _saturation = saturation;
internal void SetWalkSpeedInternal(float walkSpeed) => _walkSpeed = walkSpeed;

View file

@ -625,6 +625,19 @@ public static class FourKitHost
}
}
[UnmanagedCallersOnly]
public static void SetParticleCallbacks(IntPtr spawnParticle)
{
try
{
NativeBridge.SetParticleCallbacks(spawnParticle);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetParticleCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static long FirePlayerDropItem(int entityId, int itemId, int itemCount, int itemAux,
IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr)

View file

@ -153,6 +153,9 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetExhaustionDelegate(int entityId, float exhaustion);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSpawnParticleDelegate(int entityId, int particleId, float x, float y, float z, float offsetX, float offsetY, float offsetZ, float speed, int count);
internal static NativeDamageDelegate? DamagePlayer;
internal static NativeSetHealthDelegate? SetPlayerHealth;
@ -204,6 +207,7 @@ internal static class NativeBridge
internal static NativeSetFoodLevelDelegate? SetFoodLevel;
internal static NativeSetSaturationDelegate? SetSaturation;
internal static NativeSetExhaustionDelegate? SetExhaustion;
internal static NativeSpawnParticleDelegate? SpawnParticle;
internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
{
@ -277,4 +281,9 @@ internal static class NativeBridge
SetSaturation = Marshal.GetDelegateForFunctionPointer<NativeSetSaturationDelegate>(setSaturation);
SetExhaustion = Marshal.GetDelegateForFunctionPointer<NativeSetExhaustionDelegate>(setExhaustion);
}
internal static void SetParticleCallbacks(IntPtr spawnParticle)
{
SpawnParticle = Marshal.GetDelegateForFunctionPointer<NativeSpawnParticleDelegate>(spawnParticle);
}
}

View file

@ -0,0 +1,45 @@
namespace Minecraft.Server.FourKit;
/// <summary>
/// Enum of particle effects that can be spawned for a player.
/// </summary>
public enum Particle
{
WATER_BUBBLE = 0,
SMOKE_NORMAL = 1,
NOTE = 2,
PORTAL = 3,
EXPLOSION_NORMAL = 5,
FLAME = 6,
LAVA = 7,
FOOTSTEP = 8,
WATER_SPLASH = 9,
SMOKE_LARGE = 10,
REDSTONE = 11,
SNOWBALL = 12,
SNOW_SHOVEL = 13,
SLIME = 14,
HEART = 15,
SUSPENDED = 16,
SUSPENDED_DEPTH = 17,
CRIT = 18,
EXPLOSION_HUGE = 19,
EXPLOSION_LARGE = 20,
TOWN_AURA = 21,
SPELL = 22,
SPELL_WITCH = 23,
SPELL_MOB = 24,
SPELL_MOB_AMBIENT = 25,
SPELL_INSTANT = 26,
CRIT_MAGIC = 27,
DRIP_WATER = 28,
DRIP_LAVA = 29,
ENCHANTMENT_TABLE = 30,
DRAGON_BREATH = 31,
END_ROD = 32,
VILLAGER_ANGRY = 33,
VILLAGER_HAPPY = 34,
FIREWORKS_SPARK = 35,
ITEM_CRACK = 0x100000,
BLOCK_CRACK = 0x200000,
}

View file

@ -39,6 +39,7 @@
#include "..\Minecraft.World\SetExperiencePacket.h"
#include "..\Minecraft.World\SetHealthPacket.h"
#include "..\Minecraft.World\LevelSoundPacket.h"
#include "..\Minecraft.World\LevelParticlesPacket.h"
#include "..\Minecraft.World\SimpleContainer.h"
#include "..\Minecraft.World\Slot.h"
#include "..\Minecraft.World\Tile.h"
@ -180,6 +181,7 @@ typedef int(__stdcall *fn_fire_bed_enter)(int entityId, int dimId, int bedX, int
typedef void(__stdcall *fn_fire_bed_leave)(int entityId, int dimId, int bedX, int bedY, int bedZ);
typedef void(__stdcall *fn_set_entity_callbacks)(void *setSneaking, void *setVelocity, void *setAllowFlight, void *playSound, void *setSleepingIgnored);
typedef void(__stdcall *fn_set_experience_callbacks)(void *setLevel, void *setExp, void *giveExp, void *giveExpLevels, void *setFoodLevel, void *setSaturation, void *setExhaustion);
typedef void(__stdcall *fn_set_particle_callbacks)(void *spawnParticle);
struct OpenContainerInfo
{
@ -222,6 +224,7 @@ static fn_fire_bed_enter s_managedFireBedEnter = nullptr;
static fn_fire_bed_leave s_managedFireBedLeave = nullptr;
static fn_set_entity_callbacks s_managedSetEntityCallbacks = nullptr;
static fn_set_experience_callbacks s_managedSetExperienceCallbacks = nullptr;
static fn_set_particle_callbacks s_managedSetParticleCallbacks = nullptr;
static bool s_initialized = false;
@ -1432,6 +1435,16 @@ static void __cdecl NativeSetExhaustion(int entityId, float exhaustion)
fd->setExhaustion(exhaustion);
}
static void __cdecl NativeSpawnParticle(int entityId, int particleId, float x, float y, float z, float offsetX, float offsetY, float offsetZ, float speed, int count)
{
// todo(SYLV): i glaub des geht a gscheider
auto player = FindPlayer(entityId);
if (!player || !player->connection) return;
wchar_t buf[32];
swprintf_s(buf, L"%d", particleId);
player->connection->send(std::make_shared<LevelParticlesPacket>(std::wstring(buf), x, y, z, offsetX, offsetY, offsetZ, speed, count));
}
static std::wstring FindNet10SystemRoot()
{
// overengineered
@ -1676,6 +1689,7 @@ void Initialize()
ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedLeave", (void **)&s_managedFireBedLeave);
ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetEntityCallbacks", (void **)&s_managedSetEntityCallbacks);
ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetExperienceCallbacks", (void **)&s_managedSetExperienceCallbacks);
ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetParticleCallbacks", (void **)&s_managedSetParticleCallbacks);
if (!ok)
{
@ -1748,6 +1762,9 @@ void Initialize()
(void *)&NativeSetSaturation,
(void *)&NativeSetExhaustion);
s_managedSetParticleCallbacks(
(void *)&NativeSpawnParticle);
LogInfo("fourkit", "FourKit initialized successfully.");
}