Fishing
Fishing automation lives under client.automation, alongside the tutorial driver. The flow is persistent — once armed, the runtime warps, walks, equips the rod, and casts. On every reconnect the resume watcher rearms the same config until you explicitly stop it.
Why persistent? A fishing session is a long-lived intent ("keep this bot fishing in MANCINGNIH at (20, 30) until I say stop"), not a one-shot action. The Auto-Resume UI in the desktop app uses the exact same arm path — Lua and the UI write to one shared config slot per bot.
FishingConfig table
armFishing and fishingConfig both speak this shape:
| Field | Type | Required | Description |
|---|---|---|---|
world | string | yes | Uppercase world name, e.g. "MANCINGNIH". |
portal | string? | no | Portal entry-point id within the world. Omit to spawn at WorldStartPoint. |
rod | number | yes | Block id of the rod to equip (2406-2421, 4196, 4622). |
bait | number | yes | Block id of the bait (2462-2482, plus 3353 / 3774 / 4561 / 4585). |
baitName | string? | no | Friendly bait name. Falls back to "Lure#<id>" when omitted. |
anchor | Vector2i | yes* | Anchor tile the bot walks to before casting. |
anchorX | number? | yes* | Alternative to anchor. armFishing accepts either; fishingConfig returns both. |
anchorY | number? | yes* | See anchorX. |
direction | string | yes | "left" or "right" — picks the water tile adjacent to anchor. |
createdAt | number? | output | Unix-epoch seconds when the config was armed. Set by the runtime, ignored on input. |
* Provide either anchor (a Vector2i) or the anchorX + anchorY pair. anchor wins when both are present.
The bot must already own the chosen rod and bait in inventory.
armFishingdoesn't buy them. Useclient:buy("FishingRod…")/client:buy("LureBooster")first.
Drivers
client.automation:armFishing(cfg)
Arm persistent fishing. The runtime executes:
- Warp to
cfg.world(skipped when already in that world). - Walk to
cfg.anchor. - Equip
cfg.rodfrom inventory. - Enter the cast loop using
cfg.direction+cfg.bait.
| Field | Type |
|---|---|
| Signature | (self: Automation, cfg: FishingConfig) → string? |
| Returns | string? — nil on success, or an Errors.* code on bad input / busy / disconnected |
| Common errors | Errors.INVALID_WORLD (missing world field), Errors.NOT_CONNECTED, Errors.DISCONNECTED, Errors.OTHER (missing required field) |
| Idempotent | no — calling twice replaces the previous config |
| Async | yes — yields until the queue accepts the command |
local err = c.automation:armFishing({
world = "MANCINGNIH",
rod = 2410, -- FishingRodFiberglassBasic
bait = 2462, -- LureWorm
baitName = "LureWorm",
anchor = Vector2i.new(20, 30),
direction = "left",
})
if err then print("arm failed:", err) end
client.automation:stopFishing()
Stop the in-flight cast and clear the persistent flag so the resume watcher won't rearm. Idempotent — safe to call on a non-fishing bot.
| Field | Type |
|---|---|
| Signature | (self: Automation) → string? |
| Returns | string? — nil on success, or an Errors.* code |
c.automation:stopFishing()
For transient pauses (e.g. "stop now but rearm on reconnect"), let the runtime auto-stop on disconnect — the watcher will rearm.
stopFishingis the "Stop & Clear" semantic, not the "pause" one.
State queries
client.automation:isFishing()
| Field | Type |
|---|---|
| Signature | (self: Automation) → boolean |
| Returns | boolean — true when fishing is actively casting OR armed (config set, between reconnects) |
Mirrors the desktop UI's "is the toggle on" state. Use this to gate other automation:
if c.automation:isFishing() then
return -- fishing owns this bot, don't queue auto-mine
end
client.automation:fishingStatus()
| Field | Type |
|---|---|
| Signature | (self: Automation) → string? |
| Returns | string? — human-readable status, or nil when no driver is armed |
| Possible values | "Warping to <WORLD>…", "Walking to (x, y)…", "Casting", "Reeling", … (non-exhaustive — labels are advisory, don't == compare in production) |
print(c.automation:fishingStatus()) -- "Casting"
client.automation:fishingConfig()
| Field | Type |
|---|---|
| Signature | (self: Automation) → FishingConfig? |
| Returns | FishingConfig? — snapshot of the persistent config, or nil when no config is armed |
The returned table is a fresh snapshot — mutating it does not affect the running config. To change the config, call armFishing again with the new shape.
local cfg = c.automation:fishingConfig()
if cfg then
print(string.format("fishing %s with rod=%d at (%d, %d)",
cfg.world, cfg.rod, cfg.anchorX, cfg.anchorY))
end
Block id reference
Rods (family → tier ladder)
| Family | Basic | Fine | Superior | Flawless | Event variants |
|---|---|---|---|---|---|
| Bamboo | 2406 | 2407 | 2408 | 2409 | 4622 (Reinforced) |
| Fiberglass | 2410 | 2411 | 2412 | 2413 | — |
| Carbon Fiber | 2414 | 2415 | 2416 | 2417 | 4196 (Dark) |
| Titanium | 2418 | 2419 | 2420 | 2421 | — |
Lures
The standard 21-lure ladder is contiguous: 2462..2482. Event variants: 3353 (Radioactive Worm), 3774 (Lucky Lure), 4561 (Noob), 4585 (Crab Bait).
End-to-end recipe
local c = getClient()
local a = c.automation
-- Wait until the bot is in-world and tutorial is done.
while a:tutorialState() == nil do task.wait(0.2) end
if a:isInTutorial() then a:tutorial() end
-- Arm fishing — runtime does the warp + walk + rod-equip.
local err = a:armFishing({
world = "MANCINGNIH",
rod = 2410,
bait = 2462,
baitName = "LureWorm",
anchor = Vector2i.new(20, 30),
direction = "left",
})
if err then
print("arm failed:", err)
return
end
-- Babysit — log every status transition.
local last = nil
while a:isFishing() do
local s = a:fishingStatus()
if s ~= last then
print("[fishing]", s)
last = s
end
task.wait(1)
end
See script_fishing_basic.lua for the canonical example with cleanup.
Gotchas
- Inventory check is the runtime's job, not yours. Calling
armFishingwith a rod the bot doesn't own returnsnil(queued OK) but the equip step fails later — watchfishingStatusfor the error. stopFishingclears the persistent flag. If you only want to pause the cast loop without breaking auto-resume, just disconnect the bot — the watcher will rearm on reconnect.anchorvs water tile. The anchor is where the bot stands; the actual fishing tile is one stepdirection-ward of it (a water block). Set the anchor on solid ground next to water, not on the water itself.- The desktop UI and Lua share one config slot. Arming via Lua overwrites whatever the Auto-Resume Fishing panel shows, and vice-versa.
See also
Automation— tutorial driver (same namespace)Vector2i— anchor input typeErrors— error code reference