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

    Advanced Mage Shooter


    Features:
    • Increased pvp safety
    • Syncronized mana potions
    • Automatically uses the best strike
    • Supports all ultimates, waves, ball aoe runes and strikes



    More Details
    When checking for players the script adds some extra size on top of the spell's natural aoe in order to take moving players into account. How much extra space that should be added can be configured and should depend on your ping. This extra space is also added around the position in front of you when checking if a strike is pvp safe. Furthermore, if a strike fails the pvp-check the script will try to turn away from nearby players before retrying.

    Keep in mind that the bot can only see one square outside of the screen in each direction.
    Because of this it will only consider squares close to the character when selecting the best tile to shoot an aoe rune. How close depends on what buffer size you have configured. Finally, some beams and ultimates are too large to check with any buffer size, the script will still try to cast those spells if you have configured it to do so though.

    The script also comes bundled with a potions drinker. The potions drinker and spellcaster keeps track of cooldowns and potions will only be drunk if at least one of these circumstances hold:
    • There's at most 800ms left until the next spellcast.
    • There's at most one rune target on the screen.
    • Your mana is critically low.


    The script also has a list of blacklist ids. Here you should add things like holes, teleport exits and rope spots. If one of these ids are on the screen the script will also check for players on the floors just above and just below you. However, note that level spy doesn't work between the ground floor and the first underground floor.

    In the config you'll be asked to add a list of monsters for each spell. If this parameter is left out all monsters will be considered for that spell. If you use a string instead of a table the script will treat it as a targeting category. The script checks the contents of the category when you start the script, not when it casts spells. If you change your category you need to restart the script.

    In the config you can also add 'minhp' and 'maxhp' fields to filter out which monsters should be considered. You can also add an 'atLeastOne' parameter -- the spell will the only be used if at least one of the targets is in the specified list. This is for example useful for dealing with Demon Skeletons in the tombs.

    Finally, the built in magic shooter and potions drinker should be disabled while running this script.

    init start
    -----------------------------------------------------------------------
    -------------------------------- CONFIG -------------------------------
    -----------------------------------------------------------------------
    Config = {
    pvpsafe = true, -- Do you want to avoid hitting other players with spells?
    buffersize = 2, -- How far outside of the spell's aoe to check for players?
    ignoreParty = true, -- Is it ok to hit party members? Enable if you want to team hunt.

    multiFloor = true, -- Check for players on other floors?
    blacklistIds = {469, 1959, 1960}, -- Enable multi floor checks regardless of above settings if one of these items are on the screen. Add stuff like stairs and rope holes.

    manaPotion = "ultimate mana potion", -- Which mana potion to use?

    useStrongStrikes = true, -- Do you want to use strong strikes?
    turnForWaves = false, -- Do you want to wave in directions other than the on your are facing?
    spells = {
    {name = "exevo gran mas vis", count = 9},
    {name = "exevo gran mas vis", monsters = {"devourer", "blood Beast", "rot elemental"}, count = 7, minhp = 20},
    {name = "thunderstorm rune", count = 5, monsters = {"devourer", "blood Beast", "rot elemental", "Quara Predator Scout", "Quara Hydromancer Scout", "quara pincher scout", "quara constrictor scout"}},
    {name = "exevo vis hur", monsters = 't', count = 2},
    {name = "thunderstorm rune", count = 2, monsters = {"devourer", "blood Beast", "rot elemental", "Quara Predator Scout", "Quara Hydromancer Scout", "quara pincher scout", "quara constrictor scout"}},
    {name = "sudden death rune", monsters = {"devourer"}},
    }
    }

    -----------------------------------------------------------------------
    ---------------------------- END OF CONFIG ----------------------------
    -----------------------------------------------------------------------
    local NORTH, EAST, SOUTH, WEST = 'n', 'e', 's', 'w'
    local lastPlayerSeenTime = 0
    -- All masks must be quadratical. You cannot leave out the zeros.
    -- Also, all masks must have sizes that are odd numbers.
    masks = {

    ball = {
    {0,0,1,1,1,0,0},
    {0,1,1,1,1,1,0},
    {1,1,1,1,1,1,1},
    {1,1,1,1,1,1,1},
    {1,1,1,1,1,1,1},
    {0,1,1,1,1,1,0},
    {0,0,1,1,1,0,0},
    },

    ["exevo gran mas flam"] = {
    {0,0,0,0,0,1,0,0,0,0,0},
    {0,0,0,1,1,1,1,1,0,0,0},
    {0,0,1,1,1,1,1,1,1,0,0},
    {0,1,1,1,1,1,1,1,1,1,0},
    {0,1,1,1,1,1,1,1,1,1,0},
    {1,1,1,1,1,1,1,1,1,1,1},
    {0,1,1,1,1,1,1,1,1,1,0},
    {0,1,1,1,1,1,1,1,1,1,0},
    {0,0,1,1,1,1,1,1,1,0,0},
    {0,0,0,1,1,1,1,1,0,0,0},
    {0,0,0,0,0,1,0,0,0,0,0},
    },

    ["exevo gran mas vis"] = {
    {0,0,0,0,0,0,1,0,0,0,0,0,0},
    {0,0,0,0,0,1,1,1,0,0,0,0,0},
    {0,0,0,0,1,1,1,1,1,0,0,0,0},
    {0,0,0,1,1,1,1,1,1,1,0,0,0},
    {0,0,1,1,1,1,1,1,1,1,1,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0},
    {1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,1,1,1,1,1,1,1,1,1,1,1,0},
    {0,0,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,0,1,1,1,1,1,1,1,0,0,0},
    {0,0,0,0,1,1,1,1,1,0,0,0,0},
    {0,0,0,0,0,1,1,1,0,0,0,0,0},
    {0,0,0,0,0,0,1,0,0,0,0,0,0},
    },

    ["exevo gran mas frigo"] = {
    {0,0,0,0,0,1,0,0,0,0,0},
    {0,0,0,0,1,1,1,0,0,0,0},
    {0,0,0,1,1,1,1,1,0,0,0},
    {0,0,1,1,1,1,1,1,1,0,0},
    {0,1,1,1,1,1,1,1,1,1,0},
    {1,1,1,1,1,1,1,1,1,1,1},
    {0,1,1,1,1,1,1,1,1,1,0},
    {0,0,1,1,1,1,1,1,1,0,0},
    {0,0,0,1,1,1,1,1,0,0,0},
    {0,0,0,0,1,1,1,0,0,0,0},
    {0,0,0,0,0,1,0,0,0,0,0},
    },

    ["exevo gran mas tera"] = {
    {0,0,0,0,0,0,1,0,0,0,0,0,0},
    {0,0,0,0,1,1,1,1,1,0,0,0,0},
    {0,0,0,1,1,1,1,1,1,1,0,0,0},
    {0,0,1,1,1,1,1,1,1,1,1,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0},
    {1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,1,1,1,1,1,1,1,1,1,1,1,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0},
    {0,0,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,0,1,1,1,1,1,1,1,0,0,0},
    {0,0,0,0,1,1,1,1,1,0,0,0,0},
    {0,0,0,0,0,0,1,0,0,0,0,0,0},
    },

    -- Waves should be entered as their NORTH direction.
    -- The extra zeros in the other directions are needed for the
    -- rotations to work properly.

    -- Also frigo hur and gran frigo hur
    ["exevo flam hur"] = {
    {0,0,0,1,1,1,0,0,0,},
    {0,0,0,1,1,1,0,0,0,},
    {0,0,0,0,1,0,0,0,0,},
    {0,0,0,0,1,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,},
    },

    ["exevo vis hur"] = {
    {0,0,0,0,1,1,1,0,0,0,0,},
    {0,0,0,0,1,1,1,0,0,0,0,},
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    },
    ["exevo vis lux"] = {
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,1,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,},
    },
    ["exevo gran vis lux"] = {
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,},
    },

    -- Note that the strike mask will be centered over your look position rather than yourself.
    strikes = {
    -- Strikes dont get any buffer around them, and are treated sort of like waves in that the mask changes depending on which direction you're facing.
    {0,0,1,0,0,},
    {0,1,1,1,0,},
    {1,1,1,1,1,},
    {0,1,1,1,0,},
    {0,0,0,0,0,},
    }
    }

    masks["exevo frigo hur"] = masks["exevo flam hur"]
    masks["exevo gran frigo hur"] = masks["exevo vis hur"]

    -- Positions are stored in one dimensional tables by first converting them to numbers.
    -- Note that for the sake of this script the z-coordinate is irrelevant.
    -- Also, the function is linear. The number for a sum of two positions is the sum of the numbers for each position.
    local function posToNum(x, y)
    return 100000*x+y
    end
    -- Inverse of posToNum.
    local function numToPos(n)
    local y = n % 100000
    local x = (n - y) / 100000
    return x, y
    end

    -- Constants representing what to check for in a converted mask
    local PLAYERS, PLAYERS_AND_MONSTERS = 0, 1

    -- Converts a spell mask to a hashset containing the numerical offsets.
    -- Every position within *margin* squares from a 1 in the original mask is added to the set.
    local function convertMask(mask, margin, marginValue)
    local marginValue = marginValue or PLAYERS
    -- Node that the size of a mask must be an odd number since all spells are symmetrical.
    local xmid, ymid = math.ceil(#mask / 2), math.ceil(#mask[1] / 2)

    local convertedMask = {}

    local function fillMask(margin, value)
    for y = 1, #mask do
    for x = 1, #mask[y] do
    if mask[y][x] == 1 then
    for oy = y - margin, y + margin do
    for ox = x - margin, x + margin do
    -- The masks are stored with indexes starting at 1,
    -- but in-game we have negative offsets for squares above or to the left of the character.
    convertedMask[posToNum(ox - xmid, oy - ymid)] = value
    end
    end
    end
    end
    end
    end

    -- First write the pvp-safe area
    if margin > 0 then
    fillMask(margin, marginValue)
    end
    -- Then overwrite the area in which we check for monsters
    fillMask(0, PLAYERS_AND_MONSTERS)

    return convertedMask
    end

    local function getSurroundings(multifloor, blacklist)
    local monsters, players, shootables = {}, {}, {}

    if not multifloor then
    -- First check for blacklisted ids and enable multifloor if one is found
    for x = $posx - 7, $posx + 7 do
    for y = $posy - 5, $posy + 5 do
    local tile = gettile(x, y, $posz)
    for i = 1, tile.itemcount do
    -- Also treat blacklisted ids as players, since players may arrive
    if blacklist[tile.item[i].id] then multifloor = true end
    end
    end
    end
    end
    local maxzdiff = multifloor and 1 or 0

    local xdiff = Config.pvpsafe and math.max(2, 4 - Config.buffersize) or 4
    local ydiff = Config.pvpsafe and math.max(1, 3 - Config.buffersize) or 4
    -- First filter out the walkable positions since we cannot shoot on top of walls
    for x = $posx - xdiff, $posx + xdiff do
    for y = $posy - ydiff, $posy + ydiff do
    if tileshootable(x, y, $posz) and tileclickable(x, y, $posz) then
    shootables[posToNum(x, y)] = true
    end
    end
    end

    -- Add our creature tiles
    foreach creature c 'm' do
    if c.posz == $posz then
    if c.issummon then
    players[posToNum(c.posx, c.posy)] = c
    else
    monsters[posToNum(c.posx, c.posy)] = c
    end
    end
    end

    -- Add our player tiles
    foreach creature c 'p' do
    local isPartyMember = c.party >= 3 and c.party <= 10
    if math.abs(c.posz - $posz) <= maxzdiff and not (Config.ignoreParty and isPartyMember) and
    c.name ~= $name then
    players[posToNum(c.posx, c.posy)] = c
    end
    end

    return monsters, players, shootables
    end

    local function creatures(monsters, targetMonsters, filter)
    local validMonsters = {}
    local cogx, cogy, numMonsters = 0, 0, 0
    for pos, c in pairs(monsters) do
    if (not targetMonsters or targetMonsters[c.name:lower()]) and
    (not filter or filter(c))
    then
    validMonsters[pos] = c
    cogx = cogx + c.posx
    cogy = cogy + c.posy
    numMonsters = numMonsters + 1
    end
    end

    return validMonsters, {x = cogx/numMonsters, y = cogy/numMonsters}
    end

    local function getScore(pos, mask, monsters, players, finalcheck, pvpsafe)
    local score = 0
    local effectedMonsters = {}
    local monsters = monsters or {}

    for offset, TYPE in pairs(mask) do
    if pvpsafe and players[pos + offset] then
    return -1
    elseif TYPE == PLAYERS_AND_MONSTERS and monsters[pos + offset] then
    score = score + 1
    table.insert(effectedMonsters, monsters[pos + offset])
    end
    end
    if not finalcheck or finalcheck(effectedMonsters) then
    return score
    else
    return 0
    end
    end

    local function getBestBallPos(monsters, players, shootables, mask, monsterNames, pvpsafe, filter, finalcheck)
    local monsters, cog = creatures(monsters, monsterNames, filter)
    local cx, cy = cog.x, cog.y

    -- Ties are broken by preference for positions close to the monster's center of gravity
    local function getTieScore(num)
    local x, y = numToPos(num)
    return math.abs(cx-x) + math.abs(cy-y)
    end

    -- Then find the best one among the shootables
    local bestScore, bestTieScore, bestPos
    for pos, _ in pairs(shootables) do
    local score = getScore(pos, mask, monsters, players, finalcheck, pvpsafe)
    local tieScore = getTieScore(pos)
    if not bestScore or score > bestScore or (score == bestScore and tieScore < bestTieScore) then
    bestScore, bestTieScore, bestPos = score, tieScore, pos
    end
    end
    return bestPos, bestScore
    end

    -- For printing purposes
    local SUCCESS, SKIP, RETRY = 0, 1, 2
    statusString = {
    [SUCCESS] = "SUCCESS",
    [SKIP] = "SKIP",
    [RETRY] = "RETRY"
    }
    -- Possible return values for a function that tries to shoot a spell
    local STRIKE, SINGLE_TARGET_RUNE, BALL, UE, WAVE = 0, 1, 2, 3, 4
    local typeString = {
    [STRIKE] = "STRIKE",
    [SINGLE_TARGET_RUNE] = "SINGLE TARGET RUNE",
    [BALL] = "BALL",
    [UE] = "UE",
    [WAVE] = "WAVE",
    }

    local function getType(spell)
    if type(spell) == 'number' then
    spell = Item.GetName(spell)
    end

    spell = spell:lower()

    for _, name in ipairs({"thunderstorm", "avalanche", "great fireball", "stone shower"}) do
    if spell:match(name) then
    return BALL
    end
    end

    if spell:match("exori") then
    return STRIKE
    end

    if spell:match("exevo gran mas") then
    return UE
    end

    if spell:match("exevo") then
    return WAVE
    end

    return SINGLE_TARGET_RUNE
    end

    local function rotateMask(mask, dir)
    local ymax, xmax = #mask, #mask[1]

    local function rotate(x, y)
    if dir == NORTH then
    return x, y
    elseif dir == EAST then
    return (ymax - y) + 1, x
    elseif dir == SOUTH then
    return x, (ymax - y) + 1
    elseif dir == WEST then
    return y, x
    end
    end

    local newMask = {}
    for y = 1, ymax do
    table.insert(newMask, {})
    for x = 1, xmax do
    table.insert(newMask[y], 0)
    end
    end

    for y = 1, ymax do
    for x = 1, xmax do
    if mask[y][x] == 1 then
    local nx, ny = rotate(x, y)
    newMask[ny][nx] = 1
    end
    end
    end

    return newMask
    end

    local function getMask(spell, TYPE, margin)
    if TYPE == WAVE then
    if not masks[spell] then print("Unknown spell: " .. spell) end
    local convertedMasks = {}
    for _, dir in ipairs({NORTH, EAST, SOUTH, WEST}) do
    convertedMasks[dir] = convertMask(rotateMask(masks[spell], dir), margin)
    end
    return convertedMasks
    elseif TYPE == STRIKE then
    local convertedMasks = {}
    for _, dir in ipairs({NORTH, EAST, SOUTH, WEST}) do
    convertedMasks[dir] = convertMask(rotateMask(masks.strikes, dir), 0)
    end
    return convertedMasks
    end

    local base = masks.strikes
    if TYPE == BALL then
    base = masks.ball
    elseif TYPE == UE then
    if not masks[spell] then print("Unknown spell: " .. spell) end
    base = masks[spell]
    end
    return convertMask(base, margin)
    end

    local mana = {
    ["exevo gran mas vis"] = 600,
    ["exevo gran mas flam"] = 1100,
    ["exevo gran mas tera"] = 700,
    ["exevo gran mas frigo"] = 1050,

    ["exori vis"] = 20,
    ["exori flam"] = 20,
    ["exori tera"] = 20,
    ["exori frigo"] = 20,
    ["exori mort"] = 20,
    ["exori moe ico"] = 20,

    ["exori gran vis"] = 60,
    ["exori gran flam"] = 60,
    ["exori gran tera"] = 60,
    ["exori gran frigo"] = 60,

    ["exori max frigo"] = 100,
    ["exori max frigo"] = 100,
    ["exori max frigo"] = 100,
    ["exori max frigo"] = 100,

    ["exori min flam"] = 6,
    ["exori amp vis"] = 60,

    ["exevo flam hur"] = 25,
    ["exevo vis hur"] = 170,
    ["exevo frigo hur"] = 25,
    ["exevo gran frigo hur"] = 170,

    ["exevo vis lux"] = 40,
    ["exevo gran vis lux"] = 110,
    }

    function string:titlecase()
    return self:gsub("(%a)([%w_']*)", function(first, rest) return first:upper()..rest:lower() end)
    end

    local function convertCategory(c)
    local monsters = {}
    foreach settingsentry e 'Targeting/Creatures' do
    local name = getsetting(e, 'Name')
    local cat = getsetting(e, 'Category')
    if not name:find('category') and (cat ~= '' and c:find(cat)) then
    monsters[name:lower()] = true
    end
    end
    return monsters
    end


    function convertSpellConfig(arg)
    local TYPE = getType(arg.name)
    local spell = arg.name:lower()

    local monsters
    -- Build up the target list, if there is one
    if arg.monsters then
    if type(arg.monsters) == "string" then
    monsters = convertCategory(arg.monsters)
    else
    monsters = {}
    for _, name in ipairs(arg.monsters) do
    monsters[name:lower()] = true
    end
    end
    end

    -- Range and mana settings.
    -- Could be rewritten to use spellinfo, but
    -- I wanted to reuse as much as possible of my old Xeno code.
    local range = spell == "exori amp vis" and 5 or 3
    local mana = mana[spell] or 0

    -- Filters determine wheter a given creature is valid or not.
    -- Generally this will inspect the creatures health percentage
    local filter
    if arg.minhp or arg.maxhp then
    local minhp = arg.minhp or 0
    local maxhp = arg.maxhp or 100
    filter = function(c)
    return c.hppc < maxhp and c.hppc > minhp
    end
    end

    -- Final checks determine whether a group of creatures are valid.
    -- For example, check that an ava cast hits at least one Demon Skeleton, otherwise fall through and try a GFB instead.
    local finalcheck
    if arg.atLeastOne then
    local atLeastOneMonsters = {}
    for _, name in ipairs(arg.atLeastOne) do
    atLeastOneMonsters[name] = true
    end

    finalcheck = function(creatures)
    for _, c in ipairs(creatures) do
    if atLeastOneMonsters[c.name] then return true end
    end
    return false
    end
    end

    return {
    spell = spell,
    type = TYPE,
    monsters = monsters,
    range = range,
    mana = mana,
    count = function() return arg.count end,
    enabled = function() return true end,
    filter = filter,
    finalcheck = finalcheck,
    }
    end

    local opposite = {
    [NORTH] = SOUTH,
    [SOUTH] = NORTH,
    [EAST] = WEST,
    [WEST] = EAST,
    }

    local function tryTurnAway()
    local dirs = {}
    foreach creature c 'ps' do
    if math.max(math.abs(c.posx - $posx), math.abs(c.posy - $posy)) <= 3 then
    if c.posx - $posx > 0 then
    dirs[EAST] = true
    elseif c.posx - $posx < 0 then
    dirs[WEST] = true
    end

    if c.posy - $posy > 0 then
    dirs[SOUTH] = true
    elseif c.posy - $posy < 0 then
    dirs[NORTH] = true
    end
    end
    end

    for _, dir in ipairs({NORTH, EAST, SOUTH, WEST}) do
    if dirs[dir] and not dirs[opposite[dir]] then return turn(opposite[dir]) end
    end
    end

    local function getLookPos()
    if $self.dir == NORTH then
    return $posx, $posy - 1
    elseif $self.dir == EAST then
    return $posx + 1, $posy
    elseif $self.dir == SOUTH then
    return $posx, $posy + 1
    elseif $self.dir == WEST then
    return $posx - 1, $posy
    end
    end

    local currentMasks = {}
    local function updateMasks()
    local spells = {}
    for _, data in ipairs(Config.spells) do
    local spell = data.name:lower()
    currentMasks[spell] = getMask(spell, getType(spell), Config.buffersize)
    end

    currentMasks["exori flam"] = getMask("exori flam", STRIKE, Config.buffersize)
    end
    updateMasks()

    lastRuneCast = os.clock()
    function getShooterFunc(config, blacklist)
    if config.type == UE then
    return function(_, monsters, players)
    local spell = config.spell
    if not config.enabled() then return SKIP end

    -- UE's cant be checked precisely, so dont cast them when weve seen players around lately.
    if Config.pvpsafe and (os.clock() - lastPlayerSeenTime) < 10 then
    return SKIP
    end
    -- Return instantly if the spell is on cooldown or we dont have mana
    if cooldown(spell) > 2000 or $mp < config.mana then
    return SKIP
    elseif cooldown(spell) > 0 then
    return RETRY
    end

    local mask = currentMasks[spell]
    local monsters = creatures(monsters, config.monsters, config.filter)
    local score = getScore(posToNum($posx, $posy), mask, monsters, players, config.finalcheck, Config.pvpsafe)
    -- If we found enough creatures, try casting, else skip the spell
    -- Also, don't UE if there's more monsters standing just outside the area
    if score >= config.count() then
    cast(spell)
    return cooldown(spell) == 0 and RETRY or SUCCESS
    else
    return SKIP
    end
    end
    elseif config.type == BALL then
    return function(mask, monsters, players, shootables)
    --print(config.spell, tostring(config.enabled()))
    if not config.enabled() then return SKIP end
    -- Skip if we dont have that rune
    if itemcount(config.spell) == 0 then return SKIP end
    if cooldown("exori flam") > 0 then return RETRY end
    -- Find the best position to shoot the rune
    local numpos, score = getBestBallPos(monsters, players, shootables, mask, config.monsters, Config.pvpsafe, config.filter, config.finalcheck)
    -- Casting on self is more efficient, so always compare it with the best pos
    local monsters = creatures(monsters, config.monsters, config.filter)
    local selfScore = getScore(posToNum($posx, $posy), mask, monsters, players, config.finalcheck, Config.pvpsafe)
    -- If we found a good position)
    if numpos and score >= config.count() then
    local x, y = numToPos(numpos)
    lastRuneCast = os.clock()
    pausewalking(1000)
    if $fasthotkeys and selfScore + 0 >= score and selfScore >= config.count() then
    useoncreature(itemid(config.spell), $self)
    else
    useitemon(itemid(config.spell), 0, ground(x, y, $posz))
    end
    pausewalking(100 + $ping)

    if cooldown("exori flam") ~= 0 then
    return SUCCESS
    else
    return RETRY
    end
    end
    return SKIP
    end
    elseif config.type == WAVE then
    return function(mask, monsters, players)
    if not config.enabled() then return SKIP end

    -- Skip if the spell is on a long cooldown or we dont have mana
    if cooldown(config.spell) > 2000 or $mp < config.mana then return SKIP end
    local pos = posToNum($posx, $posy)

    local monsters = creatures(monsters, config.monsters, config.filter)
    -- Determine the best direction to wave
    local bestDir, bestScore
    for _, dir in ipairs({NORTH, EAST, SOUTH, WEST}) do
    local score = getScore(pos, mask[dir], monsters, players, config.finalcheck, Config.pvpsafe)

    -- All directions must be pvp-safe for us to wave, cause we can fail to turn.
    if score == -1 then return SKIP end

    if (Config.turnForWaves or dir == $self.dir) and (not bestScore or score > bestScore) then
    bestDir, bestScore = dir, score
    end
    end

    -- Skip if we couldn't find any sufficiently good directions
    if not bestDir or bestScore < config.count() then
    return SKIP
    else
    pausewalking(1000)
    -- Note that for waves we prefer to skip the spell rather than retry if something goes wrong
    if $self.dir ~= bestDir then
    turn(bestDir)
    waitping()
    end
    if $self.dir ~= bestDir then return end
    cast(config.spell)
    pausewalking(200)
    return cooldown(config.spell) == 0 and SKIP or SUCCESS
    end
    end
    elseif config.type == SINGLE_TARGET_RUNE then
    return function()
    -- Skip if we dont have a target or we dont have the rune
    if itemcount(config.spell) == 0 then return SKIP end
    -- Retry if the spell is on cooldown
    if cooldown("exori flam") > 0 then return SKIP end

    local function wouldDieToAStrike(c)
    local distance = math.max(math.abs($posx - c.posx), math.abs($posy - c.posy))
    if distance > 3 then return false end

    local info = creatureinfo(c.name)
    local hp = c.hppc * info.hp / 100
    local _, dmg = getBestStrike(c)
    return dmg >= hp
    end

    local rinfo = runeinfo(config.spell)
    local target, bdmg, bkills
    foreach creature c "mf" do
    if ((not config.monsters) or config.monsters[c.name:lower()]) and
    (not config.filter or config.filter(c)) and c.isshootable then
    if wouldDieToAStrike(c) then
    return SKIP
    end
    local info = creatureinfo(c.name)
    local hp = info.hp * c.hppc / 100
    local mod = info[rinfo.dmgtype:lower() .. "mod"] / 100
    local mindmg = mod * rinfo.mindmg
    local dmg = math.min(mindmg, hp)
    local kills = mindmg >= hp
    if (not target) or
    (kills and not bkills) or
    (dmg > bdmg)
    then
    target = c
    bdmg = dmg
    bkills = kills
    end
    end
    end
    -- Check that the target is valid for out spell before casting
    local c = $target
    if target then
    -- Retry if the spell is still on a short cooldown
    useoncreature(config.spell, target)
    if cooldown("exori flam") ~= 0 then
    lastRuneCast = os.clock()
    return SUCCESS
    else
    return SKIP
    end
    else
    return SKIP
    end
    end
    end
    end

    function parseConfig(ShooterConfig)
    local spells = {}

    -- The blacklist is a list of IDs. If an ID from the blacklist is on the screen
    -- the shooter will enable multi-floor pvp safety.
    local blacklist = {}
    for _, id in ipairs(ShooterConfig.blacklistIds) do
    blacklist[id] = true
    end

    -- The set of ball targets is used to allow the potion drinker to drink more frequently.
    local ballTargets = {}

    for _, spell in ipairs(ShooterConfig.spells) do
    local config = convertSpellConfig(spell)

    local shooter = getShooterFunc(config, blacklist)
    table.insert(spells, {name = config.spell, type = config.type, shooter = shooter})

    if config.type == BALL then
    if config.monsters and ballTargets then
    for _, name in ipairs(config.monsters) do
    ballTargets[name] = true
    end
    else
    ballTargets = nil
    end
    end
    end

    return {spells, blacklist}, ballTargets
    end

    function getBestStrike(target)
    local targetName = target.Name
    local info = creatureinfo(targetName)
    local hp = target.hppc * info.hp / 100
    local spells = {"exori flam", "exori frigo", "exori vis", "exori tera"}
    if $vocshort == "S" then
    table.insert(spells, "exori mort")
    if Config.useStrongStrikes then
    table.insert(spells, "exori gran vis")
    table.insert(spells, "exori gran flam")
    end
    elseif $vocshort == "D" then
    table.insert(spells, "exori moe ico")
    if Config.useStrongStrikes then
    table.insert(spells, "exori gran frigo")
    table.insert(spells, "exori gran tera")
    end
    end

    local function priority(spell)
    local sinfo = spellinfo(spell)
    local mod = info[sinfo.dmgtype:lower() .. "mod"] / 100
    if mod * sinfo.mindmg > hp then
    return hp, sinfo.mp, mod
    else
    return mod * sinfo.mindmg, sinfo.mp, mod
    end
    end

    local bspell, bdmg, bmp, bmod
    for _, spell in ipairs(spells) do
    local sdmg, smp, smod = priority(spell)
    local hotkeyed = clientspellhotkey(spell) ~= 'not found' or $fasthotkeys
    if cooldown(spell) == 0 and hotkeyed and (not bspell or sdmg > bdmg or (sdmg == bdmg and smp < bmp) or (sdmg == bdmg and smp == bmp and smod > bmod)) then
    bspell, bdmg, bmp, bmod = spell, sdmg, smp, smod
    end
    end

    return bspell, bdmg
    end

    local function tryCastBestStrike(players)
    local c = $target

    -- Don't case without a target!
    if c.id == 0 then return SKIP end

    local spell = getBestStrike(c)
    -- getBestSpell only returns nil if all strikes are on cooldown
    if not spell then return SKIP end

    -- Skip if we dont have mana
    if $mp < spellinfo(spell).mp then return SKIP end

    local lx, ly = getLookPos()
    local score = getScore(posToNum(lx, ly), currentMasks["exori flam"][$self.dir], nil, players, nil, Config.pvpsafe)
    -- If the strike isn't pvpsafe, then try turning away from any other player and then skip the spell.
    -- Don't retry since that may get us stuck in a loop.
    if score == -1 then
    tryTurnAway()
    return SKIP
    end

    -- Check that the target is in range before casting
    if math.max(math.abs(c.posx - $posx), math.abs(c.posy - $posy)) <= 3 and $target.id ~= 0 then
    cast(spell)
    return cooldown(spell) == 0 and RETRY or SUCCESS
    end
    return SKIP
    end

    local function tryCastSpell(spells, blacklist)
    local monsters, players, shootables = getSurroundings(Config.multiFloor, blacklist)
    for _, spell in ipairs(spells) do
    status = spell.shooter(currentMasks[spell.name], monsters, players, shootables)
    if status == SUCCESS or status == RETRY then
    return
    end
    monsters, players, shootables = getSurroundings(Config.multiFloor, blacklist)
    end
    tryCastBestStrike(players)

    end

    pconfig, _RuneMonsters = parseConfig(Config)
    local pconfig = pconfig

    lastRuneCast = 0
    lastDrunk = 0

    updateMasks()
    init end

    -- Check for players to disable ue
    foreach creature c 'pf' do
    local isPartyMember = c.party >= 3 and c.party <= 10
    if c.name ~= $name and not (Config.ignoreParty and isPartyMember) then
    lastPlayerSeenTime = os.clock()
    end
    end

    -- Cast a spell
    if not $pzone then
    tryCastSpell(unpack(pconfig))
    end

    -- Drink a potion
    local timeToNextCast = cooldown(SPELL_GROUP_ATTACK)
    local timeSinceRuneCast = os.clock() - lastRuneCast
    local potion = Config.manaPotion
    local precount = itemcount(potion)

    if precount > 0 then
    if (timeSinceRuneCast > 1 and os.clock() - lastDrunk > 1) and
    ($mppc < 30 or ($mppc < 80 and (maround(7, unpack(_RuneMonsters)) < 2 or timeToNextCast > 800))) then
    useoncreature(potion, $self)
    if precount ~= itemcount(potion) then
    lastDrunk = os.clock()
    end
    end
    end


    auto(100)
    Last edited by shAdOwArt; 11-03-2016 at 12:24 PM.

  2. #2
    Moderator RoxZin xD's Avatar
    Join Date
    Dec 2013
    Location
    Rio de Janeiro
    Posts
    4,914
    Reputation
    109
    Rep Power
    31
    That action though :v
    I used to like that wave caster on xenobot, it was so perfect. I'll sure as hell give it a try with windbot

    Btw, exevo gran frigo hur doesn't have the same hitrange as frigo/flam hur

    Frigo/flam hur is on a range like this:
    1 1 1 1 1
    0 1 1 1 0
    0 1 1 1 0
    0 0 1 0 0
    0 0 0 0 0

    While gran frigo hur is
    0 1 1 1 0
    0 1 1 1 0
    0 0 1 0 0
    0 0 0 0 0



    And you got an "exori tear" somewhere in your code



    Troubled Animals Quest [100+]
    ALL
    Feyrist Animals Surface [160+]
    RP
    Feyrist Silencers Underground X1 [180+]
    RP | EK
    Feyrist Silencers Underground X2 [200+]
    RP | EK
    Feyrist Silencers Surface [210+]
    RP | EK
    Rathleton Sewers [240+]
    RP
    Glooth Fairy [350+]
    ED/MS
    Hardcore Draken Walls [400+]
    EK

  3. #3
    Free User shAdOwArt's Avatar
    Join Date
    Apr 2015
    Location
    Kharos
    Posts
    189
    Reputation
    151
    Rep Power
    19
    Quote Originally Posted by RoxZin xD View Post
    That action though :v
    I used to like that wave caster on xenobot, it was so perfect. I'll sure as hell give it a try with windbot

    Btw, exevo gran frigo hur doesn't have the same hitrange as frigo/flam hur

    Frigo/flam hur is on a range like this:
    1 1 1 1 1
    0 1 1 1 0
    0 1 1 1 0
    0 0 1 0 0
    0 0 0 0 0

    While gran frigo hur is
    0 1 1 1 0
    0 1 1 1 0
    0 0 1 0 0
    0 0 0 0 0



    And you got an "exori tear" somewhere in your code
    Good thing I don't play Druids J/k I'll fix those issues tomorrow, thanks for pointing them out.

    Personally I always hated one thing about Xeno's waves. Tae vis hur for example, the monsters on the Xs would always manage to walk out of the aoe before you actually cast the wave. So I removed those from the spell aoe here:

    0 1 1 1 0
    0 X 1 X 0
    0 0 1 0 0
    0 0 0 0 0

  4. #4
    Moderator RoxZin xD's Avatar
    Join Date
    Dec 2013
    Location
    Rio de Janeiro
    Posts
    4,914
    Reputation
    109
    Rep Power
    31
    Quote Originally Posted by shAdOwArt View Post
    Good thing I don't play Druids J/k I'll fix those issues tomorrow, thanks for pointing them out.

    Personally I always hated one thing about Xeno's waves. Tae vis hur for example, the monsters on the Xs would always manage to walk out of the aoe before you actually cast the wave. So I removed those from the spell aoe here:

    0 1 1 1 0
    0 X 1 X 0
    0 0 1 0 0
    0 0 0 0 0
    Yeah same happens with the one I'm using here I'll surely try yours out when my stamina goes up



    Troubled Animals Quest [100+]
    ALL
    Feyrist Animals Surface [160+]
    RP
    Feyrist Silencers Underground X1 [180+]
    RP | EK
    Feyrist Silencers Underground X2 [200+]
    RP | EK
    Feyrist Silencers Surface [210+]
    RP | EK
    Rathleton Sewers [240+]
    RP
    Glooth Fairy [350+]
    ED/MS
    Hardcore Draken Walls [400+]
    EK

  5. #5
    Free User
    Join Date
    Aug 2014
    Location
    Rio de Janeiro - Brazil
    Posts
    356
    Reputation
    66
    Rep Power
    20
    Awesome script, as always.
    I'm thinking on ways to configure some additional things. I was using a very basic, self-made shooter, that also has waves in a simple maroundspell() check, and I did something like: Suppose there are 7 monsters to shoot GFB. It would only use Wave if the total wave damage was higher than gfb damage (I did just a simple check if there were 3+ monsters on wave area). As there was no config in my simple script I would just do like
    if maroundspell('exevo vis hur', unpack(monsters)) >= 3 and cancastspell('exevo vis hur') and (maroundspell('exevo vis hur', unpack(monsters)) - getarearunetile(not pvpsafe, unpack(monsters)).amount >= -3) then
    cast('exevo vis hur')
    end


    It`s possible to do something like this here?


    Also, let's say I shoot gfb on Blood beasts and Thunderstorms on Devourer, both on count 2, set by category 'f' and 't' respectively. If there is exactly one Blood Beast and one Devourer, the script wouldn't shoot any of the runes. Any way to set it to shoot any of those runes, or maybe even to shoot the rune that does more damage to the one with more HP.

    EDIT : Could you please add an option on config to not rotate for waves? Would be great when hunting manually, running from monsters.

    EDIT 2: Hunting on Oramond west, using Thunderstorm/GFB, like said above. There are 2 blood beasts (gfb) and 1 devourer (thunderstorm), the bot will shoot the GFB aiming only the 2 blood beasts, devourer will get ignored. If there would be no harm also targeting the devourer, how to do it, even if the gfb isn't strong as the thunder (else than doing a sloppy not-smart way, that I think it would work).

    Thanks again!
    Last edited by Jesseh; 09-20-2016 at 02:29 AM.
    If I`ve helped you somehow and you want to retribute, you can donate me Tibia Coins for char: Musonius

  6. #6
    Free User shAdOwArt's Avatar
    Join Date
    Apr 2015
    Location
    Kharos
    Posts
    189
    Reputation
    151
    Rep Power
    19
    Quote Originally Posted by Jesseh View Post
    Awesome script, as always.
    I'm thinking on ways to configure some additional things. I was using a very basic, self-made shooter, that also has waves in a simple maroundspell() check, and I did something like: Suppose there are 7 monsters to shoot GFB. It would only use Wave if the total wave damage was higher than gfb damage (I did just a simple check if there were 3+ monsters on wave area). As there was no config in my simple script I would just do like
    if maroundspell('exevo vis hur', unpack(monsters)) >= 3 and cancastspell('exevo vis hur') and (maroundspell('exevo vis hur', unpack(monsters)) - getarearunetile(not pvpsafe, unpack(monsters)).amount >= -3) then
    cast('exevo vis hur')
    end


    It`s possible to do something like this here?


    Also, let's say I shoot gfb on Blood beasts and Thunderstorms on Devourer, both on count 2, set by category 'f' and 't' respectively. If there is exactly one Blood Beast and one Devourer, the script wouldn't shoot any of the runes. Any way to set it to shoot any of those runes, or maybe even to shoot the rune that does more damage to the one with more HP.

    EDIT : Could you please add an option on config to not rotate for waves? Would be great when hunting manually, running from monsters.

    EDIT 2: Hunting on Oramond west, using Thunderstorm/GFB, like said above. There are 2 blood beasts (gfb) and 1 devourer (thunderstorm), the bot will shoot the GFB aiming only the 2 blood beasts, devourer will get ignored. If there would be no harm also targeting the devourer, how to do it, even if the gfb isn't strong as the thunder (else than doing a sloppy not-smart way, that I think it would work).

    Thanks again!
    Devourers and blood beasts should be on both the thunderstorm and the gfb categories since both runes are good against both creatures. Maximizing the number of non-listed creatures as a tie-breaker is something I might add in the future, but in this case its mostly you setting up the script in a bad way. Non-turning waves (and pvp-checks in all directions in case you fail to turn) is also something I might add in the future but not now. I want to focus on making some hunting script now.

    Automatically choosing the spell that would do the most damage so that you dont have to set up the same spell several times sounds like a cool feature that I might consider too.

    Edit: The more I think about it, the more I want to write a version that uses damage calculations to pick the best aoe spell too.
    Last edited by shAdOwArt; 09-20-2016 at 12:39 PM.

  7. #7
    Free User
    Join Date
    Aug 2014
    Location
    Rio de Janeiro - Brazil
    Posts
    356
    Reputation
    66
    Rep Power
    20
    Ok, I will set both as Thunderstorm. Anyway I checked that my exp got higher with your shooter, since it retries the rune attack, unlike my previous shooter, resulting in shooting basically every round.
    If I`ve helped you somehow and you want to retribute, you can donate me Tibia Coins for char: Musonius

  8. #8
    Free User
    Join Date
    Aug 2014
    Location
    Rio de Janeiro - Brazil
    Posts
    356
    Reputation
    66
    Rep Power
    20
    Shooter won't work against Mooh'Tah Warrior.
    If I`ve helped you somehow and you want to retribute, you can donate me Tibia Coins for char: Musonius

  9. #9
    Free User shAdOwArt's Avatar
    Join Date
    Apr 2015
    Location
    Kharos
    Posts
    189
    Reputation
    151
    Rep Power
    19
    Quote Originally Posted by Jesseh View Post
    Shooter won't work against Mooh'Tah Warrior.
    Fixed (it was because I was converting the names to title case, and the converter couldn't that there was a capital letter after an apostrophe). Also improved waving slightly.

  10. #10
    Free User Dermeister's Avatar
    Join Date
    May 2016
    Posts
    2
    Reputation
    10
    Rep Power
    0
    Can pallies use this one too?

 

 

Posting Permissions

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