mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-04-25 08:27:28 +00:00
* optimize item data communication, options for future item flags * lighten load on gc from inventory apis * dont allocate for SyncPlayerFromNative * fix blocklag
262 lines
10 KiB
C#
262 lines
10 KiB
C#
using System.Runtime.InteropServices;
|
|
using Minecraft.Server.FourKit.Entity;
|
|
using Minecraft.Server.FourKit.Event.Inventory;
|
|
using Minecraft.Server.FourKit.Inventory;
|
|
|
|
namespace Minecraft.Server.FourKit;
|
|
|
|
public static partial 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 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}");
|
|
}
|
|
}
|
|
|
|
|
|
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)));
|
|
}
|
|
|
|
static double[] s_playerSnapshotBuffer = new double[27];
|
|
static GCHandle? s_playerSnapshotBuffer_Handle = null;
|
|
|
|
// 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 }
|
|
internal static void SyncPlayerFromNative(Player player)
|
|
{
|
|
if (NativeBridge.GetPlayerSnapshot == null)
|
|
return;
|
|
|
|
if (s_playerSnapshotBuffer_Handle == null)
|
|
{
|
|
s_playerSnapshotBuffer_Handle = GCHandle.Alloc(s_playerSnapshotBuffer, GCHandleType.Pinned);
|
|
}
|
|
|
|
NativeBridge.GetPlayerSnapshot(player.getEntityId(), s_playerSnapshotBuffer_Handle.GetValueOrDefault().AddrOfPinnedObject());
|
|
|
|
int dimId = (int)s_playerSnapshotBuffer[10];
|
|
player.SetDimensionInternal(dimId);
|
|
var world = FourKit.getWorld(dimId);
|
|
player.SetLocation(new Location(world, s_playerSnapshotBuffer[0], s_playerSnapshotBuffer[1], s_playerSnapshotBuffer[2], (float)s_playerSnapshotBuffer[8], (float)s_playerSnapshotBuffer[9]));
|
|
player.SetHealthInternal(s_playerSnapshotBuffer[3]);
|
|
player.SetMaxHealthInternal(s_playerSnapshotBuffer[4]);
|
|
player.SetFallDistanceInternal((float)s_playerSnapshotBuffer[5]);
|
|
player.SetGameModeInternal((GameMode)(int)s_playerSnapshotBuffer[6]);
|
|
player.SetWalkSpeedInternal((float)s_playerSnapshotBuffer[7]);
|
|
player.SetSleepingInternal(s_playerSnapshotBuffer[11] != 0.0);
|
|
player.SetSleepTicksInternal((int)s_playerSnapshotBuffer[12]);
|
|
player.SetSneakingInternal(s_playerSnapshotBuffer[13] != 0.0);
|
|
player.SetSprintingInternal(s_playerSnapshotBuffer[14] != 0.0);
|
|
player.SetOnGroundInternal(s_playerSnapshotBuffer[15] != 0.0);
|
|
player.SetVelocityInternal(s_playerSnapshotBuffer[16], s_playerSnapshotBuffer[17], s_playerSnapshotBuffer[18]);
|
|
player.SetAllowFlightInternal(s_playerSnapshotBuffer[19] != 0.0);
|
|
player.SetSleepingIgnoredInternal(s_playerSnapshotBuffer[20] != 0.0);
|
|
player.SetLevelInternal((int)s_playerSnapshotBuffer[21]);
|
|
player.SetExpInternal((float)s_playerSnapshotBuffer[22]);
|
|
player.SetTotalExperienceInternal((int)s_playerSnapshotBuffer[23]);
|
|
player.SetFoodLevelInternal((int)s_playerSnapshotBuffer[24]);
|
|
player.SetSaturationInternal((float)s_playerSnapshotBuffer[25]);
|
|
player.SetExhaustionInternal((float)s_playerSnapshotBuffer[26]);
|
|
}
|
|
|
|
internal 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 void WriteSignOutLens(IntPtr ptr, int[] lens)
|
|
{
|
|
Marshal.Copy(lens, 0, ptr, 4);
|
|
}
|
|
|
|
private static string JavaFormat(string format, params string[] args)
|
|
{
|
|
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();
|
|
}
|
|
|
|
|
|
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),
|
|
}
|
|
};
|
|
}
|
|
|
|
private static ClickType MapNativeClickType(int nativeClickType, int button)
|
|
{
|
|
return nativeClickType switch
|
|
{
|
|
0 => button == 0 ? ClickType.LEFT : ClickType.RIGHT,
|
|
1 => button == 0 ? ClickType.SHIFT_LEFT : ClickType.SHIFT_RIGHT,
|
|
2 => ClickType.NUMBER_KEY,
|
|
3 => ClickType.MIDDLE,
|
|
4 => button == 1 ? ClickType.CONTROL_DROP : ClickType.DROP,
|
|
5 => ClickType.UNKNOWN,
|
|
6 => ClickType.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,
|
|
};
|
|
}
|
|
}
|