Description
This is a modification of @Raphael's Context Menu script that adds support for adding actions containing actual code, and for registering message handlers that only show up under certain conditions. This allows me to add support for complicated actions such as doors and levitation. Furthermore, all actions except the top three (Node, Stand, Lure) automatically insert position checkers that ensure that the waypoint isn't skipped.
Note that addaction can, under rare circumstances, add the code to the wrong waypoint if there's already an empty action on the same square as you're trying to add a new action.
init start
local defFontColor = color(223, 223, 223)
local defBackColor = color(70, 70, 70)
local defHighColor = color(128, 128, 128)
local boxColor = color(70, 70, 70)
local boxLightShadowColor = color(117, 117, 117)
local boxDarkShadowColor = color(41, 41, 41)
local borderSize = 4
local paddingSize = 3
ALIGN_LEFT = 0
ALIGN_CENTER = 1
ALIGN_RIGHT = 2
MENU_SEPARATOR = '-- SEPARATOR --'
local items, highlight, contextMenu, maxWidth, maxHeight, clicked
do
-- Fifo queue
Queue = {}
Queue.__index = Queue
function Queue.new()
local q = {head = -1, tail = 0}
setmetatable(q, Queue)
return q
end
setmetatable(Queue, {__call = function(_, ...) return Queue.new(...) end})
function Queue:empty()
return self.head < self.tail
end
function Queue:push(val)
self.head = self.head + 1
self[self.head] = val
end
function Queue:pop()
-- Can't pop from an empty queue!
if self:empty() then return nil end
local val = self[self.tail]
self[self.tail] = nil
self.tail = self.tail + 1
return val
end
end
local actionQueue = Queue()
local actionBlacklist
do
-- addaction is asynchronous since addwaypoint is, and I don't want to block the hud from redrawing.
local function addaction(x, y, z, code)
-- Note that we only set the blacklist if there arent any currently enqued actions
if not actionBlacklist then
-- Init the blacklist
actionBlacklist = {}
-- Populate the blacklist
local i = 0
foreach settingsentry e 'Cavebot/Waypoints' do
local type = getsetting(e, 'Type')
local action = getsetting(e, 'Action')
if type == 'Action' and action == '' then
actionBlacklist[i] = get(e, 'Coordinates')
end
i = i + 1
end
end
-- Add the waypoint
addwaypoint('Action', x, y, z)
-- Queue up the code
actionQueue:push(code)
end
_MHConditions = {}
local oldRMH = registermessagehandler
local function registermessagehandler(context, type, callback, condition)
local condition = condition or function() return true end
table.insert(_MHConditions, condition)
oldRMH(context, type, callback)
end
registermessagehandler('contextMenu_world', 'Node', function(m)
addwaypoint('Node', m.posx, m.posy, m.posz)
end)
registermessagehandler('contextMenu_world', 'Stand', function(m)
addwaypoint('Stand', m.posx, m.posy, m.posz)
end)
registermessagehandler('contextMenu_world', 'Lure', function(m)
addwaypoint('Lure', m.posx, m.posy, m.posz)
end)
registermessagehandler('contextMenu_world', MENU_SEPARATOR, nil)
do
-- The fucked up indention is so that the code will appear well-indented in the waypoint list
local disableTargeting = [[-- Disable targeting & looting temporarily
_SavedTargetingSettings = {}
_SavedLootingSettings = get('Looting/Enabled', 'no')
set('Looting/Enabled', 'no')
local i = 0
foreach settingsentry e 'Targeting/Creatures' do
local old = get(e, 'Setting1/OnlyIfTrapped')
set(e, 'Setting1/OnlyIfTrapped', 'yes')
_SavedTargetingSettings[i] = old
i = i + 1
end
]]
local enableTargeting = [[-- Reenable targeting & looting
set('Looting/Enabled', _SavedLootingSettings)
local i = 0
foreach settingsentry e 'Targeting/Creatures' do
set(e, 'Setting1/OnlyIfTrapped', _SavedTargetingSettings[i])
i = i + 1
end
]]
local door = [[-- Open door
if not islocation(1) then return gotolabel($wptid - 2) end
usedoor(nil, nil, nil, 'open')
]]
registermessagehandler('contextMenu_world', 'Door', function(m)
addwaypoint('Walk', m.posx, m.posy, m.posz)
addaction(m.posx, m.posy, m.posz, disableTargeting)
addaction(m.posx, m.posy, m.posz, door)
addwaypoint('Stand', m.posx, m.posy, m.posz)
addaction(m.posx, m.posy, m.posz, "-- Assert on door\nif not islocation(0) then gotolabel($wptid - 4) end")
addaction(m.posx, m.posy, m.posz, enableTargeting)
end)
end
registermessagehandler('contextMenu_world', 'Stair/Hole', function(m)
addwaypoint('Stand', m.posx, m.posy, m.posz)
addaction(m.posx, m.posy, m.posz, "if $posz == $wptz then gotolabel($wptid - 2) end")
end)
registermessagehandler('contextMenu_world', 'Shovel Hole', function(m)
addwaypoint('Shovel', m.posx, m.posy, m.posz)
addwaypoint('Stand', m.posx, m.posy, m.posz)
addaction(m.posx, m.posy, m.posz, "if $posz == $wptz then gotolabel($wptid - 2) end")
end)
for _, type in ipairs({'Rope', 'Ladder'}) do
registermessagehandler('contextMenu_world', type, function(m)
addwaypoint(type, m.posx, m.posy, m.posz)
addaction(m.posx, m.posy, m.posz, "if $posz == $wptz then gotolabel($wptid - 2) end")
end)
end
local _ShowLevitate = false
registermessagehandler('contextMenu_world', MENU_SEPARATOR, nil)
registermessagehandler('contextMenu_world', "Hide levitates", function(m)
_ShowLevitate = false
end, function()
return _ShowLevitate
end)
registermessagehandler('contextMenu_world', "Show levitates", function(m)
_ShowLevitate = true
end, function()
return not _ShowLevitate
end)
for _, xdir in ipairs({"n", "e", "s", "w"}) do
for _, ydir in ipairs({"up", "down"}) do
registermessagehandler('contextMenu_world', string.format("Levitate(%s, %s)", xdir, ydir), function(m)
addwaypoint("Stand", m.posx, m.posy, m.posz)
addaction(m.posx, m.posy, m.posz, "-- Assert is on levitation spot.\nif $posz == $wptz and not islocation(0) then gotolabel($wptid - 1) end")
addaction(m.posx, m.posy, m.posz, string.format("levitate('%s', '%s')", xdir, ydir))
addaction(m.posx, m.posy, m.posz, "-- Assert changed floor.\nif $posz == $wptz then gotolabel($wptid - 3) end")
end, function()
return _ShowLevitate
end)
end
end
registermessagehandler('contextMenu_world', MENU_SEPARATOR, nil)
registermessagehandler('contextMenu_world', 'T Special Area', function(m)
addspecialarea('targeting', m.posx, m.posy, m.posz)
end)
registermessagehandler('contextMenu_world', 'C & T Special Area', function(m)
addspecialarea('cavebot & targeting', m.posx, m.posy, m.posz)
end)
registermessagehandler('contextMenu_world', MENU_SEPARATOR, nil)
end
local function loadCategories(...)
local categories = {...}
local i = 1
for _, v in ipairs(categories) do
local cat = 'contextMenu_' .. v
foreach messagehandler m cat do
local text, fontColor, backColor, highColor, align
if type(m.name) == 'function' then
text, fontColor, highColor, backColor, align = m.name(contextMenu)
else
text = m.name
end
if text and text ~= '' and _MHConditions[i]() then
local width
if text == MENU_SEPARATOR then
maxHeight = maxHeight + 8
else
width = (measurestring(text))
maxHeight = maxHeight + 19
maxWidth = math.max(maxWidth, width)
end
table.insert(items, {
text = text,
width = width,
callback = m.callback,
fontColor = fontColor or defFontColor,
highColor = highColor or defHighColor,
backColor = backColor or defBackColor
});
end
i = i + 1
end
end
end
filterinput(false, true, true, false)
function inputevents(e)
local eventItem, itemIndex
for i, v in ipairs(items) do
if v.id == e.elementid then
eventItem = v
itemIndex = i
break
end
end
highlight = itemIndex
if e.type == IEVENT_LMOUSEUP then
if eventItem and eventItem.callback then
eventItem.callback(contextMenu)
end
clicked = true
highlight = nil
waitforevents(false)
press('[ESC]')
waitforevents(true)
end
end
setfontstyle('Tahoma', 7, 75, defFontColor, 1, 0x000000)
setantialiasing(true)
init end
-- Scan for new empty actions
if not actionQueue:empty() then
local i = 0
foreach settingsentry e 'Cavebot/Waypoints' do
local type = getsetting(e, 'Type')
local action = getsetting(e, 'Action')
local isBlacklisted = actionBlacklist[i] and actionBlacklist[i] == get(e, 'Coordinates')
if type == 'Action' and action == '' and not isBlacklisted then
setsetting(e, 'Action', actionQueue:pop())
break
end
i = i + 1
end
else
actionBlacklist = nil
end
auto(10)
contextMenu = contextmenuinfo()
-- This prevents the HUD from redrawing after clicking
if clicked then
clicked = contextMenu ~= nil
contextMenu = nil
end
if contextMenu == nil then
highlight = nil
return
end
items = {}
maxWidth, maxHeight = 0, -4
local fullWidth, fullHeight
do -- Bootstrap
-- Load categories
if contextMenu.type == 'battle' or contextMenu.itemid == 99 then
contextMenu.creature = getcreaturebyid(contextMenu.creatureid)
if contextMenu.creature.isplayer then
loadCategories(contextMenu.type .. 'Player', 'player')
elseif contextMenu.creature.isnpc then
loadCategories(contextMenu.type .. 'NPC', 'NPC')
elseif contextMenu.creature.ismonster then
loadCategories(contextMenu.type .. 'Monster', 'monster')
end
loadCategories(contextMenu.type .. 'Creature', 'creature')
end
if contextMenu.type == 'world' then
if not contextMenu.creature then
loadCategories('worldItem', 'item')
end
if contextMenu.posz == $posz then
do
local i = 0
foreach settingsentry e 'Cavebot/Waypoints' do
local x, y, z = get(e, 'Coordinates'):match(REGEX_COORDS)
x, y, z = tonumber(x), tonumber(y), tonumber(z)
if z == $posz then
local diffX, diffY = contextMenu.posx - x, contextMenu.posy - y
if diffX >= 0 and diffY >= 0 then
local w, h = get(e, 'Range'):match(REGEX_RANGE)
w, h = tonumber(w), tonumber(h)
if diffX < w and diffY < h then
contextMenu.waypoint = e
contextMenu.waypointID = i
end
end
end
i = i + 1
end
if contextMenu.waypoint then
loadCategories('worldWaypoint', 'waypoint')
end
end
do
foreach settingsentry e 'Cavebot/SpecialAreas' do
local x, y, z = get(e, 'Coordinates'):match(REGEX_COORDS)
x, y, z = tonumber(x), tonumber(y), tonumber(z)
if z == $posz then
local diffX, diffY = contextMenu.posx - x, contextMenu.posy - y
if diffX >= 0 and diffY >= 0 then
local w, h = get(e, 'Size'):match(REGEX_RANGE)
w, h = tonumber(w), tonumber(h)
if diffX < w and diffY < h then
contextMenu.specialArea = e
contextMenu.specialAreaName = get(e, 'Name')
end
end
end
end
if contextMenu.specialArea then
loadCategories('worldSpecialArea', 'specialArea')
end
end
end
elseif contextMenu.type == 'container' then
loadCategories('containerItem', 'item')
elseif contextMenu.type == 'equip' then
loadCategories('equipItem', 'item')
end
loadCategories(contextMenu.type)
-- We set it as true from the beginning so that it also removes the first
-- item if it's a separator; we obviously don't want the first item to be a
-- separator. NOTE: relies on the fact that ipairs() will traverse the
-- table in ascending order, which isn't guaranteed by the reference manual
-- but is the common implementation
local lastSep = true
-- Instead of removing the items at the for loop, we simply set it to nil
-- and normalize it after; this is because if we did remove it, it would
-- shift the indexes and end up fucking up posterior checks
for i, v in ipairs(items) do
local curSep = v.text == MENU_SEPARATOR
if curSep and (lastSep or i == #items) then
items[i] = nil
maxHeight = maxHeight - 8
end
lastSep = curSep
end
table.normalize(items)
-- No item to display, abort mission!
if #items == 0 then
return
end
-- The Tibia context menu has an extra width of 44 pixels for the longest
-- item; here we account for that
maxWidth = maxWidth + 44
fullWidth, fullHeight = maxWidth + 2*borderSize, maxHeight + 2*borderSize
setposition($clientwin.x + contextMenu.x - fullWidth - 2,$clientwin.y + contextMenu.y)
end
do -- Draw Container
-- Draw main box
setfillstyle('color', boxColor)
setbordercolor(-1)
drawrect(0, 0, fullWidth, fullHeight)
-- Draw shadows
setbordercolor(boxLightShadowColor)
drawline(0, 0, fullWidth, 0)
drawline(0, 0, 0, fullHeight)
drawline(2, fullHeight - 2, fullWidth - borderSize, 0)
drawline(fullWidth - 2, 2, 0, fullHeight - borderSize)
setbordercolor(boxDarkShadowColor)
drawline(2, 2, fullWidth - borderSize, 0)
drawline(2, 2, 0, fullHeight - borderSize)
drawline(0, fullHeight, fullWidth, 0)
drawline(fullWidth, 0, 0, fullHeight)
end
do -- Draw items
local curHeight = borderSize
for i, v in ipairs(items) do
-- Separators get special treatment here
if v.text == MENU_SEPARATOR then
setbordercolor(boxDarkShadowColor)
drawline(borderSize, curHeight, maxWidth, 0)
setbordercolor(boxLightShadowColor)
drawline(borderSize, curHeight + 1, maxWidth, 0)
curHeight = curHeight + 8
else
-- This is a dirty, dirrty attempt of making the code shorter; and
-- that's what I love the most about programming
local alignOffset = ((maxWidth - paddingSize - v.width) / 2) * (v.align or ALIGN_LEFT)
-- Set style
setbordercolor(-1)
setfontcolor(v.fontColor)
setfillstyle('color', tern(i == highlight, v.highColor, v.backColor))
-- Draw stuff
v.id = drawrect(borderSize, curHeight, maxWidth, 15)
drawtext(v.text, borderSize + paddingSize + alignOffset, curHeight + paddingSize)
curHeight = curHeight + 19
end
end
end