From a91abed6a123a22e881e03c0556aced75ceff440 Mon Sep 17 00:00:00 2001
From: sylvessa <225480449+sylvessa@users.noreply.github.com>
Date: Mon, 23 Mar 2026 19:10:35 -0500
Subject: [PATCH] add more missing useful funcs
---
Minecraft.Server.FourKit/Entity/Player.cs | 108 ++++++++++++++++++++++
Minecraft.Server.FourKit/FourKitHost.cs | 23 ++++-
Minecraft.Server.FourKit/NativeBridge.cs | 39 ++++++++
Minecraft.Server/FourKitBridge.cpp | 94 ++++++++++++++++++-
4 files changed, 260 insertions(+), 4 deletions(-)
diff --git a/Minecraft.Server.FourKit/Entity/Player.cs b/Minecraft.Server.FourKit/Entity/Player.cs
index 85c7529bc..17640a5f7 100644
--- a/Minecraft.Server.FourKit/Entity/Player.cs
+++ b/Minecraft.Server.FourKit/Entity/Player.cs
@@ -12,6 +12,11 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender
{
private float _saturation = 5.0f;
private float _walkSpeed = 0.2f;
+ private float _exhaustion;
+ private int _foodLevel = 20;
+ private int _level;
+ private float _exp;
+ private int _totalExperience;
private Guid _playerUniqueId;
private string? _displayName;
private bool _sneaking;
@@ -270,6 +275,104 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender
}
}
+ ///
+ /// Gets the players current experience level.
+ ///
+ /// Current experience level.
+ public int getLevel() => _level;
+
+ ///
+ /// Sets the players current experience level.
+ ///
+ /// New experience level.
+ public void setLevel(int level)
+ {
+ _level = level;
+ NativeBridge.SetLevel?.Invoke(getEntityId(), level);
+ }
+
+ ///
+ /// Gets the players current experience points towards the next level.
+ /// This is a percentage value. 0 is "no progress" and 1 is "next level".
+ ///
+ /// Current experience points.
+ public float getExp() => _exp;
+
+ ///
+ /// Sets the players current experience points towards the next level.
+ /// This is a percentage value. 0 is "no progress" and 1 is "next level".
+ ///
+ /// New experience points.
+ public void setExp(float exp)
+ {
+ _exp = exp;
+ NativeBridge.SetExp?.Invoke(getEntityId(), exp);
+ }
+
+ ///
+ /// Gives the player the amount of experience specified.
+ ///
+ /// Exp amount to give.
+ public void giveExp(int amount)
+ {
+ NativeBridge.GiveExp?.Invoke(getEntityId(), amount);
+ }
+
+ ///
+ /// Gives the player the amount of experience levels specified.
+ /// Levels can be taken by specifying a negative amount.
+ ///
+ /// Amount of experience levels to give or take.
+ public void giveExpLevels(int amount)
+ {
+ NativeBridge.GiveExpLevels?.Invoke(getEntityId(), amount);
+ }
+
+ ///
+ /// Gets the players current exhaustion level.
+ /// Exhaustion controls how fast the food level drops. While you have a
+ /// certain amount of exhaustion, your saturation will drop to zero, and
+ /// then your food will drop to zero.
+ ///
+ /// Exhaustion level.
+ public float getExhaustion() => _exhaustion;
+
+ ///
+ /// Sets the players current exhaustion level.
+ ///
+ /// Exhaustion level.
+ public void setExhaustion(float value)
+ {
+ _exhaustion = value;
+ NativeBridge.SetExhaustion?.Invoke(getEntityId(), value);
+ }
+
+ ///
+ /// Sets the players current saturation level.
+ ///
+ /// Saturation level.
+ public void setSaturation(float value)
+ {
+ _saturation = value;
+ NativeBridge.SetSaturation?.Invoke(getEntityId(), value);
+ }
+
+ ///
+ /// Gets the players current food level.
+ ///
+ /// Food level.
+ public int getFoodLevel() => _foodLevel;
+
+ ///
+ /// Sets the players current food level.
+ ///
+ /// New food level.
+ public void setFoodLevel(int value)
+ {
+ _foodLevel = value;
+ NativeBridge.SetFoodLevel?.Invoke(getEntityId(), value);
+ }
+
// INTERNAL
internal void SetSaturationInternal(float saturation) => _saturation = saturation;
internal void SetWalkSpeedInternal(float walkSpeed) => _walkSpeed = walkSpeed;
@@ -278,4 +381,9 @@ public class Player : HumanEntity, OfflinePlayer, CommandSender
internal void SetSprintingInternal(bool sprinting) => _sprinting = sprinting;
internal void SetAllowFlightInternal(bool allowFlight) => _allowFlight = allowFlight;
internal void SetSleepingIgnoredInternal(bool ignored) => _sleepingIgnored = ignored;
+ internal void SetLevelInternal(int level) => _level = level;
+ internal void SetExpInternal(float exp) => _exp = exp;
+ internal void SetTotalExperienceInternal(int totalExp) => _totalExperience = totalExp;
+ internal void SetFoodLevelInternal(int foodLevel) => _foodLevel = foodLevel;
+ internal void SetExhaustionInternal(float exhaustion) => _exhaustion = exhaustion;
}
diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs
index 9a8c28a8a..266b5f544 100644
--- a/Minecraft.Server.FourKit/FourKitHost.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.cs
@@ -612,6 +612,19 @@ public static class FourKitHost
}
}
+ [UnmanagedCallersOnly]
+ public static void SetExperienceCallbacks(IntPtr setLevel, IntPtr setExp, IntPtr giveExp, IntPtr giveExpLevels, IntPtr setFoodLevel, IntPtr setSaturation, IntPtr setExhaustion)
+ {
+ try
+ {
+ NativeBridge.SetExperienceCallbacks(setLevel, setExp, giveExp, giveExpLevels, setFoodLevel, setSaturation, setExhaustion);
+ }
+ catch (Exception ex)
+ {
+ ServerLog.Error("fourkit", $"SetExperienceCallbacks error: {ex}");
+ }
+ }
+
[UnmanagedCallersOnly]
public static long FirePlayerDropItem(int entityId, int itemId, int itemCount, int itemAux,
IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr)
@@ -966,12 +979,12 @@ public static class FourKitHost
};
}
- // double[21] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight, sleepingIgnored }
+ // double[27] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight, sleepingIgnored, experienceLevel, experienceProgress, totalExperience, foodLevel, saturation, exhaustion }
private static void SyncPlayerFromNative(Player player)
{
if (NativeBridge.GetPlayerSnapshot == null)
return;
- double[] buf = new double[21];
+ double[] buf = new double[27];
var gh = GCHandle.Alloc(buf, GCHandleType.Pinned);
try
{
@@ -998,6 +1011,12 @@ public static class FourKitHost
player.SetVelocityInternal(buf[16], buf[17], buf[18]);
player.SetAllowFlightInternal(buf[19] != 0.0);
player.SetSleepingIgnoredInternal(buf[20] != 0.0);
+ player.SetLevelInternal((int)buf[21]);
+ player.SetExpInternal((float)buf[22]);
+ player.SetTotalExperienceInternal((int)buf[23]);
+ player.SetFoodLevelInternal((int)buf[24]);
+ player.SetSaturationInternal((float)buf[25]);
+ player.SetExhaustionInternal((float)buf[26]);
}
private static void BroadcastNativeMessage(string message)
diff --git a/Minecraft.Server.FourKit/NativeBridge.cs b/Minecraft.Server.FourKit/NativeBridge.cs
index 425892a14..265c3b512 100644
--- a/Minecraft.Server.FourKit/NativeBridge.cs
+++ b/Minecraft.Server.FourKit/NativeBridge.cs
@@ -132,6 +132,27 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetSleepingIgnoredDelegate(int entityId, int ignored);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetLevelDelegate(int entityId, int level);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetExpDelegate(int entityId, float exp);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeGiveExpDelegate(int entityId, int amount);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeGiveExpLevelsDelegate(int entityId, int amount);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetFoodLevelDelegate(int entityId, int foodLevel);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetSaturationDelegate(int entityId, float saturation);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetExhaustionDelegate(int entityId, float exhaustion);
+
internal static NativeDamageDelegate? DamagePlayer;
internal static NativeSetHealthDelegate? SetPlayerHealth;
@@ -176,6 +197,13 @@ internal static class NativeBridge
internal static NativeSetAllowFlightDelegate? SetAllowFlight;
internal static NativePlaySoundDelegate? PlaySound;
internal static NativeSetSleepingIgnoredDelegate? SetSleepingIgnored;
+ internal static NativeSetLevelDelegate? SetLevel;
+ internal static NativeSetExpDelegate? SetExp;
+ internal static NativeGiveExpDelegate? GiveExp;
+ internal static NativeGiveExpLevelsDelegate? GiveExpLevels;
+ internal static NativeSetFoodLevelDelegate? SetFoodLevel;
+ internal static NativeSetSaturationDelegate? SetSaturation;
+ internal static NativeSetExhaustionDelegate? SetExhaustion;
internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
{
@@ -238,4 +266,15 @@ internal static class NativeBridge
PlaySound = Marshal.GetDelegateForFunctionPointer(playSound);
SetSleepingIgnored = Marshal.GetDelegateForFunctionPointer(setSleepingIgnored);
}
+
+ internal static void SetExperienceCallbacks(IntPtr setLevel, IntPtr setExp, IntPtr giveExp, IntPtr giveExpLevels, IntPtr setFoodLevel, IntPtr setSaturation, IntPtr setExhaustion)
+ {
+ SetLevel = Marshal.GetDelegateForFunctionPointer(setLevel);
+ SetExp = Marshal.GetDelegateForFunctionPointer(setExp);
+ GiveExp = Marshal.GetDelegateForFunctionPointer(giveExp);
+ GiveExpLevels = Marshal.GetDelegateForFunctionPointer(giveExpLevels);
+ SetFoodLevel = Marshal.GetDelegateForFunctionPointer(setFoodLevel);
+ SetSaturation = Marshal.GetDelegateForFunctionPointer(setSaturation);
+ SetExhaustion = Marshal.GetDelegateForFunctionPointer(setExhaustion);
+ }
}
diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp
index 245fdcc2d..a10bdb3cc 100644
--- a/Minecraft.Server/FourKitBridge.cpp
+++ b/Minecraft.Server/FourKitBridge.cpp
@@ -36,6 +36,8 @@
#include "..\Minecraft.World\Player.h"
#include "..\Minecraft.World\PlayerAbilitiesPacket.h"
#include "..\Minecraft.World\SetCarriedItemPacket.h"
+#include "..\Minecraft.World\SetExperiencePacket.h"
+#include "..\Minecraft.World\SetHealthPacket.h"
#include "..\Minecraft.World\LevelSoundPacket.h"
#include "..\Minecraft.World\SimpleContainer.h"
#include "..\Minecraft.World\Slot.h"
@@ -177,6 +179,7 @@ typedef int(__stdcall *fn_fire_inventory_click)(int entityId,
typedef int(__stdcall *fn_fire_bed_enter)(int entityId, int dimId, int bedX, int bedY, int bedZ);
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);
struct OpenContainerInfo
{
@@ -218,6 +221,7 @@ static fn_fire_inventory_click s_managedFireInventoryClick = nullptr;
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 bool s_initialized = false;
@@ -329,16 +333,18 @@ static void __cdecl NativeSetFallDistance(int entityId, float distance)
}
}
-// double[21] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight, sleepingIgnored }
+// double[27] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer, sneaking, sprinting, onGround, velocityX, velocityY, velocityZ, allowFlight, sleepingIgnored, experienceLevel, experienceProgress, totalExperience, foodLevel, saturation, exhaustion }
static void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData)
{
auto player = FindPlayer(entityId);
if (!player)
{
- memset(outData, 0, 21 * sizeof(double));
+ memset(outData, 0, 27 * sizeof(double));
outData[3] = 20.0;
outData[4] = 20.0;
outData[7] = 0.1;
+ outData[24] = 20.0;
+ outData[25] = 5.0;
return;
}
outData[0] = player->x;
@@ -363,6 +369,13 @@ static void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData)
outData[18] = player->zd;
outData[19] = player->abilities.mayfly ? 1.0 : 0.0;
outData[20] = player->fk_sleepingIgnored ? 1.0 : 0.0;
+ outData[21] = (double)player->experienceLevel;
+ outData[22] = (double)player->experienceProgress;
+ outData[23] = (double)player->totalExperience;
+ FoodData *fd = player->getFoodData();
+ outData[24] = fd ? (double)fd->getFoodLevel() : 20.0;
+ outData[25] = fd ? (double)fd->getSaturationLevel() : 5.0;
+ outData[26] = fd ? (double)fd->getExhaustionLevel() : 0.0;
}
static void __cdecl NativeBroadcastMessage(const char *utf8, int len)
@@ -1352,6 +1365,73 @@ static void __cdecl NativeSetSleepingIgnored(int entityId, int ignored)
}
}
+static void __cdecl NativeSetLevel(int entityId, int level)
+{
+ auto player = FindPlayer(entityId);
+ if (!player) return;
+ player->experienceLevel = level;
+ if (player->connection)
+ player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel));
+}
+
+static void __cdecl NativeSetExp(int entityId, float exp)
+{
+ auto player = FindPlayer(entityId);
+ if (!player) return;
+ player->experienceProgress = exp;
+ if (player->connection)
+ player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel));
+}
+
+static void __cdecl NativeGiveExp(int entityId, int amount)
+{
+ auto player = FindPlayer(entityId);
+ if (!player) return;
+ player->increaseXp(amount);
+ if (player->connection)
+ player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel));
+}
+
+static void __cdecl NativeGiveExpLevels(int entityId, int amount)
+{
+ auto player = FindPlayer(entityId);
+ if (!player) return;
+ player->giveExperienceLevels(amount);
+ if (player->connection)
+ player->connection->send(std::make_shared(player->experienceProgress, player->totalExperience, player->experienceLevel));
+}
+
+static void __cdecl NativeSetFoodLevel(int entityId, int foodLevel)
+{
+ auto player = FindPlayer(entityId);
+ if (!player) return;
+ FoodData *fd = player->getFoodData();
+ if (!fd) return;
+ fd->setFoodLevel(foodLevel);
+ if (player->connection)
+ player->connection->send(std::make_shared(player->getHealth(), fd->getFoodLevel(), fd->getSaturationLevel(), eTelemetryChallenges_Unknown));
+}
+
+static void __cdecl NativeSetSaturation(int entityId, float saturation)
+{
+ auto player = FindPlayer(entityId);
+ if (!player) return;
+ FoodData *fd = player->getFoodData();
+ if (!fd) return;
+ fd->setSaturation(saturation);
+ if (player->connection)
+ player->connection->send(std::make_shared(player->getHealth(), fd->getFoodLevel(), fd->getSaturationLevel(), eTelemetryChallenges_Unknown));
+}
+
+static void __cdecl NativeSetExhaustion(int entityId, float exhaustion)
+{
+ auto player = FindPlayer(entityId);
+ if (!player) return;
+ FoodData *fd = player->getFoodData();
+ if (!fd) return;
+ fd->setExhaustion(exhaustion);
+}
+
static std::wstring FindNet10SystemRoot()
{
// overengineered
@@ -1595,6 +1675,7 @@ void Initialize()
ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedEnter", (void **)&s_managedFireBedEnter);
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);
if (!ok)
{
@@ -1658,6 +1739,15 @@ void Initialize()
(void *)&NativePlaySound,
(void *)&NativeSetSleepingIgnored);
+ s_managedSetExperienceCallbacks(
+ (void *)&NativeSetLevel,
+ (void *)&NativeSetExp,
+ (void *)&NativeGiveExp,
+ (void *)&NativeGiveExpLevels,
+ (void *)&NativeSetFoodLevel,
+ (void *)&NativeSetSaturation,
+ (void *)&NativeSetExhaustion);
+
LogInfo("fourkit", "FourKit initialized successfully.");
}