docs/velocity/events/SKILL.md
# Events Skill — Velocity ## Purpose Reference this skill when handling events in a Velocity proxy plugin. Velocity's event system is fully async and uses `@Subscribe` annotations instead of Bukkit's `@EventHandler`. ## When to Use This Skill - Responding to player connections, disconnections, or server switches - Intercepting login to apply bans, authentication checks, or queue systems - Broadcasting proxy-level events to your own subsystems - Registering event listeners on Velocity ## API Q
npx skillsauth add MrPippi/MPS docs/velocity/eventsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference this skill when handling events in a Velocity proxy plugin. Velocity's event system is fully async and uses @Subscribe annotations instead of Bukkit's @EventHandler.
| Class / Method | Purpose | Notes |
|---------------|---------|-------|
| EventManager#register(plugin, listener) | Register all @Subscribe methods in an object | Pass your plugin instance as first arg |
| @Subscribe | Marks a method as an event listener | Method takes exactly one event parameter |
| PostOrder | Controls listener ordering | FIRST, EARLY, NORMAL, LATE, LAST, CUSTOM |
| ResultedEvent | Events with a result (allow/deny) | Set result via event.setResult(...) |
| EventManager#fire(Event) | Fire a custom or built-in event | Returns CompletableFuture<E> |
| EventManager#fireAndForget(Event) | Fire without waiting for result | For events you don't need to read |
package com.yourorg.proxyplugin.listeners;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.slf4j.Logger;
import java.util.Optional;
public class ConnectionListener {
private final ProxyServer server;
private final Logger logger;
public ConnectionListener(ProxyServer server, Logger logger) {
this.server = server;
this.logger = logger;
}
// Fires before the player is fully logged in — can deny login
@Subscribe(order = PostOrder.NORMAL)
public void onLogin(LoginEvent event) {
// Example: reject banned players (check your ban list here)
String playerName = event.getPlayer().getUsername();
if (isBanned(playerName)) {
event.setResult(ResultedEvent.ComponentResult.denied(
Component.text("You are banned from this network.")
.color(NamedTextColor.RED)
));
}
}
// Fires after player is fully connected
@Subscribe(order = PostOrder.NORMAL)
public void onPostLogin(PostLoginEvent event) {
Player player = event.getPlayer();
logger.info("{} ({}) connected to the proxy.", player.getUsername(), player.getUniqueId());
// Network-wide welcome broadcast
server.getAllPlayers().forEach(p ->
p.sendMessage(Component.text("→ ")
.color(NamedTextColor.GREEN)
.append(Component.text(player.getUsername() + " joined the network.")
.color(NamedTextColor.WHITE)))
);
}
// Choose which backend server a player initially connects to
@Subscribe(order = PostOrder.NORMAL)
public void onChooseServer(PlayerChooseInitialServerEvent event) {
Optional<RegisteredServer> lobby = server.getServer("lobby");
lobby.ifPresent(event::setInitialServer);
}
// Fires after player successfully connects to a backend server
@Subscribe(order = PostOrder.NORMAL)
public void onServerConnected(ServerConnectedEvent event) {
Player player = event.getPlayer();
String serverName = event.getServer().getServerInfo().getName();
String previousServer = event.getPreviousServer()
.map(s -> s.getServerInfo().getName())
.orElse("none");
logger.info("{} switched from {} to {}", player.getUsername(), previousServer, serverName);
}
// Fires when player disconnects from the proxy
@Subscribe(order = PostOrder.NORMAL)
public void onDisconnect(DisconnectEvent event) {
logger.info("{} disconnected ({}).",
event.getPlayer().getUsername(),
event.getLoginStatus());
}
private boolean isBanned(String playerName) {
// Check your ban storage
return false;
}
}
Register the listener in your main class:
@Subscribe
public void onProxyInitialize(ProxyInitializeEvent event) {
server.getEventManager().register(this, new ConnectionListener(server, logger));
}
Using Bukkit event annotations on Velocity: @EventHandler is a Bukkit annotation. Velocity uses @Subscribe from com.velocitypowered.api.event. Methods annotated with @EventHandler will be silently ignored.
Blocking inside event handlers: All Velocity event handlers run on an async thread pool. Blocking with Thread.sleep() or synchronous IO will starve the event thread. Use CompletableFuture for async work.
Not setting a result on ResultedEvent: If you don't call event.setResult(...), the default result applies (usually allow). Always explicitly set results for login/connection events when making decisions.
Accessing full player game state (inventory, location): Velocity is a proxy — it has no knowledge of the player's in-game state. You can read Player.getUsername(), getUniqueId(), getCurrentServer(), getRemoteAddress(), etc., but not inventory or world data.
@Subscribe with PostOrder is the standard. EventTask (returning EventTask from subscribe methods for async handling) is supported.DisconnectEvent.LoginStatus distinguishes SUCCESSFUL_LOGIN, CONFLICTING_LOGIN, PRE_SERVER_JOIN disconnect scenarios.development
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag