AgentSkillsCN

hytale-player-input

介绍 Hytale 的玩家输入系统,包括数据包拦截(PacketAdapters、PacketWatcher、PacketFilter)、SyncInteractionChains、InteractionTypes、客户端到服务器的数据包引用,以及自定义相机控制。适用于处理玩家输入、拦截数据包、创建自定义交互、修改相机行为,或处理鼠标/键盘输入时使用。触发条件:player input、packet、PacketAdapters、PacketWatcher、PacketFilter、PlayerPacketWatcher、PlayerPacketFilter、SyncInteractionChains、InteractionType、MouseInteraction、ClientMovement、camera、SetServerCamera、ServerCameraSettings、相机控制、俯视、等距、横版卷轴、入站数据包、出站数据包、数据包监听器、输入处理。

SKILL.md
--- frontmatter
name: hytale-player-input
description: 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.

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, all InteractionTypes, the complete client-to-server packet reference, and custom camera controls.

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

TaskApproach
Listen to all inbound packetsPacketAdapters.registerInbound((PacketWatcher) ...)
Listen to player-specific inbound packetsPacketAdapters.registerInbound((PlayerPacketWatcher) ...)
Block/cancel inbound packetsPacketAdapters.registerInbound((PlayerPacketFilter) ...) — return true to cancel
Listen to outbound packetsPacketAdapters.registerOutbound((PacketWatcher) ...)
Detect player interactions (left/right click, F key)Intercept SyncInteractionChains (packet ID 290)
Detect mouse inputIntercept MouseInteraction (packet ID 111)
Detect player movementIntercept ClientMovement (packet ID 108)
Customize cameraSend SetServerCamera packet with ServerCameraSettings
Reset camera to defaultSend SetServerCamera(ClientCameraView.Custom, false, null)
Deregister a listenerPacketAdapters.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

MethodInterface TypeCan BlockPlayer-Specific
registerInbound(PacketWatcher)PacketWatcherNoNo
registerInbound(PacketFilter)PacketFilterYesNo
registerInbound(PlayerPacketWatcher)PlayerPacketWatcherNoYes
registerInbound(PlayerPacketFilter)PlayerPacketFilterYesYes
registerOutbound(PacketWatcher)PacketWatcherNoNo
registerOutbound(PacketFilter)PacketFilterYesNo

Interfaces

java
// 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

java
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

FieldDescription
interactionTypeThe InteractionType enum value
activeHotbarSlotThe slot the player is currently on
data.targetSlotThe slot the player wants to switch to (for swap types)
initialWhether this is the start of a new interaction chain

Example: Listening for Use Interaction (F Key)

java
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)

java
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

java
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

All interaction types from InteractionType enum:

NameOrdinalDescription
Primary0Left click
Secondary1Right click
Ability12Ability slot 1
Ability23Ability slot 2
Ability34Ability slot 3
Use5Use key (F)
Pick6Pick action
Pickup7Pickup action
CollisionEnter8Entity collision start
CollisionLeave9Entity collision end
Collision10Ongoing collision
EntityStatEffect11Stat effect applied
SwapTo12Switching to a slot
SwapFrom13Switching from a slot
Death14Entity death
Wielding15Wielding an item
ProjectileSpawn16Projectile created
ProjectileHit17Projectile hits target
ProjectileMiss18Projectile misses
ProjectileBounce19Projectile bounces
Held20Item held in main hand
HeldOffhand21Item held in offhand
Equipped22Item equipped
Dodge23Dodge action
GameModeSwap24Game 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)

java
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

java
PacketAdapters.registerInbound((PacketHandler handler, Packet packet) -> {
    if (packet instanceof PlayerOptions skinPacket) {
        skinPacket.skin = null; // Remove skin data
    }
});

Blocking Player Packets (PlayerPacketFilter)

java
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:

java
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:

java
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

Packets are found in: com.hypixel.hytale.protocol.packets

Player Packets

PacketIDKey Fields
SetClientId100clientId
SetGameMode101gameMode
SetMovementStates102movementStates
SetBlockPlacementOverride103enabled
JoinWorld104clearWorld, fadeInOut, worldUuid
ClientReady105readyForChunks, readyForGameplay
LoadHotbar106inventoryRow
SaveHotbar107inventoryRow
ClientMovement108movementStates, relativePosition, absolutePosition, bodyOrientation, lookOrientation, teleportAck, wishMovement, velocity, mountedTo, riderMovementStates
ClientTeleport109teleportId, modelTransform, resetVelocity
UpdateMovementSettings110movementSettings
MouseInteraction111clientTimestamp, activeSlot, itemInHandId, screenPoint, mouseButton, mouseMotion, worldInteraction
DamageInfo112damageSourcePosition, damageAmount, damageCause
ReticleEvent113eventIndex
DisplayDebug114shape, matrix, color, time, fade, frustumProjection
ClearDebugShapes115(none)
SyncPlayerPreferences116showEntityMarkers, armorItemsPreferredPickupLocation, weaponAndToolItemsPreferredPickupLocation, usableItemsItemsPreferredPickupLocation, solidBlockItemsPreferredPickupLocation, miscItemsPreferredPickupLocation, allowNPCDetection, respondToHit
ClientPlaceBlock117position, rotation, placedBlockId
UpdateMemoriesFeatureStatus118isFeatureUnlocked
RemoveMapMarker119markerId

Inventory Packets

PacketIDKey Fields
UpdatePlayerInventory170storage, armor, hotbar, utility, builderMaterial, tools, backpack, sortType
SetCreativeItem171inventorySectionId, slotId, item, override
DropCreativeItem172item
SmartGiveCreativeItem173item, moveType
DropItemStack174inventorySectionId, slotId, quantity
MoveItemStack175fromSectionId, fromSlotId, quantity, toSectionId, toSlotId
SmartMoveItemStack176fromSectionId, fromSlotId, quantity, moveType
SetActiveSlot177inventorySectionId, activeSlot
SwitchHotbarBlockSet178itemId
InventoryAction179inventorySectionId, inventoryActionType, actionData

Window Packets

PacketIDKey Fields
OpenWindow200id, windowType, windowData, inventory, extraResources
UpdateWindow201id, windowData, inventory, extraResources
CloseWindow202id
SendWindowAction203id, action
ClientOpenWindow204type

Other Client Packets

PacketIDKey Fields
ClientReferral18hostTo, data
SetUpdateRate29updatesPerSecond
SetTimeDilation30timeDilation
SetChunk131x, y, z, localLight, globalLight, data
SetChunkHeightmap132x, z, heightmap
SetChunkTintmap133x, z, tintmap
SetChunkEnvironments134x, z, environments
SetFluids136x, y, z, data
SetPaused158paused
SetEntitySeed160entitySeed
SetPage216page, canCloseThroughInteraction
SetServerAccess252access, password
SetMachinimaActorModel261model, sceneName, actorName
SetServerCamera280clientCameraView, isLocked, cameraSettings
SetFlyCameraMode283entering
SyncInteractionChains290updates

Packet Handlers (Server-Side)

Packet handlers determine which packets are accepted at each phase of the connection lifecycle:

HandlerPackets Accepted
InitialPacketHandlerConnect (0), Disconnect (1)
HandshakeHandlerDisconnect (1), AuthToken (12)
PasswordPacketHandlerDisconnect (1), PasswordResponse (15)
SetupPacketHandlerDisconnect (1), RequestAssets (23), ViewRadius (32), PlayerOptions (33)
GamePacketHandlerDisconnect (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-HandlerPackets
InventoryPacketHandlerSetCreativeItem (171), DropCreativeItem (172), SmartGiveCreativeItem (173), DropItemStack (174), MoveItemStack (175), SmartMoveItemStack (176), SetActiveSlot (177), SwitchHotbarBlockSet (178), InventoryAction (179)
BuilderToolsPacketHandlerLoadHotbar (106), SaveHotbar (107), BuilderToolArgUpdate (400), BuilderToolEntityAction (401), and more
MountGamePacketHandlerDismountNPC (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

java
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

java
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

java
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

java
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

java
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)

java
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

SettingDescription
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
distanceCamera distance from player. Higher = zoomed out
rotationCamera angle as Direction(yaw, pitch, roll) in radians
rotationTypeHow rotation is calculated. RotationType.Custom uses your rotation value

Movement Alignment

SettingDescription
movementForceRotationTypeAttachedToHead = follows player look; Custom = use movementForceRotation
movementForceRotationDirection for W/S movement when using Custom. Match yaw with camera, keep pitch at 0
movementMultiplierScale movement per axis. (1,1,0) = lock Z-axis for 2D

Input & Display

SettingDescription
displayCursorShow/hide mouse cursor
mouseInputTypeLookAtPlane = cursor on plane (top-down); LookAtTarget = rotates camera
planeNormalFor LookAtPlane, defines the plane. (0,1,0) = ground, (0,0,1) = side

Advanced

SettingDescription
positionDistanceOffsetTypeDistanceOffset = simple; DistanceOffsetRaycast = prevents wall clipping
eyeOffsetOffset camera from player's eye position
isFirstPersonFirst-person vs third-person mode
allowPitchControlsAllow 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

  1. 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.
  2. Thread safety: When accessing ECS components from packet handlers, schedule work on the world thread via world.execute(() -> { ... }).
  3. 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.
  4. Deregister on shutdown: Always store filter/watcher references and deregister them in your plugin's shutdown() method.