diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs
index 2e3190e74..7a4e48771 100644
--- a/Minecraft.Server.FourKit/FourKitHost.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.cs
@@ -581,11 +581,11 @@ public static class FourKitHost
}
[UnmanagedCallersOnly]
- public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer)
+ public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot)
{
try
{
- NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer);
+ NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer, getItemMeta, setItemMeta, setHeldItemSlot);
//ServerLog.Info("fourkit", "Inventory native callbacks registered.");
}
catch (Exception ex)
diff --git a/Minecraft.Server.FourKit/Inventory/ItemStack.cs b/Minecraft.Server.FourKit/Inventory/ItemStack.cs
index 394ab637a..5b69ed231 100644
--- a/Minecraft.Server.FourKit/Inventory/ItemStack.cs
+++ b/Minecraft.Server.FourKit/Inventory/ItemStack.cs
@@ -1,5 +1,7 @@
namespace Minecraft.Server.FourKit.Inventory;
+using Minecraft.Server.FourKit.Inventory.Meta;
+
///
/// Represents a stack of items.
///
@@ -8,6 +10,7 @@ public class ItemStack
private Material _type;
private int _amount;
private short _durability;
+ private ItemMeta? _meta;
///
/// Creates a new ItemStack of the specified material with amount 1.
@@ -99,4 +102,31 @@ public class ItemStack
///
/// Durability of this item.
public void setDurability(short durability) => _durability = durability;
+
+ ///
+ /// Get a copy of this ItemStack's ItemMeta.
+ ///
+ /// A copy of the current ItemStack's ItemMeta.
+ public ItemMeta getItemMeta() => _meta?.clone() ?? new ItemMeta();
+
+ ///
+ /// Checks to see if any meta data has been defined.
+ ///
+ /// Returns true if some meta data has been set for this item.
+ public bool hasItemMeta() => _meta != null && !_meta.isEmpty();
+
+ ///
+ /// Set the ItemMeta of this ItemStack.
+ ///
+ /// New ItemMeta, or null to indicate meta data be cleared.
+ /// True if successfully applied ItemMeta.
+ public bool setItemMeta(ItemMeta? itemMeta)
+ {
+ _meta = itemMeta?.clone();
+ return true;
+ }
+
+ internal ItemMeta? getItemMetaInternal() => _meta;
+
+ internal void setItemMetaInternal(ItemMeta? meta) => _meta = meta;
}
diff --git a/Minecraft.Server.FourKit/Inventory/Meta/ItemMeta.cs b/Minecraft.Server.FourKit/Inventory/Meta/ItemMeta.cs
new file mode 100644
index 000000000..5da46c5ac
--- /dev/null
+++ b/Minecraft.Server.FourKit/Inventory/Meta/ItemMeta.cs
@@ -0,0 +1,64 @@
+namespace Minecraft.Server.FourKit.Inventory.Meta;
+
+///
+/// Represents the metadata of an , including display name and lore.
+///
+public class ItemMeta
+{
+ private string? _displayName;
+ private List? _lore;
+
+ ///
+ /// Checks for existence of a display name.
+ ///
+ /// true if this has a display name.
+ public bool hasDisplayName() => _displayName != null;
+
+ ///
+ /// Gets the display name that is set.
+ /// Plugins should check that hasDisplayName() returns true before calling this method.
+ ///
+ /// The display name that is set.
+ public string getDisplayName() => _displayName ?? string.Empty;
+
+ ///
+ /// Sets the display name.
+ ///
+ /// The name to set.
+ public void setDisplayName(string? name) => _displayName = name;
+
+ ///
+ /// Checks for existence of lore.
+ ///
+ /// true if this has lore.
+ public bool hasLore() => _lore != null && _lore.Count > 0;
+
+ ///
+ /// Gets the lore that is set.
+ /// Plugins should check if hasLore() returns true before calling this method.
+ ///
+ /// A list of lore that is set.
+ public List getLore() => _lore != null ? new List(_lore) : new List();
+
+ ///
+ /// Sets the lore for this item. Removes lore when given null.
+ ///
+ /// The lore that will be set.
+ public void setLore(List? lore)
+ {
+ _lore = lore != null ? new List(lore) : null;
+ }
+
+ public ItemMeta clone()
+ {
+ var copy = new ItemMeta();
+ copy._displayName = _displayName;
+ copy._lore = _lore != null ? new List(_lore) : null;
+ return copy;
+ }
+
+ internal bool isEmpty()
+ {
+ return _displayName == null && (_lore == null || _lore.Count == 0);
+ }
+}
diff --git a/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs b/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs
index 7a6d950c7..40f095401 100644
--- a/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs
+++ b/Minecraft.Server.FourKit/Inventory/PlayerInventory.cs
@@ -1,7 +1,9 @@
namespace Minecraft.Server.FourKit.Inventory;
using System.Runtime.InteropServices;
+using System.Text;
using Minecraft.Server.FourKit.Entity;
+using Minecraft.Server.FourKit.Inventory.Meta;
///
/// Represents a player's inventory, including armor slots and the held item.
@@ -43,9 +45,17 @@ public class PlayerInventory : Inventory
int count = buf[i * 3 + 1];
int aux = buf[i * 3 + 2];
if (id > 0 && count > 0)
- _items[i] = new ItemStack(id, count, (short)aux);
+ {
+ var stack = new ItemStack(id, count, (short)aux);
+ var meta = ReadMetaFromNative(entityId, i);
+ if (meta != null)
+ stack.setItemMetaInternal(meta);
+ _items[i] = stack;
+ }
else
+ {
_items[i] = null;
+ }
}
_heldItemSlot = buf[120];
}
@@ -62,6 +72,7 @@ public class PlayerInventory : Inventory
int count = item?.getAmount() ?? 0;
int aux = item?.getDurability() ?? 0;
NativeBridge.SetPlayerInventorySlot(_holder.getEntityId(), index, id, count, aux);
+ WriteMetaToNative(_holder.getEntityId(), index, item?.getItemMetaInternal());
}
}
@@ -147,7 +158,11 @@ public class PlayerInventory : Inventory
/// Sets the item in the player's hand.
///
/// The ItemStack to set.
- public void setItemInHand(ItemStack? stack) => setItem(_heldItemSlot, stack);
+ public void setItemInHand(ItemStack? stack)
+ {
+ EnsureSynced();
+ setItem(_heldItemSlot, stack);
+ }
///
/// Gets the slot number of the currently held item.
@@ -168,6 +183,8 @@ public class PlayerInventory : Inventory
if (slot < 0 || slot >= QUICKBAR_SIZE)
throw new ArgumentException($"Slot must be between 0 and {QUICKBAR_SIZE - 1} inclusive.");
_heldItemSlot = slot;
+ if (_holder != null)
+ NativeBridge.SetHeldItemSlot?.Invoke(_holder.getEntityId(), slot);
}
///
@@ -199,4 +216,128 @@ public class PlayerInventory : Inventory
///
/// The HumanEntity that owns this inventory.
public HumanEntity? getHolder() => _holder;
+
+ private static ItemMeta? ReadMetaFromNative(int entityId, int slot)
+ {
+ if (NativeBridge.GetItemMeta == null)
+ return null;
+
+ byte[] buf = new byte[4096];
+ int bytesWritten;
+ var gh = GCHandle.Alloc(buf, GCHandleType.Pinned);
+ try
+ {
+ bytesWritten = NativeBridge.GetItemMeta(entityId, slot, gh.AddrOfPinnedObject(), buf.Length);
+ }
+ finally
+ {
+ gh.Free();
+ }
+
+ if (bytesWritten <= 0)
+ return null;
+
+ int offset = 0;
+ int nameLen = BitConverter.ToInt32(buf, offset);
+ offset += 4;
+
+ string? displayName = null;
+ if (nameLen > 0)
+ {
+ displayName = Encoding.UTF8.GetString(buf, offset, nameLen);
+ offset += nameLen;
+ }
+
+ int loreCount = 0;
+ if (offset + 4 <= bytesWritten)
+ {
+ loreCount = BitConverter.ToInt32(buf, offset);
+ offset += 4;
+ }
+
+ List? lore = null;
+ if (loreCount > 0)
+ {
+ lore = new List(loreCount);
+ for (int i = 0; i < loreCount; i++)
+ {
+ if (offset + 4 > bytesWritten) break;
+ int lineLen = BitConverter.ToInt32(buf, offset);
+ offset += 4;
+ if (lineLen > 0 && offset + lineLen <= bytesWritten)
+ {
+ lore.Add(Encoding.UTF8.GetString(buf, offset, lineLen));
+ offset += lineLen;
+ }
+ else
+ {
+ lore.Add(string.Empty);
+ }
+ }
+ }
+
+ if (displayName == null && (lore == null || lore.Count == 0))
+ return null;
+
+ var meta = new ItemMeta();
+ if (displayName != null)
+ meta.setDisplayName(displayName);
+ if (lore != null && lore.Count > 0)
+ meta.setLore(lore);
+ return meta;
+ }
+
+ private static void WriteMetaToNative(int entityId, int slot, ItemMeta? meta)
+ {
+ if (NativeBridge.SetItemMeta == null)
+ return;
+
+ if (meta == null || meta.isEmpty())
+ {
+ NativeBridge.SetItemMeta(entityId, slot, IntPtr.Zero, 0);
+ return;
+ }
+
+ using var ms = new System.IO.MemoryStream(512);
+ using var bw = new System.IO.BinaryWriter(ms);
+
+ if (meta.hasDisplayName())
+ {
+ byte[] nameBytes = Encoding.UTF8.GetBytes(meta.getDisplayName());
+ bw.Write(nameBytes.Length);
+ bw.Write(nameBytes);
+ }
+ else
+ {
+ bw.Write(0);
+ }
+
+ if (meta.hasLore())
+ {
+ var lore = meta.getLore();
+ bw.Write(lore.Count);
+ foreach (var line in lore)
+ {
+ byte[] lineBytes = Encoding.UTF8.GetBytes(line ?? string.Empty);
+ bw.Write(lineBytes.Length);
+ bw.Write(lineBytes);
+ }
+ }
+ else
+ {
+ bw.Write(0);
+ }
+
+ bw.Flush();
+ byte[] data = ms.ToArray();
+ var gh = GCHandle.Alloc(data, GCHandleType.Pinned);
+ try
+ {
+ NativeBridge.SetItemMeta(entityId, slot, gh.AddrOfPinnedObject(), data.Length);
+ }
+ finally
+ {
+ gh.Free();
+ }
+ }
}
diff --git a/Minecraft.Server.FourKit/NativeBridge.cs b/Minecraft.Server.FourKit/NativeBridge.cs
index 8dee54294..ddad6c2f8 100644
--- a/Minecraft.Server.FourKit/NativeBridge.cs
+++ b/Minecraft.Server.FourKit/NativeBridge.cs
@@ -108,6 +108,15 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeOpenVirtualContainerDelegate(int entityId, int nativeType, IntPtr titleUtf8, int titleByteLen, int slotCount, IntPtr itemsBuf);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate int NativeGetItemMetaDelegate(int entityId, int slot, IntPtr outBuf, int bufSize);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetItemMetaDelegate(int entityId, int slot, IntPtr inBuf, int bufSize);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate void NativeSetHeldItemSlotDelegate(int entityId, int slot);
+
internal static NativeDamageDelegate? DamagePlayer;
internal static NativeSetHealthDelegate? SetPlayerHealth;
@@ -144,6 +153,9 @@ internal static class NativeBridge
internal static NativeGetContainerViewerEntityIdsDelegate? GetContainerViewerEntityIds;
internal static NativeCloseContainerDelegate? CloseContainer;
internal static NativeOpenVirtualContainerDelegate? OpenVirtualContainer;
+ internal static NativeGetItemMetaDelegate? GetItemMeta;
+ internal static NativeSetItemMetaDelegate? SetItemMeta;
+ internal static NativeSetHeldItemSlotDelegate? SetHeldItemSlot;
internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
{
@@ -184,7 +196,7 @@ internal static class NativeBridge
GetPlayerAddress = Marshal.GetDelegateForFunctionPointer(getPlayerAddress);
}
- internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer)
+ internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot)
{
GetPlayerInventory = Marshal.GetDelegateForFunctionPointer(getPlayerInventory);
SetPlayerInventorySlot = Marshal.GetDelegateForFunctionPointer(setPlayerInventorySlot);
@@ -193,5 +205,8 @@ internal static class NativeBridge
GetContainerViewerEntityIds = Marshal.GetDelegateForFunctionPointer(getContainerViewerEntityIds);
CloseContainer = Marshal.GetDelegateForFunctionPointer(closeContainer);
OpenVirtualContainer = Marshal.GetDelegateForFunctionPointer(openVirtualContainer);
+ GetItemMeta = Marshal.GetDelegateForFunctionPointer(getItemMeta);
+ SetItemMeta = Marshal.GetDelegateForFunctionPointer(setItemMeta);
+ SetHeldItemSlot = Marshal.GetDelegateForFunctionPointer(setHeldItemSlot);
}
}
diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp
index dddecdd0d..34ba09ed6 100644
--- a/Minecraft.Server/FourKitBridge.cpp
+++ b/Minecraft.Server/FourKitBridge.cpp
@@ -35,6 +35,7 @@
#include "..\Minecraft.World\LightningBolt.h"
#include "..\Minecraft.World\Player.h"
#include "..\Minecraft.World\PlayerAbilitiesPacket.h"
+#include "..\Minecraft.World\SetCarriedItemPacket.h"
#include "..\Minecraft.World\SimpleContainer.h"
#include "..\Minecraft.World\Slot.h"
#include "..\Minecraft.World\Tile.h"
@@ -141,7 +142,7 @@ typedef void(__stdcall *fn_set_player_callbacks)(void *kickPlayer, void *banPlay
typedef long long(__stdcall *fn_fire_player_drop_item)(int entityId,
int itemId, int itemCount, int itemAux,
int *outItemId, int *outItemCount, int *outItemAux);
-typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer);
+typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer, void *getItemMeta, void *setItemMeta, void *setHeldItemSlot);
typedef int(__stdcall *fn_fire_player_interact)(int entityId, int action,
int itemId, int itemCount, int itemAux,
int clickedX, int clickedY, int clickedZ,
@@ -1069,6 +1070,212 @@ static void __cdecl NativeGetContainerViewerEntityIds(int entityId, int *outIds,
*outCount = count;
}
+// [nameLen:int32][nameUTF8:bytes][loreCount:int32][lore0Len:int32][lore0UTF8:bytes]
+// todo: des muass i no a bissl überarwatn
+static int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize)
+{
+ auto player = FindPlayer(entityId);
+ if (!player || !player->inventory)
+ {
+ return 0;
+ }
+
+ unsigned int size = player->inventory->getContainerSize();
+ if (slot < 0 || slot >= (int)size)
+ {
+ return 0;
+ }
+
+ auto item = player->inventory->getItem(slot);
+ if (!item || !item->hasTag())
+ {
+ return 0;
+ }
+
+ CompoundTag *tag = item->getTag();
+ if (!tag || !tag->contains(L"display"))
+ {
+ return 0;
+ }
+
+ CompoundTag *display = tag->getCompound(L"display");
+ bool hasName = display->contains(L"Name");
+ bool hasLore = display->contains(L"Lore");
+
+ if (!hasName && !hasLore)
+ {
+ return 0;
+ }
+
+ int offset = 0;
+
+ if (hasName)
+ {
+ std::wstring wname = display->getString(L"Name");
+ std::string nameUtf8 = ServerRuntime::StringUtils::WideToUtf8(wname);
+ int nameLen = (int)nameUtf8.size();
+ if (offset + 4 + nameLen > bufSize) return 0;
+ memcpy(outBuf + offset, &nameLen, 4);
+ offset += 4;
+ memcpy(outBuf + offset, nameUtf8.data(), nameLen);
+ offset += nameLen;
+ }
+ else
+ {
+ int zero = 0;
+ if (offset + 4 > bufSize) return 0;
+ memcpy(outBuf + offset, &zero, 4);
+ offset += 4;
+ }
+
+ if (hasLore)
+ {
+ ListTag *lore = (ListTag *)display->getList(L"Lore");
+ int loreCount = lore->size();
+ if (offset + 4 > bufSize) return 0;
+ memcpy(outBuf + offset, &loreCount, 4);
+ offset += 4;
+
+ for (int i = 0; i < loreCount; i++)
+ {
+ std::wstring wline = lore->get(i)->data;
+ std::string lineUtf8 = ServerRuntime::StringUtils::WideToUtf8(wline);
+ int lineLen = (int)lineUtf8.size();
+ if (offset + 4 + lineLen > bufSize) return 0;
+ memcpy(outBuf + offset, &lineLen, 4);
+ offset += 4;
+ memcpy(outBuf + offset, lineUtf8.data(), lineLen);
+ offset += lineLen;
+ }
+ }
+ else
+ {
+ int zero = 0;
+ if (offset + 4 > bufSize) return 0;
+ memcpy(outBuf + offset, &zero, 4);
+ offset += 4;
+ }
+
+ return offset;
+}
+
+static void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bufSize)
+{
+ auto player = FindPlayer(entityId);
+ if (!player || !player->inventory)
+ {
+ return;
+ }
+
+ unsigned int size = player->inventory->getContainerSize();
+ if (slot < 0 || slot >= (int)size)
+ {
+ return;
+ }
+
+ auto item = player->inventory->getItem(slot);
+ if (!item)
+ {
+ return;
+ }
+
+ if (inBuf == nullptr || bufSize <= 0)
+ {
+ item->resetHoverName();
+ if (item->hasTag())
+ {
+ CompoundTag *tag = item->getTag();
+ if (tag && tag->contains(L"display"))
+ {
+ CompoundTag *display = tag->getCompound(L"display");
+ display->remove(L"Lore");
+ if (display->isEmpty())
+ {
+ tag->remove(L"display");
+ if (tag->isEmpty())
+ {
+ item->setTag(nullptr);
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ int offset = 0;
+
+ if (offset + 4 > bufSize) return;
+ int nameLen = 0;
+ memcpy(&nameLen, inBuf + offset, 4);
+ offset += 4;
+
+ if (nameLen > 0)
+ {
+ if (offset + nameLen > bufSize) return;
+ std::string nameUtf8(inBuf + offset, nameLen);
+ offset += nameLen;
+ std::wstring wname = ServerRuntime::StringUtils::Utf8ToWide(nameUtf8);
+ item->setHoverName(wname);
+ }
+ else
+ {
+ item->resetHoverName();
+ }
+
+ if (offset + 4 > bufSize) return;
+ int loreCount = 0;
+ memcpy(&loreCount, inBuf + offset, 4);
+ offset += 4;
+
+ if (loreCount > 0)
+ {
+ if (!item->hasTag()) item->setTag(new CompoundTag());
+ CompoundTag *tag = item->getTag();
+ if (!tag->contains(L"display")) tag->putCompound(L"display", new CompoundTag());
+ CompoundTag *display = tag->getCompound(L"display");
+
+ auto *loreList = new ListTag(L"Lore");
+ for (int i = 0; i < loreCount; i++)
+ {
+ if (offset + 4 > bufSize) break;
+ int lineLen = 0;
+ memcpy(&lineLen, inBuf + offset, 4);
+ offset += 4;
+
+ std::wstring wline;
+ if (lineLen > 0 && offset + lineLen <= bufSize)
+ {
+ std::string lineUtf8(inBuf + offset, lineLen);
+ offset += lineLen;
+ wline = ServerRuntime::StringUtils::Utf8ToWide(lineUtf8);
+ }
+ loreList->add(new StringTag(L"", wline));
+ }
+ display->put(L"Lore", loreList);
+ }
+ else
+ {
+ if (item->hasTag())
+ {
+ CompoundTag *tag = item->getTag();
+ if (tag && tag->contains(L"display"))
+ {
+ tag->getCompound(L"display")->remove(L"Lore");
+ }
+ }
+ }
+}
+
+static void __cdecl NativeSetHeldItemSlot(int entityId, int slot)
+{
+ auto player = FindPlayer(entityId);
+ if (!player || !player->inventory) return;
+ if (slot < 0 || slot >= Inventory::getSelectionSize()) return;
+ player->inventory->selected = slot;
+ if (player->connection)
+ player->connection->queueSend(std::make_shared(slot));
+}
+
static std::wstring FindNet10SystemRoot()
{
// overengineered
@@ -1362,7 +1569,10 @@ void Initialize()
(void *)&NativeSetContainerSlot,
(void *)&NativeGetContainerViewerEntityIds,
(void *)&NativeCloseContainer,
- (void *)&NativeOpenVirtualContainer);
+ (void *)&NativeOpenVirtualContainer,
+ (void *)&NativeGetItemMeta,
+ (void *)&NativeSetItemMeta,
+ (void *)&NativeSetHeldItemSlot);
LogInfo("fourkit", "FourKit initialized successfully.");
}