# Novo Adbot scripting

> How to write Novo Adbot bot scripts: a small sandboxed Python-style language with 225 bot functions and 73 events.

## Your first script

```python
PASSWORD = "my-password"

async def on_join(bot):
    bot.sleep(3)
    bot.send_chat(f"/login {PASSWORD}")

async def on_chat(bot, message):
    if "hello" in message.lower():
        message.reply("hi " + message.sender)
```

- A script is a set of **handlers**: functions named after an event (`on_join`, `on_chat`, `on_kick`, …). The bot runs a handler each time its event happens.
- The first parameter is always the bot. The others are what the event gives you, like `message` for `on_chat` or `reason` for `on_kick`.
- `bot.send_chat(...)` and `send_chat(...)` do the same thing. `await` is optional.
- Arguments go by position or by name: `bot.find_block("oak_log", radius=16)`.
- Mistakes come back with the line number and a “did you mean” suggestion.

To run a script, pick it when you start an account, or assign it under **Details → Behavior** on the Accounts page. The assigned script runs every time the account starts. Its `log.info(...)` lines and errors appear in the editor's Output panel.

## What functions give back

Functions return numbers, text, `True`/`False`, lists, `None`, or records you read with a dot. Records that stand for text act like text: `"hi" in message`, `player == "Steve"`.

| Record | Fields |
| --- | --- |
| message | `.text`, `.sender` (None for server messages), `.kind`, `.is_whisper`, `.matches(regex)`, `.reply(text)` |
| player | `.name`, `.uuid`, `.display_name`, `.prefix`, `.suffix`, `.team`, `.ping`, `.gamemode`, `.position`, `.distance` |
| entity | `.type`, `.name`, `.uuid`, `.position`, `.velocity`, `.health`, `.distance`, `.is_player`, `.is_hostile`, `.is_item`, `.item` |
| block | `.id`, `.state`, `.position`, `.x`, `.y`, `.z`, `.is_air`, `.is_solid` |
| item | `.id`, `.name`, `.count`, `.slot`, `.hotbar` |
| position | `.x`, `.y`, `.z`, `.distance_to(other)`, `.offset(dx, dy, dz)`, `.floor()`, `.center()` |

Inventory slots use Minecraft's numbers: 5–8 armor, 9–35 main inventory, 36–44 hotbar (hotbar slot 0 is 36), 45 off hand.

## Settings and variables

Put settings at the top of the script (`DELAY = 30`, `HOME = pos(0, 64, 0)`). A variable set in a handler is shared with every other handler. New names inside a helper function stay inside that function.

```python
kills = 0                       # shared by every handler

async def on_attack(bot, entity):
    kills += 1

def count_logs(bot):
    total = 0                   # only exists inside count_logs
    for item in bot.find_items("oak_log"):
        total += item.count
    return total
```

## Helpers, timers, and background tasks

Any `def` that isn't named after an event is a helper. Call it yourself, or hand it to the bot without quotes or brackets. If its first parameter is `bot`, the bot is passed in for you.

```python
def patrol(bot):
    bot.walk_to(10, 64, 10)
    bot.walk_to(-10, 64, -10)

async def on_join(bot):
    bot.every(60, patrol)            # every minute
    bot.after(5, patrol)             # once, in 5 seconds
    task = bot.run_task(patrol)      # in the background: task.pause(), .resume(), .cancel(), .wait()
```

## Waiting

Every wait takes `timeout=` and returns `None` or `False` if time runs out.

```python
msg = bot.wait_for_chat("Please log in", timeout=10)
if msg is None:
    log.warn("no login prompt")

bot.wait_for_health(15, timeout=30)
bot.wait_until(is_safe, timeout=60)   # is_safe is one of your functions
```

## Errors

```python
try:
    bot.break_block(10, 64, 10)
except Exception as e:
    log.warn("couldn't mine: " + e.message)
```

A failing handler never stops the others. The error is logged with its line number and `on_error(bot, error)` runs. `bot.try_action(fn, retries=3)` and `bot.retry(fn)` retry flaky steps; `bot.restart_on_error(True)` restarts the script after a crash.

## Staying online 24/7

The account's **Keep online** setting (on by default) already rejoins after kicks and restarts. A script lives as long as its session: handlers keep listening, and a `while True` loop with a wait inside runs for as long as the bot does. During a reconnect, anything that needs the world (moving, looking, attacking) waits until the bot is back instead of failing, so loops carry on by themselves.

```python
def guard(bot):
    while True:
        mob = bot.find_entity("hostile", radius=6)
        if mob:
            bot.attack(mob)
        bot.sleep(0.5)

async def on_start(bot):
    bot.run_task(guard)

async def on_chat(bot, message):     # keeps listening across every rejoin
    if message == "!pos":
        message.reply(str(bot.get_position()))
```

Variables, timers, and tasks survive a reconnect. `on_reconnect` runs after the rejoin and `on_join` runs again. To control reconnects from the script instead, use `bot.auto_reconnect(True, delay=10, max_attempts=0)` (0 = keep trying). A ban or mute takes the bot off the server and keeps it off.

## Anti-AFK

`bot.anti_afk(interval=30, jump=True, rotate=True, move=False, swing=False, sneak=False, command=None, no_fly=True)` does a small random action every interval (±25%). With `no_fly` the bot also lands whenever the server has it flying, like ranked players in Hypixel lobbies. It is the same as the account's Anti-AFK setting: calling it replaces that setting for this session, `bot.anti_afk(False)` turns it off, and `bot.afk_settings()` shows what is running.

## Webhooks

```python
async def on_kick(bot, reason):
    bot.webhook(reason, title="Kicked", level="error")

async def on_whisper(bot, message):
    bot.webhook(f"{message.sender}: {message.text}", webhook="dms")
```

`bot.webhook(message, title=None, level="info", webhook=None)` posts to a webhook you added under Webhooks (Discord, Slack, or any URL that accepts JSON). `webhook=` picks one by name; otherwise your default is used. Scripts never see the URL. Each bot can send 20 per minute.

## Raw packets

```python
bot.send_raw_packet("swing", ["varint", 0])                  # main hand
bot.send_raw_packet("chat_command", ["string", "spawn"])     # unsigned command
bot.send_raw_packet(0x3F, "00")                               # id + hex body
```

For experts: sends a serverbound play packet by name (or numeric id) with a body given as hex text or type/value pairs (bool, byte, ubyte, short, ushort, int, long, float, double, varint, varlong, angle, string, uuid, position, bytes). The body isn't checked, so a wrong one gets the bot kicked — use the normal functions when one exists. At most 50 per second.

## The language

**Supported:** numbers, text and f-strings (`f"hp={hp:.1f}"`), `True`/`False`/`None`, lists with indexing, slicing and `.append()`, `if`/`elif`/`else`, `while`, `for`, `break`, `continue`, `return`, `try`/`except`/`finally`, `and`/`or`/`not`, `in`, `is None`, `+= -= *= /=`, and helper functions.

**Not supported, on purpose:** imports, files, network, classes, lambdas, decorators, dictionaries, comprehensions, and a `def` inside another `def`. Scripts are sandboxed: they can only affect their own bot.

**Limit:** each handler gets about 200,000 steps of work between waits. Any wait (like `bot.sleep`) refills it, so a loop that never waits is stopped, while a loop that waits can run for days.

## Block builder

- Every function and event below is also a block. Drag an event (like **When joined server**) onto the canvas, then drag blocks under it until they snap.
- **If**, **Repeat**, **Forever**, and the other loop blocks hold blocks inside them.
- Dragging a block takes the blocks under it along. Drop it back on the block list to delete it; Ctrl+Z undoes.
- A stack that isn't under an event is kept on the canvas but doesn't run.
- Type values right on the block. Click a block to see all its options. The `{ }` switch next to a field lets you type code instead of a plain value — a variable like `message` or an expression like `bot.get_health() < 10`.
- **Script variables** holds the settings at the top of a script (`COUNT = 3`); they're set once when the script starts and every flow can read them.
- The **Code** block holds lines of Python for anything the other blocks can't show, like `items.append(x)`. Tab indents inside it.
- Saving turns the blocks into the script's code (press **Code** to read it). Code written in the code editor opens here as blocks, with any line that has no matching block kept in a Code block; it runs exactly the same. Saving from the builder drops comments.

## API reference

225 bot functions and 73 events. Call functions as `bot.name(...)`. Handle events with `async def on_event(bot, ...)` using the parameters listed.

### Connection

- `async def on_connect(bot)` — The connection to the server is established.
- `async def on_login(bot)` — Login finished; the world is loading.
- `async def on_spawn(bot)` (also: on_join) — Spawned into the world (after every join and rejoin).
- `async def on_ready(bot)` — Spawned and the surrounding chunks are loaded.
- `async def on_disconnect(bot, reason)` — The connection ended, for any reason.
- `async def on_kick(bot, reason)` — The server sent a disconnect message (reason text as received). If the server closes the socket before the message arrives, only `on_disconnect` fires.
- `async def on_ban(bot, reason)` — Disconnected with a ban/restriction message.
- `async def on_server_transfer(bot, info)` — Moved between servers behind a proxy (BungeeCord/Velocity) or transferred; `info.type` is "switch" or "transfer".
- `bot.reconnect(delay=1.0)` — Drop the connection and rejoin the same server after `delay` seconds. The script keeps running and `on_reconnect` fires after the rejoin.
- `bot.disconnect()` — Leave the server. Auto-reconnect is turned off first, so the session ends.
- `bot.is_connected()` → bool — True while connected to the server.
- `bot.is_spawned()` → bool — True once the bot has spawned into the world.
- `bot.last_kick_reason()` → str | None — The raw reason text of the most recent kick, or None.
- `bot.last_disconnect_reason()` → str | None — The raw reason of the most recent disconnect of any kind, or None.
- `bot.last_kick_classification()` → str | None — How the last disconnect was classified: kick, temporary_ban, permanent_ban, server_restart, network_failure, ...
- `bot.wait_for_connect(timeout=None)` → bool — Wait until connected. Returns False on timeout.
- `bot.wait_for_spawn(timeout=None)` → bool — Wait until spawned in the world. Returns False on timeout.

### Chat

- `async def on_chat(bot, message)` — Any chat or system message. `message.text`, `message.sender` (None for system), `message.kind`, `message.matches(regex)`.
- `async def on_system_message(bot, message)` (also: on_server_message) — A message from the server itself (not a player).
- `async def on_whisper(bot, message)` — A private message to the bot.
- `bot.send_chat(text)` (also: chat) — Send a chat message.
- `bot.send_command(command)` — Run a server command. The leading `/` is optional.
- `bot.whisper(player, text)` — Send a private message (`/msg player text`).
- `bot.wait_for_chat(text=None, timeout=None, regex=False)` → message | None — Wait for a chat or system message containing `text` (any message if omitted; a regular expression if `regex=True`). Returns the message, or None on timeout.

### Players & tab list

- `async def on_player_join(bot, player)` (also: on_player_seen) — A player appeared in the tab list.
- `async def on_player_leave(bot, player)` (also: on_player_left) — A player left the tab list.
- `async def on_player_list_update(bot, player)` — A tab-list entry changed (display name, game mode, ping).
- `bot.get_players()` → list[player] — Everyone in the tab list.
- `bot.find_player(name)` → player | None — Look up an online player by name (case-insensitive).
- `bot.find_players(prefix=None, suffix=None, team=None, tag=None, regex=None, distance_lt=None)` → list[player] — Filter online players by team prefix/suffix, team name, a tag in their display name, a name regex, or distance (only players in render range have a distance).
- `bot.is_player_online(name)` → bool — True if the player is in the tab list.

### Movement

- `bot.move_forward(seconds=None)` — Walk forward. With `seconds`, walk that long and stop; without, keep walking until `stop_moving()`.
- `bot.move_backward(seconds=None)` — Walk backward (see `move_forward`).
- `bot.move_left(seconds=None)` — Strafe left (see `move_forward`).
- `bot.move_right(seconds=None)` — Strafe right (see `move_forward`).
- `bot.move(direction, duration=None)` — Walk in a direction: forward, backward, left, right, forward_left, forward_right, backward_left, backward_right.
- `bot.stop_moving()` — Stop walking, sprinting, and following.
- `bot.jump()` — Jump once.
- `bot.sprint(on=True)` — Start sprinting forward (`sprint(False)` stops).
- `bot.crouch(on=True)` (also: sneak) — Hold or release sneak.
- `bot.walk_to(x, y=None, z=None, timeout=60.0)` → bool — Walk to coordinates or an entity/player/block without breaking blocks. Returns True when it arrives.
- `bot.follow(target, distance=2.0)` — Keep following a player (name or player record) until `stop_following()`.
- `bot.stop_following()` — Stop following.
- `bot.is_moving()` → bool — True while walking, following, or pathfinding.
- `bot.is_sprinting()` → bool — True while sprinting.
- `bot.is_crouching()` → bool — True while sneaking.
- `bot.is_swimming()` → bool — True while swimming.
- `bot.is_flying()` → bool — True while flying (creative/spectator or allowed flight).
- `bot.is_on_ground()` → bool — True while standing on a block.
- `bot.stop_flying()` — Stop flying and drop to the ground.

### Camera

- `bot.look(yaw, pitch)` — Face an absolute direction. Yaw: 0 south, 90 west, 180 north, -90 east. Pitch: -90 up, 90 down.
- `bot.look_at(x, y=None, z=None)` — Face a point, block, entity, or player.
- `bot.turn_left(degrees=90.0)` — Turn left by `degrees`.
- `bot.turn_right(degrees=90.0)` — Turn right by `degrees`.
- `bot.look_up(degrees=30.0)` — Tilt the camera up.
- `bot.look_down(degrees=30.0)` — Tilt the camera down.
- `bot.rotate(dyaw, dpitch)` — Turn relative to the current direction.
- `bot.get_yaw()` → float — Current yaw in degrees.
- `bot.get_pitch()` → float — Current pitch in degrees.
- `bot.get_facing_direction()` → str — north, south, east, or west.

### Interaction

- `bot.attack(target=None)` → bool — Hit an entity: the one given, or whatever is under the crosshair. Returns False if there was nothing to hit.
- `bot.hold_attack()` — Keep the attack button held (mines the targeted block / hits repeatedly).
- `bot.release_attack()` — Release the attack button.
- `bot.use()` (also: interact) — Right-click with the held item.
- `bot.hold_use()` — Keep right-click held (eat, draw a bow, block with a shield).
- `bot.release_use()` — Release right-click.
- `bot.interact_block(x=None, y=None, z=None)` — Right-click a block (door, lever, button, chest...). Defaults to the block under the crosshair. Walks into reach first if needed.
- `bot.interact_entity(target=None)` → bool — Right-click an entity (villager, mount...). Defaults to the crosshair target.
- `bot.break_block(x=None, y=None, z=None, timeout=15.0)` → bool — Mine a block (crosshair block by default) and wait until it is gone. Walks into reach first if needed. Returns True if it broke.
- `bot.place_block(x, y=None, z=None, item_id=None)` → bool — Place the held block (or `item_id`, equipped first) at a position next to an existing block. Walks into reach first if needed. Returns True if the block appeared.
- `bot.open_container(x=None, y=None, z=None, timeout=5.0)` → screen | None — Open a chest, barrel, furnace, crafting table, ... (walking into reach first if needed) and wait for it. Returns the open screen or None.
- `bot.close_container()` (also: close_screen) — Close the open container.
- `bot.swing_hand()` — Play the arm swing animation.
- `bot.swap_hands()` — Swap main hand and off hand items (F).
- `bot.pick_block(x=None, y=None, z=None)` → bool — Select the hotbar slot holding the targeted block's item, like middle-click.
- `bot.respawn()` — Click respawn after dying.

### Inventory

- `async def on_inventory_change(bot)` — Something in the inventory changed.
- `async def on_slot_change(bot, slot, item)` — One slot changed (`item` is None when emptied).
- `bot.get_inventory()` → inventory — The player inventory: `.items` (list of items), `.free_slots`, `.is_full`, `.selected_slot`. `inventory.is_full()` also works.
- `bot.get_slot(slot)` → item | None — The item in an inventory slot (5–8 armor, 9–35 main, 36–44 hotbar, 45 off hand).
- `bot.set_hotbar_slot(slot)` — Select hotbar slot 0–8.
- `bot.get_selected_slot()` → int — The selected hotbar slot, 0–8.
- `bot.get_held_item()` → item | None — The item in the main hand.
- `bot.find_item(item_id)` → item | None — First inventory slot holding `item_id` (e.g. "bread").
- `bot.find_items(item_id)` → list[item] — Every inventory slot holding `item_id`.
- `bot.has_item(item_id, count=1)` → bool — True if the inventory holds at least `count` of the item.
- `bot.count_item(item_id)` → int — Total count of an item across the inventory.
- `bot.inventory_count()` → int — Number of non-empty inventory slots.
- `bot.use_item(item_id=None)` → bool — Equip `item_id` if given, then right-click with it (eat, drink, throw...).
- `bot.equip_item(item_id, hand="main")` → bool — Put an item in the hand (hotbar is used and selected), the off hand (`hand="off"`), or wear armor (`hand="armor"`).
- `bot.drop_item(slot=None)` — Drop one item from the held stack (or a slot).
- `bot.drop_stack(slot=None)` — Drop the whole held stack (or a slot).
- `bot.drop_all(item_id)` → int — Drop every stack of an item. Returns how many stacks were dropped.
- `bot.move_item(from_slot, to_slot)` — Move a stack between inventory slots.
- `bot.swap_slots(a, b)` — Swap two inventory slots.
- `bot.click_slot(slot, button="left")` — Left- or right-click an inventory slot.
- `bot.shift_click(slot)` — Shift-click an inventory slot.
- `bot.get_armor()` → list[item | None] — Worn armor: [helmet, chestplate, leggings, boots].
- `bot.get_offhand()` → item | None — The off hand item.

### Containers & menus

- `async def on_screen_open(bot, screen)` — Any container/menu opened.
- `async def on_screen_close(bot)` — The open container/menu closed.
- `async def on_container_open(bot, screen)` — A storage container (chest, barrel, shulker box, ...) opened.
- `async def on_container_update(bot, screen)` — The open container's contents changed.
- `bot.get_open_screen()` → screen | None — The open container/menu: `.title`, `.type` (e.g. "generic_9x3"), `.id`.
- `bot.get_container_slots()` → list[item | None] — The open container's own slots (not your inventory part).
- `bot.click_menu_slot(slot, button="left")` — Click a slot of the open menu (works for GUIs like server selectors and shops).
- `bot.shift_click_menu_slot(slot)` — Shift-click a slot of the open menu.
- `bot.take_item(item_id, count=None)` → int (also: withdraw_item) — Move an item from the open container into your inventory. Returns the number moved.
- `bot.deposit_item(item_id, count=None)` → int — Move an item from your inventory into the open container. Returns the number moved.
- `bot.deposit_all(keep_hotbar=True)` → int — Move all inventory items (hotbar kept by default) into the open container.
- `bot.select_trade(index)` — Select a trade in an open merchant screen (0 = first).
- `bot.rename_item(name)` — Set the item name in an open anvil.

### Player state

- `async def on_respawn(bot)` — Respawned after death.
- `async def on_death(bot, message)` — The bot died (`message` is the death message).
- `async def on_damage(bot, amount, attacker)` — Health went down. `attacker` is the entity responsible, if known.
- `async def on_heal(bot, amount)` — Health went up.
- `async def on_health_change(bot, health)` — Health changed.
- `async def on_hunger_change(bot, food)` — Food level changed.
- `async def on_effect_added(bot, effect)` — A potion effect started.
- `async def on_effect_removed(bot, effect)` — A potion effect ended.
- `async def on_fire(bot)` — The bot caught fire.
- `async def on_drown(bot)` — The bot took drowning damage.
- `async def on_fall(bot, amount)` — The bot took fall damage.
- `async def on_xp_change(bot, xp)` — Total experience changed.
- `async def on_level_change(bot, level)` — Experience level changed.
- `async def on_position_change(bot, position)` — Moved at least one block (checked once per second).
- `async def on_teleport(bot, position)` — The server teleported the bot (lobby/spawn sends, /tp, ...).
- `bot.get_health()` → float (also: health) — Health, 0–20.
- `bot.get_max_health()` → float — Maximum health.
- `bot.get_hunger()` → int — Food level, 0–20.
- `bot.get_saturation()` → float — Food saturation.
- `bot.get_air()` → int — Remaining air, 0–300 ticks.
- `bot.get_armor_value()` → float — Armor points, 0–20.
- `bot.get_experience()` → experience — Experience: `.level`, `.progress` (0–1), `.total`.
- `bot.get_level()` → int — Experience level.
- `bot.get_gamemode()` → str — survival, creative, adventure, or spectator.
- `bot.get_position()` → position (also: position) — Current position (`.x`, `.y`, `.z`).
- `bot.get_dimension()` → str — Current dimension, e.g. "overworld", "the_nether".
- `bot.get_effects()` → list[effect] — Active potion effects: `.id`, `.amplifier`, `.duration` (ticks).
- `bot.has_effect(effect_id)` → bool — True if the effect (e.g. "speed") is active.
- `bot.is_dead()` → bool — True while dead.
- `bot.is_burning()` → bool — True while on fire.
- `bot.is_underwater()` → bool — True when the head is under water.
- `bot.is_in_lava()` → bool — True while touching lava.
- `bot.is_in_water()` → bool — True while touching water.
- `bot.is_riding()` → bool — True while mounted.
- `bot.get_vehicle()` → entity | None — The entity being ridden.

### World & blocks

- `async def on_world_join(bot, dimension)` — Entered a dimension/world.
- `async def on_world_leave(bot, dimension)` — Left a dimension/world.
- `async def on_block_change(bot, block)` — A block within 32 blocks changed.
- `async def on_block_break(bot, block)` — A nearby block became air.
- `async def on_block_place(bot, block)` — A nearby air block became solid.
- `async def on_weather_change(bot, weather)` — Weather changed: clear, rain, thunder.
- `async def on_time_change(bot, phase)` — The time of day crossed into day, sunset, night, or sunrise.
- `bot.get_block(x, y=None, z=None)` → block | None — The block at a position: `.id` (e.g. "oak_log"), `.position`, `.state`, `.is_air`, `.is_solid`. None if the chunk is not loaded.
- `bot.get_block_state(x, y=None, z=None)` → str | None — Full state text, e.g. "oak_log[axis=y]".
- `bot.get_block_under_player()` → block | None — The block the bot stands on.
- `bot.get_block_in_front()` → block | None — The block directly in front at foot level (by facing direction).
- `bot.find_block(block_id, radius=32.0)` → block | None — Nearest loaded block of a type within `radius`. Accepts a list of ids too.
- `bot.find_blocks(block_id, radius=32.0, limit=64)` → list[block] — All loaded blocks of a type within `radius`, nearest first.
- `bot.find_nearest_block(block_id)` → block | None — Nearest block of a type anywhere in loaded chunks.
- `bot.raycast(distance=4.5)` → hit — What the crosshair hits: `.type` ("block", "entity", "miss"), `.block`, `.entity`, `.position`, `.face`.
- `bot.can_see(x, y=None, z=None)` → bool — True if nothing solid is between the bot's eyes and the point/entity.
- `bot.is_air(x, y=None, z=None)` → bool — True if the block is air.
- `bot.is_solid(x, y=None, z=None)` → bool — True if the block has a collision shape.
- `bot.is_walkable(x, y=None, z=None)` → bool — True if a player can stand at this block position (solid below, two free blocks).
- `bot.get_world_time()` → int — Time of day in ticks, 0–23999 (0 = sunrise, 6000 = noon, 13000 = night).
- `bot.get_weather()` → str — clear, rain, or thunder.

### Entities

- `async def on_entity_spawn(bot, entity)` — An entity came into range.
- `async def on_entity_despawn(bot, entity)` — An entity left range or was removed.
- `async def on_entity_move(bot, entity)` — An entity within 16 blocks moved (at most twice a second per entity).
- `async def on_entity_damage(bot, entity)` — A nearby entity took damage.
- `async def on_entity_death(bot, entity)` — A nearby entity died.
- `bot.get_entities()` → list[entity] — Every loaded entity, nearest first (max 256).
- `bot.get_nearby_entities(radius=16.0)` → list[entity] — Entities within `radius`, nearest first.
- `bot.find_entity(type=None, name=None, radius=64.0)` → entity | None — Nearest entity matching a type ("zombie") and/or name.
- `bot.find_entities(type=None, name=None, radius=64.0)` → list[entity] — All entities matching a type and/or name, nearest first.
- `bot.get_nearest_entity(type=None)` → entity | None — The nearest entity (optionally of a type).
- `bot.get_entity(uuid)` → entity | None — Look up an entity by UUID.

### Dropped items

- `async def on_item_drop(bot, entity)` — An item entity appeared.
- `async def on_item_pickup(bot, item)` — The bot picked up an item.
- `bot.get_dropped_items(radius=32.0)` → list[entity] — Item entities on the ground; each has `.item` (`.id`, `.count`).
- `bot.find_dropped_item(item_id=None, radius=32.0)` → entity | None — Nearest dropped item, optionally of a type.
- `bot.walk_to_item(item=None, timeout=30.0)` → bool — Walk onto a dropped item (nearest one by default, or an item id / item entity) to pick it up.

### Combat

- `async def on_attack(bot, entity)` — The bot attacked an entity.
- `async def on_attacked(bot, attacker)` — Something hurt the bot.
- `bot.get_attacker()` → entity | None — The entity that last hurt the bot (within 10 seconds).
- `bot.get_target()` → entity | None — The target set with `set_target`.
- `bot.set_target(entity)` — Remember an entity as the combat target (used by `attack()` when nothing is in the crosshair).
- `bot.nearest_hostile(radius=16.0)` → entity | None — The nearest hostile mob.
- `bot.is_under_attack(seconds=5.0)` → bool — True if the bot took damage in the last `seconds`.
- `bot.distance_to(x, y=None, z=None)` → float — Distance from the bot to a point, block, entity, or player.

### Navigation

- `async def on_path_complete(bot, position)` — Arrived at a pathfinding target.
- `async def on_path_failed(bot, position)` — Pathfinding gave up.
- `bot.pathfind_to(x, y=None, z=None, avoid_lava=True, avoid_fall=True, allow_jump=True, allow_swim=True, allow_mining=False, max_distance=512.0, timeout=120.0)` → bool — Find and follow a path to a point or entity. Returns True on arrival; fires `on_path_complete` / `on_path_failed`.
- `bot.stop_pathfinding()` — Cancel the current path.
- `bot.can_reach(x, y=None, z=None, max_distance=128.0)` → bool — True if a walking path to the point exists (computed without moving).
- `bot.get_path(x, y=None, z=None, max_distance=128.0)` → list[position] — The positions a walking path would visit (empty if unreachable).
- `bot.is_pathfinding()` → bool — True while a path is being computed or followed.

### Coordinates

- `bot.distance_to_player(name)` → float | None — Distance to a player in render range, else None.
- `bot.save_position(name, position=None)` — Remember the current (or given) position under a name.
- `bot.get_saved_position(name)` → position | None — A position saved with `save_position`.

### Scoreboard & server UI

- `async def on_bossbar(bot, bar)` (also: on_bossbar_update) — A boss bar was added or changed.
- `async def on_scoreboard_update(bot, scoreboard)` — The sidebar changed (throttled to once per second).
- `bot.get_scoreboard()` → scoreboard | None — The sidebar: `.title` and `.lines` (top to bottom).
- `bot.get_scoreboard_title()` → str | None — The sidebar title.
- `bot.get_scoreboard_lines()` → list[str] — Sidebar lines top to bottom, colors stripped.
- `bot.get_tab_header()` → str — The tab list header text.
- `bot.get_tab_footer()` → str — The tab list footer text.
- `bot.get_bossbars()` → list[bossbar] — Visible boss bars: `.title`, `.progress` (0–1), `.id`.

### Titles & action bar

- `async def on_actionbar(bot, text)` — Action bar text above the hotbar.
- `async def on_title(bot, text)` — A big title on screen.
- `async def on_subtitle(bot, text)` — A subtitle on screen.
- `bot.get_title()` → str | None — The most recent title text.
- `bot.get_actionbar()` → str | None — The most recent action bar text.
- `bot.wait_for_title(text=None, timeout=None)` → str | None — Wait for a title (containing `text`). Returns its text or None.
- `bot.wait_for_actionbar(text=None, timeout=None)` → str | None — Wait for an action bar message (containing `text`).

### Sounds & particles

- `async def on_sound(bot, sound)` — A sound played: `.id`, `.position`, `.volume`, `.pitch`, `.category`.
- `async def on_particle(bot, particle)` — Particles spawned: `.id`, `.position`, `.count`.
- `bot.wait_for_sound(sound_id, timeout=None, radius=None)` → sound | None — Wait for a sound whose id contains `sound_id` (e.g. "fishing_bobber.splash"), optionally within `radius` blocks.

### Packets

- `async def on_packet_receive(bot, packet)` — A raw packet arrived: `.type`, `.data` (see `packet_filter`).
- `bot.log_packets(enabled=True, types=None)` — Write received packet types to the script log (optionally only `types`, e.g. ["SetHealth"]).
- `bot.packet_filter(types)` — Choose which packet types fire `on_packet_receive` (default: everything except chunk and movement spam).
- `bot.send_raw_packet(packet, data=None)` → int (also: send_packet) — Send a serverbound game packet you build yourself. `packet` is a name ("swing", "chat_command", "use_item") or protocol id; `data` is the body as hex text ("0a ff") or type/value pairs (["varint", 0, "string", "hi"]; types: bool, byte, ubyte, short, ushort, int, long, float, double, varint, varlong, angle, string, uuid, position, bytes). Returns the body size. Bodies are not checked: a wrong one gets the bot kicked or banned. At most 50 per second.

### Waiting

- `bot.sleep(seconds)` (also: wait) — Pause for a number of seconds.
- `bot.wait_until(condition, timeout=None, interval=0.25)` → bool — Call `condition` until it returns something true. Returns False on timeout.
- `bot.wait_while(condition, timeout=None, interval=0.25)` → bool — Wait while `condition` keeps returning true. Returns False on timeout.
- `bot.wait_for_event(event, timeout=None)` → any (also: wait_for) — Wait for an event ("chat", "on_death", or a custom one). Returns its first value (or True), None on timeout.
- `bot.wait_for_player(name, timeout=None)` → bool — Wait until a player is online.
- `bot.wait_for_player_leave(name, timeout=None)` → bool — Wait until a player is no longer online.
- `bot.wait_for_health(value, timeout=None, below=False)` → bool — Wait until health is at least `value` (or at most, with `below=True`).
- `bot.wait_for_position(x, y=None, z=None, radius=1.5, timeout=None)` → bool — Wait until the bot is within `radius` of a point.
- `bot.wait_for_inventory_change(timeout=None)` → bool — Wait until the inventory changes.
- `bot.wait_for_block(x, y=None, z=None, block_id=None, timeout=None)` → bool — Wait until the block at a position becomes `block_id` (or changes at all).

### Timers

- `bot.after(seconds, function, *args)` → int — Run a function once after a delay. Returns a timer id.
- `bot.every(seconds, function, *args)` → int — Run a function repeatedly. Returns a timer id.
- `bot.set_timeout(function, seconds, *args)` → int — Same as `after`, with the function first.
- `bot.set_interval(function, seconds, *args)` → int — Same as `every`, with the function first.
- `bot.cancel_timer(timer_id)` → bool — Stop a timer. Returns False if it was already gone.
- `bot.schedule_at(time, function, *args)` → int — Run a function at a clock time "HH:MM" (UTC, next occurrence) or a unix timestamp.
- `bot.schedule_when(condition, function, interval=1.0)` → int — Run `function` once, as soon as `condition` returns true.

### Tasks

- `bot.run_task(function, *args)` → task — Run a function in the background. Returns a task: `.cancel()`, `.pause()`, `.resume()`, `.wait()`, `.is_running`.
- `bot.cancel_all_tasks()` — Cancel every background task and timer.
- `bot.get_tasks()` → list[task] — Background tasks that are still running.
- `bot.acquire(name, timeout=None)` → bool — Take a named lock (e.g. "movement") so two tasks don't fight over the same controls. Waits if another task holds it. Released by `release` or when the holder finishes.
- `bot.release(name)` — Release a named lock.

### Custom events

- `bot.on(event, function, priority=0)` — Also run `function` for an event. Higher priority runs first; a listener that returns False stops the rest.
- `bot.once(event, function, priority=0)` — Like `on`, but only the next time.
- `bot.off(event, function=None)` — Remove a listener added with `on`/`once` (all of them for the event if no function).
- `bot.emit(event, data=None)` — Fire a custom event; `on_<event>` handlers and listeners receive `data`.

### Reconnect & recovery

- `async def on_reconnect(bot, attempts)` — Back in the world after an automatic reconnect.
- `async def on_reconnect_attempt(bot, attempt)` — A reconnect attempt is starting.
- `async def on_reconnect_failed(bot, attempts)` — Auto-reconnect used up `max_attempts`.
- `async def on_timeout(bot)` — No packets for `detect_stuck(packet_timeout=...)` seconds.
- `async def on_stuck(bot, reason)` — Logged in but never spawned within `detect_stuck(loading_timeout=...)`.
- `bot.auto_reconnect(enabled=True, delay=5.0, max_attempts=10)` — Rejoin automatically after a disconnect. The script keeps its variables, timers, and tasks; `on_reconnect` runs after each rejoin. `max_attempts=0` keeps trying forever. Failed attempts in a row wait longer each time (×1.5, up to 5 minutes).
- `bot.reconnect_attempts()` → int — How many reconnect attempts happened since the last successful join.
- `bot.detect_stuck(loading_timeout=60.0, packet_timeout=30.0, reconnect=True)` — Fire `on_stuck` when spawning takes too long or `on_timeout` when the server goes silent; optionally reconnect.
- `bot.restart_on_error(enabled=True, max_restarts=5)` — Restart the script when a handler crashes (at most `max_restarts` per 10 minutes).

### Error handling

- `async def on_error(bot, error)` — A handler or task failed: `.message`, `.line`, `.event`, `.trace`.
- `async def on_script_crash(bot, error)` — A failure is restarting the script (`restart_on_error`).
- `bot.try_action(function, *args, retries=0, delay=1.0, fallback=None)` → any — Call a function; if it fails, retry up to `retries` times, then return `fallback`.
- `bot.retry(function, *args, attempts=3, delay=1.0)` → any — Call a function until it returns something true (or stops failing). Returns the last result.
- `bot.timeout(function, seconds, *args)` → any — Run a function but give up after `seconds` (returns None).

### Logging

- `bot.log(message, level="info")` — Write to the script log (shown live in the editor and saved with the session).
- `bot.log_info(message)` — `log.info(msg)`.
- `bot.log_warn(message)` — `log.warn(msg)`.
- `bot.log_error(message)` — `log.error(msg)`.
- `bot.log_debug(message)` — `log.debug(msg)` — only written when debug logging is on.
- `bot.log_events(chat=True, events=False, commands=True, debug=False)` — Automatically log incoming chat, every event, and the bot's own chat/commands. `debug=True` enables `log.debug`.

### Notifications

- `bot.notify(message, level="info")` — Send a notification to the dashboard bell (and a desktop notification if the browser allows it).
- `bot.webhook(message, title=None, level="info", webhook=None)` (also: send_webhook) — Post a message to one of your webhooks (Settings → Webhooks): Discord, Slack, or any URL that takes JSON. `webhook` picks one by name (default: your default webhook); `level` is info, success, warn, or error (the embed color). The URL never reaches the script. At most 20 per minute per bot.

### Script lifecycle

- `async def on_start(bot)` (also: on_script_start) — The script starts (once per session, before joining).
- `async def on_stop(bot)` (also: on_script_stop) — The script is being stopped (session end or `stop_script()`).
- `async def on_reload(bot)` (also: on_script_reload) — After `reload_script()`.
- `bot.stop_script()` — Stop this script (the bot stays connected). `on_stop` runs first.
- `bot.restart_script()` — Stop everything, clear variables, and start again from `on_start`/`on_join`.
- `bot.reload_script()` — Like restart, but fires `on_reload` instead of `on_start`.
- `bot.uptime()` → float — Seconds since the script started.

### Session info

- `bot.server_info()` → info — `.host`, `.port`, `.online_mode`.
- `bot.account_info()` → info — `.name`, `.uuid`.
- `bot.proxy_info()` → info — `.protocol`, `.host`, `.port` (never credentials).
- `bot.get_ping()` → int | None — The bot's latency in ms, from the tab list.

### Utilities

- `bot.random_delay(min, max)` → float — Wait a random time between `min` and `max` seconds. Returns the time waited.
- `bot.repeat(function, times, delay=0.0, *args)` — Call a function `times` times with an optional delay between calls.
- `bot.debounce(function, seconds, *args)` — Run a function once things go quiet: every call restarts a `seconds` countdown.
- `bot.throttle(function, seconds, *args)` → bool — Run a function now unless it already ran in the last `seconds`. Returns True if it ran.

### Debugging

- `bot.event_history(limit=20)` → list[record] — The most recent events: `.event`, `.time`, `.summary`.
- `bot.stats()` → record — Profiler counters: `.uptime`, `.events`, `.errors`, `.handler_runs`, `.avg_handler_ms`, `.max_handler_ms`, `.fuel_used`, `.tasks`, `.timers`.
- `bot.get_movement_state()` → record — What the bot is doing: `.walking`, `.sprinting`, `.crouching`, `.following`, `.pathfinding`, `.on_ground`.
- `bot.assert_true(condition, message="check failed")` → bool — Log PASS/FAIL for a condition (useful while testing a script). Returns the condition.

### AFK helpers

- `async def on_server_restart(bot, reason)` — Disconnected because the server is restarting/closing.
- `async def on_sent_to_spawn(bot, position)` — Teleported to within 8 blocks of the world spawn.
- `bot.anti_afk(enabled=True, interval=30.0, jump=True, rotate=True, move=False, swing=False, command=None, command_interval=300.0, sneak=False, no_fly=True)` (also: set_afk_mode) — Built-in anti-AFK: every `interval` seconds (±25%) jump, look around, step, swing, or sneak; optionally run a command every `command_interval` seconds. With `no_fly` (on by default) the bot lands whenever the server has it flying, like ranked players in Hypixel lobbies. The same engine as the account's Anti-AFK setting: calling this replaces it for this session; `anti_afk(False)` turns it off.
- `bot.afk_settings()` → record — Current anti-AFK: `.enabled`, `.source` ("account" or "script"), `.interval`, `.jump`, `.rotate`, `.move`, `.swing`, `.sneak`, `.command`, `.no_fly`, `.actions` (performed this session).
- `bot.is_afk()` → bool — True while anti-AFK mode is running.
- `bot.get_keepalive()` → record — `.last_packet` (seconds since any packet), `.last_keepalive`, `.connected_for`.
- `bot.wait_for_server(timeout=None)` → bool — After a restart/kick with auto-reconnect on, wait until the bot is back in the world.

### Built-in functions

- `len(x)` — Length of a string or list.
- `str(x)` — Convert to text.
- `int(x)` — Convert to a whole number.
- `float(x)` — Convert to a decimal number.
- `bool(x)` — Convert to True/False.
- `list(x)` — Copy a list, or split a string into characters.
- `range(stop) / range(start, stop, step)` — A list of numbers.
- `abs(x)` — Absolute value.
- `min(a, b, ...) / min(list)` — Smallest value.
- `max(a, b, ...) / max(list)` — Largest value.
- `sum(list)` — Add up numbers.
- `round(x, digits=0)` — Round a number.
- `floor(x)` — Round down.
- `ceil(x)` — Round up.
- `sqrt(x)` — Square root.
- `sorted(list)` — Sorted copy of a list.
- `reversed(list)` — Reversed copy of a list.
- `random()` — Random decimal between 0 and 1.
- `randint(a, b)` — Random whole number between a and b (inclusive).
- `choice(list)` — Random element of a list.
- `time()` — Current unix time in seconds.
- `distance(a, b)` — Distance between two positions/entities/blocks.
- `pos(x, y, z)` — Make a position.
- `strip_colors(text)` — Remove Minecraft color codes (§a, &b, ...).
- `format_chat_text(text)` — Strip color codes and collapse whitespace.
- `parse_chat(text)` — Split "[Rank] Name: message" or "<Name> message" into `.prefix`, `.name`, `.message`.
- `regex_match(pattern, text)` — True if the regular expression matches.
- `regex_find(pattern, text)` — First match, or None.
- `regex_find_all(pattern, text)` — Every match.
- `regex_groups(pattern, text)` — Capture groups of the first match, or None.
- `regex_replace(pattern, replacement, text)` — Replace matches.

### Not available

- **on_packet_send** — Azalea does not surface outgoing packets as events at the pinned version; `log_packets()` logs packets sent with `send_raw_packet`, and `log_events(commands=True)` logs chat and commands.
- **Event cancellation of server events** — Incoming server events cannot be cancelled. Listener chains can stop early by returning False.
- **Environment variables** — Scripts are sandboxed and cannot read the worker's environment. Use top-level constants instead.
- **Secrets store** — None. Keep secrets out of scripts you share with others.
- **Sound notifications** — None. `notify()` reaches the dashboard bell and browser notifications; `webhook()` reaches Discord, Slack, or your own URL.
- **Breakpoints and step debugging** — None. Use `event_history()`, `stats()`, `log_packets()`, and `log.debug()`.
- **Hot reload on save** — None. Saving a script applies on the next session start; `reload_script()` restarts the running copy.

### Known limitations

- Pathfinding never digs or places blocks unless `allow_mining=True`, and never crosses lava or long drops.
- `on_block_break` reports what a block became, not what it was.
