namespace Minecraft.Server.FourKit; using Minecraft.Server.FourKit.Command; using Minecraft.Server.FourKit.Entity; using Minecraft.Server.FourKit.Event; using Minecraft.Server.FourKit.Inventory; public static class FourKit { private static readonly EventDispatcher _dispatcher = new(); private static readonly Dictionary _players = new(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _playersByEntityId = new(); private static readonly object _playerLock = new(); internal const int MAX_CHAT_LENGTH = 123; private static readonly Dictionary _worldsByDimId = new(); private static readonly Dictionary _worldNameToDimId = new(StringComparer.OrdinalIgnoreCase) { // grr ["world"] = 0, ["world_nether"] = -1, ["world_the_end"] = 1, }; private static readonly object _worldLock = new(); /// /// Gets a world by its name. Supported names: "world" (overworld), /// "world_nether" (nether), "world_the_end" (the end). /// /// The name of the world to retrieve. /// The world with the given name, or null if none exists. public static World? getWorld(string name) { if (_worldNameToDimId.TryGetValue(name, out int dimId)) return getWorld(dimId); return null; } /// /// Gets a world by its dimension ID (0 = overworld, -1 = nether, 1 = the end). /// /// The dimension ID. /// The world for that dimension, creating it if necessary. public static World getWorld(int dimId) { lock (_worldLock) { if (!_worldsByDimId.TryGetValue(dimId, out var world)) { string name = dimId switch { 0 => "world", -1 => "world_nether", 1 => "world_the_end", _ => $"world_dim{dimId}", }; world = new World(dimId, name); _worldsByDimId[dimId] = world; } return world; } } /// /// Registers all the events in the given listener class /// public static void addListener(Listener listener) { _dispatcher.Register(listener); //Console.WriteLine($"[FourKit] Registered listener: {listener.GetType().Name}"); } public static Player? getPlayer(string name) { lock (_playerLock) { _players.TryGetValue(name, out var p); return p; } } public static IReadOnlyList getOnlinePlayers() { lock (_playerLock) { return _players.Values.Where(p => p.IsOnline).ToList().AsReadOnly(); } } internal static Player? GetPlayerByEntityId(int entityId) { lock (_playerLock) { _playersByEntityId.TryGetValue(entityId, out var p); return p; } } internal static Player TrackPlayer(int entityId, string name) { lock (_playerLock) { if (_playersByEntityId.TryGetValue(entityId, out var existing)) { var oldName = existing.getName(); existing.SetNameInternal(name); existing.IsOnline = true; if (!string.Equals(oldName, name, StringComparison.OrdinalIgnoreCase)) { _players.Remove(oldName); _players[name] = existing; } return existing; } var player = new Player(entityId, name); _players[name] = player; _playersByEntityId[entityId] = player; return player; } } internal static Player? UntrackPlayer(int entityId) { lock (_playerLock) { if (_playersByEntityId.TryGetValue(entityId, out var player)) { player.IsOnline = false; _playersByEntityId.Remove(entityId); _players.Remove(player.getName()); return player; } return null; } } internal static void UpdatePlayerEntityId(int oldEntityId, int newEntityId) { lock (_playerLock) { if (_playersByEntityId.TryGetValue(oldEntityId, out var player)) { _playersByEntityId.Remove(oldEntityId); player.SetEntityIdInternal(newEntityId); _playersByEntityId[newEntityId] = player; } } } internal static void FireEvent(Event.Event evt) { _dispatcher.Fire(evt); } private static readonly Dictionary _commands = new(StringComparer.OrdinalIgnoreCase); private static readonly object _commandLock = new(); /// /// Gets a with the given name, creating it /// if it does not already exist. The returned command can be configured /// with , /// , etc. /// /// Name of the command. /// The command for that name. public static PluginCommand getCommand(string name) { lock (_commandLock) { if (!_commands.TryGetValue(name, out var cmd)) { cmd = new PluginCommand(name); _commands[name] = cmd; } return cmd; } } internal static bool DispatchCommand(CommandSender sender, string commandLine) { string trimmed = commandLine.StartsWith('/') ? commandLine[1..] : commandLine; if (string.IsNullOrEmpty(trimmed)) return false; string[] parts = trimmed.Split(' ', StringSplitOptions.RemoveEmptyEntries); string label = parts[0]; string[] args = parts.Length > 1 ? parts[1..] : []; PluginCommand? cmd; lock (_commandLock) { if (!_commands.TryGetValue(label, out cmd)) { foreach (var entry in _commands.Values) { if (entry.getAliases().Exists(a => string.Equals(a, label, StringComparison.OrdinalIgnoreCase))) { cmd = entry; break; } } } } if (cmd == null || cmd.getExecutor() == null) return false; try { return cmd.execute(sender, label, args); } catch (Exception ex) { ServerLog.Error("fourkit", $"Error executing command '/{label}': {ex}"); sender.sendMessage($"An internal error occurred while executing /{label}."); return false; } } internal static ConsoleCommandSender getConsoleSender() => ConsoleCommandSender.Instance; internal static bool HasCommand(string label) { lock (_commandLock) { if (_commands.TryGetValue(label, out var cmd) && cmd.getExecutor() != null) return true; foreach (var entry in _commands.Values) { if (entry.getExecutor() != null && entry.getAliases().Exists(a => string.Equals(a, label, StringComparison.OrdinalIgnoreCase))) return true; } } return false; } internal static List<(string usage, string description)> GetRegisteredCommandHelp() { var result = new List<(string, string)>(); lock (_commandLock) { foreach (var cmd in _commands.Values) { if (cmd.getExecutor() != null) result.Add((cmd.getUsage(), cmd.getDescription())); } } return result; } /// /// Broadcasts a message to all online players. /// /// The message to broadcast. public static void broadcastMessage(string message) { if (string.IsNullOrEmpty(message) || NativeBridge.BroadcastMessage == null) return; if (message.Length > MAX_CHAT_LENGTH) message = message[..MAX_CHAT_LENGTH]; IntPtr ptr = System.Runtime.InteropServices.Marshal.StringToCoTaskMemUTF8(message); try { NativeBridge.BroadcastMessage(ptr, System.Text.Encoding.UTF8.GetByteCount(message)); } finally { System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr); } } /// /// Creates a new with the specified size. /// The inventory will be of type with /// the default title. /// /// The size of the inventory (must be a multiple of 9). /// A new Inventory. public static Inventory.Inventory createInventory(int size) { return new Inventory.Inventory("Chest", InventoryType.CHEST, size); } /// /// Creates a new with the specified size /// and custom title. /// /// The size of the inventory (must be a multiple of 9). /// The title that will be shown to players. /// A new Inventory. public static Inventory.Inventory createInventory(int size, string title) { return new Inventory.Inventory(title, InventoryType.CHEST, size); } /// /// Creates a new of the specified /// with the default title and size. /// /// The type of inventory to create. /// A new Inventory. public static Inventory.Inventory createInventory(InventoryType type) { return new Inventory.Inventory(type.getDefaultTitle(), type, type.getDefaultSize()); } /// /// Creates a new of the specified /// with a custom title. /// /// The type of inventory to create. /// The title that will be shown to players. /// A new Inventory. public static Inventory.Inventory createInventory(InventoryType type, string title) { return new Inventory.Inventory(title, type, type.getDefaultSize()); } }