illarion-content/magic/arcane/targeting.lua
2026-05-16 19:08:28 +02:00

973 lines
31 KiB
Lua
Raw Permalink Blame History

--[[
Illarion Server
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
]]
local common = require("base.common")
local runes = require("magic.arcane.runes")
local range = require("magic.arcane.castingRange")
local magic = require("base.magic")
local effectScaling = require("magic.arcane.effectScaling")
local character = require("base.character")
local M = {}
M.playerTargets = {}
local function circlePoints(px, py, radius)
if not radius or type(radius) ~= "number" then
debug("Unexpected result for radius: "..tostring(radius))
return {}
end
if not px or type(px) ~= "number" then
debug("Unexpected result for px: "..tostring(px))
return {}
end
if not py or type(py) ~= "number" then
debug("Unexpected result for py: "..tostring(py))
return {}
end
radius = math.floor(radius)
if radius <= 0 or radius > 50 then
debug("Out of bounds use of DUN with radius of "..tostring(radius))
return {}
end
logPlayer("DUN circle points got through checks with values: radius "..tostring(radius).." py "..tostring(py).." px "..tostring(px))
local points = {}
local x = radius
local y = 0
local err = 0
while x >= y do
-- to try and keep the script as lag free as possible, I'm using points[#points + 1] instead of table.insert as it is supposed to be faster
points[#points + 1] = {px + x, py + y}
points[#points + 1] = {px + y, py + x}
points[#points + 1] = {px - y, py + x}
points[#points + 1] = {px - x, py + y}
points[#points + 1] = {px - x, py - y}
points[#points + 1] = {px - y, py - x}
points[#points + 1] = {px + y, py - x}
points[#points + 1] = {px + x, py - y}
y = y + 1
if err < 0 then
err = err + 2 * y + 1
else
x = x - 1
err = err + 2 * (y - x) + 1
end
end
return points
end
local function getDirection(user, target)
local ux, uy = user.pos.x, user.pos.y
local tx, ty = target.x, target.y
local direction
if ux > tx then
direction = "west"
if uy > ty then
direction = "northwest"
elseif uy < ty then
direction = "southwest"
end
elseif ux < tx then
direction = "east"
if uy > ty then
direction = "northeast"
elseif uy < ty then
direction = "southeast"
end
elseif uy > ty then
direction = "north"
else
direction = "south"
end
return direction
end
local directionCorrection = {
{dir = "east", X = 1, Y = 0},
{dir = "west", X = -1, Y = 0},
{dir = "south", X = 0, Y = 1},
{dir = "north", X = 0, Y = -1},
{dir = "northeast", X = 1, Y = -1},
{dir = "northwest", X = -1, Y = -1},
{dir = "southeast", X = 1, Y = 1},
{dir = "southwest", X = -1, Y = 1}
}
local function getNewCenterPos(targetPosition, direction)
for _, correction in pairs(directionCorrection) do
if direction == correction.dir then
return position(targetPosition.x+correction.X, targetPosition.y+correction.Y, targetPosition.z)
end
end
return targetPosition
end
local function alreadyExistsInList(newPos, positions)
for _, posit in pairs(positions) do
if newPos == posit then
return true
end
end
return false
end
local function getDunClusterPositions(player, targetPosition)
local positions = {}
local direction = getDirection(player, targetPosition)
local newCenterPos = getNewCenterPos(targetPosition, direction)
for x = 0, 1 do
for y = 0, 1 do
local newPositions = {
position(newCenterPos.x + x, newCenterPos.y + y, newCenterPos.z),
position(newCenterPos.x + x, newCenterPos.y - y, newCenterPos.z),
position(newCenterPos.x - x, newCenterPos.y + y, newCenterPos.z),
position(newCenterPos.x - x, newCenterPos.y - y, newCenterPos.z)
}
for _, newPos in pairs(newPositions) do
if newPos ~= targetPosition and newPos ~= newCenterPos then
if not alreadyExistsInList(newPos, positions) then
table.insert(positions, newPos)
end
end
end
end
end
local newPositions = {
position(newCenterPos.x + 2, newCenterPos.y, newCenterPos.z),
position(newCenterPos.x - 2, newCenterPos.y, newCenterPos.z),
position(newCenterPos.x, newCenterPos.y + 2, newCenterPos.z),
position(newCenterPos.x, newCenterPos.y - 2, newCenterPos.z)
}
for _, newPos in pairs(newPositions) do
if newPos ~= player.pos then
if not alreadyExistsInList(newPos, positions) then
table.insert(positions, newPos)
end
end
end
if not alreadyExistsInList(newCenterPos, positions) then
table.insert(positions, newCenterPos)
end
return positions
end
local function getDunPositions(player, targetPosition, spell, startedInAttackMode)
if not player then
return {}
end
local positions = {}
local px, py, pz = player.pos.x, player.pos.y, player.pos.z
local tx, ty = targetPosition.x, targetPosition.y
local seen = {}
local dx, dy = tx - px, ty - py
local distance = math.sqrt(dx*dx + dy*dy)
local PEN = runes.checkSpellForRuneByName("PEN", spell)
local LEV = runes.checkSpellForRuneByName("LEV", spell)
if distance < 3 or player.attackmode or (PEN and LEV) or startedInAttackMode then
return getDunClusterPositions(player, targetPosition)
end
if type(distance) ~= "number" then
return positions
end
local radius = math.floor(distance + 0.5)
if radius <= 0 then
return positions
end
local circle = circlePoints(px, py, radius)
logPlayer("DUN circle points finished")
for _, c in ipairs(circle) do
local gx, gy = c[1], c[2]
if not (gx == px and gy == py) then
local key = gx .. "," .. gy
if not seen[key] then
seen[key] = true
-- to try and keep the script as lag free as possible, I'm using positions[#positions + 1] instead of table.insert as it is supposed to be faster
positions[#positions + 1] = position(gx, gy, pz)
end
end
end
logPlayer("DUN circle points turned into positions")
return positions
end
local function addDunTargets(user, targetsPositions, spell, startedInAttackMode)
local RA = runes.checkSpellForRuneByName("RA", spell)
local CUN = runes.checkSpellForRuneByName("CUN", spell)
local SUL = runes.checkSpellForRuneByName("SUL", spell)
local LEV = runes.checkSpellForRuneByName("LEV", spell)
local PEN = runes.checkSpellForRuneByName("PEN", spell)
local targetPosition = targetsPositions.thePosition
if user then
if M[user.id.."LevDunPos"] and LEV and PEN then
targetPosition = M[user.id.."LevDunPos"]
end
if M[user.id.."FhenPos"] then
targetPosition = M[user.id.."FhenPos"]
end
end
local possiblePositions = getDunPositions(user, targetPosition, spell, startedInAttackMode)
for _, possiblePosition in pairs(possiblePositions) do
local field = world:getField(possiblePosition)
if field then
local foundChar = world:isCharacterOnField(possiblePosition)
if (RA or CUN) and SUL then
foundChar = false
end
if foundChar then
local char = world:getCharacterOnField(possiblePosition)
if isValidChar(char) and char:getType() ~= Character.npc then --foundChar remains true because we dont want spells cast under npcs
table.insert(targetsPositions.targets, char)
end
end
local foundItem = false
if world:isItemOnField(possiblePosition) then
local item = world:getItemOnField(possiblePosition)
if item.id ~= 0 and item.id ~= 3518 then
foundItem = true
table.insert(targetsPositions.items, item)
end
end
if not foundItem and not foundChar then
table.insert(targetsPositions.positions, possiblePosition)
end
end
end
if user then
M[user.id.."FhenPos"] = false
M[user.id.."LevDunPos"] = false
end
return targetsPositions
end
local function addPENLukDunTargets(user, targetsPositions)
local targetPosition = targetsPositions.thePosition
if user and M[user.id.."LevDunPos"] then
targetPosition = M[user.id.."LevDunPos"]
end
local possiblePositions = --This is terrible code and should have used a for loop or something, but I am too lazy to redo it at the moment as it is functional anyways. I had just started learning to code back when I wrote this.
{position(targetPosition.x+2,targetPosition.y,targetPosition.z),
position(targetPosition.x+2,targetPosition.y+1,targetPosition.z),
position(targetPosition.x+2,targetPosition.y-1,targetPosition.z),
position(targetPosition.x,targetPosition.y+2,targetPosition.z),
position(targetPosition.x+1,targetPosition.y+2,targetPosition.z),
position(targetPosition.x-1,targetPosition.y+2,targetPosition.z),
position(targetPosition.x-2,targetPosition.y,targetPosition.z),
position(targetPosition.x-2,targetPosition.y+1,targetPosition.z),
position(targetPosition.x-2,targetPosition.y-1,targetPosition.z),
position(targetPosition.x,targetPosition.y-2,targetPosition.z),
position(targetPosition.x-1,targetPosition.y-2,targetPosition.z),
position(targetPosition.x+1,targetPosition.y-2,targetPosition.z),
position(targetPosition.x+2,targetPosition.y-2,targetPosition.z),
position(targetPosition.x-2,targetPosition.y+2,targetPosition.z),
position(targetPosition.x+2,targetPosition.y+2,targetPosition.z),
position(targetPosition.x-2,targetPosition.y-2,targetPosition.z)}
for _, possiblePosition in pairs(possiblePositions) do
local field = world:getField(possiblePosition)
local foundItems = field:countItems()
local foundChar = world:isCharacterOnField(possiblePosition)
if foundChar then
local char = world:getCharacterOnField(possiblePosition)
if char:getType() ~= Character.npc then
targetsPositions.targets[#targetsPositions.targets+1] = char
end
end
if foundItems >= 1 then
local item = field:getStackItem(foundItems - 1)
targetsPositions.items[#targetsPositions.items+1] = item
end
if not (foundItems >= 1) and not foundChar then
targetsPositions.positions[#targetsPositions.positions+1] = possiblePosition
end
end
M[user.id.."LevDunPos"] = false
return targetsPositions
end
local function convertNegative(diff)
if diff < 0 then
diff = diff*-1
end
return diff
end
local function isCloser(finalTarget, target, position)
local XDiff1 = position.x - finalTarget.x
local YDiff1 = position.y - finalTarget.y
local XDiff2 = position.x - target.x
local YDiff2 = position.y - target.y
XDiff1 = convertNegative(XDiff1)
YDiff1 = convertNegative(YDiff1)
XDiff2 = convertNegative(XDiff2)
YDiff2 = convertNegative(YDiff2)
local diff1 = XDiff1+YDiff1
local diff2 = XDiff2+YDiff2
if diff2 < diff1 then
return true
end
return false
end
local function isNotExistingTarget(targetToCheck, existingTargets)
for _, target in pairs(existingTargets) do
if target == targetToCheck then
return false
end
end
return true
end
local function getValidTargets(user, originalTarget, targets, spell, existingTargets)
local validTargets = {}
for _, target in pairs(targets) do
--Check that it isnt the user themself, it is not the original target and it is on the same layer
if isNotExistingTarget(target, existingTargets) and target ~= user and (not originalTarget or originalTarget ~= target) and target.pos.z == user.pos.z then
if not range.checkForObstacles(user, spell, target.pos) then -- checking obstacles to avoid shooting through a wall
table.insert(validTargets, target)
end
end
end
if #validTargets == 0 then
return false
end
return validTargets
end
local function FhenGetTarget(user, rangeNum, originalTarget, spell, existingTargets)
if not user.attackmode or (originalTarget and not isValidChar(originalTarget)) then
originalTarget = false
end
local monsterTargets = world:getMonstersInRangeOf(user.pos, rangeNum)
local playerTargets = world:getPlayersInRangeOf(user.pos, rangeNum)
local validPlayerTargets = getValidTargets(user, originalTarget, playerTargets, spell, existingTargets)
local validMonsterTargets = getValidTargets(user, originalTarget, monsterTargets, spell, existingTargets)
if not validMonsterTargets and not validPlayerTargets then
return false
end
local targets = {}
if not originalTarget or originalTarget:getType() == Character.monster then
if validMonsterTargets then
targets = validMonsterTargets
elseif not originalTarget then
targets = validPlayerTargets
end
elseif originalTarget:getType() == Character.player then
if validPlayerTargets then
targets = validPlayerTargets
else
targets = validMonsterTargets
end
end
local finalTarget = false
for _, target in pairs(targets) do
if not finalTarget and isValidChar(target) then
finalTarget = target
elseif isCloser(finalTarget.pos, target.pos, user.pos) and isValidChar(target) then
finalTarget = target
end
end
return finalTarget
end
local function positionHasNPC(thePosition)
local targetIsOnTile = world:isCharacterOnField(thePosition)
if targetIsOnTile then
local targeted = world:getCharacterOnField(thePosition)
if targeted:getType() == Character.npc then
return true
end
end
end
local function getSlowestNearTarget(user, target, rangeNum)
local position = target
if target.pos then
position = target.pos
end
local targets = world:getCharactersInRangeOf(user.pos, rangeNum)
local returnTarget = false
local lowestSpeed
for i = 1, #targets do
local newTarget = targets[i]
local userPosCheck = true
if user then
if user.pos == newTarget.pos then
userPosCheck = false
end
end
if position ~= newTarget.pos and userPosCheck and newTarget:getType() ~= Character.npc then
if returnTarget == false then
returnTarget = newTarget
lowestSpeed = newTarget.speed
elseif newTarget.speed < lowestSpeed then
returnTarget = newTarget
lowestSpeed = newTarget.speed
end
end
end
return returnTarget
end
local function isWeaker(finalTarget, target)
local targetHealth = character.GetHP(target)
local finalTargetHealth = character.GetHP(finalTarget)
if targetHealth < finalTargetHealth then
return true
end
return false
end
local function getWeakestNearTarget(user, rangeNum, originalTarget, spell, existingTargets)
if not user.attackmode or (originalTarget and not isValidChar(originalTarget)) then
originalTarget = false
end
local monsterTargets = world:getMonstersInRangeOf(user.pos, rangeNum)
local playerTargets = world:getPlayersInRangeOf(user.pos, rangeNum)
local validPlayerTargets = getValidTargets(user, originalTarget, playerTargets, spell, existingTargets)
local validMonsterTargets = getValidTargets(user, originalTarget, monsterTargets, spell, existingTargets)
if not validMonsterTargets and not validPlayerTargets then
return false
end
local targets = {}
if not originalTarget or originalTarget:getType() == Character.monster then
if validMonsterTargets then
targets = validMonsterTargets
elseif not originalTarget then
targets = validPlayerTargets
end
elseif originalTarget:getType() == Character.player then
if validPlayerTargets then
targets = validPlayerTargets
else
targets = validMonsterTargets
end
end
local finalTarget = false
for _, target in pairs(targets) do
if not finalTarget and isValidChar(target) then
finalTarget = target
elseif isWeaker(finalTarget, target) and isValidChar(target) then
finalTarget = target
end
end
return finalTarget
end
local function getPositionWhenNotTargetingACharacter(user)
--[[
If attack mode is not in use to target, the spell still needs a target.
This defaults to the front position of the user, but a custom range can be set from 1-7,
via an inconvenient method using the grimoire but it is better than nothing.
To be replaced with a proper targeting point-and-click mechanic when we have
someone who can implement the server and client side support for such.
]]
local frontPos = common.GetFrontPosition(user)
local wand = magic.getWand(user)
if not wand then
return frontPos
end
local customRange = wand:getData("range")
if common.IsNilOrEmpty(customRange) then
return frontPos
end
customRange = tonumber(customRange)
local xDiff = frontPos.x- user.pos.x
local yDiff = frontPos.y - user.pos.y
local newX = user.pos.x + xDiff*customRange
local newY = user.pos.y + yDiff*customRange
return position(newX, newY, user.pos.z)
end
local function getTimeLimit(user, spell)
--based on spirit spell level and scaling up to approx 60 min time limit
local scaling = effectScaling.getEffectScaling(user, nil, spell)
local base = 300
return base*(scaling*6)
end
local function getStoredTarget(user, id)
local playerList = world:getPlayersOnline()
for _, player in pairs(playerList) do
if player.id == id then
return player
end
end
user:inform("Die spirituellen Wellen des Ziels k<>nnen nicht erreicht werden. Vielleicht ist es <20>ber die Grenzen Illarions hinausgereist oder schl<68>ft einfach.", "The targets spiritual waves can not be reached. Perhaps they have travelled beyond the borders of Illarion, or are simply asleep.")
return false
end
function M.getFriendlistTargets(user)
local savedTargetsFound, savedTargets = ScriptVars:find("heptTargets"..tostring(user.id))
local retTable = {}
if not savedTargetsFound then
return retTable
end
local parts = {}
for part in string.gmatch(savedTargets, "([^,]+)") do
table.insert(parts, part)
end
for i = 1, #parts, 2 do
local name = parts[i]
local id = tonumber(parts[i + 1])
if name and id then
table.insert(retTable, { name = name, id = id })
end
end
return retTable
end
function M.initPosition(user, spell, delayed, startedInAttackMode)
local thePosition, targetToTeach
local RA = runes.checkSpellForRuneByName("RA", spell)
local CUN = runes.checkSpellForRuneByName("CUN", spell)
local LEV = runes.checkSpellForRuneByName("LEV", spell)
local PEN = runes.checkSpellForRuneByName("PEN", spell)
local SUL = runes.checkSpellForRuneByName("SUL", spell)
local dodgable = (CUN or RA) and SUL
if not delayed then
if user.attackmode and magic.getWand(user) then --wand required to aim
startedInAttackMode = true
local id = user.id
local targeted = M.playerTargets[id]
if not targeted then --onAttack did not load the target yet, very rarely happens
return
end
if not isValidChar(targeted) then
return
end
thePosition = targeted.pos
if not dodgable and not (PEN and LEV) then
targetToTeach = targeted
end
else
thePosition = getPositionWhenNotTargetingACharacter(user) -- To be replaced with a proper targeting entry point when we have someone who can write the server and client support for point-and-click targeting :)
local foundCharacter = world:isCharacterOnField(thePosition)
if foundCharacter and not dodgable and not (PEN and LEV) then
local target = world:getCharacterOnField(thePosition)
if target:getType() == Character.player or target:getType() == Character.monster then
targetToTeach = target
end
end
end
else
thePosition = delayed
local foundCharacter = world:isCharacterOnField(thePosition)
if foundCharacter and not dodgable then
local target = world:getCharacterOnField(thePosition)
if target:getType() == Character.player or target:getType() == Character.monster then
targetToTeach = target
end
end
end
return thePosition, targetToTeach, startedInAttackMode
end
local function getPosition(user, spell, positionsAndTargets, delayed, trap, startedInAttackMode, targetToTeach, thePosition)
if not thePosition then
thePosition, targetToTeach, startedInAttackMode = M.initPosition(user, spell, delayed, startedInAttackMode)
end
local element = runes.fetchElement(spell)
local rangeNum = range.getCastingRange(user, spell, element)
local TAH = runes.checkSpellForRuneByName("TAH", spell)
local RA = runes.checkSpellForRuneByName("RA", spell)
local CUN = runes.checkSpellForRuneByName("CUN", spell)
local HEPT = runes.checkSpellForRuneByName("HEPT", spell)
local LEV = runes.checkSpellForRuneByName("LEV", spell)
local PEN = runes.checkSpellForRuneByName("PEN", spell)
local FHEN = runes.checkSpellForRuneByName("FHEN", spell)
local SOLH = runes.checkSpellForRuneByName("SOLH", spell)
local SUL = runes.checkSpellForRuneByName("SUL", spell)
local ORL = runes.checkSpellForRuneByName("ORL", spell)
local JUS = runes.checkSpellForRuneByName("JUS", spell)
local DUN = runes.checkSpellForRuneByName("DUN", spell)
local earthTrap = SOLH and ORL and not trap
local dodgable = (CUN or RA) and SUL
local setPos = true
if targetToTeach then
table.insert(positionsAndTargets.targets, targetToTeach)
positionsAndTargets.targetToTeach = targetToTeach
end
if #positionsAndTargets.targets > 0 then
setPos = false
end
positionsAndTargets.startedInAttackMode = startedInAttackMode
local amount = 0
if (RA or CUN) and TAH then
amount = amount + 1
end
if (RA or CUN or JUS) and LEV then
amount = amount + 1
end
if amount >= 1 then
for i = 1, amount do
local target = getWeakestNearTarget(user, rangeNum, M.playerTargets[user.id], spell, positionsAndTargets.targets)
if target then
if target:getType() == Character.player or target:getType() == Character.monster then
if not dodgable then
positionsAndTargets.targets[#positionsAndTargets.targets+1] = target
setPos = false
else
positionsAndTargets.positions[#positionsAndTargets.positions+1] = target.pos
end
end
end
end
end
if LEV and SOLH and not earthTrap then
local target = getSlowestNearTarget(user, thePosition, rangeNum)
if target then
if target:getType() == Character.player or target:getType() == Character.monster then
positionsAndTargets.targets[#positionsAndTargets.targets+1] = target
setPos = false
end
end
end
if PEN and HEPT then
local id = user.id
M.playerTargets["HEPT"..id] = thePosition
if user.attackmode and magic.getWand(user) then --wand required to aim
local targeted = M.playerTargets[id]
M.playerTargets["HEPT"..id] = targeted
end
M.playerTargets["HEPT"..id.."time"] = tonumber(world:getTime("unix"))
end
if PEN and LEV then
if _G.storedPenLevTargets[user.id] then
setPos = false
local found
if DUN then
local friendIds = M.getFriendlistTargets(user)
for _, friend in pairs(friendIds) do
local storedTarget = getStoredTarget(user, friend.id)
if storedTarget then
table.insert(positionsAndTargets.targets, storedTarget)
M[user.id.."LevDunPos"] = storedTarget.pos
found = true
end
end
else
local storedTarget = getStoredTarget(user, _G.storedPenLevTargets[user.id])
if storedTarget then
table.insert(positionsAndTargets.targets, storedTarget)
M[user.id.."LevDunPos"] = storedTarget.pos
found = true
end
end
if not found then
_G.storedPenLevTargets[user.id] = nil
return
end
else
local timeLimit = getTimeLimit(user, spell)
local currentTime = world:getTime("unix")
local id = user.id
local heptPosition = M.playerTargets["HEPT"..id]
local heptTime = M.playerTargets["HEPT"..id.."time"]
if heptTime then
if currentTime-heptTime > timeLimit then
user:inform("Du bemerkst, dass es zu lange her ist, das du das letzte Mal einen Zauberspruch mit HEPT genutzt hast, denn du sp<73>rst wie das Mana aus deinem K<>rper flie<69>t und kein Ziel findet.", "You realise it's been too long since you last cast this spell with HEPT, as you feel the mana draining from your body finds no target.")
elseif heptPosition then
if heptPosition.pos then
heptPosition = heptPosition.pos
end
setPos = false
local targetExists = world:isCharacterOnField(heptPosition)
local setLevPos = true
M[id.."LevDunPos"] = heptPosition
if targetExists then
local LevTarget = world:getCharacterOnField(heptPosition)
if LevTarget:getType() == Character.player or LevTarget:getType() == Character.monster then
positionsAndTargets.targets[#positionsAndTargets.targets+1] = LevTarget
setLevPos = false
end
end
if setLevPos then
positionsAndTargets.positions[#positionsAndTargets.positions+1] = heptPosition
end
end
end
end
end
if FHEN and (RA or CUN or SOLH) and not earthTrap then
local fhenTarget = FhenGetTarget(user, rangeNum, M.playerTargets[user.id], spell, positionsAndTargets.targets)
if fhenTarget and isValidChar(fhenTarget) then
if fhenTarget:getType() == Character.player or fhenTarget:getType() == Character.monster then
if not dodgable then
M[user.id.."FhenPos"] = fhenTarget.pos
table.insert(positionsAndTargets.targets, fhenTarget)
setPos = false
else
table.insert(positionsAndTargets.positions, fhenTarget.pos)
end
end
end
end
positionsAndTargets.thePosition = thePosition
local field = world:getField(thePosition)
if field then
local foundItems = field:countItems()
if foundItems >= 1 then
local item = field:getStackItem(foundItems - 1)
if item.id ~= 0 and item.id ~= 3518 then
positionsAndTargets.items[#positionsAndTargets.items+1] = item
setPos = false
end
end
end
if positionHasNPC(thePosition) then
setPos = false
end
if setPos then
positionsAndTargets.positions[#positionsAndTargets.positions+1] = thePosition
end
return positionsAndTargets
end
function M.addTargets(user, spell, positionsAndTargets, startedInAttackMode)
local DUN = runes.checkSpellForRuneByName("DUN", spell)
local PEN = runes.checkSpellForRuneByName("PEN", spell)
local LUK = runes.checkSpellForRuneByName("LUK", spell)
if DUN and PEN and LUK then
positionsAndTargets = addPENLukDunTargets(user, positionsAndTargets)
elseif (PEN and LUK) or DUN then
positionsAndTargets = addDunTargets(user, positionsAndTargets, spell, startedInAttackMode)
end
return positionsAndTargets
end
function M.getPositionsAndTargets(user, spell, delayed, trap, startedInAttackMode, targetToTeach, thePosition)
local positionsAndTargets = {positions = {}, targets = {}, items = {}}
positionsAndTargets = getPosition(user, spell, positionsAndTargets, delayed, trap, startedInAttackMode, targetToTeach, thePosition)
return positionsAndTargets
end
function M.refreshTargets(targets)
for _, pos in pairs(targets.positions) do
local foundCharacter = world:isCharacterOnField(pos)
if foundCharacter then
local target = world:getCharacterOnField(pos)
if target:getType() == Character.player or target:getType() == Character.monster then
targets.targets[#targets.targets+1] = target
end
end
end
for _, item in pairs(targets.items) do
local foundCharacter = world:isCharacterOnField(item.pos)
if foundCharacter then
local target = world:getCharacterOnField(item.pos)
if target:getType() == Character.player or target:getType() == Character.monster then
targets.targets[#targets.targets+1] = target
end
end
end
return targets
end
return M