# 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