Skip to content

noname-mods/PlayerAPI

Repository files navigation

PlayerAPI

A client-side Fabric library that exposes a clean, high-level API for player interaction, world querying, and human-like automation. Designed to be a shared foundation so other mods don't have to reimplement the same low-level mixins.


For Players

PlayerAPI does nothing on its own. If you downloaded this, another mod you use requires it as a dependency. Simply place the .jar in your mods folder alongside that mod.


For Developers

PlayerAPI — Design & API Reference

Version: 1.8.0
Minecraft: 1.21.11 (Fabric)
Maven coordinate: com.playerapi:playerapi:1.8.0


Table of Contents

  1. Overview & Philosophy
  2. Architecture
  3. Adding PlayerAPI as a Dependency
  4. Threading Model
  5. Events — PlayerAPIEvents
  6. Scheduler
  7. Forced Actions
  8. Human Actions
  9. Info (Read-Only Queries)
  10. Data Types
  11. Key Names Reference
  12. Known Limitations

1. Overview & Philosophy

PlayerAPI is a client-side Fabric library that provides a clean, stable abstraction over everything a client-side automation or utility mod needs to do with the local player. It exists so that consumer mods never have to import Minecraft internals directly — all game interaction flows through PlayerAPI's public API classes.

Core design principles:

  • Separation of concerns. Every API class has one clear responsibility: info classes only read state, action classes only perform actions. Nothing is mixed.
  • Safe defaults everywhere. Every method that reads player state returns a safe default (0, false, "", empty list, EMPTY snapshot) if the player is not in a world. Consumer mods never need null checks.
  • Main-thread only. All action calls and event callbacks run on the main game thread. There is no concurrency to manage.
  • Forced vs. Human. There are two parallel action systems. Forced actions are instant and computer-precise (good for precise positioning). Human actions add reaction delays, spring-physics look interpolation, and mouse noise (good for looking natural). Consumer mods choose which fits their use case.
  • No internal state leaks. Snapshot types (ItemSnapshot, EntitySnapshot, BlockSnapshot, EffectSnapshot) are immutable Java records. They can be stored and compared freely without the underlying game object becoming stale.

2. Architecture

┌─────────────────────────────────────────────────────────┐
│                    Consumer Mod                         │
│   (depends on PlayerAPI, never imports MC internals)    │
└───────────────────┬─────────────────────────────────────┘
                    │ uses
┌───────────────────▼─────────────────────────────────────┐
│                   PlayerAPI Public Surface               │
│                                                         │
│  Events          Scheduler       Actions (Forced)        │
│  PlayerAPIEvents  Scheduler      MovementActions         │
│                                 LookActions              │
│  Info (read)                    InventoryActions         │
│  PlayerInfo                     InteractionActions       │
│  InventoryInfo                  ChatActions              │
│  WorldInfo                      SoundActions             │
│  TabListInfo                    DisplayActions           │
│                                                         │
│  Actions (Human)   Data Types                           │
│  HumanActions      ItemSnapshot                         │
│  HumanProfile      EntitySnapshot                       │
│                    BlockSnapshot                         │
│                    EffectSnapshot                        │
└───────────────────┬─────────────────────────────────────┘
                    │ wraps
┌───────────────────▼─────────────────────────────────────┐
│               PlayerAPI Internals (not public)          │
│                                                         │
│  PlayerAPIMod      — wires Fabric events, drives ticks  │
│  KeyStateTracker   — held-key set, mixin read target    │
│  SchedulerState    — tick-based task queue              │
│  HumanLookState    — spring-damper look interpolation   │
│  MovementInputMixin — @Mixin(KeyboardInput) override    │
│  PlayerInventoryAccessor — @Mixin accessor for slots    │
└─────────────────────────────────────────────────────────┘

Tick Flow (every game tick)

ClientTickEvents.END_CLIENT_TICK
        │
        ├─ 1. SchedulerState.tick()         — fires due scheduled tasks
        ├─ 2. HumanLookState.tick()         — advances spring interpolation
        ├─ 3. diff-based event checks       — health, position changes
        └─ 4. PlayerAPIEvents.TICK.invoke() — consumer mod tick callbacks

Key Override Mechanism

MovementInputMixin is a @Mixin on KeyboardInput with a TAIL injection into its tick() method. At the end of every vanilla input tick, if KeyStateTracker.isActive() is true, the mixin overwrites input.pressingForward, input.pressingBack, input.pressingLeft, input.pressingRight, input.jumping, and input.sneaking with the values stored in KeyStateTracker. This means the override is completely transparent to the rest of the game — movement, sprint physics, and server packets all behave normally.


3. Adding PlayerAPI as a Dependency

build.gradle

repositories {
    mavenLocal()
}

dependencies {
    modImplementation "com.playerapi:playerapi:1.7.0"
}

gradle.properties

playerapi_version=1.7.0

fabric.mod.json

"depends": {
    "playerapi": ">=1.7.0"
}

Build PlayerAPI first:

cd PlayerAPI
./gradlew publishToMavenLocal

Then build your mod normally.


4. Threading Model

All PlayerAPI calls must be made from the main game thread. This is the thread that runs ClientTickEvents, renders frames, and processes network packets. In practice this means:

  • Inside PlayerAPIEvents.TICK handlers — ✅ always safe
  • Inside any Fabric event handler registered on the main thread — ✅ safe
  • From a background thread or CompletableFuture — ❌ never safe; use MinecraftClient.getInstance().execute(() -> ...) to marshal back

The Scheduler always fires callbacks on the main thread.


5. Events — PlayerAPIEvents

All events live on com.playerapi.PlayerAPIEvents. Register listeners from your mod's onInitializeClient().

PlayerAPIEvents.TICK.register(() -> {
    // runs every tick
});

All events use Fabric's EventFactory.createArrayBacked pattern — multiple mods can register independently and all listeners fire.


TICK

Event<PlayerAPIEvents.Tick> TICK

Fires once per game tick, after END_CLIENT_TICK, after the scheduler has processed due tasks.

Interface:

@FunctionalInterface
interface Tick {
    void onTick();
}

Notes:

  • This is the primary entry point for any per-tick automation logic.
  • Call PlayerInfo.isInWorld() at the top if your logic requires a loaded world.
  • The scheduler runs before this event fires, so tasks scheduled from a previous tick are already complete when your TICK handler runs.

CHAT_RECEIVED

Event<PlayerAPIEvents.ChatReceived> CHAT_RECEIVED

Fires when any chat message arrives — player chat, system messages, or game messages. Covers both signed player messages and unsigned server messages.

Interface:

@FunctionalInterface
interface ChatReceived {
    void onChatReceived(String sender, String message);
}
Parameter Description
sender Display name of the sender, stripped of colour codes. Empty string for system/game messages.
message Full message text, stripped of colour codes.

Notes:

  • Action bar messages (overlay=true) are not forwarded.
  • Both ClientReceiveMessageEvents.CHAT (player messages) and ClientReceiveMessageEvents.GAME (system/game messages) feed into this single event.

HEALTH_CHANGED

Event<PlayerAPIEvents.HealthChanged> HEALTH_CHANGED

Fires whenever the local player's health value changes by any amount.

Interface:

@FunctionalInterface
interface HealthChanged {
    void onHealthChanged(float oldHealth, float newHealth);
}

Notes:

  • PLAYER_DEATH fires first if health dropped to 0, then HEALTH_CHANGED fires immediately after.
  • Not fired on world join (initial health read). Only fires on changes after the first tick.

INVENTORY_CHANGED

Event<PlayerAPIEvents.InventoryChanged> INVENTORY_CHANGED

Fires when the player's main inventory changes.

Interface:

@FunctionalInterface
interface InventoryChanged {
    void onInventoryChanged(int slot, String newItemId);
}
Parameter Description
slot Inventory slot that changed. -1 means a bulk/unknown change.
newItemId Namespaced item ID of the new item in that slot, e.g. "minecraft:diamond_sword". "minecraft:air" for an empty slot.

POSITION_CHANGED

Event<PlayerAPIEvents.PositionChanged> POSITION_CHANGED

Fires when the player moves more than 0.1 blocks from their position at the last firing. This is a threshold-based event — it does not fire every tick, only on meaningful movement.

Interface:

@FunctionalInterface
interface PositionChanged {
    void onPositionChanged(double x, double y, double z);
}

Notes:

  • Coordinates are the player's feet position.
  • 0.1 blocks is roughly the minimum visible movement at normal render scale.
  • Not suitable for millimetre-precision tracking — use PlayerInfo.getX/Y/Z() inside TICK for that.

WORLD_JOIN

Event<PlayerAPIEvents.WorldJoin> WORLD_JOIN

Fires once after the client connects to a server or loads a world and the player entity is available.

Interface:

@FunctionalInterface
interface WorldJoin {
    void onWorldJoin();
}

Notes:

  • Health/position diff state is reset on world join, so HEALTH_CHANGED and POSITION_CHANGED will not fire spuriously on the first tick.
  • PlayerInfo.isInWorld() returns true at the time this fires.

WORLD_LEAVE

Event<PlayerAPIEvents.WorldLeave> WORLD_LEAVE

Fires when the client disconnects or leaves a world. All held keys are released and any active human look interpolation is cancelled automatically before this event fires.

Interface:

@FunctionalInterface
interface WorldLeave {
    void onWorldLeave();
}

PLAYER_DEATH

Event<PlayerAPIEvents.PlayerDeath> PLAYER_DEATH

Fires when the player's health reaches 0.

Interface:

@FunctionalInterface
interface PlayerDeath {
    void onPlayerDeath();
}

Notes:

  • HEALTH_CHANGED also fires immediately after with newHealth = 0.
  • The player entity still exists at the time this fires (the death screen appears later).

TAB_LIST_UPDATED

Event<PlayerAPIEvents.TabListUpdated> TAB_LIST_UPDATED

Fires when the server updates the tab-list player list. Throttled to at most once per tick.

Interface:

@FunctionalInterface
interface TabListUpdated {
    void onTabListUpdated();
}

BLOCK_BROKEN

Event<PlayerAPIEvents.BlockBroken> BLOCK_BROKEN

Fires when the local player successfully breaks a block. Backed by Fabric's ClientPlayerBlockBreakEvents.AFTER.

Interface:

@FunctionalInterface
interface BlockBroken {
    void onBlockBroken();
}

Notes:

  • Instant-break blocks (crops, tall grass, flowers) fire immediately client-side without waiting for server confirmation. This is the most important case for farming automation.
  • Survival-mode blocks (stone, wood, etc.) fire after the server confirms the break.
  • No position or block type information is passed. If you need that, read WorldInfo.getBlockAtCrosshair() from the TICK event just before.

6. Scheduler

com.playerapi.Scheduler — tick-based task scheduling on the main game thread.

1 tick ≈ 50 ms at 20 TPS.

All callbacks are Runnable and fire on the main game thread at the end of the tick they are due.


schedule(int delayTicks, Runnable task) → int

Schedule a task to run after delayTicks ticks. Returns a cancel token.

Scheduler.schedule(20, () -> doSomethingAfterOneSecond());
// token = return value, save it if you need to cancel
  • delayTicks = 0 fires on the very next tick.
  • Tasks are not guaranteed exact-tick timing if the game is lagging below 20 TPS, but they will never fire early.

scheduleMs(long delayMs, Runnable task) → int

Convenience overload. Converts milliseconds to ticks by dividing by 50, rounded up to a minimum of 1.

Scheduler.scheduleMs(500, () -> doSomethingAfterHalfSecond());

scheduleRepeating(int periodTicks, int times, Runnable task) → int

Schedule a repeating task.

Parameter Description
periodTicks Ticks between each invocation.
times Number of times to fire. Pass 0 for infinite (runs until cancelled).
task The callback to run.
// Fire 5 times, once per second:
Scheduler.scheduleRepeating(20, 5, () -> BotLogger.info("tick!"));

// Fire forever (infinite loop — cancel manually):
int token = Scheduler.scheduleRepeating(20, 0, () -> doPeriodicCheck());
// later:
Scheduler.cancel(token);

Important: Infinite repeating tasks self-reschedule after each firing. Cancelling the initial token only prevents the current pending invocation from firing — if the task has already fired once, use cancelAll() or track re-scheduled tokens yourself if you need hard cancellation.


cancel(int token)

Cancel a pending task by its token. Safe to call with an invalid/already-fired token.


cancelAll()

Cancel every pending task across all mods using this scheduler. Useful in stopBot() to ensure no lingering delayed actions fire after stopping.


getCurrentTick() → long

Returns the current global tick count as tracked by PlayerAPI. Starts at 0 on mod load and increments once per END_CLIENT_TICK. Useful for timestamps and rate-limiting.

long start = Scheduler.getCurrentTick();
// ...later...
long elapsed = Scheduler.getCurrentTick() - start; // in ticks
double seconds = elapsed / 20.0;

7. Forced Actions

Forced actions are instant and computer-precise. They take effect on the very next game tick with no delay, noise, or spring physics. Use these when precision matters more than appearance.


7.1 MovementActions

Controls which movement keys the player holds. The override only applies when setActive(true) has been called.

Valid key names: "forward", "back", "left", "right", "sprint", "sneak", "jump", "attack", "use"


setActive(boolean active)

Enable or disable the movement key override. When disabled, all keys are released and the player regains full manual control. Call setActive(true) before pressing any keys.

MovementActions.setActive(true);
MovementActions.pressKey("forward");
MovementActions.pressKey("sprint");
// player now walks forward

isActive() → boolean

Returns whether the movement override is currently enabled.


pressKey(String key)

Hold a key indefinitely. The key stays held on every subsequent tick until releaseKey or releaseAll is called.

MovementActions.pressKey("jump"); // holds space bar

releaseKey(String key)

Release a single previously held key.

MovementActions.releaseKey("sprint");

releaseAll()

Release all currently held keys (forward, back, left, right, sprint, sneak, jump, attack, use). Does not disable the override — setActive remains unchanged.


tapKey(String key, long durationMs)

Press a key for durationMs milliseconds then automatically release it. Internally converts to ticks.

MovementActions.tapKey("jump", 100); // tap space for 100 ms (2 ticks)

pressKeys(Iterable<String> keys)

Press multiple keys simultaneously. Equivalent to calling pressKey for each.


releaseDirectionKeys()

Release only the four directional keys (forward, back, left, right). Sprint, sneak, and jump states are unchanged. Useful for stopping movement while staying in sprint mode.


getHeldKeys() → Set<String>

Returns an immutable snapshot of all currently held key names.


isPressed(String key) → boolean

Returns true if the given key is currently being held by the override.


randomDelay() → long

Returns a random delay between 50 ms and 260 ms. Useful as a timing jitter between programmatic key presses for anti-pattern detection.


7.2 LookActions

Instantly set the player's camera direction. All changes take effect immediately — there is no animation.

Minecraft yaw convention:

  • = South (+Z)
  • 90° = West (−X)
  • 180° = North (−Z)
  • 270° = East (+X)

Pitch: −90° = straight up, = horizontal, 90° = straight down.


setYaw(float yaw)

Set the player's horizontal rotation to exactly yaw degrees.


setPitch(float pitch)

Set the player's vertical rotation to exactly pitch degrees. Automatically clamped to [−90, 90].


lookAt(double x, double y, double z)

Instantly rotate to look at an exact world position. Calculates the required yaw and pitch automatically from the player's current eye position.

LookActions.lookAt(100.0, 64.0, -200.0);
// For level aim: pass player.getY() + 1.62 as the y argument

lookAtBlock(int x, int y, int z)

Instantly look at the centre of a block (x+0.5, y+0.5, z+0.5).


lookAtBlock(BlockPos pos)

Same as above, accepting a BlockPos.


lookAtBlock(int x, int y, int z, Direction face)

Look at the centre of a specific face of a block. face is a Minecraft Direction enum value (UP, DOWN, NORTH, SOUTH, EAST, WEST).

LookActions.lookAtBlock(10, 64, -5, Direction.UP); // aim at top face

lookAtBlock(int x, int y, int z, double xOffset, double yOffset, double zOffset)

Look at a custom point within a block. Offsets are fractions in [0, 1] where 0.5, 0.5, 0.5 is the block centre.

LookActions.lookAtBlock(10, 64, -5, 0.5, 1.0, 0.5); // top-face centre

snapToNearestCardinal()

Snap the player's yaw to the nearest multiple of 90°. Useful for aligning before a cardinal-facing path.


getSnappedYaw() → int

Returns the current yaw rounded to the nearest cardinal direction (0, 90, 180, or 270).


7.3 InventoryActions

Direct inventory manipulation. All operations take effect instantly on the next tick.


switchToSlot(int slot)

Switch the selected hotbar slot to slot (0–8). Has no effect if slot is out of range.


switchToItem(String displayName) → boolean

Switch to the first hotbar slot whose item's display name matches exactly (case-insensitive). Returns true if found and switched, false if not in hotbar.

InventoryActions.switchToItem("Wheat Hoe");

switchToItemStartingWith(String prefix) → boolean

Switch to the first hotbar slot whose item display name starts with the given prefix (case-insensitive). Useful for tiered items like "Pest Repellent I" / "Pest Repellent MAX" where you only know the prefix.

InventoryActions.switchToItemStartingWith("Pest Repellent");

switchToItemId(String idSubstring) → boolean

Switch to the first hotbar slot whose item's namespaced ID contains idSubstring (case-insensitive). More stable than display name matching when item names are localised or can change.

InventoryActions.switchToItemId("diamond_hoe");

dropItem()

Drop one item from the currently held stack (Q key equivalent).


dropStack()

Drop the entire currently held stack (Ctrl+Q equivalent).


dropAllInHotbar(String displayName)

Switch to each hotbar slot matching displayName and drop the entire stack. Restores the original selected slot afterward.


7.4 InteractionActions

Player interactions with items, blocks, and entities.


useItem()

Use (right-click) the item in the main hand. Sends the appropriate use-item packet to the server. Works for consumables, throwables, placing blocks, opening blocks, etc.


useOffhandItem()

Same as useItem() but for the off-hand slot.


useItemHeld(long durationMs)

Continuously send useItem() every tick for durationMs milliseconds. Useful for items that require holding right-click (fishing rods, bows, charging crossbows).

InteractionActions.useItemHeld(1500); // hold right-click for 1.5 seconds

attackTargeted()

Attack the entity currently under the player's crosshair (client.targetedEntity). Does nothing if no entity is targeted.


attackBlockAtCrosshair()

Send a left-click-block event for the block currently under the crosshair. Call every tick to simulate holding left-click to mine a block. Does nothing if the crosshair is not targeting a block.


interactTargeted()

Right-click interact with the entity currently under the crosshair (client.targetedEntity). Used for talking to NPCs, riding entities, etc.


closeScreen()

Close any currently open screen (inventory, container, sign, etc.). Equivalent to pressing Escape.


respawn()

Send a respawn request to the server. Call this when the player is on the death screen to respawn.


7.5 ChatActions


sendChat(String message)

Send a regular chat message on behalf of the player. Do not include a leading /.

ChatActions.sendChat("Hello, world!");

sendCommand(String command)

Send a command without the leading slash. PlayerAPI prefixes the slash internally.

ChatActions.sendCommand("tp 0 64 0");  // sends /tp 0 64 0
ChatActions.sendCommand("warp garden"); // sends /warp garden

7.6 SoundActions

Play sounds from the built-in Minecraft sound library or from your own mod's .ogg files.

Browse all Minecraft sound IDs at: https://misode.github.io/sounds/


registerSound(String namespace, String name) → SoundEvent

Register a new sound event. Call once during onInitializeClient(). The .ogg file must be at assets/<namespace>/sounds/<name>.ogg inside your mod's resources jar.

SoundActions.registerSound("mymod", "my_alert"); // assets/mymod/sounds/my_alert.ogg

play(Identifier soundId, float volume, float pitch)

Play a sound by its Identifier. Works for both registered mod sounds and all built-in Minecraft sounds.

Parameter Range Description
volume 0.0 – 2.0 1.0 = normal volume
pitch 0.5 – 2.0 1.0 = normal pitch/speed

play(String namespace, String name, float volume, float pitch)

Convenience overload accepting namespace and name as separate strings.

SoundActions.play("minecraft", "entity.player.levelup", 1.0f, 1.0f);

playById(String fullSoundId, float volume, float pitch)

Play by a full string ID such as "minecraft:entity.player.levelup". If no namespace colon is present, defaults to "minecraft".

SoundActions.playById("entity.experience_orb.pickup", 1.0f, 1.5f);

playRepeated(Identifier soundId, float volume, float pitch, int times, int intervalTicks)

Play a sound times times with intervalTicks ticks between each play. Uses the Scheduler internally.

// Play level-up sound 3 times, 1 second apart:
Identifier id = Identifier.of("minecraft", "entity.player.levelup");
SoundActions.playRepeated(id, 1.0f, 1.0f, 3, 20);

playRepeated(Identifier soundId, float volume, float pitch, int times)

Same as above with a fixed 20-tick (1 second) interval.


playByIdRepeated(String fullSoundId, float volume, float pitch, int times, int intervalTicks)

String-ID convenience version of playRepeated.

SoundActions.playByIdRepeated("minecraft:entity.player.levelup", 1.0f, 1.0f, 5, 20);

stopAll()

Stop all currently playing sounds.


7.7 DisplayActions

Show in-game HUD text without relying on Minecraft internals directly.


showTitle(String title, String subtitle)

Show a title and optional subtitle with the default timing (10 ticks fade-in / 70 ticks stay / 20 ticks fade-out).

Supports § colour codes. Pass "" for either parameter to leave it blank.

DisplayActions.showTitle("§aBot Started", "§7Primary path loaded");

showTitle(String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks)

Same as above with custom timing.


showActionBar(String message)

Show a short message on the action bar — the line that appears above the hotbar. Disappears after approximately 2 seconds. Supports § colour codes.

DisplayActions.showActionBar("§ePaused — waiting for repellent");

8. Human Actions

The Human API mirrors the Forced API but adds human-like imperfection: reaction delays, spring-physics camera movement with overshoot, random end-of-movement noise, and scroll-wheel slot switching. All timing parameters are tunable through HumanProfile.


8.1 HumanActions


Look methods (smooth, spring-physics)

All look methods start a spring-damper interpolation toward the target. There is a configurable reaction delay before movement begins, the camera accelerates toward the target, naturally overshoots, and settles with a small random noise offset. A 3-second watchdog forces completion if the spring is too slow.

Method Description
lookAt(float yaw, float pitch) Smooth look to a yaw/pitch pair.
lookAt(double x, double y, double z) Smooth look toward an exact world position.
lookAt(int x, int y, int z) Smooth look at the centre of a block (integer coords).
lookAt(BlockPos pos) Smooth look at a block position.
lookAt(int x, int y, int z, Direction face) Smooth look at a specific face of a block.
lookAt(BlockPos pos, Direction face) Same, with a BlockPos.
lookAt(int x, int y, int z, double xOffset, double yOffset, double zOffset) Smooth look at a custom point within a block.
lookYaw(float yaw) Change only yaw, keep current pitch.
lookPitch(float pitch) Change only pitch, keep current yaw.
cancelLook() Abort any in-progress look interpolation. View stays where it is.
isLooking() → boolean Returns true while a look (including reaction delay) is active.

Key methods (with reaction delay)

Method Description
pressKey(String key) Hold a key after a random [reactionMin, reactionMax] tick delay.
releaseKey(String key) Release a key after a short [keyDelayMin, keyDelayMax] tick delay.
tapKey(String key, int holdTicks) Tap a key for approximately holdTicks ticks, with jitter on both press and release.
pressKeys(String... keys) Press multiple keys simultaneously, all after the same reaction delay.
releaseAll() Release all keys after 1–2 ticks.

Inventory methods (with delay and scroll-through)

Method Description
switchToSlot(int targetSlot) Switch to hotbar slot, optionally scrolling through intermediate slots if HumanProfile.scrollThroughSlots is true.
switchToItem(String displayName) Switch to first matching display name, with delay.
switchToItemId(String idSubstring) Switch to first matching item ID, with delay.

Interaction methods (with delay)

Method Description
useItem() Use main-hand item after a [useItemDelayMin, useItemDelayMax] tick delay.
useOffhandItem() Use off-hand item after the same delay.
attackTargeted() Attack crosshair entity after a reaction delay.
interactTargeted() Interact with crosshair entity after a reaction delay.

Compound sequences

lookThenDo(double x, double y, double z, Runnable callback)

Start a human look toward the world position, then execute callback once the look has fully settled. Internally polls isLooking() every tick until false, then fires the callback. A 3-second watchdog fires the callback anyway to prevent hangs.

HumanActions.lookThenDo(100.0, 64.0, -200.0, () -> {
    HumanActions.useItem(); // fires only after camera settles
});

Overloads:

  • lookThenDo(int x, int y, int z, Runnable callback) — block centre
  • lookThenDo(BlockPos pos, Runnable callback) — BlockPos
  • lookThenDo(BlockPos pos, Direction face, Runnable callback) — block face
  • lookThenDo(float yaw, float pitch, Runnable callback) — raw yaw/pitch

getProfile() → HumanProfile

Returns the shared HumanProfile instance for reading or changing timing parameters.


8.2 HumanProfile

com.playerapi.HumanProfile — a singleton configuration object. All fields are public and can be modified at any time. Changes take effect on the next HumanActions call.

HumanProfile p = HumanProfile.getInstance();

Reaction time

Field Type Default Description
reactionMinTicks int 2 (~100 ms) Minimum delay before any human action begins.
reactionMaxTicks int 5 (~250 ms) Maximum delay before any human action begins. A real human averages 200–300 ms.

A random value in [reactionMin, reactionMax] is chosen each time.


Look / camera (spring-damper)

Field Type Default Description
lookSpringK float 0.30 Spring constant. Fraction of angular error added to velocity per tick. Higher = faster approach. Range: 0.15 (slow) – 0.50 (fast).
lookDamping float 0.55 Damping multiplier per tick. Values below ~2·√K produce overshoot. Range: 0.4 (bouncy) – 0.85 (barely any overshoot). Critical damping for K=0.30 is ≈1.095.
lookMaxDegPerTick float 35° Hard cap on rotational speed per tick. Prevents instant flicks.
lookNoiseMaxDeg float 0.8° Random noise added once the look settles. Simulates imprecision.
lookMaxSettleTicks int 40 If the look hasn't settled after this many ticks, it is forced to the exact target. Watchdog against infinite loops.

Micro-movements (idle hand tremor)

Field Type Default Description
enableMicroLook boolean false When true, tiny random camera nudges apply every tick while no look is in progress.
microLookChancePerTick float 0.012 Probability per tick of a micro-movement firing (≈ once per 4 seconds).
microLookMaxDeg float 0.4° Maximum yaw change per micro-movement. Pitch is half this value.

Key timing

Field Type Default Description
keyDelayMinTicks int 1 Minimum delay between key command and key going down.
keyDelayMaxTicks int 4 Maximum delay.
tapHoldJitterTicks int 3 Extra random ticks added to a tap's hold duration.

Inventory / scroll

Field Type Default Description
scrollThroughSlots boolean true When true, switchToSlot scrolls through intermediate slots rather than jumping directly.
scrollIntervalTicks int 2 Ticks between each scroll step when scrollThroughSlots is enabled.

Interaction timing

Field Type Default Description
useItemDelayMinTicks int 1 Minimum delay before a use-item action fires.
useItemDelayMaxTicks int 3 Maximum delay.

Built-in presets

HumanProfile.getInstance().applyFastPreset();    // near-instant, snappy look, minimal noise
HumanProfile.getInstance().applyCasualPreset();  // slow reactions, bouncy look, micro-movements
HumanProfile.getInstance().resetDefaults();      // restore all factory defaults
Preset reactionMin reactionMax lookSpringK lookDamping enableMicroLook
Default 2 5 0.30 0.55 false
Fast 1 2 0.48 0.58 false
Casual 3 7 0.20 0.50 true

9. Info (Read-Only Queries)

Info classes provide read-only access to game state. They never modify anything. All return safe defaults if the player is not in a world.


9.1 PlayerInfo

Query the local player's state.

Position & movement

Method Returns Description
getX() double Player feet X coordinate.
getY() double Player feet Y coordinate.
getZ() double Player feet Z coordinate.
getEyeY() double Player eye-level Y (getY() + eyeHeight).
getYaw() float Horizontal rotation (degrees, Minecraft convention).
getPitch() float Vertical rotation (degrees).
getVelocityX/Y/Z() double Per-axis velocity in blocks per tick.
isOnGround() boolean True if standing on a solid surface.
isSprinting() boolean True if the sprint flag is set.
isSneaking() boolean True if sneaking/crouching.
isSwimming() boolean True if in swim animation.
isFlying() boolean True if creative flight is active.
isCreativeFlightEnabled() boolean True if the player has fly ability (creative/spectator).
isSubmergedInWater() boolean True if fully submerged.
isInLava() boolean True if touching lava.

Vitals

Method Returns Description
getHealth() float Current health points (typically 0–20).
getMaxHealth() float Maximum health points.
getAbsorptionAmount() float Absorption (yellow hearts) amount.
getFoodLevel() int Hunger level (0–20).
getSaturation() float Saturation level.
getAir() int Remaining air ticks (max 300).
getMaxAir() int Maximum air ticks (typically 300).

Experience

Method Returns Description
getXpLevel() int Current XP level.
getXpProgress() float Progress toward next level (0.0–1.0).
getTotalXp() int Total accumulated XP points.

Identity & misc

Method Returns Description
getName() String Player's Minecraft username.
getUuid() String Player's UUID as a string.
getGameMode() String "SURVIVAL", "CREATIVE", "ADVENTURE", "SPECTATOR", or "UNKNOWN".
getHeldItem() ItemSnapshot Snapshot of the main-hand item.
getOffhandItem() ItemSnapshot Snapshot of the off-hand item.
getActiveEffects() List<EffectSnapshot> All active status effects.
hasEffect(String idSubstring) boolean True if any active effect's ID contains the substring (case-insensitive).
isInWorld() boolean True if the player entity exists in a loaded world. Use this as a guard at the top of tick handlers.
isScreenOpen() boolean True if any GUI screen is open (inventory, chest, chat, etc.).

9.2 InventoryInfo

Read inventory contents. Never modifies anything.

Inventory slot layout:

Range Contents
0–8 Hotbar
9–35 Main storage
36 Head armour
37 Chest armour
38 Leg armour
39 Feet armour
40 Off-hand

Hotbar

Method Returns Description
getSelectedSlot() int Currently selected hotbar slot (0–8).
getHotbarSlot(int slot) ItemSnapshot Snapshot of one hotbar slot.
getHotbar() List<ItemSnapshot> Snapshots of all 9 hotbar slots.
getHotbarSnapshot() List<ItemSnapshot> Identical to getHotbar(). Intended for change-detection by comparing with .equals().
hasItemInHotbar(String displayName) boolean True if any hotbar slot has an item matching the display name (case-insensitive exact match).
hasItemIdInHotbar(String idSubstring) boolean True if any hotbar slot has an item whose ID contains the substring.
findHotbarSlotByName(String displayName) int First matching slot index, or -1.
findHotbarSlotById(String idSubstring) int First matching slot index by item ID, or -1.

Full inventory

Method Returns Description
getMainSlot(int slot) ItemSnapshot Snapshot of any slot 0–35.
getMainInventory() List<ItemSnapshot> All 36 main inventory slots.
countByName(String displayName) int Total item count matching display name across all 36 slots.
countById(String idSubstring) int Total item count matching ID substring across all 36 slots.
hasEmptySlot() boolean True if at least one main inventory slot is empty.
emptySlotCount() int Number of empty slots in the main inventory.

Armour & off-hand

Method Returns Description
getArmorSlot(int armorIndex) ItemSnapshot 0=head, 1=chest, 2=legs, 3=feet.
getArmor() List<ItemSnapshot> All 4 armour slots in order (head → feet).
getOffhandItem() ItemSnapshot Off-hand slot (slot 40).

9.3 WorldInfo

Query the current world/dimension.

Dimension

Method Returns Description
getDimensionId() String e.g. "minecraft:overworld", "minecraft:the_nether".
isInOverworld() boolean
isInNether() boolean
isInEnd() boolean
getServerAddress() String Hostname:port of the server, "local" for singleplayer, "none" if not connected.
isSinglePlayer() boolean True if singleplayer or LAN.
isWorldLoaded() boolean True if a world is currently loaded client-side.

Time

Method Returns Description
getTimeOfDay() long Ticks into the current day (0–24000). Day starts at 0, night at ~13000.
getWorldAge() long Total world age in ticks (never resets).
isDay() boolean True if time < 13000.
isNight() boolean Inverse of isDay().

Weather

Method Returns Description
isRaining() boolean
isThundering() boolean

Blocks

Method Returns Description
getBlockId(int x, int y, int z) String Namespaced block ID, e.g. "minecraft:grass_block". Returns "minecraft:air" if unloaded.
getBlockIdRelative(int dx, int dy, int dz) String Block ID at an offset from the player's feet.
isAir(int x, int y, int z) boolean True if the block is air.
getSkyLight(int x, int y, int z) int Sky light level 0–15.
getBlockLight(int x, int y, int z) int Block light level 0–15.
getLight(int x, int y, int z) int Max of sky and block light.
isChunkLoaded(int x, int z) boolean True if the chunk at world coords x,z is loaded.
getBiomeId() String Biome at the player's current position.

Entities

Method Returns Description
getNearbyEntities(double radius) List<EntitySnapshot> All living entities within radius of the player. Excludes self. Uses a bounding-box search.
getNearbyPlayers(double radius) List<EntitySnapshot> Other player entities within radius.
getNearbyEntitiesMatching(double radius, Predicate<EntitySnapshot> filter) List<EntitySnapshot> Filtered version of getNearbyEntities.
getEntityAtCrosshair() Optional<EntitySnapshot> Entity under the crosshair (Minecraft's raycast target), or empty.
getBlockAtCrosshair() Optional<BlockSnapshot> Block under the crosshair, or empty. Includes all block-state properties.
getEntityNbt(int entityId) Map<String, String> Entity NBT as a flat string map. Currently returns an empty map — see Known Limitations.

Block scanning

// Find all wheat crops within 10 blocks:
List<BlockSnapshot> wheat = WorldInfo.getNearbyBlocks(10.0, "minecraft:wheat");

// Custom filter — mature crops only:
List<BlockSnapshot> matureWheat = WorldInfo.getNearbyBlocksMatching(10.0,
    b -> b.blockId().equals("minecraft:wheat") && b.stateEquals("age", "7"));

Performance: getNearbyBlocks and getNearbyBlocksMatching scan a full sphere. Keep radius ≤ 32 to avoid frame drops. The scan is O(r³).


9.4 TabListInfo

Query the tab-list player list. Servers use display names in the tab list to show sidebar information (scores, status text, etc.).

All methods call MinecraftClient.getNetworkHandler().getPlayerList() and return safe defaults when not connected.

Note on text stripping: Display names in the tab list are Minecraft Text objects. getAllLines() and search methods strip all formatting codes (colours, bold, etc.) and return plain strings. To inspect formatting (e.g. detect strikethrough), use isLineStrikethrough().


getAllLines() → List<String>

Returns all tab-list display-name strings, stripped of colour codes. Ordered by score (descending) then by username.


getAllLinesRaw() → List<String>

Returns all tab-list display-name strings. Intended for raw inspection, though note that Text.getString() in Fabric already strips §-style codes. Useful primarily to iterate PlayerListEntry objects when combined with other methods.


findLineWithPrefix(String prefix) → String

Returns the first stripped tab-list line that starts with prefix, or null.

String line = TabListInfo.findLineWithPrefix("Area:");
// → "Area: Garden"

findLinesWithPrefix(String prefix) → List<String>

Returns all stripped lines starting with prefix.


findLineContaining(String substring) → String

Returns the first stripped tab-list line that contains substring (case-insensitive), or null.

String line = TabListInfo.findLineContaining("Alive:");
// → "  Alive: 3 pests"

findLinesContaining(String substring) → List<String>

Returns all stripped lines containing substring (case-insensitive).


isLineStrikethrough(String substring) → boolean

Returns true if the first tab-list line containing substring has strikethrough styling applied to any of its text segments. Uses Text.visit() to walk the Style tree — the only reliable way to detect strikethrough since getString() strips all formatting.

// Detect a disabled/crossed-out line:
if (TabListInfo.isLineStrikethrough("Bonus Pest Chance:")) {
    // line is crossed out → bonus is disabled
}

extractValueAfterPrefix(String prefix) → String

Find the first line starting with prefix and return the text after the prefix, trimmed. Returns null if not found.

String area = TabListInfo.extractValueAfterPrefix("Area:"); // → "Garden"

parseKeyValueLines(String... prefixes) → Map<String, String>

Extract multiple key-value pairs in one call. Returns a map of prefix → value (or null if not found).

Map<String, String> data = TabListInfo.parseKeyValueLines("Area:", " Alive:", " Repellent:");
String area     = data.get("Area:");
String alive    = data.get(" Alive:");

extractInt(String prefix) → int

Find the first integer after the prefix. Returns -1 if not found or not parseable.

int pestCount = TabListInfo.extractInt(" Alive:"); // → 3

anyLineMatches(String regex) → boolean

Returns true if any stripped tab-list line matches the given Java regex.


Player list queries

Method Returns Description
getPlayerUsernames() List<String> Minecraft account names of all players in the tab list.
isPlayerOnline(String username) boolean True if a player with that username is in the tab list (case-insensitive).
getPlayerCount() int Total number of entries in the tab list.

10. Data Types

All types are immutable Java records. They are safe to store, pass between methods, and compare with .equals(). They hold no live references to Minecraft objects.


10.1 ItemSnapshot

com.playerapi.types.ItemSnapshot

record ItemSnapshot(
    String  itemId,      // namespaced ID, e.g. "minecraft:diamond_hoe"
    String  displayName, // localised display name, e.g. "Diamond Hoe"
    int     count,       // stack size
    int     damage,      // damage/durability value
    boolean enchanted    // true if any enchantments are present
)

Static fields:

  • ItemSnapshot.EMPTY — represents an empty/air slot. itemId = "minecraft:air", count = 0.

Methods:

  • isEmpty() — true if itemId is "minecraft:air" or count == 0.

Factory:

  • ItemSnapshot.from(ItemStack stack) — create from a live stack. Returns EMPTY for null/empty stacks.
ItemSnapshot held = PlayerInfo.getHeldItem();
if (!held.isEmpty() && held.displayName().contains("Hoe")) {
    System.out.println("Holding: " + held); // "Wheat Hoe x1 [skyblock:wheat_hoe]"
}

10.2 EntitySnapshot

com.playerapi.types.EntitySnapshot

record EntitySnapshot(
    int    entityId,    // client-side entity ID (not UUID)
    String typeId,      // namespaced entity type, e.g. "minecraft:zombie"
    String displayName, // display name (may include custom name tags)
    Vec3d  position,    // world position (x, y, z)
    float  health,      // current health (0 for non-LivingEntity)
    float  maxHealth    // max health (0 for non-LivingEntity)
)

Note: entityId is the client-side integer ID, not the UUID. It changes on every world join. Do not persist entity IDs across sessions.

List<EntitySnapshot> nearby = WorldInfo.getNearbyEntities(16.0);
for (EntitySnapshot e : nearby) {
    System.out.printf("%s at %.1f/%.1f/%.1f (%.0f/%.0f HP)%n",
        e.displayName(), e.position().x, e.position().y, e.position().z,
        e.health(), e.maxHealth());
}

10.3 BlockSnapshot

com.playerapi.types.BlockSnapshot

record BlockSnapshot(
    int                 x,
    int                 y,
    int                 z,
    String              blockId,    // namespaced ID, e.g. "minecraft:wheat"
    Map<String, String> blockState  // property name → value, e.g. {"age": "7"}
)

Methods:

Method Description
getState(String key) Returns the value of a block-state property, or null.
hasState(String key) True if the property exists.
stateEquals(String key, String value) True if the property value equals value (case-insensitive).
Optional<BlockSnapshot> block = WorldInfo.getBlockAtCrosshair();
block.ifPresent(b -> {
    System.out.println(b.blockId());         // "minecraft:wheat"
    System.out.println(b.getState("age"));   // "7"
    System.out.println(b.stateEquals("age", "7")); // true (fully grown)
});

Common block state properties:

Block Key Values
Wheat / Carrot / Potato age "0""7" (7 = fully grown)
Sugarcane (no state; check block above)
Pumpkin / Melon stem age "0""7"
Nether Wart age "0""3"
Any log/plank axis "x", "y", "z"

10.4 EffectSnapshot

com.playerapi.types.EffectSnapshot

record EffectSnapshot(
    String effectId,       // namespaced ID, e.g. "minecraft:speed"
    String displayName,    // localised name, e.g. "Speed"
    int    amplifier,      // 0-indexed (amplifier 0 = Level I)
    int    remainingTicks  // ticks remaining
)

Methods:

  • level() — returns amplifier + 1 (1-indexed for display).
  • remainingSeconds() — returns remainingTicks / 20.0f.
for (EffectSnapshot e : PlayerInfo.getActiveEffects()) {
    System.out.printf("%s %d — %.1fs remaining%n",
        e.displayName(), e.level(), e.remainingSeconds());
}

boolean hasSpeed = PlayerInfo.hasEffect("speed");

11. Key Names Reference

Used by MovementActions, HumanActions.pressKey/releaseKey/tapKey:

Key Name Description
"forward" W key (walk forward)
"back" S key (walk backward)
"left" A key (strafe left)
"right" D key (strafe right)
"sprint" Left Ctrl (sprint modifier)
"sneak" Left Shift (sneak/crouch)
"jump" Space bar
"attack" Left mouse button (break block / attack entity)
"use" Right mouse button (use item / place block / interact)

12. Known Limitations

WorldInfo.getEntityNbt() — not implemented

getEntityNbt(int entityId) always returns an empty map. The underlying entity.saveSelfNbt() API changed in Minecraft 1.21.x to require a RegistryManager argument, and the correct method name needs verification via ./gradlew genSources. This is a stub pending a fix in a future version.

TabListInfo.getAllLinesRaw() — same as getAllLines()

Despite the name, getAllLinesRaw() still uses Text.getString() which strips §-style codes. Use isLineStrikethrough() when you need formatting information.

Scheduler cancelAll() is global

Scheduler.cancelAll() cancels tasks from all mods using PlayerAPI, not just the caller. If multiple mods are running simultaneously and one calls cancelAll(), the other's scheduled tasks are also cancelled. Design mods accordingly — avoid cancelAll() if other mods may be relying on scheduled tasks.

scheduleRepeating() with times=0 — cancel token only covers first invocation

Infinite repeating tasks self-reschedule after firing. The token returned by scheduleRepeating(period, 0, task) can only cancel the currently pending invocation. If the task fires before you cancel, it will reschedule itself and continue. Use a shared flag inside the callback to implement clean infinite-loop cancellation:

boolean[] running = {true};
Scheduler.scheduleRepeating(20, 0, () -> {
    if (!running[0]) return; // effectively stops the loop
    doSomething();
});
// To stop: running[0] = false;

MovementActions requires setActive(true) first

Calling pressKey() without calling setActive(true) first has no effect. The mixin ignores the key state tracker when not active. Always call setActive(true) in your bot's start method and setActive(false) / releaseAll() in the stop method.

Block break events on non-instant blocks

PlayerAPIEvents.BLOCK_BROKEN fires via ClientPlayerBlockBreakEvents.AFTER. For survival-mode blocks that require hold-to-mine, this fires after server confirmation, not immediately on the client. For instant-break blocks (crops, flowers, tall grass), it fires immediately.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages