# API Reference Bookshelf-API provides the public interfaces, utilities, and abstractions that other Lodestone plugins use. The API can be consumed either by depending on the Bookshelf plugin at runtime or by shading it directly into your plugin. --- ## Accessing the API The `BookshelfAPI` class is the static entry point for all API access. ### With Bookshelf Plugin Installed When the Bookshelf plugin is present on the server, the API is initialized automatically. Access it after your plugin enables: ```java IBookshelfAPI api = BookshelfAPI.getApi(); IMenuManager menuManager = api.getMenuManager(); ICooldownManager cooldownManager = api.getCooldownManager(); ``` ### Standalone (Shaded) If you shade Bookshelf-API into your plugin, initialize it during `onEnable()`. Remember to relocate the package to avoid conflicts. ```java // Initialize with all managers enabled BookshelfAPI.init(this); // Initialize with selective managers BookshelfAPI.init(this, BookshelfAPI.Builder.create() .useMenuManager(true) .useCooldownManager(true) .useItemManager(false) .useScoreboardManager(false) ); ``` **Note:** When running standalone, the following managers are only available with the full Bookshelf plugin installed: - `IChatManager` - `IGameManager` - `IPlayerManager` - `IVanishManager` Calling these without the plugin throws `UnsupportedOperationException`. --- ## Manager Interfaces ### IMenuManager Manages player menu registration and retrieval. ```java IMenuManager menuManager = BookshelfAPI.getApi().getMenuManager(); // Register and open a menu menuManager.registerAndOpen(player, myMenu); // Get active menu for a player Menu activeMenu = menuManager.getActiveMenu(player); ``` | Method | Description | |---|---| | `register(Player, Menu)` | Registers a menu for a player. | | `register(UUID, Menu)` | Registers a menu by UUID. | | `registerAndOpen(Player, Menu)` | Registers and immediately opens a menu. | | `getActiveMenu(Player)` | Returns the player's currently active menu. | | `getActiveMenu(UUID)` | Returns the active menu by UUID. | ### ICooldownManager Manages timed cooldowns per player or globally. ```java ICooldownManager cooldowns = BookshelfAPI.getApi().getCooldownManager(); // Set a 5-second cooldown cooldowns.setCooldown(player, "ability.fireball", 5000); // Check before executing if (cooldowns.hasCooldown(player, "ability.fireball")) { // Player is on cooldown return; } // Set with callback when cooldown expires cooldowns.setCooldown(player, "ability.fireball", 5000, p -> { p.sendMessage("Fireball is ready!"); }); // Notify with message if on cooldown (returns true if on cooldown) cooldowns.notifyPlayerWithCooldown(player, "ability.fireball", "<red>Fireball is on cooldown!"); ``` | Method | Description | |---|---| | `setCooldown(Player, String, long)` | Sets a player cooldown in milliseconds. | | `setCooldown(String, long)` | Sets a global cooldown. | | `setCooldown(Player, String, long, Consumer<Player>)` | Sets a cooldown with an expiry callback. | | `hasCooldown(Player, String)` | Checks if a player has an active cooldown. | | `hasCooldown(String)` | Checks if a global cooldown is active. | | `getCooldown(Player, String)` | Returns remaining cooldown time in ms. | | `notifyPlayerWithCooldown(Player, String, Component)` | Sends a message if on cooldown. Returns `true` if on cooldown. | ### ICustomItemManager Registers and manages custom items. ```java ICustomItemManager items = BookshelfAPI.getApi().getItemManager(); // Register custom items items.register(new MyCustomSword(), new MyCustomBow()); // Check if an ItemStack is a specific custom item boolean isCustom = items.isCustomItem(itemStack, MyCustomSword.class); boolean isById = items.isCustomItem(itemStack, "my_custom_sword"); // Get an ItemStack from a custom item ItemStack stack = items.getItemStackById("my_custom_sword"); ItemStack stackByClass = items.getItemStackByClass(MyCustomSword.class); ``` ### IScoreboardManager Manages per-player scoreboards. ```java IScoreboardManager scoreboards = BookshelfAPI.getApi().getScoreboardManager(); // Add a scoreboard to a player scoreboards.addPlayer(player, myBoard); // Start automatic updates (every 20 ticks) scoreboards.startScoreboard(plugin, 20); // Change name color for a player scoreboards.changeColor(viewer, target, NamedTextColor.RED); ``` ### IVanishManager (Plugin Only) Controls player visibility. Requires the full Bookshelf plugin. ```java IVanishManager vanish = BookshelfAPI.getApi().getVanishManager(); vanish.vanishPlayer(player); vanish.unvanishPlayer(player); boolean hidden = vanish.isVanished(player); ``` ### IChatManager (Plugin Only) Controls global chat state. Requires the full Bookshelf plugin. ```java IChatManager chat = BookshelfAPI.getApi().getChatManager(); chat.setChatMuted(true); boolean muted = chat.isChatMuted(); boolean canBypass = chat.canPlayerBypassChat(player.getUniqueId()); ``` ### IGameManager (Plugin Only) Controls game state. Requires the full Bookshelf plugin. ```java IGameManager game = BookshelfAPI.getApi().getGameManager(); game.setPVPEnabled(false); boolean pvp = game.isPVPEnabled(); ``` ### IPlayerManager (Plugin Only) Queries player state. Requires the full Bookshelf plugin. ```java IPlayerManager players = BookshelfAPI.getApi().getPlayerManager(); boolean god = players.hasGodMode(player); String channel = players.getCurrentChatChannel(player); ``` --- ## Menu System Bookshelf provides a builder-based menu system. Extend `Menu` (which implements `InventoryHolder`) to create interactive inventory GUIs. ### Creating a Menu ```java public class MyMenu extends Menu { public MyMenu(Player player) { super(player); } @Override protected @NotNull TopMenuBuilder getTopMenuBuilder(TopMenuBuilder builder) { ItemStack background = new ItemBuilder(Material.GRAY_STAINED_GLASS_PANE) .title(" ") .build(); return builder .setTitle("My Custom Menu") .setRows(3) .fill(background) .insertInRow(1, 4, new ItemBuilder(Material.DIAMOND) .title("<aqua>Click Me") .build(), event -> { event.setCancelled(true); player.sendMessage("You clicked the diamond!"); }) .addCloseAction(event -> { // Runs when the menu is closed }) .addOpenAction(() -> { // Runs when the menu is opened }); } @Override protected @Nullable MenuBuilder getBottomMenuBuilder(MenuBuilder builder) { return null; // No bottom inventory modification } } ``` ### Opening a Menu ```java MyMenu menu = new MyMenu(player); menu.open(); ``` ### TopMenuBuilder Methods | Method | Description | |---|---| | `setTitle(String)` | Sets the menu title (supports MiniMessage). | | `setTitle(Component)` | Sets the menu title as a Component. | | `setRows(int)` | Sets row count (1-6). | | `setPacketBased(boolean)` | Toggles packet-based mode for the menu. See below. | | `fill(ItemStack)` | Fills all slots with an item. | | `outline(ItemStack)` | Fills only border slots. | | `insertInRow(int, int, ItemStack)` | Places an item at a specific row and slot. | | `insertInRow(int, int, ItemStack, Consumer<InventoryClickEvent>)` | Places an item with a click handler. | | `buildRow(int, Consumer<RowBuilder>)` | Configures a specific row. | | `editRow(int, Consumer<RowBuilder>)` | Modifies an existing row. | | `addOpenAction(Runnable)` | Adds a callback for when the menu opens. | | `addCloseAction(Consumer<InventoryCloseEvent>)` | Adds a callback for when the menu closes. | | `addClickAction(Consumer<InventoryClickEvent>)` | Adds a global click callback. | ### Packet-Based Menus Marking a menu packet-based (`setPacketBased(true)`) prevents players from grabbing items out of the menu under any TPS condition. Click handlers receive an `InventoryClickEvent` exactly as before — only the protection layer changes. Per-slot overrides are supported, so a menu can mix display-only buttons with interactive drop zones in the same view: | Slot Setter | Behavior | |---|---| | `setSlot(...)` | Inherits the menu default. | | `setPacketSlot(...)` | Force packet-only — clicks cannot transfer the item. | | `setServerSlot(...)` | Force server-side — player can grab/swap the item. | | `setEmptyServerSlot(...)` | Empty server-side drop zone — player can deposit items. | ```java return builder .setPacketBased(true) .setRows(3) .setTitle("Hybrid Menu") .fill(background) // packet (inherits) .editRow(1, row -> { row.setServerSlot(0, lootItem, event -> { /* take handler */ }); row.setEmptyServerSlot(1, null); // drop zone row.setPacketSlot(8, closeButton, event -> close()); }); ``` When opening, ensure your platform module wires a `PacketMenuHandler`. Bookshelf-Paper installs one automatically on enable. ### Refreshing a Packet Menu Slot For packet-based menus, the server inventory holds a sentinel placeholder so the client view is driven by the menu's stored slot data. To update a slot live, write to the open inventory directly — the platform listener detects the write, syncs it into the menu, and forwards the new visual to the client: ```java Inventory inv = player.getOpenInventory().getTopInventory(); inv.setItem(rawSlot, newStack); ``` No `update()` or re-open call is required. Click handlers attached to the slot are preserved. For full rebuilds (e.g. switching tabs, changing a whole layout), call `menu.update()` instead. ### Hybrid Bottom Inventory The packet pipeline extends to the bottom (player) inventory. Packet-flagged bottom slots show the menu's display item to the client without ever writing to the player's real inventory. Mix packet decorations with real player items in the same view, or hide the bottom inventory entirely. | Method | Behavior | |---|---| | `MenuBuilder.set(...)` / `setServer(...)` | Real slot — items live in `player.getInventory()`, vanilla shift-click works. | | `MenuBuilder.setPacket(row, col, item, handler)` | Packet slot — client sees `item`, server inventory untouched, click dispatched to `handler`. | | `MenuBuilder.setEmptyPacket(row, col, handler)` | Packet slot rendered as empty (AIR) on the client. | | `MenuBuilder.hideAll()` | Replaces the bottom builder with 36 empty packet slots — overlays an entirely empty inventory on the client. | Live overlay toggles on `Menu`: ```java menu.hideBottomInventory(); // overlay empty visuals on all 36 bottom slots menu.restoreBottomInventory(); // resync real player items to client ``` Shift-clicks within the bottom inventory are auto-cancelled while any bottom packet slot exists, so vanilla cannot shuffle real items into a slot the client thinks is decorative. --- ## Configuration Utility The `Configuration` class wraps `YamlConfiguration` for simplified file access. ```java Configuration config = new Configuration(plugin, "config.yml"); config.initialize(); // Reads or creates the file // Read values String value = config.getString("path.to.value", "default"); int number = config.getInt("path.to.value", 0); boolean flag = config.getBoolean("path.to.flag", false); double decimal = config.getDouble("path.to.value", 0.0); // Write values config.set("path.to.value", "new value"); config.save(); ``` --- ## ItemBuilder A fluent builder for creating `ItemStack` instances. ```java ItemStack item = new ItemBuilder(Material.DIAMOND_SWORD) .title("<red>Flame Sword") .lore("<gray>A burning blade", "<yellow>+10 Fire Damage") .enchantment(Enchantment.FIRE_ASPECT, 2) .unbreakable(true) .modelData(1001) .flags() .build(); ``` Key methods: `title(String)`, `lore(String...)`, `enchantment(Enchantment, int)`, `amount(int)`, `type(Material)`, `skull(Player)`, `skull(OfflinePlayer)`, `skull(String)`, `tag(NamespacedKey)`, `unbreakable(boolean)`, `modelData(int)`, `itemModel(NamespacedKey)`, `maxStackSize(int)`, `trimPattern(TrimPattern)`, `trimMaterial(TrimMaterial)`, `leatherColor(String)`, `potionData(PotionData)`, `glider(boolean)`, `rarity(Object)`. `skull(String)` accepts either the base64 textures property value or a raw `https://textures.minecraft.net/texture/...` URL. It uses Bukkit's `PlayerProfile` API, so behavior is stable across Paper's component-based item refactor (1.20.5+). --- ## CustomItem Extend `CustomItem` to create items with event-driven behavior. ```java public class FlameSword extends CustomItem { @Override public String id() { return "flame_sword"; } @Override public void builder(ItemBuilder builder) { builder.type(Material.DIAMOND_SWORD) .title("<red>Flame Sword") .enchantment(Enchantment.FIRE_ASPECT, 3); } @Override public void onInteract(Player player, PlayerInteractEvent event, ItemStack item) { player.sendMessage("The blade burns!"); } @Override public void onKill(Player player, EntityDeathEvent event, ItemStack item) { player.sendMessage("Vanquished by fire!"); } } ``` Available event hooks: `onHeld`, `onUnheld`, `onShift`, `onInteract`, `onRightInteract`, `onLeftInteract`, `onBlockBreak`, `onBlockPlace`, `onFish`, `onHurt`, `onKill`, `onHarvest`, `onShoot`, `onInventoryClick`, `onInventoryPlace`, `onInventoryHotbar`, `onDrop`, `onOffhand`. --- ## Utility Classes | Class | Description | |---|---| | `MiniMessageHelper` | Deserialize/serialize MiniMessage strings, center text, convert legacy codes. | | `VariableContext` | Template variable replacement. Supports `<var>` and `<<var>>` syntax with formatters. | | `StringHelper` | String manipulation utilities. | | `LocationHelper` | Location serialization and math. | | `EntityHelper` | Entity lookup and manipulation. | | `InventoryHelper` | Inventory utilities. | | `ArrayHelper` | Array manipulation. | | `ByteHelper` | Byte conversion utilities. | | `EnumHelper` | Enum parsing utilities. | | `WorldHelper` | World lookup and management. | | `PlayerLookupHelper` | Online/offline player resolution. | | `ReflectionHelper` | Reflection utilities. | | `PackHelper` | Resource pack utilities. | | `TrueDamageHelper` | Armor-and-enchantment-scaled true damage via `setHealth`. Calibrated against a configurable baseline (default: full Prot III diamond). | | `PaperCapabilities` | Version-safe access to 1.21.4+ item meta features. | ### MiniMessageHelper Examples ```java // Deserialize MiniMessage strings Component msg = MiniMessageHelper.deserialize("<red>Hello <bold>World!"); // With variables VariableContext ctx = VariableContext.of("player", player.getName()); Component msg = MiniMessageHelper.deserialize("Welcome, <player>!", ctx); // Center text in chat MiniMessageHelper.centerAndSend(player, "<gold>Welcome!", new VariableContext()); // Convert legacy formatting String modern = MiniMessageHelper.convertAmpersandToMiniMessage("&cHello &lWorld"); // Result: "<red>Hello <bold>World" ``` --- ## Scoreboard System Extend `AbstractBoard` to create per-player scoreboards. ```java public class MyBoard extends AbstractBoard { public MyBoard(Player player) { super(player, Component.text("My Server")); } @Override public void update() { setLineFromList(List.of( "<gray>Welcome!", "", "<aqua>Players: <white>" + Bukkit.getOnlinePlayers().size(), "<aqua>World: <white>" + player.getWorld().getName(), "", "<yellow>lode.gg" )); } } // Register and start IScoreboardManager scoreboards = BookshelfAPI.getApi().getScoreboardManager(); scoreboards.addPlayer(player, new MyBoard(player)); scoreboards.startScoreboard(plugin, 20); // Update every 20 ticks ``` --- ## Related Pages - [[Bookshelf/Developers/Overview]] - Architecture and module structure - [[Events]] - Custom event system - [[Command System]] - Command creation