Hytale Player Input Skill
Use this skill when working with player input handling in Hytale plugins. This covers how the client communicates input to the server via packets, how to intercept and filter those packets, the interaction types you will commonly use in plugins, a practical client-to-server packet reference, and custom camera controls.
Sources: https://hytalemodding.dev/en/docs/guides/plugin/listening-to-packets, https://hytalemodding.dev/en/docs/guides/plugin/player-input-guide, https://hytalemodding.dev/en/docs/server/client-inputs-reference, https://hytalemodding.dev/en/docs/server/interaction-reference
Related skills: For hotbar-specific slot customization (ability slots), see hytale-hotbar-actions. For game events (PlayerReady, chat, damage, etc.), see hytale-events. For UI-based input, see hytale-ui-modding.
Quick Reference
| Task |
Approach |
| Listen to all inbound packets |
PacketAdapters.registerInbound((PacketWatcher) ...) |
| Listen to player-specific inbound packets |
PacketAdapters.registerInbound((PlayerPacketWatcher) ...) |
| Block/cancel inbound packets |
PacketAdapters.registerInbound((PlayerPacketFilter) ...) — return true to cancel |
| Listen to outbound packets |
PacketAdapters.registerOutbound((PacketWatcher) ...) |
| Detect player interactions (left/right click, F key) |
Intercept SyncInteractionChains (packet ID 290) |
| Browse the canonical interaction list |
See server/interaction-reference |
| Detect mouse input |
Intercept MouseInteraction (packet ID 111) |
| Detect player movement |
Intercept ClientMovement (packet ID 108) |
| Customize camera |
Send SetServerCamera packet with ServerCameraSettings |
| Reset camera to default |
Send SetServerCamera(ClientCameraView.Custom, false, null) |
| Deregister a listener |
PacketAdapters.deregisterInbound(filter) or deregisterOutbound(watcher) |
Part 1: How Player Input Works
Hytale servers do not receive raw keyboard input. The client interprets keypresses and sends packets describing what action the player wants to perform. To create custom input behavior, you intercept these packets server-side.
Key concepts:
- Inbound packets = Client → Server (player actions)
- Outbound packets = Server → Client (state updates, camera, etc.)
- Packets are defined in
com.hypixel.hytale.protocol and organized by category in com.hypixel.hytale.protocol.packets
- Base class is
Packet; the low-level Netty handler is PlayerChannelHandler which delegates to PacketAdapters
Part 2: PacketAdapters System
The PacketAdapters class provides the injection point for packet interception. You do not need to hook into Netty manually.
Registration Methods
| Method |
Interface Type |
Can Block |
Player-Specific |
registerInbound(PacketWatcher) |
PacketWatcher |
No |
No |
registerInbound(PacketFilter) |
PacketFilter |
Yes |
No |
registerInbound(PlayerPacketWatcher) |
PlayerPacketWatcher |
No |
Yes |
registerInbound(PlayerPacketFilter) |
PlayerPacketFilter |
Yes |
Yes |
registerOutbound(PacketWatcher) |
PacketWatcher |
No |
No |
registerOutbound(PacketFilter) |
PacketFilter |
Yes |
No |
Interfaces
// Read-only observer — cannot block packets
public interface PacketWatcher {
void accept(PacketHandler packetHandler, Packet packet);
}
// Can block packets — return true to cancel, false to allow
public interface PacketFilter {
boolean test(PacketHandler packetHandler, Packet packet);
}
// Player-specific read-only observer
public interface PlayerPacketWatcher {
void accept(@Nonnull PlayerRef playerRef, @Nonnull Packet packet);
}
// Player-specific filter — return true to cancel, false to allow
public interface PlayerPacketFilter {
boolean test(@Nonnull PlayerRef playerRef, @Nonnull Packet packet);
}
Imports
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.server.core.io.adapter.PacketAdapters;
import com.hypixel.hytale.server.core.io.adapter.PacketFilter;
import com.hypixel.hytale.server.core.io.adapter.PacketWatcher;
import com.hypixel.hytale.server.core.io.adapter.PlayerPacketFilter;
import com.hypixel.hytale.server.core.io.adapter.PlayerPacketWatcher;
import com.hypixel.hytale.server.core.io.adapter.PacketHandler;
import com.hypixel.hytale.server.core.io.adapter.GamePacketHandler;
import com.hypixel.hytale.server.core.universe.PlayerRef;
Part 3: Intercepting Interactions (SyncInteractionChains)
When a player performs interactions (left click, right click, F key, etc.), the client sends a SyncInteractionChains packet (ID 290) containing SyncInteractionChain objects.
SyncInteractionChain Fields
| Field |
Description |
interactionType |
The InteractionType enum value |
activeHotbarSlot |
The slot the player is currently on |
data.targetSlot |
The slot the player wants to switch to (for swap types) |
initial |
Whether this is the start of a new interaction chain |
Example: Listening for Use Interaction (F Key)
public class PacketListener implements PacketWatcher {
@Override
public void accept(PacketHandler packetHandler, Packet packet) {
if (packet.getId() != 290) {
return;
}
SyncInteractionChains interactionChains = (SyncInteractionChains) packet;
SyncInteractionChain[] updates = interactionChains.updates;
for (SyncInteractionChain item : updates) {
PlayerAuthentication playerAuthentication = packetHandler.getAuth();
String uuid = playerAuthentication.getUuid().toString();
InteractionType interactionType = item.interactionType;
if (interactionType == InteractionType.Use) {
// Handle "F" key interaction
}
}
}
}
Example: Filtering Interactions (Cancel Specific Actions)
public class InteractionFilter implements PlayerPacketFilter {
@Override
public boolean test(@Nonnull PlayerRef playerRef, @Nonnull Packet packet) {
if (!(packet instanceof SyncInteractionChains syncPacket)) {
return false;
}
for (SyncInteractionChain chain : syncPacket.updates) {
if (chain.interactionType == InteractionType.Primary) {
// Block left-click interactions
return true;
}
}
return false; // Allow all other packets
}
}
Interaction Imports
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain;
import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChains;
import com.hypixel.hytale.server.core.auth.PlayerAuthentication;
Part 4: InteractionType Reference
Use the official server/interaction-reference page as the canonical source for the full enum and any additions in newer server drops. The table below is the practical set commonly referenced in plugin code:
| Name |
Ordinal |
Description |
Primary |
0 |
Left click |
Secondary |
1 |
Right click |
Ability1 |
2 |
Ability slot 1 |
Ability2 |
3 |
Ability slot 2 |
Ability3 |
4 |
Ability slot 3 |
Use |
5 |
Use key (F) |
Pick |
6 |
Pick action |
Pickup |
7 |
Pickup action |
CollisionEnter |
8 |
Entity collision start |
CollisionLeave |
9 |
Entity collision end |
Collision |
10 |
Ongoing collision |
EntityStatEffect |
11 |
Stat effect applied |
SwapTo |
12 |
Switching to a slot |
SwapFrom |
13 |
Switching from a slot |
Death |
14 |
Entity death |
Wielding |
15 |
Wielding an item |
ProjectileSpawn |
16 |
Projectile created |
ProjectileHit |
17 |
Projectile hits target |
ProjectileMiss |
18 |
Projectile misses |
ProjectileBounce |
19 |
Projectile bounces |
Held |
20 |
Item held in main hand |
HeldOffhand |
21 |
Item held in offhand |
Equipped |
22 |
Item equipped |
Dodge |
23 |
Dodge action |
GameModeSwap |
24 |
Game mode changed |
Common input triggers: Primary (left click), Secondary (right click), Use (F key). For hotbar slot-based ability triggers, see the hytale-hotbar-actions skill.
Part 5: Modifying & Observing Packets
Observing Outbound Packets (Server → Client)
PacketAdapters.registerOutbound((PacketHandler handler, Packet packet) -> {
var handlerName = handler.getClass().getSimpleName();
var packetName = packet.getClass().getSimpleName();
// Exclude noisy packets
if (!"EntityUpdates".equals(packetName) && !"CachedPacket".equals(packetName)) {
logger.at(Level.INFO)
.log("[" + handlerName + "] Sent packet id=" + packet.getId() + ": " + packetName);
}
});
Modifying Inbound Packets
PacketAdapters.registerInbound((PacketHandler handler, Packet packet) -> {
if (packet instanceof PlayerOptions skinPacket) {
skinPacket.skin = null; // Remove skin data
}
});
Blocking Player Packets (PlayerPacketFilter)
PacketAdapters.registerInbound((PlayerPacketFilter) (player, packet) -> {
if (packet instanceof ClientMovement movementPacket) {
// Block movement — return true to cancel
return true;
}
return false;
});
Warning: While you can cancel packets, client-side prediction still occurs. The player's client will still show movement locally. Preventing specific player actions requires additional work beyond just cancelling packets.
Packet Tracker Utility
Track all packets sent to/from players for debugging:
public class PlayerPacketTracker {
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
private static class PlayerStats {
final Map<String, AtomicInteger> sent = new ConcurrentHashMap<>();
final Map<String, AtomicInteger> received = new ConcurrentHashMap<>();
}
private static final Map<String, PlayerStats> stats = new ConcurrentHashMap<>();
private static String getPlayerName(PacketHandler handler) {
if (handler instanceof GamePacketHandler gpHandler) {
return gpHandler.getPlayerRef().getUsername();
}
return null;
}
public static void registerPacketCounters() {
PacketAdapters.registerInbound((PacketHandler handler, Packet packet) -> {
String playerName = getPlayerName(handler);
if (playerName != null) {
stats.computeIfAbsent(playerName, k -> new PlayerStats())
.received.computeIfAbsent(packet.getClass().getSimpleName(),
k -> new AtomicInteger(0))
.incrementAndGet();
}
});
PacketAdapters.registerOutbound((PacketHandler handler, Packet packet) -> {
String playerName = getPlayerName(handler);
if (playerName != null) {
stats.computeIfAbsent(playerName, k -> new PlayerStats())
.sent.computeIfAbsent(packet.getClass().getSimpleName(),
k -> new AtomicInteger(0))
.incrementAndGet();
}
});
// Log every 3 seconds
HytaleServer.SCHEDULED_EXECUTOR.scheduleAtFixedRate(() -> {
if (stats.isEmpty()) return;
for (Map.Entry<String, PlayerStats> entry : stats.entrySet()) {
String player = entry.getKey();
PlayerStats pStats = entry.getValue();
StringBuilder sb = new StringBuilder();
List<String> sentLogs = new ArrayList<>();
pStats.sent.forEach((type, atomic) -> {
int count = atomic.getAndSet(0);
if (count > 0) sentLogs.add(type + " x" + count);
});
if (!sentLogs.isEmpty()) {
sb.append("Sent ").append(String.join(", ", sentLogs));
}
List<String> recvLogs = new ArrayList<>();
pStats.received.forEach((type, atomic) -> {
int count = atomic.getAndSet(0);
if (count > 0) recvLogs.add(type + " x" + count);
});
if (!recvLogs.isEmpty()) {
if (!sb.isEmpty()) sb.append("\n");
sb.append("Received ").append(String.join(", ", recvLogs));
}
if (!sb.isEmpty()) {
LOGGER.atInfo().log("To " + player + ":\n" + sb);
}
}
}, 3, 3, TimeUnit.SECONDS);
}
}
Call PlayerPacketTracker.registerPacketCounters() in your plugin's setup() method.
Part 6: Plugin Registration & Cleanup
Always store references to registered filters/watchers and deregister them on shutdown:
public class MyPlugin extends HytaleServerPlugin {
private PacketFilter inboundFilter;
@Override
protected void setup() {
inboundFilter = PacketAdapters.registerInbound(
(PlayerPacketFilter) (player, packet) -> {
// Your filter logic
return false;
}
);
}
@Override
protected void shutdown() {
if (inboundFilter != null) {
PacketAdapters.deregisterInbound(inboundFilter);
}
}
}
Part 7: Client-to-Server Packet Reference
This is a plugin-focused reference for the packets most relevant to input handling. For exhaustive protocol details, verify against the current packet registry in the decompiled server source.
Packets are found in: com.hypixel.hytale.protocol.packets
Player Packets
| Packet |
ID |
Key Fields |
SetClientId |
100 |
clientId |
SetGameMode |
101 |
gameMode |
SetMovementStates |
102 |
movementStates |
SetBlockPlacementOverride |
103 |
enabled |
JoinWorld |
104 |
clearWorld, fadeInOut, worldUuid |
ClientReady |
105 |
readyForChunks, readyForGameplay |
LoadHotbar |
106 |
inventoryRow |
SaveHotbar |
107 |
inventoryRow |
ClientMovement |
108 |
movementStates, relativePosition, absolutePosition, bodyOrientation, lookOrientation, teleportAck, wishMovement, velocity, mountedTo, riderMovementStates |
ClientTeleport |
109 |
teleportId, modelTransform, resetVelocity |
UpdateMovementSettings |
110 |
movementSettings |
MouseInteraction |
111 |
clientTimestamp, activeSlot, itemInHandId, screenPoint, mouseButton, mouseMotion, worldInteraction |
DamageInfo |
112 |
damageSourcePosition, damageAmount, damageCause |
ReticleEvent |
113 |
eventIndex |
DisplayDebug |
114 |
shape, matrix, color, time, fade, frustumProjection |
ClearDebugShapes |
115 |
(none) |
SyncPlayerPreferences |
116 |
showEntityMarkers, armorItemsPreferredPickupLocation, weaponAndToolItemsPreferredPickupLocation, usableItemsItemsPreferredPickupLocation, solidBlockItemsPreferredPickupLocation, miscItemsPreferredPickupLocation, allowNPCDetection, respondToHit |
ClientPlaceBlock |
117 |
position, rotation, placedBlockId |
UpdateMemoriesFeatureStatus |
118 |
isFeatureUnlocked |
RemoveMapMarker |
119 |
markerId |
Inventory Packets
| Packet |
ID |
Key Fields |
UpdatePlayerInventory |
170 |
storage, armor, hotbar, utility, builderMaterial, tools, backpack, sortType |
SetCreativeItem |
171 |
inventorySectionId, slotId, item, override |
DropCreativeItem |
172 |
item |
SmartGiveCreativeItem |
173 |
item, moveType |
DropItemStack |
174 |
inventorySectionId, slotId, quantity |
MoveItemStack |
175 |
fromSectionId, fromSlotId, quantity, toSectionId, toSlotId |
SmartMoveItemStack |
176 |
fromSectionId, fromSlotId, quantity, moveType |
SetActiveSlot |
177 |
inventorySectionId, activeSlot |
SwitchHotbarBlockSet |
178 |
itemId |
InventoryAction |
179 |
inventorySectionId, inventoryActionType, actionData |
Window Packets
| Packet |
ID |
Key Fields |
OpenWindow |
200 |
id, windowType, windowData, inventory, extraResources |
UpdateWindow |
201 |
id, windowData, inventory, extraResources |
CloseWindow |
202 |
id |
SendWindowAction |
203 |
id, action |
ClientOpenWindow |
204 |
type |
Other Client Packets
| Packet |
ID |
Key Fields |
ClientReferral |
18 |
hostTo, data |
SetUpdateRate |
29 |
updatesPerSecond |
SetTimeDilation |
30 |
timeDilation |
SetChunk |
131 |
x, y, z, localLight, globalLight, data |
SetChunkHeightmap |
132 |
x, z, heightmap |
SetChunkTintmap |
133 |
x, z, tintmap |
SetChunkEnvironments |
134 |
x, z, environments |
SetFluids |
136 |
x, y, z, data |
SetPaused |
158 |
paused |
SetEntitySeed |
160 |
entitySeed |
SetPage |
216 |
page, canCloseThroughInteraction |
SetServerAccess |
252 |
access, password |
SetMachinimaActorModel |
261 |
model, sceneName, actorName |
SetServerCamera |
280 |
clientCameraView, isLocked, cameraSettings |
SetFlyCameraMode |
283 |
entering |
SyncInteractionChains |
290 |
updates |
Packet Handlers (Server-Side)
Packet handlers determine which packets are accepted at each phase of the connection lifecycle:
| Handler |
Packets Accepted |
| InitialPacketHandler |
Connect (0), Disconnect (1) |
| HandshakeHandler |
Disconnect (1), AuthToken (12) |
| PasswordPacketHandler |
Disconnect (1), PasswordResponse (15) |
| SetupPacketHandler |
Disconnect (1), RequestAssets (23), ViewRadius (32), PlayerOptions (33) |
| GamePacketHandler |
Disconnect (1), Pong (3), ClientMovement (108), ChatMessage (211), RequestAssets (23), CustomPageEvent (219), ViewRadius (32), UpdateLanguage (232), MouseInteraction (111), SendWindowAction (203), CloseWindow (202), ClientReady (105), SyncInteractionChains (290), SetPaused (158), and more |
GamePacketHandler sub-handlers:
| Sub-Handler |
Packets |
| InventoryPacketHandler |
SetCreativeItem (171), DropCreativeItem (172), SmartGiveCreativeItem (173), DropItemStack (174), MoveItemStack (175), SmartMoveItemStack (176), SetActiveSlot (177), SwitchHotbarBlockSet (178), InventoryAction (179) |
| BuilderToolsPacketHandler |
LoadHotbar (106), SaveHotbar (107), BuilderToolArgUpdate (400), BuilderToolEntityAction (401), and more |
| MountGamePacketHandler |
DismountNPC (294) |
If a packet arrives during the wrong connection phase, the handler disconnects the sender.
Part 8: Custom Camera Controls
Camera is controlled by sending a SetServerCamera packet with ServerCameraSettings to the player.
Camera Imports
import com.hypixel.hytale.protocol.ClientCameraView;
import com.hypixel.hytale.protocol.Direction;
import com.hypixel.hytale.protocol.MouseInputType;
import com.hypixel.hytale.protocol.MovementForceRotationType;
import com.hypixel.hytale.protocol.PositionDistanceOffsetType;
import com.hypixel.hytale.protocol.RotationType;
import com.hypixel.hytale.protocol.ServerCameraSettings;
import com.hypixel.hytale.protocol.Vector3f;
import com.hypixel.hytale.protocol.packets.camera.SetServerCamera;
Basic Camera Setup
ServerCameraSettings settings = new ServerCameraSettings();
settings.distance = 10.0f; // Zoom distance from player
settings.isFirstPerson = false; // Third-person mode
settings.positionLerpSpeed = 0.2f; // Smooth camera follow
playerRef.getPacketHandler().writeNoCache(
new SetServerCamera(ClientCameraView.Custom, true, settings)
);
Reset Camera to Default
playerRef.getPacketHandler().writeNoCache(
new SetServerCamera(ClientCameraView.Custom, false, null)
);
Camera Presets
Top-Down (RTS/ARPG Style)
Source: com.hypixel.hytale.server.core.command.commands.player.camera.PlayerCameraTopdownCommand
ServerCameraSettings settings = new ServerCameraSettings();
settings.positionLerpSpeed = 0.2f;
settings.rotationLerpSpeed = 0.2f;
settings.distance = 20.0f;
settings.displayCursor = true;
settings.isFirstPerson = false;
settings.movementForceRotationType = MovementForceRotationType.Custom;
// Align movement with camera yaw (horizontal rotation only)
settings.movementForceRotation = new Direction(-0.7853981634f, 0.0f, 0.0f); // 45° right
settings.eyeOffset = true;
settings.positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset;
settings.rotationType = RotationType.Custom;
settings.rotation = new Direction(0.0f, -1.5707964f, 0.0f); // Look straight down
settings.mouseInputType = MouseInputType.LookAtPlane;
settings.planeNormal = new Vector3f(0.0f, 1.0f, 0.0f); // Ground plane
playerRef.getPacketHandler().writeNoCache(
new SetServerCamera(ClientCameraView.Custom, true, settings)
);
Side-Scroller (2D Platformer Style)
Source: com.hypixel.hytale.server.core.command.commands.player.camera.PlayerCameraSideScrollerCommand
ServerCameraSettings settings = new ServerCameraSettings();
settings.positionLerpSpeed = 0.2f;
settings.rotationLerpSpeed = 0.2f;
settings.distance = 15.0f;
settings.displayCursor = true;
settings.isFirstPerson = false;
settings.movementForceRotationType = MovementForceRotationType.Custom;
settings.movementMultiplier = new Vector3f(1.0f, 1.0f, 0.0f); // Lock Z-axis
settings.eyeOffset = true;
settings.positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset;
settings.rotationType = RotationType.Custom;
settings.mouseInputType = MouseInputType.LookAtPlane;
settings.planeNormal = new Vector3f(0.0f, 0.0f, 1.0f); // Side plane
playerRef.getPacketHandler().writeNoCache(
new SetServerCamera(ClientCameraView.Custom, true, settings)
);
Isometric (Diablo Style)
ServerCameraSettings settings = new ServerCameraSettings();
settings.positionLerpSpeed = 0.2f;
settings.rotationLerpSpeed = 0.2f;
settings.isFirstPerson = false;
settings.distance = 6f;
settings.allowPitchControls = false;
settings.displayCursor = true;
// Force the camera's rotation to be set by the server
settings.applyLookType = ApplyLookType.Rotation;
settings.rotationType = RotationType.Custom;
// Set the typical isometric rotation
Direction direction = new Direction(
(float) Math.toRadians(45f), // yaw
(float) Math.toRadians(-35f), // pitch
0f // roll
);
settings.rotation = direction;
settings.movementForceRotation = direction;
playerRef.getPacketHandler().writeNoCache(
new SetServerCamera(ClientCameraView.Custom, true, settings)
);
ServerCameraSettings Reference
Position & Rotation
| Setting |
Description |
positionLerpSpeed (0.0-1.0) |
How smoothly camera follows player. Lower = smoother but slower |
rotationLerpSpeed (0.0-1.0) |
How smoothly camera rotates. Lower = smoother but slower |
distance |
Camera distance from player. Higher = zoomed out |
rotation |
Camera angle as Direction(yaw, pitch, roll) in radians |
rotationType |
How rotation is calculated. RotationType.Custom uses your rotation value |
Movement Alignment
| Setting |
Description |
movementForceRotationType |
AttachedToHead = follows player look; Custom = use movementForceRotation |
movementForceRotation |
Direction for W/S movement when using Custom. Match yaw with camera, keep pitch at 0 |
movementMultiplier |
Scale movement per axis. (1,1,0) = lock Z-axis for 2D |
Input & Display
| Setting |
Description |
displayCursor |
Show/hide mouse cursor |
mouseInputType |
LookAtPlane = cursor on plane (top-down); LookAtTarget = rotates camera |
planeNormal |
For LookAtPlane, defines the plane. (0,1,0) = ground, (0,0,1) = side |
Advanced
| Setting |
Description |
positionDistanceOffsetType |
DistanceOffset = simple; DistanceOffsetRaycast = prevents wall clipping |
eyeOffset |
Offset camera from player's eye position |
isFirstPerson |
First-person vs third-person mode |
allowPitchControls |
Allow player to control pitch |
isLocked (packet parameter) |
Set true in SetServerCamera to prevent player camera changes |
Camera Tips
- Zoom: Adjust
distance (higher = further out)
- Smoothness:
positionLerpSpeed and rotationLerpSpeed control camera response speed
- Wall clipping: Use
PositionDistanceOffsetType.DistanceOffsetRaycast
- Lock camera: Set
isLocked = true in the SetServerCamera packet
- 2D movement: Set
movementMultiplier to zero out an axis
- Isometric cameras: Always set
movementForceRotation to match camera yaw
- Angle math: Use
Math.toRadians(degrees) to convert degrees to radians
Key Warnings
- Client-side prediction: Cancelling packets does not prevent client-side visual effects. The player will still see movement/actions locally even if the server blocks the packet.
- Thread safety: When accessing ECS components from packet handlers, schedule work on the world thread via
world.execute(() -> { ... }).
- Packet IDs may change: Always use
instanceof checks or class references rather than hardcoded packet IDs when possible. The ID-based approach (packet.getId() != 290) is brittle across server versions.
- Deregister on shutdown: Always store filter/watcher references and deregister them in your plugin's
shutdown() method.
1---2name: hytale-player-input3description: Documents Hytale's player input system including packet interception (PacketAdapters, PacketWatcher, PacketFilter), SyncInteractionChains, InteractionTypes, client-to-server packet reference, and custom camera controls. Use when handling player input, intercepting packets, creating custom interactions, modifying camera behavior, or working with mouse/keyboard input. Triggers - player input, packet, PacketAdapters, PacketWatcher, PacketFilter, PlayerPacketWatcher, PlayerPacketFilter, SyncInteractionChains, InteractionType, MouseInteraction, ClientMovement, camera, SetServerCamera, ServerCameraSettings, camera controls, top-down, isometric, side-scroller, inbound packet, outbound packet, packet listener, input handling.4---56# Hytale Player Input Skill78Use this skill when working with player input handling in Hytale plugins. This covers how the client communicates input to the server via packets, how to intercept and filter those packets, the interaction types you will commonly use in plugins, a practical client-to-server packet reference, and custom camera controls.910> **Sources:** <https://hytalemodding.dev/en/docs/guides/plugin/listening-to-packets>, <https://hytalemodding.dev/en/docs/guides/plugin/player-input-guide>, <https://hytalemodding.dev/en/docs/server/client-inputs-reference>, <https://hytalemodding.dev/en/docs/server/interaction-reference>1112> **Related skills:** For hotbar-specific slot customization (ability slots), see `hytale-hotbar-actions`. For game events (PlayerReady, chat, damage, etc.), see `hytale-events`. For UI-based input, see `hytale-ui-modding`.1314---1516## Quick Reference1718| Task | Approach |19|------|----------|20| Listen to all inbound packets | `PacketAdapters.registerInbound((PacketWatcher) ...)` |21| Listen to player-specific inbound packets | `PacketAdapters.registerInbound((PlayerPacketWatcher) ...)` |22| Block/cancel inbound packets | `PacketAdapters.registerInbound((PlayerPacketFilter) ...)` — return `true` to cancel |23| Listen to outbound packets | `PacketAdapters.registerOutbound((PacketWatcher) ...)` |24| Detect player interactions (left/right click, F key) | Intercept `SyncInteractionChains` (packet ID 290) |25| Browse the canonical interaction list | See `server/interaction-reference` |26| Detect mouse input | Intercept `MouseInteraction` (packet ID 111) |27| Detect player movement | Intercept `ClientMovement` (packet ID 108) |28| Customize camera | Send `SetServerCamera` packet with `ServerCameraSettings` |29| Reset camera to default | Send `SetServerCamera(ClientCameraView.Custom, false, null)` |30| Deregister a listener | `PacketAdapters.deregisterInbound(filter)` or `deregisterOutbound(watcher)` |3132---3334## Part 1: How Player Input Works3536Hytale servers **do not receive raw keyboard input**. The client interprets keypresses and sends **packets** describing what action the player wants to perform. To create custom input behavior, you intercept these packets server-side.3738Key concepts:39- **Inbound packets** = Client → Server (player actions)40- **Outbound packets** = Server → Client (state updates, camera, etc.)41- Packets are defined in `com.hypixel.hytale.protocol` and organized by category in `com.hypixel.hytale.protocol.packets`42- Base class is `Packet`; the low-level Netty handler is `PlayerChannelHandler` which delegates to `PacketAdapters`4344---4546## Part 2: PacketAdapters System4748The `PacketAdapters` class provides the injection point for packet interception. You do **not** need to hook into Netty manually.4950### Registration Methods5152| Method | Interface Type | Can Block | Player-Specific |53|--------|---------------|-----------|-----------------|54| `registerInbound(PacketWatcher)` | `PacketWatcher` | No | No |55| `registerInbound(PacketFilter)` | `PacketFilter` | Yes | No |56| `registerInbound(PlayerPacketWatcher)` | `PlayerPacketWatcher` | No | Yes |57| `registerInbound(PlayerPacketFilter)` | `PlayerPacketFilter` | Yes | Yes |58| `registerOutbound(PacketWatcher)` | `PacketWatcher` | No | No |59| `registerOutbound(PacketFilter)` | `PacketFilter` | Yes | No |6061### Interfaces6263```java64// Read-only observer — cannot block packets65public interface PacketWatcher {66 void accept(PacketHandler packetHandler, Packet packet);67}6869// Can block packets — return true to cancel, false to allow70public interface PacketFilter {71 boolean test(PacketHandler packetHandler, Packet packet);72}7374// Player-specific read-only observer75public interface PlayerPacketWatcher {76 void accept(@Nonnull PlayerRef playerRef, @Nonnull Packet packet);77}7879// Player-specific filter — return true to cancel, false to allow80public interface PlayerPacketFilter {81 boolean test(@Nonnull PlayerRef playerRef, @Nonnull Packet packet);82}83```8485### Imports8687```java88import com.hypixel.hytale.protocol.Packet;89import com.hypixel.hytale.server.core.io.adapter.PacketAdapters;90import com.hypixel.hytale.server.core.io.adapter.PacketFilter;91import com.hypixel.hytale.server.core.io.adapter.PacketWatcher;92import com.hypixel.hytale.server.core.io.adapter.PlayerPacketFilter;93import com.hypixel.hytale.server.core.io.adapter.PlayerPacketWatcher;94import com.hypixel.hytale.server.core.io.adapter.PacketHandler;95import com.hypixel.hytale.server.core.io.adapter.GamePacketHandler;96import com.hypixel.hytale.server.core.universe.PlayerRef;97```9899---100101## Part 3: Intercepting Interactions (SyncInteractionChains)102103When a player performs interactions (left click, right click, F key, etc.), the client sends a `SyncInteractionChains` packet (ID 290) containing `SyncInteractionChain` objects.104105### SyncInteractionChain Fields106107| Field | Description |108|-------|-------------|109| `interactionType` | The `InteractionType` enum value |110| `activeHotbarSlot` | The slot the player is currently on |111| `data.targetSlot` | The slot the player wants to switch to (for swap types) |112| `initial` | Whether this is the start of a new interaction chain |113114### Example: Listening for Use Interaction (F Key)115116```java117public class PacketListener implements PacketWatcher {118 @Override119 public void accept(PacketHandler packetHandler, Packet packet) {120 if (packet.getId() != 290) {121 return;122 }123 SyncInteractionChains interactionChains = (SyncInteractionChains) packet;124 SyncInteractionChain[] updates = interactionChains.updates;125126 for (SyncInteractionChain item : updates) {127 PlayerAuthentication playerAuthentication = packetHandler.getAuth();128 String uuid = playerAuthentication.getUuid().toString();129 InteractionType interactionType = item.interactionType;130 if (interactionType == InteractionType.Use) {131 // Handle "F" key interaction132 }133 }134 }135}136```137138### Example: Filtering Interactions (Cancel Specific Actions)139140```java141public class InteractionFilter implements PlayerPacketFilter {142 @Override143 public boolean test(@Nonnull PlayerRef playerRef, @Nonnull Packet packet) {144 if (!(packet instanceof SyncInteractionChains syncPacket)) {145 return false;146 }147148 for (SyncInteractionChain chain : syncPacket.updates) {149 if (chain.interactionType == InteractionType.Primary) {150 // Block left-click interactions151 return true;152 }153 }154155 return false; // Allow all other packets156 }157}158```159160### Interaction Imports161162```java163import com.hypixel.hytale.protocol.InteractionType;164import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChain;165import com.hypixel.hytale.protocol.packets.interaction.SyncInteractionChains;166import com.hypixel.hytale.server.core.auth.PlayerAuthentication;167```168169---170171## Part 4: InteractionType Reference172173Use the official `server/interaction-reference` page as the canonical source for the full enum and any additions in newer server drops. The table below is the practical set commonly referenced in plugin code:174175| Name | Ordinal | Description |176|------|---------|-------------|177| `Primary` | 0 | Left click |178| `Secondary` | 1 | Right click |179| `Ability1` | 2 | Ability slot 1 |180| `Ability2` | 3 | Ability slot 2 |181| `Ability3` | 4 | Ability slot 3 |182| `Use` | 5 | Use key (F) |183| `Pick` | 6 | Pick action |184| `Pickup` | 7 | Pickup action |185| `CollisionEnter` | 8 | Entity collision start |186| `CollisionLeave` | 9 | Entity collision end |187| `Collision` | 10 | Ongoing collision |188| `EntityStatEffect` | 11 | Stat effect applied |189| `SwapTo` | 12 | Switching to a slot |190| `SwapFrom` | 13 | Switching from a slot |191| `Death` | 14 | Entity death |192| `Wielding` | 15 | Wielding an item |193| `ProjectileSpawn` | 16 | Projectile created |194| `ProjectileHit` | 17 | Projectile hits target |195| `ProjectileMiss` | 18 | Projectile misses |196| `ProjectileBounce` | 19 | Projectile bounces |197| `Held` | 20 | Item held in main hand |198| `HeldOffhand` | 21 | Item held in offhand |199| `Equipped` | 22 | Item equipped |200| `Dodge` | 23 | Dodge action |201| `GameModeSwap` | 24 | Game mode changed |202203> **Common input triggers:** `Primary` (left click), `Secondary` (right click), `Use` (F key). For hotbar slot-based ability triggers, see the `hytale-hotbar-actions` skill.204205---206207## Part 5: Modifying & Observing Packets208209### Observing Outbound Packets (Server → Client)210211```java212PacketAdapters.registerOutbound((PacketHandler handler, Packet packet) -> {213 var handlerName = handler.getClass().getSimpleName();214 var packetName = packet.getClass().getSimpleName();215 // Exclude noisy packets216 if (!"EntityUpdates".equals(packetName) && !"CachedPacket".equals(packetName)) {217 logger.at(Level.INFO)218 .log("[" + handlerName + "] Sent packet id=" + packet.getId() + ": " + packetName);219 }220});221```222223### Modifying Inbound Packets224225```java226PacketAdapters.registerInbound((PacketHandler handler, Packet packet) -> {227 if (packet instanceof PlayerOptions skinPacket) {228 skinPacket.skin = null; // Remove skin data229 }230});231```232233### Blocking Player Packets (PlayerPacketFilter)234235```java236PacketAdapters.registerInbound((PlayerPacketFilter) (player, packet) -> {237 if (packet instanceof ClientMovement movementPacket) {238 // Block movement — return true to cancel239 return true;240 }241 return false;242});243```244245> **Warning:** While you can cancel packets, client-side prediction still occurs. The player's client will still show movement locally. Preventing specific player actions requires additional work beyond just cancelling packets.246247### Packet Tracker Utility248249Track all packets sent to/from players for debugging:250251```java252public class PlayerPacketTracker {253 private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();254255 private static class PlayerStats {256 final Map<String, AtomicInteger> sent = new ConcurrentHashMap<>();257 final Map<String, AtomicInteger> received = new ConcurrentHashMap<>();258 }259260 private static final Map<String, PlayerStats> stats = new ConcurrentHashMap<>();261262 private static String getPlayerName(PacketHandler handler) {263 if (handler instanceof GamePacketHandler gpHandler) {264 return gpHandler.getPlayerRef().getUsername();265 }266 return null;267 }268269 public static void registerPacketCounters() {270 PacketAdapters.registerInbound((PacketHandler handler, Packet packet) -> {271 String playerName = getPlayerName(handler);272 if (playerName != null) {273 stats.computeIfAbsent(playerName, k -> new PlayerStats())274 .received.computeIfAbsent(packet.getClass().getSimpleName(),275 k -> new AtomicInteger(0))276 .incrementAndGet();277 }278 });279280 PacketAdapters.registerOutbound((PacketHandler handler, Packet packet) -> {281 String playerName = getPlayerName(handler);282 if (playerName != null) {283 stats.computeIfAbsent(playerName, k -> new PlayerStats())284 .sent.computeIfAbsent(packet.getClass().getSimpleName(),285 k -> new AtomicInteger(0))286 .incrementAndGet();287 }288 });289290 // Log every 3 seconds291 HytaleServer.SCHEDULED_EXECUTOR.scheduleAtFixedRate(() -> {292 if (stats.isEmpty()) return;293 for (Map.Entry<String, PlayerStats> entry : stats.entrySet()) {294 String player = entry.getKey();295 PlayerStats pStats = entry.getValue();296 StringBuilder sb = new StringBuilder();297298 List<String> sentLogs = new ArrayList<>();299 pStats.sent.forEach((type, atomic) -> {300 int count = atomic.getAndSet(0);301 if (count > 0) sentLogs.add(type + " x" + count);302 });303 if (!sentLogs.isEmpty()) {304 sb.append("Sent ").append(String.join(", ", sentLogs));305 }306307 List<String> recvLogs = new ArrayList<>();308 pStats.received.forEach((type, atomic) -> {309 int count = atomic.getAndSet(0);310 if (count > 0) recvLogs.add(type + " x" + count);311 });312 if (!recvLogs.isEmpty()) {313 if (!sb.isEmpty()) sb.append("\n");314 sb.append("Received ").append(String.join(", ", recvLogs));315 }316317 if (!sb.isEmpty()) {318 LOGGER.atInfo().log("To " + player + ":\n" + sb);319 }320 }321 }, 3, 3, TimeUnit.SECONDS);322 }323}324```325326Call `PlayerPacketTracker.registerPacketCounters()` in your plugin's `setup()` method.327328---329330## Part 6: Plugin Registration & Cleanup331332Always store references to registered filters/watchers and deregister them on shutdown:333334```java335public class MyPlugin extends HytaleServerPlugin {336 private PacketFilter inboundFilter;337338 @Override339 protected void setup() {340 inboundFilter = PacketAdapters.registerInbound(341 (PlayerPacketFilter) (player, packet) -> {342 // Your filter logic343 return false;344 }345 );346 }347348 @Override349 protected void shutdown() {350 if (inboundFilter != null) {351 PacketAdapters.deregisterInbound(inboundFilter);352 }353 }354}355```356357---358359## Part 7: Client-to-Server Packet Reference360361This is a plugin-focused reference for the packets most relevant to input handling. For exhaustive protocol details, verify against the current packet registry in the decompiled server source.362363Packets are found in: `com.hypixel.hytale.protocol.packets`364365### Player Packets366367| Packet | ID | Key Fields |368|--------|----|------------|369| `SetClientId` | 100 | `clientId` |370| `SetGameMode` | 101 | `gameMode` |371| `SetMovementStates` | 102 | `movementStates` |372| `SetBlockPlacementOverride` | 103 | `enabled` |373| `JoinWorld` | 104 | `clearWorld`, `fadeInOut`, `worldUuid` |374| `ClientReady` | 105 | `readyForChunks`, `readyForGameplay` |375| `LoadHotbar` | 106 | `inventoryRow` |376| `SaveHotbar` | 107 | `inventoryRow` |377| `ClientMovement` | 108 | `movementStates`, `relativePosition`, `absolutePosition`, `bodyOrientation`, `lookOrientation`, `teleportAck`, `wishMovement`, `velocity`, `mountedTo`, `riderMovementStates` |378| `ClientTeleport` | 109 | `teleportId`, `modelTransform`, `resetVelocity` |379| `UpdateMovementSettings` | 110 | `movementSettings` |380| `MouseInteraction` | 111 | `clientTimestamp`, `activeSlot`, `itemInHandId`, `screenPoint`, `mouseButton`, `mouseMotion`, `worldInteraction` |381| `DamageInfo` | 112 | `damageSourcePosition`, `damageAmount`, `damageCause` |382| `ReticleEvent` | 113 | `eventIndex` |383| `DisplayDebug` | 114 | `shape`, `matrix`, `color`, `time`, `fade`, `frustumProjection` |384| `ClearDebugShapes` | 115 | (none) |385| `SyncPlayerPreferences` | 116 | `showEntityMarkers`, `armorItemsPreferredPickupLocation`, `weaponAndToolItemsPreferredPickupLocation`, `usableItemsItemsPreferredPickupLocation`, `solidBlockItemsPreferredPickupLocation`, `miscItemsPreferredPickupLocation`, `allowNPCDetection`, `respondToHit` |386| `ClientPlaceBlock` | 117 | `position`, `rotation`, `placedBlockId` |387| `UpdateMemoriesFeatureStatus` | 118 | `isFeatureUnlocked` |388| `RemoveMapMarker` | 119 | `markerId` |389390### Inventory Packets391392| Packet | ID | Key Fields |393|--------|----|------------|394| `UpdatePlayerInventory` | 170 | `storage`, `armor`, `hotbar`, `utility`, `builderMaterial`, `tools`, `backpack`, `sortType` |395| `SetCreativeItem` | 171 | `inventorySectionId`, `slotId`, `item`, `override` |396| `DropCreativeItem` | 172 | `item` |397| `SmartGiveCreativeItem` | 173 | `item`, `moveType` |398| `DropItemStack` | 174 | `inventorySectionId`, `slotId`, `quantity` |399| `MoveItemStack` | 175 | `fromSectionId`, `fromSlotId`, `quantity`, `toSectionId`, `toSlotId` |400| `SmartMoveItemStack` | 176 | `fromSectionId`, `fromSlotId`, `quantity`, `moveType` |401| `SetActiveSlot` | 177 | `inventorySectionId`, `activeSlot` |402| `SwitchHotbarBlockSet` | 178 | `itemId` |403| `InventoryAction` | 179 | `inventorySectionId`, `inventoryActionType`, `actionData` |404405### Window Packets406407| Packet | ID | Key Fields |408|--------|----|------------|409| `OpenWindow` | 200 | `id`, `windowType`, `windowData`, `inventory`, `extraResources` |410| `UpdateWindow` | 201 | `id`, `windowData`, `inventory`, `extraResources` |411| `CloseWindow` | 202 | `id` |412| `SendWindowAction` | 203 | `id`, `action` |413| `ClientOpenWindow` | 204 | `type` |414415### Other Client Packets416417| Packet | ID | Key Fields |418|--------|----|------------|419| `ClientReferral` | 18 | `hostTo`, `data` |420| `SetUpdateRate` | 29 | `updatesPerSecond` |421| `SetTimeDilation` | 30 | `timeDilation` |422| `SetChunk` | 131 | `x`, `y`, `z`, `localLight`, `globalLight`, `data` |423| `SetChunkHeightmap` | 132 | `x`, `z`, `heightmap` |424| `SetChunkTintmap` | 133 | `x`, `z`, `tintmap` |425| `SetChunkEnvironments` | 134 | `x`, `z`, `environments` |426| `SetFluids` | 136 | `x`, `y`, `z`, `data` |427| `SetPaused` | 158 | `paused` |428| `SetEntitySeed` | 160 | `entitySeed` |429| `SetPage` | 216 | `page`, `canCloseThroughInteraction` |430| `SetServerAccess` | 252 | `access`, `password` |431| `SetMachinimaActorModel` | 261 | `model`, `sceneName`, `actorName` |432| `SetServerCamera` | 280 | `clientCameraView`, `isLocked`, `cameraSettings` |433| `SetFlyCameraMode` | 283 | `entering` |434| `SyncInteractionChains` | 290 | `updates` |435436### Packet Handlers (Server-Side)437438Packet handlers determine which packets are accepted at each phase of the connection lifecycle:439440| Handler | Packets Accepted |441|---------|-----------------|442| **InitialPacketHandler** | Connect (0), Disconnect (1) |443| **HandshakeHandler** | Disconnect (1), AuthToken (12) |444| **PasswordPacketHandler** | Disconnect (1), PasswordResponse (15) |445| **SetupPacketHandler** | Disconnect (1), RequestAssets (23), ViewRadius (32), PlayerOptions (33) |446| **GamePacketHandler** | Disconnect (1), Pong (3), ClientMovement (108), ChatMessage (211), RequestAssets (23), CustomPageEvent (219), ViewRadius (32), UpdateLanguage (232), MouseInteraction (111), SendWindowAction (203), CloseWindow (202), ClientReady (105), SyncInteractionChains (290), SetPaused (158), and more |447448**GamePacketHandler sub-handlers:**449450| Sub-Handler | Packets |451|-------------|---------|452| **InventoryPacketHandler** | SetCreativeItem (171), DropCreativeItem (172), SmartGiveCreativeItem (173), DropItemStack (174), MoveItemStack (175), SmartMoveItemStack (176), SetActiveSlot (177), SwitchHotbarBlockSet (178), InventoryAction (179) |453| **BuilderToolsPacketHandler** | LoadHotbar (106), SaveHotbar (107), BuilderToolArgUpdate (400), BuilderToolEntityAction (401), and more |454| **MountGamePacketHandler** | DismountNPC (294) |455456> If a packet arrives during the wrong connection phase, the handler disconnects the sender.457458---459460## Part 8: Custom Camera Controls461462Camera is controlled by sending a `SetServerCamera` packet with `ServerCameraSettings` to the player.463464### Camera Imports465466```java467import com.hypixel.hytale.protocol.ClientCameraView;468import com.hypixel.hytale.protocol.Direction;469import com.hypixel.hytale.protocol.MouseInputType;470import com.hypixel.hytale.protocol.MovementForceRotationType;471import com.hypixel.hytale.protocol.PositionDistanceOffsetType;472import com.hypixel.hytale.protocol.RotationType;473import com.hypixel.hytale.protocol.ServerCameraSettings;474import com.hypixel.hytale.protocol.Vector3f;475import com.hypixel.hytale.protocol.packets.camera.SetServerCamera;476```477478### Basic Camera Setup479480```java481ServerCameraSettings settings = new ServerCameraSettings();482settings.distance = 10.0f; // Zoom distance from player483settings.isFirstPerson = false; // Third-person mode484settings.positionLerpSpeed = 0.2f; // Smooth camera follow485486playerRef.getPacketHandler().writeNoCache(487 new SetServerCamera(ClientCameraView.Custom, true, settings)488);489```490491### Reset Camera to Default492493```java494playerRef.getPacketHandler().writeNoCache(495 new SetServerCamera(ClientCameraView.Custom, false, null)496);497```498499### Camera Presets500501#### Top-Down (RTS/ARPG Style)502503Source: `com.hypixel.hytale.server.core.command.commands.player.camera.PlayerCameraTopdownCommand`504505```java506ServerCameraSettings settings = new ServerCameraSettings();507settings.positionLerpSpeed = 0.2f;508settings.rotationLerpSpeed = 0.2f;509settings.distance = 20.0f;510settings.displayCursor = true;511settings.isFirstPerson = false;512settings.movementForceRotationType = MovementForceRotationType.Custom;513// Align movement with camera yaw (horizontal rotation only)514settings.movementForceRotation = new Direction(-0.7853981634f, 0.0f, 0.0f); // 45° right515settings.eyeOffset = true;516settings.positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset;517settings.rotationType = RotationType.Custom;518settings.rotation = new Direction(0.0f, -1.5707964f, 0.0f); // Look straight down519settings.mouseInputType = MouseInputType.LookAtPlane;520settings.planeNormal = new Vector3f(0.0f, 1.0f, 0.0f); // Ground plane521522playerRef.getPacketHandler().writeNoCache(523 new SetServerCamera(ClientCameraView.Custom, true, settings)524);525```526527#### Side-Scroller (2D Platformer Style)528529Source: `com.hypixel.hytale.server.core.command.commands.player.camera.PlayerCameraSideScrollerCommand`530531```java532ServerCameraSettings settings = new ServerCameraSettings();533settings.positionLerpSpeed = 0.2f;534settings.rotationLerpSpeed = 0.2f;535settings.distance = 15.0f;536settings.displayCursor = true;537settings.isFirstPerson = false;538settings.movementForceRotationType = MovementForceRotationType.Custom;539settings.movementMultiplier = new Vector3f(1.0f, 1.0f, 0.0f); // Lock Z-axis540settings.eyeOffset = true;541settings.positionDistanceOffsetType = PositionDistanceOffsetType.DistanceOffset;542settings.rotationType = RotationType.Custom;543settings.mouseInputType = MouseInputType.LookAtPlane;544settings.planeNormal = new Vector3f(0.0f, 0.0f, 1.0f); // Side plane545546playerRef.getPacketHandler().writeNoCache(547 new SetServerCamera(ClientCameraView.Custom, true, settings)548);549```550551#### Isometric (Diablo Style)552553```java554ServerCameraSettings settings = new ServerCameraSettings();555settings.positionLerpSpeed = 0.2f;556settings.rotationLerpSpeed = 0.2f;557settings.isFirstPerson = false;558settings.distance = 6f;559settings.allowPitchControls = false;560settings.displayCursor = true;561// Force the camera's rotation to be set by the server562settings.applyLookType = ApplyLookType.Rotation;563settings.rotationType = RotationType.Custom;564565// Set the typical isometric rotation566Direction direction = new Direction(567 (float) Math.toRadians(45f), // yaw568 (float) Math.toRadians(-35f), // pitch569 0f // roll570);571settings.rotation = direction;572settings.movementForceRotation = direction;573574playerRef.getPacketHandler().writeNoCache(575 new SetServerCamera(ClientCameraView.Custom, true, settings)576);577```578579### ServerCameraSettings Reference580581#### Position & Rotation582583| Setting | Description |584|---------|-------------|585| `positionLerpSpeed` (0.0-1.0) | How smoothly camera follows player. Lower = smoother but slower |586| `rotationLerpSpeed` (0.0-1.0) | How smoothly camera rotates. Lower = smoother but slower |587| `distance` | Camera distance from player. Higher = zoomed out |588| `rotation` | Camera angle as `Direction(yaw, pitch, roll)` in **radians** |589| `rotationType` | How rotation is calculated. `RotationType.Custom` uses your `rotation` value |590591#### Movement Alignment592593| Setting | Description |594|---------|-------------|595| `movementForceRotationType` | `AttachedToHead` = follows player look; `Custom` = use `movementForceRotation` |596| `movementForceRotation` | Direction for W/S movement when using `Custom`. Match yaw with camera, keep pitch at 0 |597| `movementMultiplier` | Scale movement per axis. `(1,1,0)` = lock Z-axis for 2D |598599#### Input & Display600601| Setting | Description |602|---------|-------------|603| `displayCursor` | Show/hide mouse cursor |604| `mouseInputType` | `LookAtPlane` = cursor on plane (top-down); `LookAtTarget` = rotates camera |605| `planeNormal` | For `LookAtPlane`, defines the plane. `(0,1,0)` = ground, `(0,0,1)` = side |606607#### Advanced608609| Setting | Description |610|---------|-------------|611| `positionDistanceOffsetType` | `DistanceOffset` = simple; `DistanceOffsetRaycast` = prevents wall clipping |612| `eyeOffset` | Offset camera from player's eye position |613| `isFirstPerson` | First-person vs third-person mode |614| `allowPitchControls` | Allow player to control pitch |615| `isLocked` (packet parameter) | Set `true` in `SetServerCamera` to prevent player camera changes |616617### Camera Tips618619- **Zoom:** Adjust `distance` (higher = further out)620- **Smoothness:** `positionLerpSpeed` and `rotationLerpSpeed` control camera response speed621- **Wall clipping:** Use `PositionDistanceOffsetType.DistanceOffsetRaycast`622- **Lock camera:** Set `isLocked = true` in the `SetServerCamera` packet623- **2D movement:** Set `movementMultiplier` to zero out an axis624- **Isometric cameras:** Always set `movementForceRotation` to match camera yaw625- **Angle math:** Use `Math.toRadians(degrees)` to convert degrees to radians626627---628629## Key Warnings6306311. **Client-side prediction:** Cancelling packets does not prevent client-side visual effects. The player will still see movement/actions locally even if the server blocks the packet.6322. **Thread safety:** When accessing ECS components from packet handlers, schedule work on the world thread via `world.execute(() -> { ... })`.6333. **Packet IDs may change:** Always use `instanceof` checks or class references rather than hardcoded packet IDs when possible. The ID-based approach (`packet.getId() != 290`) is brittle across server versions.6344. **Deregister on shutdown:** Always store filter/watcher references and deregister them in your plugin's `shutdown()` method.