Scripting Guide¶
This page covers the example scripts bundled with Wand — a complete, modular hunting framework written in Lua.
Overview¶
The example scripts are split into two parts:
- Main scripts (
Main_*.lua) — one per class/level range, containing all user configuration (maps, potions, attack ranges, loot rules, etc.) - Script library (
scriptlib/) — shared modules that implement the actual logic (hunting, looting, shopping, traveling, buffs, alerts, etc.)
You only need to edit a Main script to configure the bot for your character. The scriptlib handles everything else.
Main Scripts¶
Each Main_*.lua is a self-contained config file for a specific class and level range. For example:
| Script | Purpose |
|---|---|
Main.lua |
Generic template |
Main_Warrior_1_to_10.lua |
Beginner warrior, levels 1-10 |
Main_Royal_Warrior_1_to_10.lua |
Royal warrior, levels 1-10 |
Main_Royal_Warrior_30_up_Shanghai.lua |
Royal warrior 30+, Shanghai maps |
Main_Royal_Warrior_60_up_Sleepywood.lua |
Royal warrior 60+, Sleepywood maps |
All Main scripts follow the same structure: load modules, configure settings, call maple.run(). To create your own, copy any existing Main script and adjust the settings.
Getting Started: dump_class.lua¶
Before writing your own scripts, run dump_class.lua first. It's a read-only demo that calls most of the Lua API and prints everything to the log — player stats, mobs, drops, buffs, inventory, map geometry, NPCs, other players, pets, chat log, and dialog state.
Start here
Load dump_class.lua in Wand and hit Run. Read through the output to see what data is available and how each API function returns it. This is the fastest way to understand the API before writing your own scripts.
It covers:
| Section | API calls used |
|---|---|
| Player stats | get_player() — position, HP/MP, level, stats, state flags |
| Mobs | get_mobs() — ID, name, HP%, position, platform |
| Drops | get_drops() — item ID, owner, meso flag, position |
| Buffs | get_buffs() — active buff IDs, remaining time, key bindings |
| Inventory | get_inventory(), get_inventory_tab() — all tabs with item stats |
| Map data | get_physical_space() — footholds, ropes, portals, platforms, bounds |
| Other players | get_other_players() — name, job, party status, position |
| NPCs | get_npcs() — ID, name, position |
| Pets | get_pets() — name, fullness |
| Chat log | get_chatlog() — recent messages |
| Dialog | get_dialog_text() — NPC dialog options |
The script makes no changes to game state — it only reads and prints. Use it as a reference when building your own scripts.
Script Library (scriptlib)¶
The scriptlib/ folder contains 10 modules:
| Module | Purpose |
|---|---|
maple.lua |
Main orchestrator — runs the grind loop, coordinates all other modules |
hunt.lua |
Mob targeting, attack range checks, movement to targets |
loot.lua |
Item pickup with priority filtering and anti-stuck |
shop.lua |
Auto buy/sell trips with return scroll support |
travel.lua |
Inter-map navigation via portals and NPCs |
level.lua |
Auto AP/SP distribution and job advancement |
distanceFunction.lua |
Pluggable distance functions for target selection |
virtualKey.lua |
Virtual key code constants |
global.lua |
Shared state flags |
store.lua |
Legacy packet-based shop (older alternative to shop.lua) |
How It Works¶
When you call maple.run(), the main loop executes every 5ms:
maple.run() loop:
1. checkMapleStates() -- health checks: dialog removal, death revive, stuck detection
2. checkLevel() -- level-up tracking, stop condition
3. shop.checkInventory() -- trigger sell trip if equip slots full
4. leveling.run() -- job advance if pending (blocks hunting)
5. shop.run() -- buy/sell trip if triggered (blocks hunting)
6. checkRotation() -- rotate to next map if time/CC limit reached
7. checkMapOwnership() -- stranger detection, CC if map contested
8. handlePendingCC() -- move to safe spot and change channel
9. checkStats() -- potions (always), buffs (at hunt map only)
10. grind() -- loot + hunt at hunt map, or travel if not there
Configuration Reference¶
Below is every setting you can configure in a Main script, organized by module.
maple — General¶
maple.maxRunTimeMin = 3000 -- Auto-stop after X minutes
maple.stopAtLevel = 70 -- Stop at this level (0 = disabled)
maple — Hunt Maps & Rotation¶
-- List of maps to hunt in (bot rotates through them)
maple.huntMaps = {
{ id = 100020000, safeSpot = { x = 99, y = 5 } },
{ id = 100030000, safeSpot = { x = -4096, y = -63 } },
}
-- Rotation triggers (whichever comes first)
maple.switchMapAfterMin = 10 -- Rotate after X minutes at one map
maple.switchMapAfterCC = 3 -- Rotate after X channel changes
The safeSpot is where the bot stands before changing channel to avoid dying during the transition.
maple — Map Ownership¶
maple.ownershipTimeMin = 1 -- Minutes alone to claim the map
maple.ownerWaitTimeMin = 1 -- Grace period before leaving if you own the map
maple.strangerAlertIntervalSec = 6 -- Alert sound interval when strangers present
maple.whitelist = { "FriendName" } -- Players that don't trigger stranger logic
When a stranger appears:
- If you don't own the map: immediately trigger channel change
- If you own the map: wait for grace period, then CC if they're still there
- Party members are auto-filtered and never trigger stranger logic
maple — Buffs¶
maple.buffList = {
{ id = 1001003, onRope = false }, -- Iron Body
{ id = 1101006, onRope = true }, -- Rage (needs rope)
}
maple.rebuffSec = 10 -- Rebuff when remaining time < X seconds
maple.buffCooldownSec = 2 -- Min seconds between buff key presses
Set onRope = true for buffs that require standing on a rope/ladder to cast. The bot will find the nearest rope and move to it before casting. Buff keys are auto-detected from the game's key bindings.
maple — Potions¶
-- All three are optional (set to nil to disable)
maple.hpPotion = { id = 2000001, minNum = 50, threshold = 100, buy = 200 }
maple.mpPotion = { id = 2000003, minNum = 50, threshold = 50, buy = 150 }
maple.petPotion = { id = 2120000, threshold = 15 }
maple.potionCooldownMs = 200 -- Min ms between potion uses
maple.potionWarnIntervalMin = 1 -- Min minutes between low-stock warnings
| Field | Description |
|---|---|
id |
Item ID of the potion |
threshold |
Use potion when HP/MP falls below this raw value |
minNum |
Warn and trigger shop trip when count drops below this |
buy |
How many to buy per shop trip |
Pet food works differently: the bot checks all active pets via get_pets(), finds the one with the lowest fullness, and feeds it when fullness drops below threshold. An alert fires when pet food stock drops below 10.
maple — Error Management¶
maple.alwaysEnsureInput = true -- Auto-close dialogs/UI blocking input
maple.removeDialog = true -- Dismiss unexpected NPC dialogs
hunt — Attack Configuration¶
hunt.Attack.canTurn = true -- Allow turning to face mobs
hunt.Attack.key = vk.VK_SHIFT -- Single target attack key
hunt.Attack.keyAoe = vk.VK_A -- AoE attack key
hunt.Attack.mobsToAttack = 1 -- Min mobs in range to attack
hunt.Attack.mobsToSeek = 1 -- Min mobs at destination to move there
hunt.Attack.stopOnAttack = false -- Stop walking during attack animation
hunt.Attack.Range = {
isFan = false, -- false = rectangle, true = cone
front = 120, -- pixels in front
back = 0, -- pixels behind
top = 30, -- pixels above
bottom = 10, -- pixels below
}
When mobsToAttack > 1, the bot switches to keyAoe for the attack. The range defines the area where mobs are considered hittable.
Fan mode (isFan = true): The attack range is a cone that widens with distance. front becomes the radius, and top/bottom define the cone's vertical spread at max range.
hunt — KeepAway Zone¶
Mobs inside this zone are ignored for attacking (useful for ranged classes that need distance). Set all to 0 to disable.
hunt — Filtering¶
hunt.mobIdFilter = { 100100 } -- Ignore these mob IDs
hunt.platformFilter = { 0, 1 } -- Ignore mobs on these platforms
hunt.dangerZones = { -- Rectangular zones to avoid
{ x1 = 100, y1 = -200, x2 = 300, y2 = 0 },
}
hunt — Distance Function¶
hunt.distanceFunc = dist.manhattan -- Target selection algorithm
hunt.distanceParams = {} -- Parameters for the function
See Distance Functions below for all options.
loot — Looting¶
loot.casualLoot = true -- Pick up nearby items while hunting
loot.playerDropsOnly = true -- Only loot your own drops
loot.lootStyle = 2 -- 1 = stop hunting for must-picks, 2 = attack while moving to loot
loot.mustPickTypes = { 0, 1 } -- Always pick: 0=Mesos, 1=Equip, 2=Use, 3=Setup, 4=Etc, 5=Cash
loot.mustPickIds = { 4001207 } -- Always pick these specific item IDs
loot.maxAttempts = 10 -- Give up on unreachable items after X attempts
loot.maxIgnored = 6 -- Max ignored items tracked (oldest removed first)
shop — Shopping¶
shop.enable = { buy = true, sell = true }
shop.shopMapId = 100000102 -- Map ID of the shop
shop.shopLocation = { x = -281, y = 182 } -- Stand here to open shop
shop.sellExcludeIds = { 1462003 } -- Never sell these item IDs
shop.equipSlotBuffer = 1 -- Trigger sell when empty equip slots <= this
shop.sellUSE = true -- Sell Use tab items
shop.sellETC = true -- Sell Etc tab items
shop.sellAfterBuy = true -- Also sell after a buy trip
shop.ShoppingDelay = 600 -- ms between shop actions
shop.useReturnScrollId = nil -- Item ID of return scroll (nil = walk)
shop.additionalBuy = { -- Extra items to buy besides potions
-- { id = 2010004, targetCount = 1 },
}
Potion IDs (from maple.hpPotion, maple.mpPotion, maple.petPotion) are automatically added to the sell exclude list.
leveling — Auto Level¶
leveling.enable = { ap = true, sp = true, job_adv = true }
leveling.ap_per_level = {
hp = 0, mp = 0,
str = 4, dex = 1, int = 0, luk = 0,
}
leveling.jobs = {
{ name = "swordman", lastJob = 0, level = 10 },
{ name = "fighter", lastJob = 100, level = 30 },
{ name = "crusader", lastJob = 111, level = 70 },
}
AP is distributed on every level-up. Job advancement triggers automatically when the character reaches the specified level and has the correct lastJob.
Distance Functions¶
The distanceFunction module provides pluggable algorithms for target selection. Lower values = higher priority. Set via hunt.distanceFunc and hunt.distanceParams.
| Function | Params | Description |
|---|---|---|
dist.manhattan |
none | Sum of axis distances (|dx| + |dy|). Default, good general choice |
dist.euclidean |
none | Straight-line distance |
dist.chebyshev |
none | Max of axis distances. "King's move" metric |
dist.y_scaled |
{y_scale} |
Euclidean with vertical axis scaled. y_scale > 1 penalizes vertical distance |
dist.xy_scaled |
{x_scale, y_scale} |
Independent scaling per axis |
dist.minkowski |
{x_power, y_power} |
Generalized distance (p=1 is Manhattan, p=2 is Euclidean) |
dist.facing_weighted |
{weight, y_scale} |
Prefer targets in facing direction. weight=2 makes front targets appear 2x closer |
dist.input_weighted |
{h_weight, v_weight} |
Prefer targets matching current arrow key input |
Example: Prefer mobs in front, penalize vertical movement:
Alerts & Notifications¶
The bot sends messages through play_alert(msg) and play_notify(msg) at key events. If the Discord bot is connected, these appear in your Discord channel.
Alerts (something needs attention):
| Event | Message |
|---|---|
| Stranger on map | Stranger: PlayerName |
| Character died | Died! Reviving... |
| Unexpected dialog | Unexpected dialog! |
| Debuff applied | Debuff: Seal |
| Potion key not bound | HP potion not on key! |
| Pet food low | Pet food low: 3 |
| Shop trip failed | Buy failed! Trip aborted |
| No huntable mobs | No huntable mobs! (5 filtered) |
| Max runtime | Max runtime reached, stopping! |
| Only 1 channel | Only 1 channel, can't CC! |
Notifications (informational):
| Event | Message |
|---|---|
| Level up | Level Up! 29 -> 30 |
| Target level reached | Target level 70 reached, stopping! |
| Potion low | HP potion low: 12 |
| Map claimed | Map claimed! |
| Map rotation | Rotating to map 2 |
| Channel change | CC -> ch5 |
| Shop trip started | Shop: buy trip |
| Job advance done | Job advance complete! |
Creating Your Own Main Script¶
- Copy any existing
Main_*.luaas a template - Adjust the settings for your class, level range, and server
- Key things to configure:
maple.huntMaps— which maps to hunt and safe spotsmaple.hpPotion/maple.mpPotion— potion IDs and thresholdshunt.Attack.Range— match your main attack skill's rangehunt.Attack.key/keyAoe— your attack keysshop.shopMapId/shopLocation— nearest shop NPCleveling.ap_per_level— AP distribution for your classleveling.jobs— job advancement path
- Load the script in Wand and click Run
Tip
Use the Inventory Tab to look up item IDs and the WZ Browser for map IDs and mob IDs.