Best Practices
Pick the Right Function
The most common source of bugs is using the wrong API for the job. Before writing any logic, ask yourself:
- Am I breaking a block? Use
hexis.mining.* - Am I walking somewhere? Use
hexis.navigate.* - Am I interacting with an entity? Use
hexis.player.interact_entity() - Am I clicking in a menu? Use
hexis.gui.*
See Choosing the Right Function for a full decision tree.
Navigation Patterns
Use start_async for production scripts
Most working scripts use start_async() instead of to() because it lets you handle interrupts during navigation:
hexis.navigate.start_async({
x = target.x, y = target.y, z = target.z,
distance = 2.0
})
while hexis.navigate.is_navigating() do
if not hexis.script.is_running() then return end
-- Can check for competition events, danger, etc.
hexis.wait(0.05)
end
Use near() for solid blocks
When approaching something you'll interact with (chest, NPC area, TNT), use near() — it validates line-of-sight and exposed faces:
hexis.navigate.near({
x = chest.x, y = chest.y, z = chest.z,
range = 3.0
})
Always check is_running() in wait loops
Every polling loop should have an exit condition:
-- Good: Can be stopped cleanly
while hexis.navigate.is_navigating() do
if not hexis.script.is_running() then return end
hexis.wait(0.05)
end
-- Bad: Blocks forever if script is stopped
while not hexis.navigate.arrived() do
hexis.wait(0.1)
end
Mining Patterns
Use sessions for multi-block mining
Sessions keep the attack key held between blocks, preventing camera stutter:
hexis.mining.start_session()
for _, block in ipairs(blocks) do
if not hexis.script.is_running() then break end
hexis.mining.mine_block({x = block.x, y = block.y, z = block.z})
end
hexis.mining.end_session()
Prefer mine_block() over async primitives
mine_block() handles the full pipeline (vantage finding, navigation, aiming, mining). The async primitives (start_mining_async, aim_tick) are deprecated — only use them if you need frame-by-frame control.
Human-Like Behavior
To appear human and avoid detection:
- Use variable delays — Don't use fixed 100ms everywhere
- Don't move camera instantly — Use smooth aim speeds
- Include occasional pauses — Random micro-breaks
- Vary attack timing — Don't attack at exact intervals
-- Bad: Fixed timing
hexis.wait(0.1)
-- Good: Variable timing
hexis.wait(math.random(80, 150) / 1000)
Movement + Inventory
You cannot walk while inventory is open.
The GUI Interaction Guard automatically enforces safety when you click in a container:
- First click is delayed 200-500ms after the container opens (prevents ChestStealer detection)
- Consecutive clicks have a 100-150ms minimum gap
- Pathfinder, camera tracking, and movement keys are automatically stopped on first click
- Movement stays blocked until the container closes
-- Safe by default — no manual delays needed
hexis.chat.command("/bz")
hexis.gui.wait_for("Bazaar", 5)
hexis.gui.click_item({ name = "Buy Instantly" }) -- Auto-delayed safely
hexis.wait(0.3)
hexis.gui.click_item({ name = "Confirm", required = true })
hexis.gui.close()
For minigame scripts that need fast clicking (Harp, Chronomatron), use unsafe = true:
hexis.gui.click({ slot = note_slot, unsafe = true }) -- Skip inter-click delay
hexis.gui.safe_mode() is still available but mostly redundant — only needed if you want to stop movement before opening a container.
Entity Interaction
Hypixel NPCs are armor stands. Find them by name, then interact:
local entities = hexis.world.get_nearby_entities(6, {type = "armor_stand"})
for _, entity in ipairs(entities) do
if entity.name and entity.name:find("Emissary") then
hexis.player.look_at(entity)
hexis.wait(0.2)
hexis.player.interact_entity(entity.id)
break
end
end
get_nearby_entities requires a table for the filter parameter: {type = "armor_stand"}, not just the string "armor_stand".
Timing
All timing in Hexis uses seconds, never milliseconds.
hexis.wait(1.5) -- 1.5 seconds
hexis.wait(0.05) -- 50 milliseconds
hexis.wait(500) -- THIS IS 500 SECONDS, not 500ms!
For random delays, divide by 1000:
hexis.wait(math.random(200, 500) / 1000) -- 200-500ms
Staff Detection
When staff_detection = true in script metadata:
- Monitors for staff activity
- Auto-disables script if detected
- Sends system notification
Pause/resume manually around warps:
hexis.script.pause_staff_detection()
hexis.chat.command("/hub")
hexis.wait(5.0) -- Wait for teleport
hexis.script.resume_staff_detection()
Resource Cleanup
Scripts automatically clean up when stopped, but for long-running scripts:
-- Register cleanup for event listeners
local sound_id = hexis.events.on("sound", "pattern", handler)
-- Later, if you need to remove it:
hexis.events.remove(sound_id)
-- Or clear all events:
hexis.events.clear()
Efficient Loops
Use yield() in tight loops to prevent freezing:
for i = 1, 1000 do
-- Processing
if i % 100 == 0 then
hexis.yield() -- Let other things run
end
end
Script Structure
For complex scripts, use a state machine pattern. It's easier to debug and extend than deeply nested if/else chains:
local state = "setup"
local handlers = {
setup = function() --[[ ... ]] return "navigate" end,
navigate = function() --[[ ... ]] return "mine" end,
mine = function() --[[ ... ]] return "navigate" end,
}
function hexis.main()
while hexis.script.is_running() do
state = handlers[state]()
hexis.wait(0.05)
end
end
See Common Patterns for a full example.