# Developer Overview Lectern provides a public API module (`Lectern-API`) that allows other plugins to trigger and control client-side effects on players running the Lectern client. Effects include camera manipulation, screen effects, HUD rendering, interactive menus, composable components, emotes, audio, lighting overrides, and more. --- ## Maven / Gradle The Lectern-API artifact is hosted on JitPack. Add it as a `compileOnly` / `provided` dependency so that Lectern is not shaded into your plugin jar. **Gradle (Kotlin DSL):** ```kotlin repositories { maven("https://jitpack.io") } dependencies { compileOnly("com.github.Lodestones:Lectern-API:1.0.0") } ``` **Maven:** ```xml <repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories> <dependencies> <dependency> <groupId>com.github.Lodestones</groupId> <artifactId>Lectern-API</artifactId> <version>1.0.0</version> <scope>provided</scope> </dependency> </dependencies> ``` Add `Lectern` as a `depend` or `softdepend` in your `plugin.yml`: ```yaml depend: - Lectern ``` Or, if Lectern is optional for your plugin: ```yaml softdepend: - Lectern ``` --- ## Accessing the API The API is accessed through the static `LecternAPI` class. The API instance is available after Lectern has been enabled. ```java import gg.lode.lecternapi.LecternAPI; import gg.lode.lecternapi.ILecternAPI; ILecternAPI api = LecternAPI.getApi(); if (api == null) { // Lectern is not loaded return; } ``` --- ## Architecture | Module | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | **Lectern-API** | Public interfaces. Depend on this module. | | **Lectern-Paper** | Implementation. Contains commands, network handling, and effect dispatching. Do not depend on this directly. | The API entry point is `LecternAPI.getApi()`, which returns an `ILecternAPI` instance. --- ## Common Operations ### Camera Control ```java ILecternAPI api = LecternAPI.getApi(); Player player = /* ... */; // Move camera to a position with rotation api.getCameraManager().moveCamera(player, x, y, z, yaw, pitch, roll, durationTicks); // Screen shake api.getCameraManager().screenshake(player, intensity, durationTicks); api.getCameraManager().stopScreenshake(player); // Field of view api.getCameraManager().setFov(player, 120.0f); api.getCameraManager().resetFov(player); ``` ### HUD Rendering ```java // Display a texture on the player's HUD api.getHUDManager().renderTexture(player, "my_texture", "namespace:path/to/texture.png", x, y, width, height); api.getHUDManager().removeTexture(player, "my_texture"); // Show a progress bar api.getHUDManager().showSecondBar(player, 0.75f); // 75% filled api.getHUDManager().hideSecondBar(player); ``` ### Packet Components (Tickable HUD) ```java // Define a component public class HealthBar extends PacketComponent { private final Player target; public HealthBar(Player target) { super("health_bar", target, MenuTransform.at(10, 10)); this.target = target; } @Override protected void build(Builder builder) { builder.addTexture("bg", "mygame:hud/bar_bg", MenuTransform.at(0, 0), 200, 20); builder.addTexture("fill", "mygame:hud/bar_fill", MenuTransform.at(2, 2), 196, 16) .tickable(); } @Override protected void tick() { float pct = (float) target.getHealth() / 20f; update("fill", texture -> texture.width(196 * pct)); } } // Show it new HealthBar(player).show(); // Or via the manager api.getPacketComponentManager().show(player, new HealthBar(player)); ``` ### Packet Menus (Interactive Screens) ```java public class ShopMenu extends PacketMenu { public ShopMenu(Player player) { super("shop", player); setBlur(0.7f); } @Override protected void build(Builder builder) { builder.addTexture("bg", "myplugin:textures/gui/shop.png", MenuTransform.at(0, 0), 256, 256); builder.addButton("buy", "myplugin:textures/gui/buy.png", MenuTransform.at(50, 80).layer(5), 64, 32, ButtonListener.onClick(origin -> { origin.player().sendMessage("Purchased!"); origin.menu().close(); })); builder.addText("title", "<gold>Shop</gold>", MenuTransform.at(0, 20).centered(), 2.0f); } } // Open it api.getPacketMenuManager().open(player, new ShopMenu(player)); ``` ### Emotes ```java // Play an emote on an entity, visible to a specific player api.getEmoteManager().playEmote(viewer, entityUuid, "lodestone:wave", true, 1); // Play a local emote (player sees it on themselves) api.getEmoteManager().playLocalEmote(player, "lodestone:dance", true, 1); // Broadcast an emote to all online players api.getEmoteManager().broadcastEmote(target, "lodestone:wave", true, 1); // Stop emotes api.getEmoteManager().stopEmote(viewer, entityUuid, "lodestone:wave"); api.getEmoteManager().broadcastStopAllEmotes(target); ``` ### Environment Effects ```java // True darkness (no ambient light) api.getEnvironmentManager().setTrueDarkness(player, true); // Fog color override api.getEnvironmentManager().setFogColor(player, 0.2f, 0.0f, 0.0f); // dark red fog api.getEnvironmentManager().resetFogColor(player); ``` ### Entity Manipulation ```java // Custom cape api.getEntityManager().setCape(player, target, "namespace:textures/cape.png"); api.getEntityManager().removeCape(player, target); // Tint an entity api.getEntityManager().tintEntity(player, target, 255, 0, 0, 128); // red tint api.getEntityManager().removeTint(player, target); ``` ### Audio ```java // Play a sound effect api.getAudioManager().playSound(player, "namespace:sound.effect", 1.0f, 1.0f); ``` ### Input Control ```java // Disable a keybind api.getInputManager().disableKeybind(player, "key.attack"); api.getInputManager().enableKeybind(player, "key.attack"); api.getInputManager().clearDisabledKeybinds(player); // Lock the player's camera api.getInputManager().setHeadLocked(player, true); api.getInputManager().setHeadLocked(player, false); ``` ### Screen Effects ```java // Flash the screen api.getScreenManager().flash(player, 255, 255, 255, 200, 20); // white flash, 20 ticks // X-ray vision api.getScreenManager().setXray(player, true); api.getScreenManager().addXrayBlock(player, "minecraft:diamond_ore"); api.getScreenManager().removeXrayBlock(player, "minecraft:diamond_ore"); ``` ### Modal Prompts ```java import gg.lode.lecternapi.api.prompt.ModalPromptButton; // Show a modal prompt with configurable buttons List<ModalPromptButton> buttons = List.of( ModalPromptButton.consumer("Accept", "accept_rules", 0.2f, 0.7f, 0.2f), ModalPromptButton.link("Website", "https://example.com", 0.2f, 0.4f, 0.8f), ModalPromptButton.close("Dismiss", 0.7f, 0.2f, 0.2f) ); api.getScreenManager().showModalPrompt(player, "rules", "Server Rules", "# Welcome\n\nPlease read and accept our rules.\n\n" + "## Rules\n\n- Be respectful\n- No griefing\n- Have fun", buttons); // Close programmatically (buttons auto-close on click) api.getScreenManager().closeModalPrompt(player); ``` ### Cutscenes ```java Cutscene intro = Cutscene.builder("intro") .at(0) .letterbox(true) .hideHud(true) .disableInput(true) .setCamera(100, 80, 200, 0, -15, 0) .cameraPath(path -> path .waypoint(Vec.of(100, 80, 200), 0, -15, 0, 0) .waypoint(Vec.of(130, 85, 230), 45, -10, 0, 60) .interpolation(CameraInterpolation.CATMULL_ROM)) .then(20) .showText("title", "<gold>Chapter 1</gold>", MenuTransform.at(0, 0).centered(), 3.0f) .then(60) .hideText("title") .callback(() -> player.sendMessage("Midpoint reached!")) .then(40) .letterbox(false) .hideHud(false) .disableInput(false) .releaseCamera() .onComplete(() -> player.teleport(spawnLocation)) .build(); api.getCutsceneManager().play(player, intro); ``` ### Voice Chat Control ```java // Force mute during cutscene (player cannot override) api.getVoiceChatManager().forceMutePlayer(player); api.getVoiceChatManager().forceDeafenPlayer(player); // Release control api.getVoiceChatManager().stopForceMute(player); api.getVoiceChatManager().stopForceDeafen(player); // Suggestive (player can override in their client) api.getVoiceChatManager().mutePlayer(player); ``` ### Listening for C2S Events Lectern dispatches Bukkit events when the client sends data to the server: ```java @EventHandler public void onKeybindPressed(KeybindPressedEvent event) { Player player = event.getPlayer(); String key = event.getKey(); boolean pressed = event.isPressed(); // Handle keybind press/release } @EventHandler public void onButtonClick(ButtonClickEvent event) { Player player = event.getPlayer(); String reference = event.getReference(); // Handle HUD button click } @EventHandler public void onCutsceneComplete(CutsceneCompleteEvent event) { Player player = event.getPlayer(); String id = event.getCutsceneId(); // Handle cutscene completion } @EventHandler public void onModalPromptClick(ModalPromptClickEvent event) { Player player = event.getPlayer(); String promptId = event.getPromptId(); String reference = event.getReference(); // Handle modal prompt CONSUMER button click } ``` Available C2S events: - `KeybindPressedEvent` — Player pressed/released a keybind - `ButtonClickEvent` — Player clicked a HUD button - `ButtonHoverEvent` — Player hovered/unhovered a HUD button - `ModalPromptClickEvent` — Player clicked a CONSUMER button on a modal prompt - `MenuCloseEvent` — Player closed a menu - `CutsceneCallbackEvent` — Cutscene reached a callback marker - `CutsceneCompleteEvent` — Cutscene finished playing - `PlayerMuteEvent` / `PlayerUnmuteEvent` — Voice chat mute state changed - `PlayerDeafenEvent` / `PlayerUndeafenEvent` — Voice chat deafen state changed - `ClientModsReportEvent` — Client reported installed mods - `ClientPacksReportEvent` — Client reported installed resource packs - `PossibleInjectedClientEvent` — Client detected a potentially injected mod --- ## Related Pages - [[Lectern/Developers/API Reference]] — Full interface documentation - [[Lectern/Server Owners/Overview]] — Plugin overview and installation - [[Lectern/Server Owners/Commands]] — Command reference