EloriaBot scripts are written in Lua and run inside the client, in one of the 5 slots of the Scripts tab. They are compatible with most ZeroBot scripts. A script runs once when it is enabled: use Timers for anything repeated and events to react to the game.
Your first script
-- Shows your HP and level on screen, refreshed every second.
local info = HUD(20, 80, '', true)
info:setColor(255, 220, 80)
Timer('my-info', function()
if not Client.isConnected() then info:setText('Offline') return end
info:setText(string.format('HP %d%% | Level %d',
Player.getHealthPercent(), Player.getLevel()))
end, 1000) A clickable button
-- A clickable button that toggles Targeting.
local button = HUD(20, 110, 'Targeting: ?', true)
local function refresh()
button:setText('Targeting: ' .. (Engine.isTargetingEnabled() and 'ON' or 'OFF'))
end
button:setCallback(function()
Engine.enableTargeting(not Engine.isTargetingEnabled())
refresh()
end)
refresh() Reacting to the game
-- React to server messages and NPC/quest windows.
Game.registerEvent(Game.Events.TEXT_MESSAGE, function(m)
if m.text:find('depleted') then print('Rock is empty!') end
end)
Game.registerEvent(Game.Events.MODAL_WINDOW, function(m)
print('Window opened: ' .. m.title)
-- answer with the first button and first choice:
-- Game.modalWindowAnswer(m.id, m.buttons[1].id, m.choices[1] and m.choices[1].id or 0, true)
end) Good to know
No wait()
wait()/sleep() do not pause: scripts share the game's thread. Use a Timer instead.
Speed limits
Actions are rate limited (walking ~20/s, items ~12/s, chat ~3/s). A call that is too fast returns false - just try again on the next tick.
Safe sandbox
No file or network access. Max 256 KB per script, 500 HUD elements, 100 timers, 100 event callbacks. A callback may run for at most 250 ms.
Return values
Most actions return true when sent and false when offline, throttled or invalid.
HUD - text, items and outfits on screen
| Function | What it does |
|---|
HUD(x, y, value, newFeatures) | Creates a HUD element. value = text, an item id (number) or an outfit. Same as HUD.new(...). |
hud:setText(text) / setColor(r, g, b) / setFontSize(n) | Change the text, its colour and size. |
hud:setPos(x, y) / getPos() / getMargins() | Move it or read its position. |
hud:show() / hide() / destroy() | Show, hide or remove it. |
hud:setCallback(function) | Runs your function when the element is clicked. |
hud:setDraggable(bool) / setPhantom(bool) | Let the player drag it / let clicks pass through it. |
hud:setItemId(id) / setSpellIconId(id) | Show an item sprite or a spell icon. |
hud:setOutfitId(id) / setOutfitAddons(n) / setOutfitColors(h, b, l, f) / setOutfitDirection(d) / setOutfitMoving(bool) | Show and animate an outfit. |
hud:setSize(w, h) / setScale(n) / setOpacity(0-1) | Size, scale and transparency. |
hud:setBackgroundColor(r, g, b) / setBorderColor(r, g, b) / setBorderWidth(n) | Background and border. |
hud:setHorizontalAlignment(a) / setVerticalAlignment(a) | Text alignment (Enums.HorizontalAlign / VerticalAlign). |
hud:setZIndex(n) / getId() | Drawing order / unique id. |
Timer - repeat code
| Function | What it does |
|---|
Timer(name, function, delayMs, autoStart) | Runs the function every delayMs (10 ms - 1 h). Starts at once unless autoStart is false. Same as Timer.new(...). |
timer:start() / stop() / isActive() | Start, stop, check. |
timer:update(delayMs) / run() / name() | Change the delay, run it once now, read its name. |
destroyTimer(name) | Removes a timer by name. |
Player - your character
| Function | What it does |
|---|
Player.getId() / getName() / getLevel() / getLevelPercent() / getExperience() | Basic info. |
Player.getHealth() / getMaxHealth() / getHealthPercent() | Health. |
Player.getMana() / getMaxMana() / getManaPercent() | Mana. |
Player.getMagicShield() / getMaxMagicShield() | Magic shield. |
Player.getMagicLevel() / getSkills() | Magic level and a table of all skills. |
Player.getCapacity() / getSoulPoints() / getStamina() / getSpeed() | Capacity, soul, stamina (minutes), speed. |
Player.getPosition() / getDirection() | {x, y, z} and facing direction. |
Player.getVocation() / isPremium() / getBlessingState() | Vocation, premium, blessings. |
Player.getTargetId() / getFollowId() | Id of the creature you attack / follow (0 = none). |
Player.getStates() / getState(Enums.States.X) | Conditions (poisoned, hasted, in PZ...). |
Player.isHungry() | True when you can eat. |
Player.getInventoryItem(Enums.InventorySlot.X) | The item in a slot. |
Player.getContainers() | Your open containers. |
Player.getDusts() / getDustsMaximum() | Forge dust and its limit. |
Player.getPartyIcon() / inviteParty(id) / joinParty(id) / leaveParty() / passLeadershipParty(id) / enableSharedExpParty(bool) | Party. |
Game - actions
| Function | What it does |
|---|
Game.walk(direction) / Game.turn(direction) | Step or turn one tile (Enums.Directions). |
Game.attack(creatureId) / Game.follow(creatureId) | Attack or follow a creature. |
Game.talk(text, type) / talkChannel(text, channelId) / talkPrivate(text, name) / openChannel(id) | Say / whisper / yell / NPC talk, write in a channel or a private message. |
Game.useItem(itemId) | Use an item from your backpack (potion, rune...). |
Game.equipItem(itemId, tier) | Equip an item. |
Game.useItemWithCreature(itemId, creatureId) | Use an item on a creature (runes, potions on friends). |
Game.useItemOnGround(itemId, x, y, z) | Use an item on a tile (pick, rope, fishing rod). |
Game.useItemOnInventory(itemId, slot) | Use an item on an equipped item. |
Game.useItemFromGround(x, y, z) | Use the top item of a tile (levers, statues, chests). Same as Map.useItem. |
Game.lootCorpse(x, y, z) | Open a corpse. |
Game.getItemCount(itemId, tier) | How many of an item you carry. |
Game.getContainers() / getInventoryItems() | All open containers / equipped items. |
Game.setFightMode(mode) / getFightMode() / setChaseMode(mode) / getChaseMode() | Offensive/balanced/defensive and stand/chase. |
Game.modalWindowAnswer(id, buttonId, choiceId, close) | Answer a server window (NPC, dungeon, altar...). |
Game.bossSequenceStart(category, bossesPerWave) | Start a Boss Sequence Full run. Category 1-3 = Easy/Medium/Hard, waves of 1, 3 or 5. |
Game.bossSequenceStartSingle(category, bossName) | Start a single boss. |
Game.bossSequenceRefresh() | Refresh the Boss Sequence panel. |
Game.registerEvent(eventId, function) / unregisterEvent(eventId, function) | Listen to game events (below). |
Map - tiles and creatures
| Function | What it does |
|---|
Map.goTo(x, y, z) | Auto-walk to a position. |
Map.getCameraPosition() | Centre of the screen. |
Map.getTiles() | All visible tiles: {position, things}. |
Map.getThings(x, y, z) / getThingsCount(x, y, z) | Items/creatures on a tile. |
Map.getTopItemId(x, y, z) / getTopCreatureId(x, y, z) | Top item or creature of a tile. |
Map.isTileWalkable(x, y, z, ignoreBlockPath, ignoreMagicField, ignoreMonsters, ignoreNpcs) | Can you walk there? |
Map.getAllPositionsWithTopItemId(itemId, sameFloor) | Every visible tile whose top item is itemId. |
Map.getCreatureIds(sameFloor, onlyPlayers) / getCreatures(...) | Creatures on screen. |
Map.getPlayerOnScreen(name) | Find a player on screen. |
Map.useItem(x, y, z) / browseField(x, y, z) | Use a tile / browse its items. |
Map.moveItemToGround(...) / moveItemToInventory(...) / moveItemToContainer(...) / moveCreatureToGround(...) | Move things from a tile. |
Map.useItemWithInventory(...) / useItemWithContainer(...) / useItemWithCreature(...) | Use a tile item on something. |
Creature, Container, Inventory
| Function | What it does |
|---|
Creature(id) | A creature handle. Methods: getId getName getType getPosition getHealthPercent getDirection getSpeed getOutfit getSkull getIcon getIcons getVocation getPartyIcon getGuildEmblem isPlayer isMonster isNpc isOnScreen attack follow. |
Container(index) | An open container. Methods: getName getCapacity getItems getItem getItemsCount getItemCount getItemCountById getCurrentPage getTotalPages goToPage moveItemToInventory moveItemToGround moveItemToContainer useItem useItemWithContainerItem useItemWithCreature lookAt moveUp close. |
Inventory(slot) | An equipment slot. Methods: getSlot getItem moveItemToGround moveItemToContainer useItem useItemWithCreature lookAt. |
Engine - the bot's own switches
| Function | What it does |
|---|
Engine.isBotEnabled() / enableBot(bool) | The main Helper Status switch. |
Engine.isTargetingEnabled() / enableTargeting(bool) | Auto target. |
Engine.isMagicShooterEnabled() / enableMagicShooter(bool) | Magic shooter. |
Engine.isEquipmentEnabled() / enableEquipment(bool) | Equipment rules. |
Engine.isTimerEnabled() / enableTimer(bool) | Timer rules. |
Engine.isHealFriendEnabled() / enableHealFriend(bool) | Heal friend. |
Engine.isCaveBotEnabled() / enableCaveBot(bool) | Cavebot. |
Analyzers - hunting statistics
Read-only snapshots of the analyzer windows (they do not need to be open). Each returns a table or nil while offline. Analyzers.getAll() returns all of them, Analyzers.get(name) one by name.
| Function | What it does |
|---|
Analyzers.getXP() | xpGain, xpHour, level, elapsedSeconds ... |
Analyzers.getHunting() | loot, supplies, balance, damage, healing, killedMonsters, lootedItems ... |
Analyzers.getLoot() / getSupply() | Gold value, gold per hour, items. |
Analyzers.getImpact() / getInput() | Damage and healing dealt / received, DPS. |
Analyzers.getDropTracker() / getPartyHunt() / getBossCooldowns() / getMisc() | Tracked drops, party hunt, boss cooldowns, charms and imbuements. |
Client, Spells, Npc and more
| Function | What it does |
|---|
Client.isConnected() / getLatency() / getFps() / getVersion() / getWorldName() | Connection and client info. |
Client.showMessage(text) | Shows a message on the game screen. |
Client.getGameWindowDimensions() / getCursorMapPosition() | Game window size / tile under the mouse. |
Client.hasFocus() / focus() / flashWindow() / setWindowTitle(text) | Window control (e.g. flash on an alarm). |
Client.isKeyPressed(key) / isChatEnabled() / toggleChatEnabled() / isTradeShopOpen() | Keyboard, chat and NPC trade state. |
Spells.getIdByName(name) / getIdByWords(words) / getDataById(id) / isInCooldown(id) | Spell information and cooldowns. |
Npc.buy(itemId, amount, ignoreCap, inBackpacks) / Npc.sell(itemId, amount, ignoreEquipped) | Trade with an open NPC shop. |
Sound.play(file) | Plays one of the client's own sounds. |
CustomModalWindow(caption, text) | Your own window: setCaption setDescription addButton setCallback destroy. |
print(...) | Writes to the bot status line. |
JSON.encode(t) / JSON.decode(s) | Convert tables to text and back. |
Windows - use game windows for you
Scripts can use some game windows the way you would with the mouse: open Prey and press re-roll, talk to an NPC and press a button in the Expedition window that opens, answer the "Are you sure?" box. There is no wait(), so each step runs in a Timer.
| Function | What it does |
|---|
Windows.list() | Every window a script may use: {name, title, open, canOpen}. |
Windows.isOpen(name) | Is that window open right now? |
Windows.open(name) / Windows.close(name) | Open or close it (only windows with canOpen). |
Windows.getControls(name, withLabels) | Everything you could use in it right now: {path, id, text, type, enabled, checked, on, tooltip}. Pass true to get the texts too. Print it once to find the names of the buttons. |
Windows.find(name, control) / getText(name, control) | One control as a table / its text. |
Windows.click(name, control) | Press a button, tick a checkbox or select a list row. |
Windows.setText(name, control, text) | Type into a field, e.g. a search box (max 64 characters). |
Windows.select(name, control, option) | Pick an option of a drop-down list. |
Windows.getConfirmation() | The "Are you sure?" box opened by your last click: {title, text, buttons}, or nil. |
Windows.confirm("Yes") | Press a button of that box. |
Which windows
Prey, Prey Hunting, Hunting Tasks and Cyclopedia can be opened by a script. Expeditions (with its modifiers, party, levels and invasions) and Solo Dungeons open in the game first (NPC / dungeon board), then a script can use them.
Naming a button
Use its name ("rerollButton"), names along its path ("slot2.rerollButton") or its text ({ text = "Yes" }). Only visible buttons count. Some buttons use on to say they are available.
Safe by design
Only these windows - never the Store, Market, trade or your account. Nothing that spends Eloria Coins. Only what you could click yourself: visible and enabled.
Your confirmations
A script can only answer an "Are you sure?" box that its own click opened, within 5 seconds. Boxes you or the server open are never touched. At most 2 clicks per second.
Example: Prey re-roll
-- Prey: re-roll the creature list of slot 1 (normal re-roll price or the free one).
local SLOT = 'slot1' -- slot1, slot2 or slot3
local step = 'open'
Timer('prey-reroll', function()
if step == 'open' then
Windows.open('prey')
step = 'click'
elseif step == 'click' then
local button = Windows.find('prey', SLOT .. '.rerollButton')
if not button then return end -- still loading
if not button.on then
print('Re-roll is not available right now')
destroyTimer('prey-reroll')
return
end
Windows.click('prey', SLOT .. '.rerollButton')
step = 'confirm'
elseif step == 'confirm' then
local box = Windows.getConfirmation()
if box then print(box.text) Windows.confirm('Yes') end
destroyTimer('prey-reroll')
end
end, 500) Expedition - start runs from a script
The Expedition functions use the Eternum Expeditions window for you: choose the cycle, the monster, the map and the payment, then press Begin expedition. Open the window first by talking to the Eternum Questmaster (Game.talk('hi'), then Game.talk('expeditions', Enums.TalkTypes.NPC)). Every function returns true, or false and the reason (for example stage 40 is not unlocked).
| Function | What it does |
|---|
Expedition.isOpen() | Is the Expedition window open right now? |
Expedition.getState() | Everything the window shows, as a table (or nil when it is closed): currentCycle, currentStage (the stage with your next crystal), selectedCycle, selectedStage, level, stages ({index, name, unlocked, current, completed}), maps ({id, name, gem}), selectedMap, payment, mods, cost and balance ({rt, htp, es}), canAfford, partyReady, canBegin. |
Expedition.selectBest() | Your strongest run: your current cycle and its current stage (highest monster level, with the Corrupted Crystal). |
Expedition.selectCycle(n) | Pick cycle n (1 = Cycle 1). Only up to your current cycle; earlier cycles only after you finished a full cycle. |
Expedition.selectStage(n) | Pick stage n of the selected cycle. Only stages you have unlocked. |
Expedition.selectMap(map) | Pick a map by id or name, e.g. 'venomfen' or 'Venomfen Hollows'. |
Expedition.setPayment(p) | 'es' = Extracted Strengths, 'rt+htp' = Reward Tokens + Hunting Task Points. |
Expedition.setMod(id, on) | Turn a run modifier on or off: 'bounty', 'dulled', 'frail', 'pvp'. |
Expedition.begin() | Press Begin expedition. |
Expedition.close() | Close the window. |
Fair play: a script can only choose what you could click yourself - cycles up to your current one, stages you have unlocked and the maps the window offers. The price is the window's own price, and the server checks everything again when the expedition starts. Map ids: venomfen, sunless, verdant, prism, cinder, sand, scarlet, root, blood, mines.
Example: walk to the Questmaster and start your best expedition
Start it with the Eternum Questmaster on your screen. The script follows him, says hi and expeditions, picks your strongest run (or the cycle and monster you set at the top), the map and the payment, and presses Begin expedition.
local NPC_NAME = 'Eternum Questmaster'
local MAP = 'venomfen' -- map id or name; nil = keep the window's default (first map)
-- Entry payment:
-- 'es' = Extracted Strengths (consumed ES balance)
-- 'rt+htp' = Reward Tokens + Hunting Task Points
local PAYMENT = 'es'
local TALK_DISTANCE = 2 -- start talking when this close (tiles)
-- Which run to pick. Both nil = the best one (current cycle, its crystal stage).
-- Only unlocked choices work: cycles up to your current one (earlier cycles only after
-- finishing a full cycle) and stages you have unlocked in that cycle.
local CYCLE = nil -- e.g. 2 for "Cycle 2"; nil = your current cycle
local MONSTER = nil -- monster name ('Rotworm') or stage number (12); nil = current stage
local step, tries = 'follow', 0
local function findNpc()
for _, creature in ipairs(Map.getCreatures(true) or {}) do
if tostring(creature.name or ''):lower() == NPC_NAME:lower() then return creature end
end
end
local function stop(text)
print(text)
Game.follow(0) -- stop following
destroyTimer('expeditionBest')
end
Timer('expeditionBest', function()
if step == 'follow' or step == 'approach' then
local npc = findNpc()
if not npc then return stop(NPC_NAME .. ' is not on screen') end
local me = Player.getPosition()
local distance = math.max(math.abs(me.x - npc.position.x), math.abs(me.y - npc.position.y))
if distance <= TALK_DISTANCE then
Game.follow(0)
step, tries = 'hi', 0
elseif step == 'follow' then
-- Same as right-click > Follow: the client keeps walking after the NPC.
if not Game.follow(npc.id) then return stop('Could not follow ' .. NPC_NAME) end
step = 'approach'
else
tries = tries + 1
if tries > 30 then return stop('Could not reach ' .. NPC_NAME) end
end
elseif step == 'hi' then
Game.talk('hi')
step = 'ask'
elseif step == 'ask' then
Game.talk('expeditions', Enums.TalkTypes.NPC)
step = 'wait'
elseif step == 'wait' then
tries = tries + 1
local state = Expedition.getState()
-- Wait until the maps and prices have arrived too.
if state and #state.maps > 0 and state.cost then
step = 'choose'
elseif tries > 10 then
return stop('The expedition window did not open')
end
elseif step == 'choose' then
local ok, reason
if CYCLE == nil and MONSTER == nil then
ok, reason = Expedition.selectBest()
if not ok then return stop('selectBest: ' .. tostring(reason)) end
else
ok, reason = Expedition.selectCycle(CYCLE or Expedition.getState().currentCycle)
if not ok then return stop('selectCycle: ' .. tostring(reason)) end
local stage = tonumber(MONSTER)
if MONSTER ~= nil and not stage then
-- Look the monster up by name in the selected cycle's stage list.
for _, entry in ipairs(Expedition.getState().stages) do
if entry.name:lower() == tostring(MONSTER):lower() then stage = entry.index break end
end
if not stage then return stop('No expedition monster called ' .. tostring(MONSTER)) end
end
if stage then
ok, reason = Expedition.selectStage(stage)
if not ok then return stop('selectStage: ' .. tostring(reason)) end
end
end
if MAP then
ok, reason = Expedition.selectMap(MAP)
if not ok then return stop('selectMap: ' .. tostring(reason)) end
end
step = 'pay'
elseif step == 'pay' then
local ok, reason = Expedition.setPayment(PAYMENT)
if not ok then return stop('setPayment: ' .. tostring(reason)) end
local s = Expedition.getState()
print(string.format('Cycle %d, stage %d (%s), level %d, map %s, cost %d ES / %d RT / %d HTP',
s.selectedCycle, s.selectedStage, s.stages[s.selectedStage] and s.stages[s.selectedStage].name or '?',
s.level, tostring(s.selectedMap), s.cost.es, s.cost.rt, s.cost.htp))
if not s.canAfford then return stop('Not enough currency for this expedition') end
step = 'begin'
elseif step == 'begin' then
local ok, reason = Expedition.begin()
if not ok then return stop('begin: ' .. tostring(reason)) end
stop('Embarking...')
end
end, 1000) Webhooks - Discord messages
Scripts can send messages to your own Discord channel: a private message arrived, a boss spawned, your HP is low, you reached a new level. You need a webhook URL from Discord: Channel settings › Integrations › Webhooks › New Webhook › Copy Webhook URL. Give it a short name with Webhook.add and use that name everywhere in your script.
| Function | What it does |
|---|
Webhook.add(name, url) | Save a Discord webhook URL under a short name (up to 20 per script). Returns true or false, reason. |
Webhook.send(name, text, callback) | Send a plain text message. callback is optional. |
Webhook.embed(name, embed, callback) | Send a Discord embed: title, description, color, fields, footer, author, url, thumbnail, image, timestamp. |
Webhook.send(name, message, callback) | Full message table: content, username, avatar_url, embeds, allowMentions. |
Webhook.has(name) / Webhook.remove(name) | Is that name saved? / Forget it. |
Webhook.names() | All names saved by this script. |
Webhook.isValid(url) | true if the URL is a Discord webhook the bot accepts. |
Webhook.canSend() | true if a message sent right now would not be refused by the rate limit. |
Webhook.Colors.RED | Ready colours: RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, GREY, BLACK. |
color is a number (0xE74C3C) or text ("#e74c3c"). fields is a list like { {name = "HP", value = "30%", inline = true} }. footer and author can be plain text. timestamp = true adds the current time. The callback(ok, err) runs when Discord answers. send returning true means the message was sent off, not yet that Discord accepted it.
Limits: only Discord webhook links work (https://discord.com/api/webhooks/...), no other website. At most 1 message every 2 seconds (a burst of 5 is fine); if Discord says "too many requests" all webhooks wait 30 seconds. Text-to-speech is always off and @everyone / role pings are off unless you add allowMentions = true. Your character must be online.
Keep your webhook URL private. Anyone who has it can post in your channel. Remove it before you share a script with other players. If it leaked, delete the webhook in Discord and create a new one.
Example: your first Discord message
-- Paste your own webhook URL here.
Webhook.add('me', 'https://discord.com/api/webhooks/123456789/your-token')
local ok, reason = Webhook.send('me', 'Hello from ' .. Player.getName() .. '!', function(success, err)
if success then print('Discord got it') else print('Discord said: ' .. err) end
end)
if not ok then print('Not sent: ' .. reason) end Example: level-up announcement
-- Posts a green card to Discord every time you gain a level.
Webhook.add('levels', 'https://discord.com/api/webhooks/123456789/your-token')
local lastLevel = Player.getLevel()
Timer('discord-levels', function()
local level = Player.getLevel()
if level > lastLevel then
Webhook.embed('levels', {
title = Player.getName() .. ' advanced to level ' .. level .. '!',
color = Webhook.Colors.GREEN,
fields = {
{ name = 'HP', value = Player.getHealthPercent() .. '%', inline = true },
{ name = 'Mana', value = Player.getManaPercent() .. '%', inline = true },
},
footer = 'Eloria',
timestamp = true,
})
end
lastLevel = level
end, 2000) Example: level-up announcement that never misses one
A more careful version of the script above. Discord accepts only a few messages at a time, and nothing can be sent while you are offline. This one remembers a level-up it could not send yet and posts it as soon as it can, so several quick levels become one card like +3 levels (from 120). It checks Webhook.canSend() before sending and tells you in the console when Discord refuses a message.
-- Posts a green card to Discord for every level-up, and never loses one:
-- if the rate limit is busy or you were offline, it waits and sends it later.
Webhook.add('levels', 'PUT YOUR WEBHOOK URL HERE')
local lastLevel = Player.getLevel()
local pendingFrom -- level before an unsent level-up (rate limit / offline)
Timer('webhook-levelup', function()
if not Client.isConnected() then return end
local level = Player.getLevel()
if level > lastLevel then
pendingFrom = pendingFrom or lastLevel
end
lastLevel = math.max(lastLevel, level)
if pendingFrom and Webhook.canSend() then
local gained = level - pendingFrom
local ok, reason = Webhook.embed('levels', {
title = string.format('%s advanced to level %d!', Player.getName() or '?', level),
description = gained > 1 and string.format('+%d levels (from %d)', gained, pendingFrom) or nil,
color = Webhook.Colors.GREEN,
fields = {
{ name = 'HP', value = Player.getHealthPercent() .. '%', inline = true },
{ name = 'Mana', value = Player.getManaPercent() .. '%', inline = true },
},
timestamp = true,
}, function(success, err)
if not success then print('Discord: ' .. tostring(err)) end
end)
if ok then
pendingFrom = nil
else
print('Not sent: ' .. tostring(reason))
end
end
end, 500) Example: Discord alerts (private messages, bosses, low HP)
One script for the most useful alerts. Leave a URL empty ('') to switch that alert off, or paste the same URL three times.
local HOOKS = {
whisper = '', -- private messages
boss = '', -- "... will be spawned" / "... spawned in" messages
danger = '', -- health below LOW_HEALTH
}
local LOW_HEALTH = 30 -- percent
for name, url in pairs(HOOKS) do
if url ~= '' then
local ok, reason = Webhook.add(name, url)
if not ok then print(name .. ': ' .. reason) end
end
end
local function alert(hook, title, text, color)
if not Webhook.has(hook) then return end
Webhook.embed(hook, {
title = title, description = text, color = color,
footer = 'Eloria | ' .. (Player.getName() or '?'), timestamp = true,
})
end
-- Private messages (mode 4 = a message from another player).
Game.registerEvent(Game.Events.TALK, function(name, level, mode, x, y, z, text, channelId)
if mode == 4 then alert('whisper', 'Message from ' .. name, text, Webhook.Colors.PURPLE) end
end)
-- Boss messages, at most once per minute for the same text.
local lastBoss = {}
Game.registerEvent(Game.Events.TEXT_MESSAGE, function(message)
local text = message.text
if text:find('will be spawned') or text:find('spawned in') then
local key = text:sub(1, 40)
if lastBoss[key] and os.time() - lastBoss[key] < 60 then return end
lastBoss[key] = os.time()
alert('boss', 'Boss alert', text, Webhook.Colors.RED)
end
end)
-- Low health: once when it drops, again only after you healed up.
local warned = false
Timer('discord-low-health', function()
local hp = Player.getHealthPercent()
if hp > 0 and hp < LOW_HEALTH and not warned then
warned = true
local pos = Player.getPosition()
alert('danger', 'Low health!', string.format('HP %d%% Mana %d%%\nPosition %d, %d, %d',
hp, Player.getManaPercent(), pos and pos.x or 0, pos and pos.y or 0, pos and pos.z or 0),
Webhook.Colors.ORANGE)
elseif hp >= LOW_HEALTH + 10 then
warned = false
end
end, 500)
print('Discord alerts running: ' .. table.concat(Webhook.names(), ', ')) Events
| Event | Your function receives |
|---|
Game.Events.TEXT_MESSAGE | a table: messageType, text |
Game.Events.MODAL_WINDOW | a table: id, title, message, buttons, choices, defaultEnterButton, defaultEscapeButton (buttons/choices are {id, text}) |
Game.Events.TALK | name, level, mode, x, y, z, text, channelId |
Game.Events.HUD_CLICK / HUD_DRAG | the HUD element id |
Game.Events.CUSTOM_MODAL_WINDOW_BUTTON_CLICK | your window id and the button index |
Game.Events.QUEST_LOG / QUEST_LINES | quest data |
Enums
Enums.Directions (NORTH=0, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST), Enums.InventorySlot (HEAD=1 ... AMMO=10), Enums.TalkTypes, Enums.FightMode, Enums.ChaseMode, Enums.States, Enums.CreatureTypes, Enums.Skulls, Enums.MessageTypes, Enums.Vocations, Enums.PartyIcons, Enums.HorizontalAlign / VerticalAlign. Standard Lua: math string table os.time os.date os.clock pairs ipairs pcall.
A few ZeroBot functions exist only so old scripts keep running and always return false, 0 or an empty table: login/logout, hotkeys, store, imbuing, forge conversion, daily rewards, hunting-task actions, script loading and profile switching.
Community script: Eloria HUD 6.4 by Slandish

A full HUD made by player Slandish: a movable Eloria menu with Runners (Dungeon, Boss Run with Boss Sequence, Bosstiary), Fishing modes, Mining with a teleport guard and automatic Drome Potions. It shows how to use HUD tiles, Timers, the modal window event and path finding. Paste the whole file into one script slot.