MinecraftConsoles/Minecraft.Server.FourKit/FourKitHost.cs

1226 lines
43 KiB
C#

using System.Runtime.InteropServices;
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Event;
using Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Event.Entity;
using Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Event.Inventory;
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit;
// todo: clean up
public static class FourKitHost
{
private static PluginLoader? s_loader;
[UnmanagedCallersOnly]
public static void Initialize()
{
try
{
ServerLog.Info("fourkit", "Initializing plugin system...");
string pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins");
s_loader = new PluginLoader();
s_loader.LoadPlugins(pluginsDir);
s_loader.EnableAll();
ServerLog.Info("fourkit", "Plugin system ready.");
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"Initialize failed: {ex}");
}
}
[UnmanagedCallersOnly]
public static void FirePlayerJoin(int entityId, IntPtr namePtr, int nameByteLen, IntPtr uuidPtr, int uuidByteLen)
{
try
{
string name = nameByteLen > 0
? Marshal.PtrToStringUTF8(namePtr, nameByteLen) ?? string.Empty
: string.Empty;
string uuidStr = uuidByteLen > 0
? Marshal.PtrToStringUTF8(uuidPtr, uuidByteLen) ?? string.Empty
: string.Empty;
var player = FourKit.TrackPlayer(entityId, name);
player.SetPlayerUniqueIdInternal(ParseOrHashGuid(uuidStr));
SyncPlayerFromNative(player);
var evt = new PlayerJoinEvent(player);
FourKit.FireEvent(evt);
BroadcastNativeMessage(evt.getJoinMessage());
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerJoin error: {ex}");
}
}
private static Guid ParseOrHashGuid(string s)
{
if (Guid.TryParse(s, out var g)) return g;
if (string.IsNullOrEmpty(s)) return Guid.NewGuid();
return new Guid(System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes(s)));
}
[UnmanagedCallersOnly]
public static void FirePlayerQuit(int entityId)
{
try
{
var player = FourKit.UntrackPlayer(entityId);
if (player != null)
{
SyncPlayerFromNative(player);
var evt = new PlayerQuitEvent(player);
FourKit.FireEvent(evt);
BroadcastNativeMessage(evt.getQuitMessage());
}
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerQuit error: {ex}");
}
}
[UnmanagedCallersOnly]
public static int FirePlayerKick(int entityId, int disconnectReason,
IntPtr reasonPtr, int reasonByteLen,
IntPtr outBuf, int outBufSize, IntPtr outLenPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.WriteInt32(outLenPtr, 0);
return 0;
}
SyncPlayerFromNative(player);
var reason = Enum.IsDefined(typeof(DisconnectReason), disconnectReason)
? (DisconnectReason)disconnectReason
: DisconnectReason.NONE;
string defaultLeave = $"{player.getName()} was kicked from the game";
var evt = new PlayerKickEvent(player, reason, defaultLeave);
FourKit.FireEvent(evt);
if (evt.isCancelled())
{
Marshal.WriteInt32(outLenPtr, 0);
return 1;
}
string leaveMessage = evt.getLeaveMessage();
if (!string.IsNullOrEmpty(leaveMessage))
{
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(leaveMessage);
if (utf8Bytes.Length < outBufSize)
{
Marshal.Copy(utf8Bytes, 0, outBuf, utf8Bytes.Length);
Marshal.WriteInt32(outLenPtr, utf8Bytes.Length);
}
else
{
Marshal.WriteInt32(outLenPtr, 0);
}
}
else
{
Marshal.WriteInt32(outLenPtr, 0);
}
return 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerKick error: {ex}");
Marshal.WriteInt32(outLenPtr, 0);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FirePlayerMove(int entityId,
double fromX, double fromY, double fromZ,
double toX, double toY, double toZ,
IntPtr outCoords)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3);
return 0;
}
SyncPlayerFromNative(player);
var from = new Location(fromX, fromY, fromZ);
var to = new Location(toX, toY, toZ);
var evt = new PlayerMoveEvent(player, from, to);
FourKit.FireEvent(evt);
var finalTo = evt.getTo();
Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerMove error: {ex}");
Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3);
return 0;
}
}
[UnmanagedCallersOnly]
public static void UpdatePlayerEntityId(int oldEntityId, int newEntityId)
{
try
{
FourKit.UpdatePlayerEntityId(oldEntityId, newEntityId);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"UpdatePlayerEntityId error: {ex}");
}
}
[UnmanagedCallersOnly]
public static int FirePlayerChat(int entityId, IntPtr msgPtr, int msgByteLen,
IntPtr outBuf, int outBufSize, IntPtr outLenPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.WriteInt32(outLenPtr, 0);
return 0;
}
SyncPlayerFromNative(player);
string message = msgByteLen > 0
? Marshal.PtrToStringUTF8(msgPtr, msgByteLen) ?? string.Empty
: string.Empty;
var evt = new PlayerChatEvent(player, message);
FourKit.FireEvent(evt);
if (evt.isCancelled())
{
Marshal.WriteInt32(outLenPtr, 0);
return 1;
}
string formatted = JavaFormat(evt.getFormat(), player.getDisplayName(), evt.getMessage());
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(formatted);
if (utf8Bytes.Length < outBufSize)
{
Marshal.Copy(utf8Bytes, 0, outBuf, utf8Bytes.Length);
Marshal.WriteInt32(outLenPtr, utf8Bytes.Length);
}
else
{
Marshal.WriteInt32(outLenPtr, 0);
}
return 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerChat error: {ex}");
Marshal.WriteInt32(outLenPtr, 0);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireBlockPlace(int entityId, int dimId,
int placedX, int placedY, int placedZ,
int againstX, int againstY, int againstZ,
int itemId, int itemCount, int canBuild)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
return 0;
SyncPlayerFromNative(player);
var world = FourKit.getWorld(dimId);
var placedBlock = new Block.Block(world, placedX, placedY, placedZ);
var againstBlock = new Block.Block(world, againstX, againstY, againstZ);
Material mat = Enum.IsDefined(typeof(Material), itemId) ? (Material)itemId : Material.AIR;
var itemInHand = new ItemStack(mat, itemCount);
var evt = new BlockPlaceEvent(placedBlock, againstBlock, itemInHand, player, canBuild != 0);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireBlockPlace error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireBlockBreak(int entityId, int dimId,
int x, int y, int z, int tileId, int data, int exp)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
return exp;
SyncPlayerFromNative(player);
var world = FourKit.getWorld(dimId);
var block = new Block.Block(world, x, y, z);
var evt = new BlockBreakEvent(block, player, exp);
FourKit.FireEvent(evt);
if (evt.isCancelled())
return -1;
return evt.getExpToDrop();
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireBlockBreak error: {ex}");
return exp;
}
}
[UnmanagedCallersOnly]
public static int FireEntityDamage(int entityId, int entityTypeId, int dimId,
double x, double y, double z, int causeId, double damage, IntPtr outDamage,
int damagerEntityId, int damagerEntityTypeId,
double damagerX, double damagerY, double damagerZ)
{
try
{
Entity.Entity? entity = FourKit.GetPlayerByEntityId(entityId);
if (entity is Player player)
{
SyncPlayerFromNative(player);
}
else
{
var entityType = Enum.IsDefined(typeof(EntityType), entityTypeId)
? (EntityType)entityTypeId
: EntityType.UNKNOWN;
entity = new LivingEntity(entityId, entityType, dimId, x, y, z);
}
var cause = Enum.IsDefined(typeof(EntityDamageEvent.DamageCause), causeId)
? (EntityDamageEvent.DamageCause)causeId
: EntityDamageEvent.DamageCause.CUSTOM;
EntityDamageByEntityEvent? byEntityEvt = null;
if (damagerEntityId >= 0)
{
Entity.Entity? damager = FourKit.GetPlayerByEntityId(damagerEntityId);
if (damager is Player damagerPlayer)
{
SyncPlayerFromNative(damagerPlayer);
}
else
{
var damagerType = Enum.IsDefined(typeof(EntityType), damagerEntityTypeId)
? (EntityType)damagerEntityTypeId
: EntityType.UNKNOWN;
damager = new LivingEntity(damagerEntityId, damagerType, dimId, damagerX, damagerY, damagerZ);
}
byEntityEvt = new EntityDamageByEntityEvent(damager, entity!, cause, damage);
FourKit.FireEvent(byEntityEvt);
damage = byEntityEvt.getDamage();
}
var evt = new EntityDamageEvent(entity!, cause, damage);
if (byEntityEvt != null && byEntityEvt.isCancelled())
evt.setCancelled(true);
FourKit.FireEvent(evt);
Marshal.Copy(new double[] { evt.getDamage() }, 0, outDamage, 1);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireEntityDamage error: {ex}");
Marshal.Copy(new double[] { damage }, 0, outDamage, 1);
return 0;
}
}
[UnmanagedCallersOnly]
public static void Shutdown()
{
try
{
ServerLog.Info("fourkit", "Shutting down plugin system...");
s_loader?.DisableAll();
s_loader = null;
ServerLog.Info("fourkit", "Plugin system shut down.");
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"Shutdown error: {ex}");
}
}
[UnmanagedCallersOnly]
public static int FireSignChange(int entityId, int dimId,
int x, int y, int z,
IntPtr line0Ptr, int line0Len,
IntPtr line1Ptr, int line1Len,
IntPtr line2Ptr, int line2Len,
IntPtr line3Ptr, int line3Len,
IntPtr outBuf, int outBufSize, IntPtr outLensPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
WriteSignOutLens(outLensPtr, [0, 0, 0, 0]);
return 0;
}
SyncPlayerFromNative(player);
string[] lines =
[
line0Len > 0 ? Marshal.PtrToStringUTF8(line0Ptr, line0Len) ?? string.Empty : string.Empty,
line1Len > 0 ? Marshal.PtrToStringUTF8(line1Ptr, line1Len) ?? string.Empty : string.Empty,
line2Len > 0 ? Marshal.PtrToStringUTF8(line2Ptr, line2Len) ?? string.Empty : string.Empty,
line3Len > 0 ? Marshal.PtrToStringUTF8(line3Ptr, line3Len) ?? string.Empty : string.Empty,
];
var world = FourKit.getWorld(dimId);
var block = new Block.Block(world, x, y, z);
var evt = new Event.Block.SignChangeEvent(block, player, lines);
FourKit.FireEvent(evt);
int offset = 0;
int[] lens = new int[4];
for (int i = 0; i < 4; i++)
{
byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(evt.getLine(i));
if (offset + utf8.Length <= outBufSize)
{
Marshal.Copy(utf8, 0, outBuf + offset, utf8.Length);
lens[i] = utf8.Length;
offset += utf8.Length;
}
}
WriteSignOutLens(outLensPtr, lens);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireSignChange error: {ex}");
WriteSignOutLens(outLensPtr, [0, 0, 0, 0]);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireEntityDeath(int entityId, int entityTypeId, int dimId,
double x, double y, double z, int exp)
{
try
{
var entityType = Enum.IsDefined(typeof(EntityType), entityTypeId)
? (EntityType)entityTypeId
: EntityType.UNKNOWN;
var entity = new LivingEntity(entityId, entityType, dimId, x, y, z);
var drops = new List<ItemStack>();
var evt = new EntityDeathEvent(entity, drops, exp);
FourKit.FireEvent(evt);
return evt.getDroppedExp();
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireEntityDeath error: {ex}");
return exp;
}
}
[UnmanagedCallersOnly]
public static int FirePlayerDeath(int entityId,
IntPtr deathMsgPtr, int deathMsgByteLen, int exp,
IntPtr outMsgBuf, int outMsgBufSize, IntPtr outMsgLenPtr, IntPtr outKeepInventoryPtr,
IntPtr outNewExpPtr, IntPtr outNewLevelPtr, IntPtr outKeepLevelPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.WriteInt32(outMsgLenPtr, 0);
Marshal.WriteInt32(outKeepInventoryPtr, 0);
Marshal.WriteInt32(outNewExpPtr, 0);
Marshal.WriteInt32(outNewLevelPtr, 0);
Marshal.WriteInt32(outKeepLevelPtr, 0);
return exp;
}
SyncPlayerFromNative(player);
string deathMessage = deathMsgByteLen > 0
? Marshal.PtrToStringUTF8(deathMsgPtr, deathMsgByteLen) ?? string.Empty
: string.Empty;
var drops = new List<ItemStack>();
var playerEvt = new PlayerDeathEvent(player, drops, exp, deathMessage);
FourKit.FireEvent(playerEvt);
var entityEvt = new EntityDeathEvent(player, playerEvt.getDrops(), playerEvt.getDroppedExp());
FourKit.FireEvent(entityEvt);
int finalExp = entityEvt.getDroppedExp();
string finalMsg = playerEvt.getDeathMessage();
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(finalMsg);
if (utf8Bytes.Length < outMsgBufSize)
{
Marshal.Copy(utf8Bytes, 0, outMsgBuf, utf8Bytes.Length);
Marshal.WriteInt32(outMsgLenPtr, utf8Bytes.Length);
}
else
{
Marshal.WriteInt32(outMsgLenPtr, 0);
}
Marshal.WriteInt32(outKeepInventoryPtr, playerEvt.getKeepInventory() ? 1 : 0);
Marshal.WriteInt32(outNewExpPtr, playerEvt.getNewExp());
Marshal.WriteInt32(outNewLevelPtr, playerEvt.getNewLevel());
Marshal.WriteInt32(outKeepLevelPtr, playerEvt.getKeepLevel() ? 1 : 0);
return finalExp;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerDeath error: {ex}");
Marshal.WriteInt32(outMsgLenPtr, 0);
Marshal.WriteInt32(outKeepInventoryPtr, 0);
Marshal.WriteInt32(outNewExpPtr, 0);
Marshal.WriteInt32(outNewLevelPtr, 0);
Marshal.WriteInt32(outKeepLevelPtr, 0);
return exp;
}
}
private static void WriteSignOutLens(IntPtr ptr, int[] lens)
{
Marshal.Copy(lens, 0, ptr, 4);
}
[UnmanagedCallersOnly]
public static void SetNativeCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
{
try
{
NativeBridge.SetCallbacks(damage, setHealth, teleport, setGameMode, broadcastMessage, setFallDistance, getPlayerSnapshot, sendMessage, setWalkSpeed, teleportEntity);
ServerLog.Info("fourkit", "Native callbacks registered.");
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetNativeCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetWorldCallbacks(IntPtr getTileId, IntPtr setTile, IntPtr setTileData, IntPtr breakBlock, IntPtr getHighestBlockY, IntPtr getWorldInfo, IntPtr setWorldTime, IntPtr setWeather, IntPtr createExplosion, IntPtr strikeLightning, IntPtr setSpawnLocation, IntPtr dropItem)
{
try
{
NativeBridge.SetWorldCallbacks(getTileId, setTile, setTileData, breakBlock, getHighestBlockY, getWorldInfo, setWorldTime, setWeather, createExplosion, strikeLightning, setSpawnLocation, dropItem);
//ServerLog.Info("fourkit", "World native callbacks registered.");
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetWorldCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetPlayerCallbacks(IntPtr kickPlayer, IntPtr banPlayer, IntPtr banPlayerIp, IntPtr getPlayerAddress)
{
try
{
NativeBridge.SetPlayerCallbacks(kickPlayer, banPlayer, banPlayerIp, getPlayerAddress);
//ServerLog.Info("fourkit", "Player native callbacks registered.");
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetPlayerCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer)
{
try
{
NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer);
//ServerLog.Info("fourkit", "Inventory native callbacks registered.");
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetInventoryCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static long FirePlayerDropItem(int entityId, int itemId, int itemCount, int itemAux,
IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.WriteInt32(outItemIdPtr, itemId);
Marshal.WriteInt32(outItemCountPtr, itemCount);
Marshal.WriteInt32(outItemAuxPtr, itemAux);
return 0;
}
SyncPlayerFromNative(player);
Material mat = Enum.IsDefined(typeof(Material), itemId) ? (Material)itemId : Material.AIR;
var itemStack = new ItemStack(mat, itemCount, (short)itemAux);
var evt = new PlayerDropItemEvent(player, itemStack);
FourKit.FireEvent(evt);
var result = evt.getItemDrop();
Marshal.WriteInt32(outItemIdPtr, result.getTypeId());
Marshal.WriteInt32(outItemCountPtr, result.getAmount());
Marshal.WriteInt32(outItemAuxPtr, result.getDurability());
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerDropItem error: {ex}");
Marshal.WriteInt32(outItemIdPtr, itemId);
Marshal.WriteInt32(outItemCountPtr, itemCount);
Marshal.WriteInt32(outItemAuxPtr, itemAux);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FirePlayerInteract(int entityId, int action,
int itemId, int itemCount, int itemAux,
int clickedX, int clickedY, int clickedZ,
int blockFace, int dimId,
IntPtr outUseItemInHandPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.WriteInt32(outUseItemInHandPtr, 1);
return 0;
}
SyncPlayerFromNative(player);
var actionEnum = Enum.IsDefined(typeof(Block.Action), action)
? (Block.Action)action
: Block.Action.RIGHT_CLICK_AIR;
var faceEnum = Enum.IsDefined(typeof(Block.BlockFace), blockFace)
? (Block.BlockFace)blockFace
: Block.BlockFace.SELF;
ItemStack? itemStack = null;
if (itemId > 0)
itemStack = new ItemStack(itemId, itemCount, (short)itemAux);
Block.Block? clickedBlock = null;
bool hasBlock = actionEnum == Block.Action.LEFT_CLICK_BLOCK
|| actionEnum == Block.Action.RIGHT_CLICK_BLOCK
|| actionEnum == Block.Action.PHYSICAL;
if (hasBlock)
{
var world = FourKit.getWorld(dimId);
if (world != null)
clickedBlock = new Block.Block(world, clickedX, clickedY, clickedZ);
}
var evt = new PlayerInteractEvent(
player, actionEnum, itemStack, clickedBlock, faceEnum);
FourKit.FireEvent(evt);
Marshal.WriteInt32(outUseItemInHandPtr, evt.useItemInHand() ? 1 : 0);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerInteract error: {ex}");
Marshal.WriteInt32(outUseItemInHandPtr, 1);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FirePlayerInteractEntity(int playerEntityId,
int targetEntityId, int targetEntityTypeId,
int dimId, double targetX, double targetY, double targetZ,
float targetHealth, float targetMaxHealth, float targetEyeHeight)
{
try
{
var player = FourKit.GetPlayerByEntityId(playerEntityId);
if (player == null)
return 0;
SyncPlayerFromNative(player);
Entity.Entity? target = FourKit.GetPlayerByEntityId(targetEntityId);
if (target is Player targetPlayer)
{
SyncPlayerFromNative(targetPlayer);
}
else
{
var entityType = Enum.IsDefined(typeof(EntityType), targetEntityTypeId)
? (EntityType)targetEntityTypeId
: EntityType.UNKNOWN;
var living = new LivingEntity(targetEntityId, entityType, dimId, targetX, targetY, targetZ,
targetHealth, targetMaxHealth);
living.SetEyeHeightInternal(targetEyeHeight);
target = living;
}
var evt = new PlayerInteractEntityEvent(player, target);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerInteractEntity error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FirePlayerPickupItem(int playerEntityId,
int itemEntityId, int dimId, double itemX, double itemY, double itemZ,
int itemId, int itemCount, int itemAux, int remaining,
IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(playerEntityId);
if (player == null)
{
Marshal.WriteInt32(outItemIdPtr, itemId);
Marshal.WriteInt32(outItemCountPtr, itemCount);
Marshal.WriteInt32(outItemAuxPtr, itemAux);
// todo: fix
return 1;
}
SyncPlayerFromNative(player);
var itemStack = new Inventory.ItemStack(itemId, itemCount, (short)itemAux);
var item = new Entity.Item(itemEntityId, dimId, itemX, itemY, itemZ, itemStack);
var evt = new PlayerPickupItemEvent(player, item, remaining);
FourKit.FireEvent(evt);
var result = evt.getItem().getItemStack();
Marshal.WriteInt32(outItemIdPtr, result.getTypeId());
Marshal.WriteInt32(outItemCountPtr, result.getAmount());
Marshal.WriteInt32(outItemAuxPtr, result.getDurability());
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerPickupItem error: {ex}");
Marshal.WriteInt32(outItemIdPtr, itemId);
Marshal.WriteInt32(outItemCountPtr, itemCount);
Marshal.WriteInt32(outItemAuxPtr, itemAux);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireInventoryOpen(int entityId, int nativeContainerType,
IntPtr titlePtr, int titleByteLen, int containerSize)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null) return 0;
SyncPlayerFromNative(player);
string title = titleByteLen > 0
? Marshal.PtrToStringUTF8(titlePtr, titleByteLen) ?? string.Empty
: string.Empty;
InventoryType invType = MapNativeContainerType(nativeContainerType);
Inventory.Inventory topInv = CreateContainerInventory(invType, nativeContainerType, title, containerSize, entityId);
var bottomInv = player.getInventory();
var view = new InventoryView(topInv, bottomInv, player, invType);
var evt = new InventoryOpenEvent(view);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireInventoryOpen error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int HandlePlayerCommand(int entityId, IntPtr cmdUtf8, int cmdByteLen)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
return 0;
SyncPlayerFromNative(player);
string commandLine = cmdByteLen > 0
? Marshal.PtrToStringUTF8(cmdUtf8, cmdByteLen) ?? string.Empty
: string.Empty;
if (string.IsNullOrEmpty(commandLine))
return 0;
return FourKit.DispatchCommand(player, commandLine) ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"HandlePlayerCommand error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int HandleConsoleCommand(IntPtr cmdUtf8, int cmdByteLen)
{
try
{
string commandLine = cmdByteLen > 0
? Marshal.PtrToStringUTF8(cmdUtf8, cmdByteLen) ?? string.Empty
: string.Empty;
if (string.IsNullOrEmpty(commandLine))
return 0;
string trimmed = commandLine.StartsWith('/') ? commandLine[1..] : commandLine;
string[] parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
return 0;
if (!FourKit.HasCommand(parts[0]))
return 0;
FourKit.DispatchCommand(FourKit.getConsoleSender(), commandLine);
return 1;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"HandleConsoleCommand error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int GetPluginCommandHelp(IntPtr outBuf, int outBufSize, IntPtr outLenPtr)
{
try
{
var entries = FourKit.GetRegisteredCommandHelp();
if (entries.Count == 0)
{
if (outLenPtr != IntPtr.Zero)
Marshal.WriteInt32(outLenPtr, 0);
return 0;
}
var sb = new System.Text.StringBuilder();
foreach (var (usage, description) in entries)
{
sb.Append(usage);
sb.Append('\0');
sb.Append(description);
sb.Append('\0');
}
byte[] utf8 = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
int copyLen = Math.Min(utf8.Length, outBufSize);
if (outBuf != IntPtr.Zero && copyLen > 0)
Marshal.Copy(utf8, 0, outBuf, copyLen);
if (outLenPtr != IntPtr.Zero)
Marshal.WriteInt32(outLenPtr, copyLen);
return entries.Count;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"GetPluginCommandHelp error: {ex}");
if (outLenPtr != IntPtr.Zero)
Marshal.WriteInt32(outLenPtr, 0);
return 0;
}
}
private static InventoryType MapNativeContainerType(int nativeType)
{
return nativeType switch
{
0 => InventoryType.CHEST, // CONTAINER
1 => InventoryType.WORKBENCH, // WORKBENCH
2 => InventoryType.FURNACE, // FURNACE
3 => InventoryType.DISPENSER, // TRAP (dispenser)
4 => InventoryType.ENCHANTING, // ENCHANTMENT
5 => InventoryType.BREWING, // BREWING_STAND
6 => InventoryType.MERCHANT, // TRADER_NPC
7 => InventoryType.BEACON, // BEACON
8 => InventoryType.ANVIL, // REPAIR_TABLE
9 => InventoryType.HOPPER, // HOPPER
10 => InventoryType.DROPPER, // DROPPER
11 => InventoryType.CHEST, // HORSE
12 => InventoryType.WORKBENCH, // FIREWORKS
13 => InventoryType.CHEST, // BONUS_CHEST
14 => InventoryType.CHEST, // LARGE_CHEST
15 => InventoryType.ENDER_CHEST, // ENDER_CHEST
16 => InventoryType.CHEST, // MINECART_CHEST
17 => InventoryType.HOPPER, // MINECART_HOPPER
_ => InventoryType.CHEST,
};
}
private static Inventory.Inventory CreateContainerInventory(InventoryType invType, int nativeType, string title, int containerSize, int entityId)
{
string name = string.IsNullOrEmpty(title) ? invType.getDefaultTitle() : title;
int size = containerSize > 0 ? containerSize : invType.getDefaultSize();
return invType switch
{
InventoryType.FURNACE => new Inventory.FurnaceInventory(name, size, entityId),
InventoryType.BEACON => new Inventory.BeaconInventory(name, size, entityId),
InventoryType.ENCHANTING => new Inventory.EnchantingInventory(name, size, entityId),
_ => nativeType switch
{
11 => new Inventory.HorseInventory(name, size, entityId), // HORSE
14 => new Inventory.DoubleChestInventory(name, size, entityId), // LARGE_CHEST
_ => new Inventory.Inventory(name, invType, size, entityId),
}
};
}
// double[11] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension }
private static void SyncPlayerFromNative(Player player)
{
if (NativeBridge.GetPlayerSnapshot == null)
return;
double[] buf = new double[11];
var gh = GCHandle.Alloc(buf, GCHandleType.Pinned);
try
{
NativeBridge.GetPlayerSnapshot(player.getEntityId(), gh.AddrOfPinnedObject());
}
finally
{
gh.Free();
}
int dimId = (int)buf[10];
player.SetDimensionInternal(dimId);
var world = FourKit.getWorld(dimId);
player.SetLocation(new Location(world, buf[0], buf[1], buf[2], (float)buf[8], (float)buf[9]));
player.SetHealthInternal(buf[3]);
player.SetMaxHealthInternal(buf[4]);
player.SetFallDistanceInternal((float)buf[5]);
player.SetGameModeInternal((GameMode)(int)buf[6]);
player.SetWalkSpeedInternal((float)buf[7]);
}
private static void BroadcastNativeMessage(string message)
{
if (string.IsNullOrEmpty(message) || NativeBridge.BroadcastMessage == null)
return;
IntPtr ptr = Marshal.StringToCoTaskMemUTF8(message);
try
{
NativeBridge.BroadcastMessage(ptr, System.Text.Encoding.UTF8.GetByteCount(message));
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
private static string JavaFormat(string format, params string[] args)
{
// hack to replicate java bukkit format
var sb = new System.Text.StringBuilder(format.Length + 64);
int seqIndex = 0;
for (int i = 0; i < format.Length; i++)
{
char c = format[i];
if (c == '%' && i + 1 < format.Length)
{
char next = format[i + 1];
if (next == '%')
{
sb.Append('%');
i++;
continue;
}
if (next == 's')
{
if (seqIndex < args.Length)
sb.Append(args[seqIndex]);
seqIndex++;
i++;
continue;
}
if (char.IsDigit(next))
{
int numStart = i + 1;
int j = numStart;
while (j < format.Length && char.IsDigit(format[j]))
j++;
if (j + 1 < format.Length && format[j] == '$' && format[j + 1] == 's')
{
int argIndex = int.Parse(format.AsSpan(numStart, j - numStart)) - 1;
if (argIndex >= 0 && argIndex < args.Length)
sb.Append(args[argIndex]);
i = j + 1;
continue;
}
}
sb.Append(c);
}
else
{
sb.Append(c);
}
}
return sb.ToString();
}
[UnmanagedCallersOnly]
public static int FirePlayerTeleport(int entityId,
double fromX, double fromY, double fromZ, int fromDimId,
double toX, double toY, double toZ, int toDimId,
int cause, IntPtr outCoords)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3);
return 0;
}
SyncPlayerFromNative(player);
var fromWorld = FourKit.getWorld(fromDimId);
var toWorld = FourKit.getWorld(toDimId);
var from = new Location(fromWorld, fromX, fromY, fromZ);
var to = new Location(toWorld, toX, toY, toZ);
var teleportCause = cause >= 0 && cause <= (int)PlayerTeleportEvent.TeleportCause.UNKNOWN
? (PlayerTeleportEvent.TeleportCause)cause
: PlayerTeleportEvent.TeleportCause.UNKNOWN;
var evt = new PlayerTeleportEvent(player, from, to, teleportCause);
FourKit.FireEvent(evt);
var finalTo = evt.getTo();
Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerTeleport error: {ex}");
Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FirePlayerPortal(int entityId,
double fromX, double fromY, double fromZ, int fromDimId,
double toX, double toY, double toZ, int toDimId,
int cause, IntPtr outCoords)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
{
Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3);
return 0;
}
SyncPlayerFromNative(player);
var fromWorld = FourKit.getWorld(fromDimId);
var toWorld = FourKit.getWorld(toDimId);
var from = new Location(fromWorld, fromX, fromY, fromZ);
var to = new Location(toWorld, toX, toY, toZ);
var teleportCause = cause >= 0 && cause <= (int)PlayerTeleportEvent.TeleportCause.UNKNOWN
? (PlayerTeleportEvent.TeleportCause)cause
: PlayerTeleportEvent.TeleportCause.UNKNOWN;
var evt = new PlayerPortalEvent(player, from, to, teleportCause);
FourKit.FireEvent(evt);
var finalTo = evt.getTo();
Marshal.Copy(new double[] { finalTo.X, finalTo.Y, finalTo.Z }, 0, outCoords, 3);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePlayerPortal error: {ex}");
Marshal.Copy(new double[] { toX, toY, toZ }, 0, outCoords, 3);
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireInventoryClick(int entityId, int slot, int button, int clickType,
int nativeContainerType, int containerSize, IntPtr titleUtf8Ptr, int titleByteLen)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null) return 0;
SyncPlayerFromNative(player);
ClickType click = MapNativeClickType(clickType, button);
InventoryAction action = DetermineInventoryAction(click, slot);
InventoryType invType;
Inventory.Inventory topInv;
if (nativeContainerType < 0)
{
invType = InventoryType.PLAYER;
topInv = player.getInventory();
}
else
{
invType = MapNativeContainerType(nativeContainerType);
int size = containerSize > 0 ? containerSize : invType.getDefaultSize();
string title = titleByteLen > 0 && titleUtf8Ptr != IntPtr.Zero
? Marshal.PtrToStringUTF8(titleUtf8Ptr, titleByteLen) ?? invType.getDefaultTitle()
: invType.getDefaultTitle();
topInv = CreateContainerInventory(invType, nativeContainerType, title, size, entityId);
}
var bottomInv = player.getInventory();
var view = new InventoryView(topInv, bottomInv, player, invType);
SlotType slotType = SlotType.CONTAINER;
if (slot == InventoryView.OUTSIDE)
slotType = SlotType.OUTSIDE;
Inventory.Inventory._slotModifiedByPlugin = false;
int hotbarKey = click == ClickType.NUMBER_KEY ? button : -1;
var evt = new InventoryClickEvent(view, slotType, slot, click, action, hotbarKey);
FourKit.FireEvent(evt);
if (evt.isCancelled()) return 1;
if (Inventory.Inventory._slotModifiedByPlugin) return 2;
return 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireInventoryClick error: {ex}");
return 0;
}
}
private static ClickType MapNativeClickType(int nativeClickType, int button)
{
return nativeClickType switch
{
0 => button == 0 ? ClickType.LEFT : ClickType.RIGHT, // CLICK
1 => button == 0 ? ClickType.SHIFT_LEFT : ClickType.SHIFT_RIGHT, // SHIFT_CLICK
2 => ClickType.NUMBER_KEY, // NUMBER_KEY
3 => ClickType.MIDDLE, // MIDDLE_CLICK
4 => button == 1 ? ClickType.CONTROL_DROP : ClickType.DROP, // DROP
5 => ClickType.UNKNOWN, // DRAG (does not exist lol but maybe in the future?)
6 => ClickType.DOUBLE_CLICK, // DOUBLE_CLICK
_ => ClickType.UNKNOWN,
};
}
private static InventoryAction DetermineInventoryAction(ClickType click, int slot)
{
if (slot == InventoryView.OUTSIDE)
{
return click switch
{
ClickType.LEFT => InventoryAction.DROP_ALL_CURSOR,
ClickType.RIGHT => InventoryAction.DROP_ONE_CURSOR,
ClickType.WINDOW_BORDER_LEFT => InventoryAction.DROP_ALL_CURSOR,
ClickType.WINDOW_BORDER_RIGHT => InventoryAction.DROP_ONE_CURSOR,
_ => InventoryAction.NOTHING,
};
}
return click switch
{
ClickType.LEFT => InventoryAction.PICKUP_ALL,
ClickType.RIGHT => InventoryAction.PICKUP_HALF,
ClickType.SHIFT_LEFT or ClickType.SHIFT_RIGHT => InventoryAction.MOVE_TO_OTHER_INVENTORY,
ClickType.NUMBER_KEY => InventoryAction.HOTBAR_SWAP,
ClickType.MIDDLE => InventoryAction.CLONE_STACK,
ClickType.DROP => InventoryAction.DROP_ONE_SLOT,
ClickType.CONTROL_DROP => InventoryAction.DROP_ALL_SLOT,
ClickType.DOUBLE_CLICK => InventoryAction.COLLECT_TO_CURSOR,
_ => InventoryAction.UNKNOWN,
};
}
}