Signup Now
Results 1 to 5 of 5
  1. #1
    Free User shAdOwArt's Avatar
    Join Date
    Apr 2015
    Location
    Kharos
    Posts
    189
    Reputation
    151
    Rep Power
    19

    Context Menu With Actions



    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
    Last edited by shAdOwArt; 11-04-2016 at 02:12 PM.

  2. #2
    Free User Borges's Avatar
    Join Date
    Feb 2014
    Location
    Brazil
    Posts
    1,469
    Reputation
    205
    Rep Power
    25
    Good Job!
    Helped you? REP+

  3. #3
    Wind Powered
    Join Date
    Dec 2013
    Location
    dvscripts.com
    Posts
    7,105
    Reputation
    433
    Rep Power
    39
    Gj ! ;-)

  4. #4
    Free User Talendar's Avatar
    Join Date
    Nov 2014
    Location
    Brazil
    Posts
    134
    Reputation
    20
    Rep Power
    19
    Nice! Thanks, mate!

  5. #5
    Free User downloadkct's Avatar
    Join Date
    Dec 2013
    Location
    Rio de Janeiro
    Posts
    1,166
    Reputation
    25
    Rep Power
    23
    Whoa, this is nice o.o thanks man
    Old 'n Proud Neobot-Elfbot and blackd user

 

 

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •