COWMODSКаталог модов

PlayerAPI

Client-side Fabric library providing player movement, inventory, world queries, and human-like automation APIs for mod developers.

Автор: NoName_5810обновлён 7 мая 2026 г.
ТехнологииУтилиты

Описание

Notice Version 1.8.0 is the last version available on Modrinth. New versions will continue to be released on the GitHub page https://github.com/noname-mods/PlayerAPI.

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.7.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);
}
ParameterDescription
senderDisplay name of the sender, stripped of colour codes. Empty string for system/game messages.
messageFull 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);
}
ParameterDescription
slotInventory slot that changed. -1 means a bulk/unknown change.
newItemIdNamespaced 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.

ParameterDescription
periodTicksTicks between each invocation.
timesNumber of times to fire. Pass 0 for infinite (runs until cancelled).
taskThe 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:

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

Pitch: −90° = straight up, 0° = 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.

ParameterRangeDescription
volume0.0 – 2.01.0 = normal volume
pitch0.5 – 2.01.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.

MethodDescription
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() → booleanReturns true while a look (including reaction delay) is active.

Key methods (with reaction delay)

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

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

MethodDescription
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

FieldTypeDefaultDescription
reactionMinTicksint2 (~100 ms)Minimum delay before any human action begins.
reactionMaxTicksint5 (~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)

FieldTypeDefaultDescription
lookSpringKfloat0.30Spring constant. Fraction of angular error added to velocity per tick. Higher = faster approach. Range: 0.15 (slow) – 0.50 (fast).
lookDampingfloat0.55Damping 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.
lookMaxDegPerTickfloat35°Hard cap on rotational speed per tick. Prevents instant flicks.
lookNoiseMaxDegfloat0.8°Random noise added once the look settles. Simulates imprecision.
lookMaxSettleTicksint40If 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)

FieldTypeDefaultDescription
enableMicroLookbooleanfalseWhen true, tiny random camera nudges apply every tick while no look is in progress.
microLookChancePerTickfloat0.012Probability per tick of a micro-movement firing (≈ once per 4 seconds).
microLookMaxDegfloat0.4°Maximum yaw change per micro-movement. Pitch is half this value.

Key timing

FieldTypeDefaultDescription
keyDelayMinTicksint1Minimum delay between key command and key going down.
keyDelayMaxTicksint4Maximum delay.
tapHoldJitterTicksint3Extra random ticks added to a tap's hold duration.

Inventory / scroll

FieldTypeDefaultDescription
scrollThroughSlotsbooleantrueWhen true, switchToSlot scrolls through intermediate slots rather than jumping directly.
scrollIntervalTicksint2Ticks between each scroll step when scrollThroughSlots is enabled.

Interaction timing

FieldTypeDefaultDescription
useItemDelayMinTicksint1Minimum delay before a use-item action fires.
useItemDelayMaxTicksint3Maximum 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
PresetreactionMinreactionMaxlookSpringKlookDampingenableMicroLook
Default250.300.55false
Fast120.480.58false
Casual370.200.50true

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

MethodReturnsDescription
getX()doublePlayer feet X coordinate.
getY()doublePlayer feet Y coordinate.
getZ()doublePlayer feet Z coordinate.
getEyeY()doublePlayer eye-level Y (getY() + eyeHeight).
getYaw()floatHorizontal rotation (degrees, Minecraft convention).
getPitch()floatVertical rotation (degrees).
getVelocityX/Y/Z()doublePer-axis velocity in blocks per tick.
isOnGround()booleanTrue if standing on a solid surface.
isSprinting()booleanTrue if the sprint flag is set.
isSneaking()booleanTrue if sneaking/crouching.
isSwimming()booleanTrue if in swim animation.
isFlying()booleanTrue if creative flight is active.
isCreativeFlightEnabled()booleanTrue if the player has fly ability (creative/spectator).
isSubmergedInWater()booleanTrue if fully submerged.
isInLava()booleanTrue if touching lava.

Vitals

MethodReturnsDescription
getHealth()floatCurrent health points (typically 0–20).
getMaxHealth()floatMaximum health points.
getAbsorptionAmount()floatAbsorption (yellow hearts) amount.
getFoodLevel()intHunger level (0–20).
getSaturation()floatSaturation level.
getAir()intRemaining air ticks (max 300).
getMaxAir()intMaximum air ticks (typically 300).

Experience

MethodReturnsDescription
getXpLevel()intCurrent XP level.
getXpProgress()floatProgress toward next level (0.0–1.0).
getTotalXp()intTotal accumulated XP points.

Identity & misc

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

9.2 InventoryInfo

Read inventory contents. Never modifies anything.

Inventory slot layout:

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

Hotbar

MethodReturnsDescription
getSelectedSlot()intCurrently selected hotbar slot (0–8).
getHotbarSlot(int slot)ItemSnapshotSnapshot 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)booleanTrue if any hotbar slot has an item matching the display name (case-insensitive exact match).
hasItemIdInHotbar(String idSubstring)booleanTrue if any hotbar slot has an item whose ID contains the substring.
findHotbarSlotByName(String displayName)intFirst matching slot index, or -1.
findHotbarSlotById(String idSubstring)intFirst matching slot index by item ID, or -1.

Full inventory

MethodReturnsDescription
getMainSlot(int slot)ItemSnapshotSnapshot of any slot 0–35.
getMainInventory()List<ItemSnapshot>All 36 main inventory slots.
countByName(String displayName)intTotal item count matching display name across all 36 slots.
countById(String idSubstring)intTotal item count matching ID substring across all 36 slots.
hasEmptySlot()booleanTrue if at least one main inventory slot is empty.
emptySlotCount()intNumber of empty slots in the main inventory.

Armour & off-hand

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

9.3 WorldInfo

Query the current world/dimension.

Dimension

MethodReturnsDescription
getDimensionId()Stringe.g. "minecraft:overworld", "minecraft:the_nether".
isInOverworld()boolean
isInNether()boolean
isInEnd()boolean
getServerAddress()StringHostname:port of the server, "local" for singleplayer, "none" if not connected.
isSinglePlayer()booleanTrue if singleplayer or LAN.
isWorldLoaded()booleanTrue if a world is currently loaded client-side.

Time

MethodReturnsDescription
getTimeOfDay()longTicks into the current day (0–24000). Day starts at 0, night at ~13000.
getWorldAge()longTotal world age in ticks (never resets).
isDay()booleanTrue if time < 13000.
isNight()booleanInverse of isDay().

Weather

MethodReturnsDescription
isRaining()boolean
isThundering()boolean

Blocks

MethodReturnsDescription
getBlockId(int x, int y, int z)StringNamespaced block ID, e.g. "minecraft:grass_block". Returns "minecraft:air" if unloaded.
getBlockIdRelative(int dx, int dy, int dz)StringBlock ID at an offset from the player's feet.
isAir(int x, int y, int z)booleanTrue if the block is air.
getSkyLight(int x, int y, int z)intSky light level 0–15.
getBlockLight(int x, int y, int z)intBlock light level 0–15.
getLight(int x, int y, int z)intMax of sky and block light.
isChunkLoaded(int x, int z)booleanTrue if the chunk at world coords x,z is loaded.
getBiomeId()StringBiome at the player's current position.

Entities

MethodReturnsDescription
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

MethodReturnsDescription
getPlayerUsernames()List<String>Minecraft account names of all players in the tab list.
isPlayerOnline(String username)booleanTrue if a player with that username is in the tab list (case-insensitive).
getPlayerCount()intTotal 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:

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

BlockKeyValues
Wheat / Carrot / Potatoage"0" – "7" (7 = fully grown)
Sugarcane(no state; check block above)—
Pumpkin / Melon stemage"0" – "7"
Nether Wartage"0" – "3"
Any log/plankaxis"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 NameDescription
"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.

Последние версии

Все версии →
release

PlayerAPI 1.8.0

1.8.0
7 мая 2026 г.454Minecraft: 1.21.11fabric
Скачать
Что нового
## PlayerAPI — Patch Notes --- #### New: `DisplayActions` New class for showing in-game overlays without touching Minecraft internals directly. - `showTitle(title, subtitle)` — shows a title and subtitle with default fade timing (10/70/20 ticks) - `showTitle(title, subtitle, fadeIn, stay, fadeOut)` — same but with custom timing - `showActionBar(message)` — shows text on the action bar above the hotbar - All methods accept `§` colour codes --- #### New: `BlockSnapshot` type New immutable record representing a block in the world. - Fields: `x`, `y`, `z`, `blockId` (e.g. `"minecraft:wheat"`), `blockState` (`Map<String, String>`) - Helpers: `getState(String key)`, `hasState(String key)`, `stateEquals(String key, String value)` --- #### `WorldInfo` — new methods - `getNearbyEntitiesMatching(double radius, Predicate<EntitySnapshot>)` — filtered version of `getNearbyEntities`; avoids iterating the full list on the caller's side - `getNearbyBlocks(double radius, String blockId)` — returns all blocks of a specific type within radius (sphere scan) - `getNearbyBlocksMatching(double radius, Predicate<BlockSnapshot>)` — same but with a custom predicate - `getEntityAtCrosshair()` → `Optional<EntitySnapshot>` — entity the player is currently looking at (uses the client crosshair raycast) - `getBlockAtCrosshair()` → `Optional<BlockSnapshot>` — block the player is currently looking at - `getEntityNbt(int entityId)` → `Map<String, String>` — reads top-level NBT values for a nearby entity; moderately expensive, do not call every tick > **Bug fix:** `getNearbyEntities` and `getNearbyPlayers` were incorrectly passing `x`, `y`, `z` as > separate arguments to `Box.of()` instead of `new Vec3d(x, y, z)`. The bounding box was being > constructed with the wrong overload, causing entities to be missed or wrongly included. Now fixed. --- #### `TabListInfo` — new methods - `getAllLinesRaw()` — returns display name strings before formatting stripping (for inspecting raw style data) - `findLinesWithPrefix(String prefix)` — returns **all** lines with the prefix, not just the first - `findLinesContaining(String substring)` — case-insensitive substring search, returns all matches - `parseKeyValueLines(String... prefixes)` — batch extracts multiple `"Key: Value"` pairs in one call - `anyLineMatches(String regex)` — tests whether any tab list line matches a regex - `isLineStrikethrough(String substring)` — returns `true` if the first line containing the substring has any segment with strikethrough styling applied; uses `Text.visit()` to walk the style tree (the only reliable method — `getString()` strips all formatting) - `getPlayerUsernames()` — returns actual Minecraft account names (not display names) from the tab list - `isPlayerOnline(String username)` — case-insensitive check for a player's presence - `getPlayerCount()` — total number of entries in the tab list --- #### `SoundActions` — new methods - `playById(String fullSoundId, float volume, float pitch)` — plays any vanilla or registered sound by its string ID (e.g. `"entity.player.levelup"`); if no namespace is given, defaults to `minecraft:` - `playRepeated(Identifier, float volume, float pitch, int times, int intervalTicks)` — plays a sound N times with a configurable tick gap between repeats - `playRepeated(Identifier, float volume, float pitch, int times)` — same with the default 20-tick (1 second) interval - `playByIdRepeated(String fullSoundId, float volume, float pitch, int times, int intervalTicks)` — string-ID convenience overload of `playRepeated` --- #### New: Update checker PlayerAPI now checks for newer releases on every world join and shows a single chat message if one is found. Nothing is downloaded automatically. - Runs on a daemon thread so the network request never blocks the game thread - Reads the `tag_name` field from the GitHub Releases API and compares it against the installed version using numeric segment comparison (handles both `v1.2.0` and `1.2.0` tag formats) - Only fires the notification once per world join - Silent on failure (network errors, offline, restricted networks) - Can be disabled in the new config screen --- #### New: `PlayerAPIConfig` GSON-backed config singleton persisted to `config/playerapi/config.json`. - `isUpdateCheckEnabled()` / `setUpdateCheckEnabled(boolean)` — controls the update check - `load()` / `save()` — called automatically on world init and config screen close --- #### New: Config screen A YACL config screen with a single **Updates** tab containing the update check toggle. Accessible from the ModMenu mods list if ModMenu is installed. --- #### New: ModMenu integration Implements `ModMenuApi` to add a **Config** button to PlayerAPI's entry in the ModMenu mods list. ModMenu remains an optional dependency — PlayerAPI runs normally without it. --- #### New dependencies | Dependency | Type | Notes | |---|---|---| | `yet-another-config-lib` (YACL3) | Required | Needed for the config screen | | `modmenu` | Optional | Adds the Config button to the mods list if present |
release

PlayerAPI 1.6.0

1.6.0
25 апр. 2026 г.38Minecraft: 1.21.11fabric
Скачать
Что нового
Added method to notify other mods that a block was broken
release

PlayerAPI 1.5.0

1.5.0
1 апр. 2026 г.66Minecraft: 1.21.11fabric
Скачать
release

PlayerAPI 1.1.0

1.1.0
23 мар. 2026 г.23Minecraft: 1.21.11fabric
Скачать
Что нового
This is the first public release of this mod, even though it is version 1.1.0.