Skip to main content

Camera and held input

hexis.camera.look aims through Hexis's camera controller. Choose exactly one target form: angles for an exact yaw and pitch, point for precise world coordinates, or at for the center of an integer block position.

local result = hexis.await(hexis.camera.look({
angles = {yaw = 90, pitch = 5},
timeout_seconds = 3,
}), {timeout = 4})

Yaw wraps to the normal heading range; pitch must be between -90 and 90. timeout_seconds defaults to 2 and accepts 0.2–10 seconds. A timeout is a failure. Success means the aim settled and this action released its camera claim. Precision targets resolve to the nearest mouse-sensitivity step, with a tolerance that accounts for half a step. Exact aiming uses the shared smooth controller; randomized idle scanning remains separate. Exact targets do not receive per-turn random offsets.

Initial camera acquisition can wait up to one second for the shared controller, within the action's overall timeout. Losing control after acquisition stops the action. Camera acquisition and ownership errors name the active owner when one exists; the script does not silently reacquire after a takeover.

Hold a bounded input

Observations take a request table, even when there are no options. Use hexis.world.self.observe({}) to read the player.

hexis.input.hold owns camera, movement, attack/use, and hotbar together. It requires input.control and camera.scan; attack or use also requires interaction. Another owner causes a busy result rather than taking over.

local self = hexis.world.self.observe({})
local p = self.position
local result = hexis.await(hexis.input.hold({
world_generation = self.world_generation,
keys = {left = true, sneak = true},
look = {angles = {yaw = self.yaw, pitch = self.pitch}},
bounds = {
min = {x = p.x - 1, y = p.y - 0.1, z = p.z - 1},
max = {x = p.x + 1, y = p.y + 0.1, z = p.z + 1},
},
duration_seconds = 0.5,
}), {timeout = 2})

Keys are booleans: forward, left, right, back, jump, sprint, sneak, attack, and use. Omitted keys are false. Opposing directions and simultaneous attack/use are rejected. For attack/use, provide held_item = {name_patterns = {"Hoe"}}; the action checks the selected tool.

Bounds describe player feet and must be ordered, no larger than 512 by 32 by 512 blocks. Duration accepts 0.1–60 seconds and includes aiming. Inputs start only after aim settles. If the camera never settles, the action fails.

Stop at a row end

Supply these optional fields to stop after crossing an endpoint plane:

stop_at = {
point = {x = 10.5, y = 70, z = 0.5},
normal = {x = 1, y = 0, z = 0},
},
progress = {window_seconds = 2, min_distance = 0.02},

The normal points toward the finish. Start on its negative side; the endpoint must be within the bounds. Progress uses fresh game ticks. Its window accepts 0.2–10 seconds and minimum forward distance accepts 0.001–5 blocks. A missed endpoint fails on timeout; standing still doesn't trigger a turn.

A successful result has data.reason equal to arrived or elapsed, plus the final observed position, tick, and world generation. Cancel with hexis.cancel(handle), or stop the script. The action releases its own inputs; physical momentum still takes time to stop.

Renew without releasing keys

Start a hold without immediately awaiting it. The native action keeps applying its keys while Lua reads observations. hexis.input.update accepts that handle and a complete replacement request with the same shape as input.hold:

local held = hexis.input.hold(initial_request)
local event = hexis.select({held}, {timeout = 0.05})
if event.kind == "timeout" then
local result = hexis.input.update({
action = held,
request = next_checked_request,
})
end

initial_request and next_checked_request must describe space your script has actually checked. This fragment illustrates the call shape; it omits result handling and is not a complete farm script. Use the farming library to reuse crop traversal.

An update returns an action result directly, not a second handle. It preserves the look target, world and held-item selector. Keys, bounds, endpoint, progress settings and duration are replaced together. Renewing a straight movement does not restart its stall window. Expired, completed or cancelled holds cannot be revived. Retryable busy means the initial hold has not started yet.

select with a timeout leaves input running; an action event consumes the handle and contains its completion result. await with a timeout cancels on timeout, so do not use it to poll. A hold can finish between polling and updating; consume its actual result and obtain fresh observations before starting another hold.

The renewed duration is bounded independently of Lua. If Lua cannot refresh in time, input stops at its existing endpoint or deadline. Allow room for physical coasting and observation latency. To change the camera target, cancel the hold, aim through the camera controller, then start another hold. Do not run a competing camera action while a hold owns it.

Filter held attack without interrupting movement

Add attack_target to an input.hold request with keys.attack = true:

attack_target = {
block_ids = {"minecraft:carrots"},
properties = {age = "7"},
}

The native input tick checks the current crosshair. It holds attack only for a matching block, releasing it on misses, entities, immature crops and backing blocks while movement continues. properties contains exact string values. For a protected base, below_block_ids can require an allowed block directly below the target, for example another minecraft:sugar_cane block.

This optional predicate does not select a tool, extend reach, open a menu or replace the request's bounds. Attack still requires interaction and a matching held_item. input.update accepts the same field inside its replacement request.

Wait after a server position correction

An interrupted hold can return navigation_failed with data.reason = "server_correction", sequence, position and world_generation. Its owned keys and camera have already been released. Expected moves, ordinary bounds failures and lost ownership do not produce this recoverable reason.

Use the shared library with the terminal result and a player observation saved before the correction:

local recovery = require("hexis/recovery")
local settled = recovery.wait({
result = terminal_result,
before = previous_player_observation,
})
if not settled.ok then
hexis.script.stop({reason = settled.message})
end

On success, the result contains player and sequence. Recheck the terrain, workload and intended direction before starting another action. This helper never repeats an action or acquires controls itself.

The wait requires the same world and a location within 32 blocks. It allows up to five seconds to settle, with at least three stable tick intervals and 0.8 seconds since the latest correction. hexis.world.self.observe({}) exposes correction_sequence, initially zero; a newer sequence restarts settling. Rotation-only and unchanged-position updates do not advance this counter.

Cancellation or a safety stop still ends the script. If renewal failed, consume the original hold's terminal result rather than treating the renewal response as the correction evidence.