• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

TFS 1.X+ About bouncing spell

Lopaskurwa

Well-Known Member
Joined
Oct 6, 2017
Messages
912
Solutions
2
Reaction score
50
I have a question about this bouncing spell. Instead of making the spell bounce between monsters, how can I make it so that the spell casts multiple blasts from the player, with a 200-unit interval between each blast? The spell should be based on a maximum of 6 targets, meaning if there are 6 targets around, it will cast 6 blasts to those targets
LUA:
local config = {
    level = 20,
    magicLevel = 5,
    range = 4,
    cooldown = 1000,
    manaCost = 100,

    maxTargets = 6,
    specRange = 3,

    combatType = COMBAT_ENERGYDAMAGE,
    combatEffect = CONST_ME_ENERGYHIT
}

local setmetatable = setmetatable
local contains = table.contains
local insert = table.insert
local sort = table.sort
local BounceSpell = {}

local function getBounceSpell(player, target)
    local bounceSpell = setmetatable({}, {__index = BounceSpell})
    bounceSpell.playerId = player:getId()
    local targetId = target:getId()
    bounceSpell.targets = {targetId}
    bounceSpell.currentTargetId = targetId
    bounceSpell.lastTargetPos = player:getPosition()
    return bounceSpell
end

function BounceSpell:checkTarget(target)
    local player = Player(self.playerId)
    if not player then return false end
    local targetPlayer = target:getPlayer()
    return not targetPlayer or
               (not player:hasSecureMode() and not targetPlayer:getGroup():getAccess() and
                   not self:isFriend(targetPlayer))
end

do
    local getSpectators = Game.getSpectators
    local x, y = config.specRange, config.specRange

    function BounceSpell:getSpectators(pos)
        local spectators = getSpectators(pos, false, false, x, x, y, y)
        local spects = {}
        for _, spectator in ipairs(spectators) do
            local spectatorId = spectator:getId()
            if spectatorId ~= self.playerId and
                not Tile(spectator:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) and
                not contains(self.targets, spectatorId) and self:checkTarget(spectator) then
                insert(spects, spectator)
            end
        end
        return spects
    end
end

function BounceSpell:isFriend(target)
    local player = Player(self.playerId)
    if not player then return false end
    local party = player:getParty()
    if not party then return false end

    local targetId = target:getId()
    if targetId == party:getLeader():getId() then return true end
    for _, member in ipairs(party:getMembers()) do
        if targetId == member:getId() then return true end
    end
    return false
end

local function getDistanceTo(from, to) return from:getDistance(to) end

function BounceSpell:getTargets(pos)
    local spects = self:getSpectators(pos)
    if #spects > 1 then
        sort(spects, function(a, b)
            return getDistanceTo(pos, a:getPosition()) <
                       getDistanceTo(pos, b:getPosition())
        end)
    end

    local target = nil
    for _, spectator in ipairs(spects) do
        if pos:isSightClear(spectator:getPosition(), true) then
            target = spectator
            break
        end
    end

    if not target then return false end

    local targetId = target:getId()
    insert(self.targets, targetId)
    self.currentTargetId = targetId
    self.lastTargetPos = pos
    return true
end

local combats = {}

for divisor = 1, config.maxTargets do
    local combat = Combat()
    combat:setParameter(COMBAT_PARAM_TYPE, config.combatType)
    combat:setParameter(COMBAT_PARAM_EFFECT, config.combatEffect)

    ---@param player Player
    function onGetFormulaValues(player, level, magicLevel)
        local min = (level * 4.3) + (magicLevel * 190) + 52
        local max = (level * 4.6) + (magicLevel * 280) + 79
        return -min / divisor, -max / divisor
    end

    combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")

    combats[#combats + 1] = combat
end

---@return boolean
function BounceSpell:execute()
    local player = Player(self.playerId)
    if not player then return false end

    local target = Creature(self.currentTargetId)
    if not target then return false end

    local combat = combats[#self.targets] or combats[#combats]
    if not combat then error("Bounce Spell: Invalid combat.") end

    local targetPos = target:getPosition()
    self.lastTargetPos:sendDistanceEffect(targetPos, CONST_ANI_ENERGY)
    combat:execute(player, Variant(targetPos))

    if #self.targets >= config.maxTargets then return false end

    self.currentTargetId = nil
    if self:getTargets(target:getPosition()) then
        addEvent(self.execute, 100 + (#self.targets * 10), self)
    end
    return true
end


function onCastSpell(player, variant, isHotkey)
    local target = player:getTarget()
    if not target then error("Bounce Spell: Invalid target.") end

    return getBounceSpell(player, target):execute()
end
 
Improved it a little bit, should be crash free
LUA:
local config = {
    level = 20,
    maglevel = 5,
    range = 4,
    cooldown = 1000,
    manaCost = 100,

    maxTargets = 3,
    specRange = 3,

    combatType = COMBAT_ENERGYDAMAGE
}

local setmetatable = setmetatable
local contains = table.contains
local insert = table.insert
local sort = table.sort
local BounceSpell = {}

local function getBounceSpell(player, target)
    local bounceSpell = setmetatable({}, {__index = BounceSpell})
    bounceSpell.playerId = player:getId()
    bounceSpell.targets = {target:getId()}
    bounceSpell.lastTargetPos = player:getPosition()
    return bounceSpell
end

function BounceSpell:checkTarget(target)
    local player = Player(self.playerId)
    if not player then return false end
    local targetPlayer = target:getPlayer()
    return not targetPlayer or
               (not player:hasSecureMode() and not targetPlayer:getGroup():getAccess() and
                   not self:isFriend(targetPlayer))
end

do
    local getSpectators = Game.getSpectators
    local x, y = config.specRange, config.specRange

    function BounceSpell:getSpectators(pos)
        local spectators = getSpectators(pos, false, false, x, x, y, y)
        local spects = {}
        for _, spectator in ipairs(spectators) do
            local spectatorId = spectator:getId()
            if spectatorId ~= self.playerId and
                not Tile(spectator:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) and
                not contains(self.targets, spectatorId) and self:checkTarget(spectator) then
                insert(spects, spectator)
            end
        end
        return spects
    end
end

function BounceSpell:isFriend(target)
    local player = Player(self.playerId)
    if not player then return false end
    local party = player:getParty()
    if not party then return false end

    local targetId = target:getId()
    if targetId == party:getLeader():getId() then return true end
    for _, member in ipairs(party:getMembers()) do
        if targetId == member:getId() then return true end
    end
    return false
end

local function getDistanceTo(from, to) return from:getDistance(to) end

function BounceSpell:getTargets(pos)
    local spects = self:getSpectators(pos)
    if #spects > 1 then
        sort(spects, function(a, b)
            return getDistanceTo(pos, a:getPosition()) <
                       getDistanceTo(pos, b:getPosition())
        end)
    end

    local targets = {}
    for _, spectator in ipairs(spects) do
        if pos:isSightClear(spectator:getPosition(), true) then
            insert(targets, spectator)
            if #targets >= config.maxTargets then break end
        end
    end

    return targets
end

local combats = {}

for divisor = 1, config.maxTargets do
    local combat = Combat()
    combat:setParameter(COMBAT_PARAM_TYPE, config.combatType)

    function onGetFormulaValues(player, level, maglevel)
        local min = (level * 5) + (maglevel * 12.5) + 25
        local max = (level * 5) + (maglevel * 14) + 50
        return -min / divisor, -max / divisor
    end

    combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")

    combats[#combats + 1] = combat
end

function BounceSpell:execute()
    local player = Player(self.playerId)
    if not player then return false end

    local function castOnTarget(index)
        local player = Player(self.playerId)
        if not player then return false end

        local targetId = self.targets[index]
        if not targetId then return false end

        local target = Creature(targetId)
        if not target then return false end

        local combat = combats[1]
        if not combat then error("Bounce Spell: Invalid combat.") end

        local targetPos = target:getPosition()
        local playerPos = player:getPosition()
        local distance = playerPos:getDistance(targetPos)
        local milliseconds = distance * 80 -- 80ms per tile

        playerPos:sendDistanceEffect(targetPos, 17)

        addEvent(function()
            local player = Player(self.playerId)
            local target = Creature(targetId)
            if not player or not target then return false end

            combat:execute(player, Variant(targetPos))

            local effectPosition = targetPos + Position(1, 1, 0)
            effectPosition:sendMagicEffect(218)
        end, milliseconds)

        if index < #self.targets then
            addEvent(castOnTarget, 100, index + 1)
        end

        return true
    end

    local initialTarget = Creature(self.targets[1])
    if not initialTarget then return false end
    local initialTargetPos = initialTarget:getPosition()
    local targets = self:getTargets(initialTargetPos)
    for _, target in ipairs(targets) do
        table.insert(self.targets, target:getId())
    end

    castOnTarget(1)

    return true
end

function onCastSpell(player, variant, isHotkey)
    local target = player:getTarget()
    if not target then error("Bounce Spell: Invalid target.") end

    return getBounceSpell(player, target):execute()
end
 
Improved it a little bit, should be crash free
LUA:
local config = {
    level = 20,
    maglevel = 5,
    range = 4,
    cooldown = 1000,
    manaCost = 100,

    maxTargets = 3,
    specRange = 3,

    combatType = COMBAT_ENERGYDAMAGE
}

local setmetatable = setmetatable
local contains = table.contains
local insert = table.insert
local sort = table.sort
local BounceSpell = {}

local function getBounceSpell(player, target)
    local bounceSpell = setmetatable({}, {__index = BounceSpell})
    bounceSpell.playerId = player:getId()
    bounceSpell.targets = {target:getId()}
    bounceSpell.lastTargetPos = player:getPosition()
    return bounceSpell
end

function BounceSpell:checkTarget(target)
    local player = Player(self.playerId)
    if not player then return false end
    local targetPlayer = target:getPlayer()
    return not targetPlayer or
               (not player:hasSecureMode() and not targetPlayer:getGroup():getAccess() and
                   not self:isFriend(targetPlayer))
end

do
    local getSpectators = Game.getSpectators
    local x, y = config.specRange, config.specRange

    function BounceSpell:getSpectators(pos)
        local spectators = getSpectators(pos, false, false, x, x, y, y)
        local spects = {}
        for _, spectator in ipairs(spectators) do
            local spectatorId = spectator:getId()
            if spectatorId ~= self.playerId and
                not Tile(spectator:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) and
                not contains(self.targets, spectatorId) and self:checkTarget(spectator) then
                insert(spects, spectator)
            end
        end
        return spects
    end
end

function BounceSpell:isFriend(target)
    local player = Player(self.playerId)
    if not player then return false end
    local party = player:getParty()
    if not party then return false end

    local targetId = target:getId()
    if targetId == party:getLeader():getId() then return true end
    for _, member in ipairs(party:getMembers()) do
        if targetId == member:getId() then return true end
    end
    return false
end

local function getDistanceTo(from, to) return from:getDistance(to) end

function BounceSpell:getTargets(pos)
    local spects = self:getSpectators(pos)
    if #spects > 1 then
        sort(spects, function(a, b)
            return getDistanceTo(pos, a:getPosition()) <
                       getDistanceTo(pos, b:getPosition())
        end)
    end

    local targets = {}
    for _, spectator in ipairs(spects) do
        if pos:isSightClear(spectator:getPosition(), true) then
            insert(targets, spectator)
            if #targets >= config.maxTargets then break end
        end
    end

    return targets
end

local combats = {}

for divisor = 1, config.maxTargets do
    local combat = Combat()
    combat:setParameter(COMBAT_PARAM_TYPE, config.combatType)

    function onGetFormulaValues(player, level, maglevel)
        local min = (level * 5) + (maglevel * 12.5) + 25
        local max = (level * 5) + (maglevel * 14) + 50
        return -min / divisor, -max / divisor
    end

    combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")

    combats[#combats + 1] = combat
end

function BounceSpell:execute()
    local player = Player(self.playerId)
    if not player then return false end

    local function castOnTarget(index)
        local player = Player(self.playerId)
        if not player then return false end

        local targetId = self.targets[index]
        if not targetId then return false end

        local target = Creature(targetId)
        if not target then return false end

        local combat = combats[1]
        if not combat then error("Bounce Spell: Invalid combat.") end

        local targetPos = target:getPosition()
        local playerPos = player:getPosition()
        local distance = playerPos:getDistance(targetPos)
        local milliseconds = distance * 80 -- 80ms per tile

        playerPos:sendDistanceEffect(targetPos, 17)

        addEvent(function()
            local player = Player(self.playerId)
            local target = Creature(targetId)
            if not player or not target then return false end

            combat:execute(player, Variant(targetPos))

            local effectPosition = targetPos + Position(1, 1, 0)
            effectPosition:sendMagicEffect(218)
        end, milliseconds)

        if index < #self.targets then
            addEvent(castOnTarget, 100, index + 1)
        end

        return true
    end

    local initialTarget = Creature(self.targets[1])
    if not initialTarget then return false end
    local initialTargetPos = initialTarget:getPosition()
    local targets = self:getTargets(initialTargetPos)
    for _, target in ipairs(targets) do
        table.insert(self.targets, target:getId())
    end

    castOnTarget(1)

    return true
end

function onCastSpell(player, variant, isHotkey)
    local target = player:getTarget()
    if not target then error("Bounce Spell: Invalid target.") end

    return getBounceSpell(player, target):execute()
end
Thanks works perfectly 🥰
 
Back
Top