Nope. It does not know how to cast the paladin spells. Also, unlike mages paladins should use a spellcaster that syncs their spells with their distance attack.
Nope. It does not know how to cast the paladin spells. Also, unlike mages paladins should use a spellcaster that syncs their spells with their distance attack.
Updated the script to prefer casting runes with "use on self" hotkeys. This really improves performance when surrounded or close to. Since they are so fast self casts are given a 20% hit count boost when compared to regular casts (they still have to hit the configured min amount though, and the boost also only applies when compared to using the same rune without self cast -- not against other spells).
Great once again! I was thinking on how to do it but didn't managed to implement it
If I`ve helped you somehow and you want to retribute, you can donate me Tibia Coins for char: Musonius
I tweaked a bit and it's working for my paladin. The downside is that you won't use potions here, as I'm using my own multi potion user, danger based.
init start
-----------------------------------------------------------------------
-------------------------------- CONFIG -------------------------------
-----------------------------------------------------------------------
ShooterConfig = {
pvpsafe = false,
buffersize = 2,
blacklistIds = {469, 1959, 1960},
manaPotion = "ultimate mana potion",
useStrongStrikes = true,
spells = {
{name = "exevo mas san", monsters = {'Spectre','Banshee','Grim Reaper','Gravedigger','Shadow Pupil','Banshee','Mutated Bat','Werewolf','Nightmare Scion','Nightmare','Hellspawn','Spectre','Vampire Bride','Vampire','Nightstalker',"Medusa","Serpent Spawn","Green Djinn","Efreet","Elder Bonelord","Behemoth","Giant Spider","Massive Earth Elemental","Hydra","Hero","Vicious Squire","Necromancer","Renegade Knight","Vile Grandmaster","Blood Priest","Undead Gladiator","Bonebeast","Lich","Mooh'Tah Warrior","Worm Priestess","Minotaur Hunter","Minotaur Amazon","Execowtioner","Moohtant",'Glooth powered minotaur','Execowtioner','Worm Priestess','Minotaur Invader','Moohtant','Minotaur Amazon','Lost Berserker','Metal Gargoyle','Rustheap Golem','Lost Husher','Lost Thrower','Enslaved Dwarf','Lost Basher',"Medusa","Serpent Spawn","Hydra","Bonebeast","Grim Reaper","Rot Elemental", "Blood Beast","Quara Constrictor Scout","Quara Hydromancer Scout","Quara Mantassin Scout","Quara Predator Scout", "Quara Pincher Scout", "Devourer","Glooth Anemone","Glooth Golem","Glooth Brigand","Glooth Bandit","Fury","Spectre","Nightmare","Metal Gargoyle","Rustheap Golem"}, count = 2},
-- {name = "exevo gran mas vis", monsters = {'Glooth powered minotaur','Execowtioner','Worm Priestess','Minotaur Invader','Moohtant','Minotaur Amazon','Lost Berserker','Metal Gargoyle','Rustheap Golem','Lost Husher','Lost Thrower','Enslaved Dwarf','Lost Basher',"Medusa","Serpent Spawn","Hydra","Bonebeast","Glooth Blob","Grim Reaper","Rot Elemental", "Blood Beast","Quara Constrictor Scout","Quara Hydromancer Scout","Quara Mantassin Scout","Quara Predator Scout", "Quara Pincher Scout", "Devourer","Glooth Anemone","Glooth Golem"}, count = 5, minhp = 20},
-- {name = "thunderstorm rune", count = 5, atLeastOne = {'Glooth powered minotaur','Execowtioner','Worm Priestess','Minotaur Invader','Moohtant','Minotaur Amazon','Lost Berserker','Metal Gargoyle','Rustheap Golem','Lost Husher','Lost Thrower','Enslaved Dwarf','Lost Basher',"Medusa","Serpent Spawn","Hydra","Bonebeast","Glooth Blob","Grim Reaper","Rot Elemental", "Blood Beast","Quara Constrictor Scout","Quara Hydromancer Scout","Quara Mantassin Scout","Quara Predator Scout", "Quara Pincher Scout", "Devourer","Glooth Anemone","Glooth Golem"}},
--{name = "great fireball rune", monsters = 't', count = 5},
-- {name = "exevo vis hur", monsters = 't', count = 4},
-- {name = "exevo gran vis lux", monsters = 't', count = 2},
--{name = "thunderstorm rune", count = 2, atLeastOne = {"Quara Constrictor Scout","Quara Hydromancer Scout","Quara Mantassin Scout","Quara Predator Scout", "Quara Pincher Scout"}},-- atLeastOne = {'Glooth powered minotaur','Execowtioner','Worm Priestess','Minotaur Invader','Moohtant','Minotaur Amazon','Lost Berserker','Metal Gargoyle','Rustheap Golem','Lost Husher','Lost Thrower','Enslaved Dwarf','Lost Basher',"Medusa","Serpent Spawn","Hydra","Bonebeast","Glooth Blob","Grim Reaper","Rot Elemental", "Blood Beast","Quara Constrictor Scout","Quara Hydromancer Scout","Quara Mantassin Scout","Quara Predator Scout", "Quara Pincher Scout", "Devourer","Glooth Anemone","Glooth Golem"}},
{name = "thunderstorm rune", monsters = 't', count = 2},
{name = "great fireball rune", monsters = 'f', count = 2},
-- {name = "great fireball rune", monsters = 'f', count = 2},
--{name = "avalanche rune", monsters = 'i', count = 2},
-- {name = "thunderstorm rune", count = 1},-- atLeastOne = {'Glooth powered minotaur','Execowtioner','Worm Priestess','Minotaur Invader','Moohtant','Minotaur Amazon','Lost Berserker','Metal Gargoyle','Rustheap Golem','Lost Husher','Lost Thrower','Enslaved Dwarf','Lost Basher',"Medusa","Serpent Spawn","Hydra","Bonebeast","Glooth Blob","Grim Reaper","Rot Elemental", "Blood Beast","Quara Constrictor Scout","Quara Hydromancer Scout","Quara Mantassin Scout","Quara Predator Scout", "Quara Pincher Scout", "Devourer","Glooth Anemone","Glooth Golem"}},
}
}
-----------------------------------------------------------------------
---------------------------- END OF CONFIG ----------------------------
-----------------------------------------------------------------------
local NORTH, EAST, SOUTH, WEST = 'n', 'e', 's', 'w'
-- 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 mas san"] = {
{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,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,0,1,1,1,1,1,1,1,0,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,0,0,0,0,0,0},
{0,0,0,0,0,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 have no aoe. However, we still need a mask in order to add a safety margin to it.
{1}
}
}
masks["exevo frigo hur"] = masks["exevo flam hur"]
masks["exevo gran frigo hur"] = masks["exevo flam 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)
-- 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, PLAYERS)
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 = ShooterConfig.pvpsafe and math.max(2, 4 - ShooterConfig.buffersize) or 4
local ydiff = ShooterConfig.pvpsafe and math.max(1, 4 - ShooterConfig.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) then
shootables[posToNum(x, y)] = true
end
end
end
-- Add our creature tiles
foreach creature c 'm' do
if c.posz == $posz then
monsters[posToNum(c.posx, c.posy)] = c
end
end
-- Add our player tiles
foreach creature c 'p' do
-- TODO: Also add party member checks
if math.abs(c.posz - $posz) <= maxzdiff 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 mas") then
return UE
end
if spell:match("exmageevo") 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
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 mas san"] = 1800,
["exori san"] = 20,
["exori con"] = 25,
["exori gran con"] = 55,
}
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 == c 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 gran con" and 7 or 4
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 function tryTurnAway()
local dirs = {}
foreach creature c 'ps' do
if math.max(math.abs(c.posx - $posx), math.abs(c.posy - $posy)) <= 4 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 not dirs[dir] then return turn(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 lastRuneCast = os.clock()
function getShooterFunc(config, blacklist)
if config.type == UE then
return function(mask, monsters, players)
if not config.enabled() then return SKIP end
-- Return instantly if the spell is on cooldown or we dont have mana
if cooldown(config.spell) > 1000 or $mp < config.mana then
return SKIP
elseif cooldown(config.spell) > 0 then
return RETRY
end
local monsters = creatures(monsters, config.monsters, config.filter)
local score = getScore(posToNum($posx, $posy), mask, monsters, players, config.finalcheck, ShooterConfig.pvpsafe)
-- If we found enough creatures, try casting, else skip the spell
if score >= config.count() then
cast(config.spell)
return cooldown(config.spell) == 0 and RETRY or SUCCESS
else
return SKIP
end
end
elseif config.type == BALL then
return function(mask, monsters, players, shootables)
-- Skip if we dont have that rune
if itemcount(config.spell) == 0 then return SKIP end
if cooldown("exori con") > 0 then return RETRY end
-- Find the best position to shoot the rune
local numpos, score = getBestBallPos(monsters, players, shootables, mask, config.monsters, ShooterConfig.pvpsafe, config.filter, config.finalcheck)
-- Check the self score since self shooting is faster
local monsters = creatures(monsters, config.monsters, config.filter)
local selfScore = getScore(posToNum($posx, $posy), mask, monsters, players, config.finalcheck, ShooterConfig.pvpsafe)
-- If we found a good position)
local mincount = config.count()
if numpos and score >= mincount then
local x, y = numToPos(numpos)
pausewalking(1000)
local selfBonus = math.max(1, 0.2 * score)
if selfScore + selfBonus >= score and selfScore >= mincount then
useoncreature(config.spell, $self)
else
useitemon(config.spell, 0, ground(x, y, $posz))
end
pausewalking(0)
lastRuneCast = os.clock()
wait(200)
if cooldown("exori con") ~= 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, ShooterConfig.pvpsafe)
if 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(0)
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 $target.id == 0 or itemcount(config.spell) == 0 then return SKIP end
-- Retry if the spell is on cooldown
if cooldown("exori san") > 0 then return RETRY end
-- Check that the target is valid for out spell before casting
local c = $target
if (not config.monsters or config.monsters[c.name]) and
(not config.filter or filter(c))
then
-- Retry if the spell is still on a short cooldown
useoncreature(config.spell, $target)
if cooldown("exori san") ~= 0 then
lastRuneCast = os.clock()
return SUCCESS
else
return RETRY
end
else
return SKIP
end
end
end
end
local currentMasks = {}
local function updateMasks()
local spells = {}
for _, data in ipairs(ShooterConfig.spells) do
local spell = data.name:lower()
currentMasks[spell] = getMask(spell, getType(spell), ShooterConfig.buffersize)
end
currentMasks["exori san"] = getMask("exori san", STRIKE, ShooterConfig.buffersize)
end
updateMasks()
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
local avoidElement = {
-- Druid
[16] = "death",
-- Sorcerer
[8] = "physical",
-- Paladin
[4] = "fire", "energy", "earth", "ice", "death",
}
local elementSpells = {
-- Druid
[16] = {
ice = (ShooterConfig.useStrongStrikes and {"exori gran frigo", "exori frigo"} or {"exori frigo"}),
earth = (ShooterConfig.useStrongStrikes and {"exori gran tera", "exori tear"} or {"exori tera"}),
fire = {"exori flam"},
energy = {"exori vis"},
physical = {"exori moe ico"}
},
-- Sorcerer
[8] = {
fire = (ShooterConfig.useStrongStrikes and {"exori gran flam", "exori flam"} or {"exori flam"}),
energy = (ShooterConfig.useStrongStrikes and {"exori gran vis", "exori vis"} or {"exori vis"}),
earth = {"exori tera"},
ice = {"exori frigo"},
death = {"exori mort"}
},
-- Paladin
[4] = {
physical = (ShooterConfig.useStrongStrikes and {"exori gran con", "exori con"} or {"exori con"}),
holy = {"exori san"}
}
}
local modmap = {
holy = "holymod",
physical = "physicalmod",
}
local function getBestStrike(targetName)
local info = creatureinfo(targetName)
local elements = {"holy", "physical"}
table.sort(elements, function(a, b)
return info[a.."mod"] > info[b.."mod"]
end)
for _, element in ipairs(elements) do
if element ~= avoidElement[$voc] then
for _, spell in ipairs(elementSpells[$voc][element]) do
if cooldown(spell) == 0 then
return spell
end
end
end
end
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.name)
-- 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 san"], nil, players, nil, ShooterConfig.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 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(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
end
--if $vocshort == 'S' or $vocshort == 'D' then
tryCastBestStrike(players)
--end
end
local lastDrunk = os.clock()
local function tryDrinkPotion(potion, runeMonsters)
local timeToNextCast = cooldown("exori san")
local timeSinceRuneCast = os.clock() - lastRuneCast
local precount = itemcount(potion)
if precount > 0 then
if (timeSinceRuneCast > 0.9 and os.clock() - lastDrunk > 0.9) and
($mppc < 30 or ($mppc < 80 and (maround(runeMonsters and unpack(runeMonsters)) < 2 or timeToNextCast > 800))) then
useoncreature(potion, $self)
if precount ~= itemcount(potion) then
lastDrunk = os.clock()
end
end
end
end
local pconfig, runeTargets = parseConfig(ShooterConfig)
init end
if ($trapped or not ShooterDisabled) and cooleddown('attack') then
tryCastSpell(unpack(pconfig))
end
if $vocshort == 'S' or $vocshort == 'D' then
tryDrinkPotion(ShooterConfig.manaPotion, runeTargets)
end
auto(100)
If I`ve helped you somehow and you want to retribute, you can donate me Tibia Coins for char: Musonius
Shadow, all work fine, less the rune shooter, he miss a lot of runes, prioritizing shoot in self instead more amount of mobs. How i can fix it ?
Another thing, how i can change strong strike to cast only if target is about 10%+ hp?
In generally a excellent work here.
Thanks
Add the strong strike as a spell in the config, with a minhp field, and it will be checked before any other strikes. I've explained self-cast runes above. Windbot is truly horrendously awful at shooting runes without self-cast when you get surrounded, 5 seconds between shots is not rare. If you're not going to block monsters and you don't run a high risk of getting trapped you might be better off without self-cast, but then you'll simply have to wait until I feel like adding an option to disable it.
Nice one,
You should add a safe list for pvp safety tho, for those who wants to use it in a teamhunt or so
กิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิ ิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิ ิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิ ิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิ ิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิ ิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิ ิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิ ิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิก ิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิ ิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิ ิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิ ิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิ ิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิ ิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิ ิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิ ิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิ ิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิ ิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิ ิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิ ิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิ ิิิิิิิิ
กิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิ ิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิ ิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิ ิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิ ิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิ ิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิ ิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิ ิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิก ิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิ ิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิ ิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิ ิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิ ิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิ ิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิ ิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิ ิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิ ิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิ ิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิ ิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิ ิิิิิิิิิิิิิิิิกิิิิิิิิิิิิิิิิิิิิกิิิิิิิิิิิิ ิิิิิิิิ