diff --git a/Minecraft.Server.FourKit/Entity/Player.cs b/Minecraft.Server.FourKit/Entity/Player.cs index 17640a5f7..e46930485 100644 --- a/Minecraft.Server.FourKit/Entity/Player.cs +++ b/Minecraft.Server.FourKit/Entity/Player.cs @@ -373,6 +373,238 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender NativeBridge.SetFoodLevel?.Invoke(getEntityId(), value); } + /// + /// Spawns the particle (the number of times specified by count) + /// at the target location. Only this player will see the particle. + /// + /// The particle to spawn. + /// The location to spawn at. + /// The number of particles. + public void spawnParticle(Particle particle, Location location, int count) + { + spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, null); + } + + /// + /// Spawns the particle (the number of times specified by count) + /// at the target location. Only this player will see the particle. + /// + /// The particle to spawn. + /// The position on the x axis to spawn at. + /// The position on the y axis to spawn at. + /// The position on the z axis to spawn at. + /// The number of particles. + public void spawnParticle(Particle particle, double x, double y, double z, int count) + { + spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, null); + } + + /// + /// Spawns the particle (the number of times specified by count) + /// at the target location. Only this player will see the particle. + /// + /// The particle to spawn. + /// The location to spawn at. + /// The number of particles. + /// The data to use for the particle or null. + /// The type of the particle data. + public void spawnParticle(Particle particle, Location location, int count, T? data) + { + spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, data); + } + + /// + /// Spawns the particle (the number of times specified by count) + /// at the target location. Only this player will see the particle. + /// + /// The particle to spawn. + /// The position on the x axis to spawn at. + /// The position on the y axis to spawn at. + /// The position on the z axis to spawn at. + /// The number of particles. + /// The data to use for the particle or null. + /// The type of the particle data. + public void spawnParticle(Particle particle, double x, double y, double z, int count, T? data) + { + spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, data); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The location to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + 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); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The position on the x axis to spawn at. + /// The position on the y axis to spawn at. + /// The position on the z axis to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + 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); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The location to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + /// The data to use for the particle or null. + /// The type of the particle data. + public void spawnParticle(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); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The position on the x axis to spawn at. + /// The position on the y axis to spawn at. + /// The position on the z axis to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + /// The data to use for the particle or null. + /// The type of the particle data. + public void spawnParticle(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); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The location to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + /// The extra data for this particle, depends on the particle used (normally speed). + 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); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The position on the x axis to spawn at. + /// The position on the y axis to spawn at. + /// The position on the z axis to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + /// The extra data for this particle, depends on the particle used (normally speed). + 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); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The location to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + /// The extra data for this particle, depends on the particle used (normally speed). + /// The data to use for the particle or null. + /// The type of the particle data. + public void spawnParticle(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); + } + + /// + /// 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. + /// + /// The particle to spawn. + /// The position on the x axis to spawn at. + /// The position on the y axis to spawn at. + /// The position on the z axis to spawn at. + /// The number of particles. + /// The maximum random offset on the X axis. + /// The maximum random offset on the Y axis. + /// The maximum random offset on the Z axis. + /// The extra data for this particle, depends on the particle used (normally speed). + /// The data to use for the particle or null. + /// The type of the particle data. + public void spawnParticle(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; diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs index 266b5f544..45594f359 100644 --- a/Minecraft.Server.FourKit/FourKitHost.cs +++ b/Minecraft.Server.FourKit/FourKitHost.cs @@ -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) diff --git a/Minecraft.Server.FourKit/NativeBridge.cs b/Minecraft.Server.FourKit/NativeBridge.cs index 265c3b512..f415c6613 100644 --- a/Minecraft.Server.FourKit/NativeBridge.cs +++ b/Minecraft.Server.FourKit/NativeBridge.cs @@ -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(setSaturation); SetExhaustion = Marshal.GetDelegateForFunctionPointer(setExhaustion); } + + internal static void SetParticleCallbacks(IntPtr spawnParticle) + { + SpawnParticle = Marshal.GetDelegateForFunctionPointer(spawnParticle); + } } diff --git a/Minecraft.Server.FourKit/Particle.cs b/Minecraft.Server.FourKit/Particle.cs new file mode 100644 index 000000000..7df6c196f --- /dev/null +++ b/Minecraft.Server.FourKit/Particle.cs @@ -0,0 +1,45 @@ +namespace Minecraft.Server.FourKit; + +/// +/// Enum of particle effects that can be spawned for a player. +/// +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, +} diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp index a10bdb3cc..4cecf840e 100644 --- a/Minecraft.Server/FourKitBridge.cpp +++ b/Minecraft.Server/FourKitBridge.cpp @@ -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(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."); }