• 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!

Spell [TFS 1.3] Skillshot spells

Azakelis

Premium User
Premium User
Joined
Feb 17, 2014
Messages
232
Solutions
17
Reaction score
319
lib + example, gl hf
Lua:
function onCastSpell(creature, variant)

    function onCreatureHit2(self, hitCreature)
        doTargetCombat(self.casterId, hitCreature, COMBAT_ENERGYDAMAGE, -1, -1, CONST_ME_NONE)
    end
 
    function onCreatureHit(self, hitCreature)
        local hitCreatureDirection = hitCreature:getDirection()
        local hitCreaturePosition = hitCreature:getPosition()
        doTargetCombat(self.casterId, hitCreature, COMBAT_ENERGYDAMAGE, -1, -1, CONST_ME_NONE)

        SkillShot:new({
            onCreatureHit = onCreatureHit2,
            animation = CONST_ANI_ENERGY,
            effect = CONST_ME_ENERGYAREA,
            casterId = creature:getId(),
            chain = {
                ChainElement:new({direction = ChainElementDirections.forward, delay = 0, ignore = true}),
                ChainElement:new({direction = ChainElementDirections.forward, delay = 600, steps = 2}),
                ChainElement:new({direction = ChainElementDirections.forward, delay = 600, steps = 2}),
                ChainElement:new({direction = ChainElementDirections.forward, delay = 600, steps = 2}),
                ChainElement:new({direction = ChainElementDirections.backward, delay = 600, steps = 2}),
                ChainElement:new({direction = ChainElementDirections.backward, delay = 600, steps = 2})
            }}):fire({customStartCoordinates = {x = hitCreaturePosition.x, y = hitCreaturePosition.y, z = hitCreaturePosition.z}, customDirection = hitCreatureDirection})
    end

    local skillShot = SkillShot:new({
        stopOnHit = true,
        onCreatureHit = onCreatureHit,
        animation = CONST_ANI_ENERGY,
        effect = CONST_ME_ENERGYAREA,
        casterId = creature:getId(),
        chain = {
            ChainElement:new({direction = ChainElementDirections.forward, delay = 0}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 600})
        }
    })

    local skillShot2 = SkillShot:new({
        stopOnHit = true,
        onCreatureHit = onCreatureHit,
        animation = CONST_ANI_ENERGY,
        effect = CONST_ME_ENERGYAREA,
        casterId = creature:getId(),
        chain = {
            ChainElement:new({direction = ChainElementDirections.forwardleft, delay = 0}),
            ChainElement:new({direction = ChainElementDirections.forwardleft, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.forwardleft, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.backwardright, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.backwardright, delay = 600})
        }
    })

    local skillShot3 = SkillShot:new({
        stopOnHit = true,
        onCreatureHit = onCreatureHit,
        animation = CONST_ANI_ENERGY,
        effect = CONST_ME_ENERGYAREA,
        casterId = creature:getId(),
        chain = {
            ChainElement:new({direction = ChainElementDirections.forwardright, delay = 0}),
            ChainElement:new({direction = ChainElementDirections.forwardright, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.forwardright, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.backwardleft, delay = 600}),
            ChainElement:new({direction = ChainElementDirections.backwardleft, delay = 600})
        }
    })

    skillShot:fire()
    skillShot2:fire()
    skillShot3:fire()
    return true
end

SkillShot = {
    chain = {},

    onCreatureHit = function(self, hitCreature) end,
    onTileCreaturesHit = function(self, hitCreatureIdList) end,

    onHit = function(self, hitCreatureIdList)
        self:onTileCreaturesHit(hitCreatureIdList)
        for _, hitCreatureId in ipairs(hitCreatureIdList) do
            local hitCreature = Creature(hitCreatureId)
            if hitCreature then self:onCreatureHit(hitCreature) end
        end
    end,

    onChain = function(self, currentChain)
        local caster = Creature(self.casterId)
        if not caster then return end
        local chain = self.chain[currentChain]
        local positionAdjustment = chain:getPositionAdjustment(self.direction)
        local fromPos = Position(self.coordinates)
        self.coordinates.x = self.coordinates.x + positionAdjustment.x
        self.coordinates.y = self.coordinates.y + positionAdjustment.y

        local toPos = Position(self.coordinates)
        local toTile = Tile(toPos)

        if not toTile then return end

        if toTile:hasProperty(CONST_PROP_BLOCKPROJECTILE) or
            toTile:hasFlag(TILESTATE_PROTECTIONZONE) then return end

        if chain.ignore then
            self:nextChain(currentChain)
            return
        end

        fromPos:sendDistanceEffect(toPos, self.animation)
        toPos:sendMagicEffect(self.effect)

        local creatures = Tile(toPos):getCreatures()
        local hitCreatureIdList = {}
        if creatures and #creatures > 0 then
            for _, creature in pairs(creatures) do
                if creature:isMonster() or (creature:isPlayer() and
                    not creature:getGroup():hasFlag(PlayerFlag_CannotBeAttacked)) then
                    hitCreatureIdList[#hitCreatureIdList + 1] = creature:getId()
                end
            end
        end

        if #hitCreatureIdList > 0 then
            self:onHit(hitCreatureIdList)
            if self.stopOnHit then return end
        end

        self:nextChain(currentChain)
    end,

    nextChain = function (self, currentChain)
        local nextChain = currentChain + 1
        if #self.chain < nextChain then return end
        addEvent(self.onChain, self.chain[nextChain].delay, self, nextChain)
    end,

    fire = function(self, args)
        args = args or {}
        local creature = Creature(self.casterId)
        if not creature then return end
        self.direction = creature:getDirection()
        if args.customDirection then
            self.direction = args.customDirection
        end
        local creaturePosition = creature:getPosition()
        self.coordinates = {
            x = creaturePosition.x,
            y = creaturePosition.y,
            z = creaturePosition.z
        }
        if args.customStartCoordinates then
            self.coordinates = args.customStartCoordinates
        end
        self:nextChain(0)
    end,

    new = function(self, args)
        local animation = args.animation or CONST_ANI_NONE
        local effect = args.effect or CONST_ME_NONE
        local onTileCreaturesHit = args.onTileCreaturesHit or self.onTileCreaturesHit
        local onCreatureHit = args.onCreatureHit or self.onCreatureHit
        local stopOnHit = false
        local chain = args.chain or self.chain
        if args.stopOnHit then stopOnHit = true end

        local instance = {
            casterId = args.casterId,
            chain = chain,
            onCreatureHit = onCreatureHit,
            onTileCreaturesHit = onTileCreaturesHit,
            stopOnHit = stopOnHit,
            animation = animation,
            effect = effect
        }
        setmetatable(instance, self)
        self.__index = self
        return instance
    end
}

ChainElementDirections = {
    none = 0,
    forward = 1,
    left = 2,
    right = 3,
    backward = 4,
    forwardleft = 5,
    forwardright = 6,
    backwardleft = 7,
    backwardright = 8
}

PositionAdjustmentMap = {
    [DIRECTION_NORTH] = {
        [ChainElementDirections.none] = {x = 0, y = 0},
        [ChainElementDirections.forward] = {x = 0, y = -1},
        [ChainElementDirections.left] = {x = -1, y = 0},
        [ChainElementDirections.right] = {x = 1, y = 0},
        [ChainElementDirections.backward] = {x = 0, y = 1},
        [ChainElementDirections.forwardleft] = {x = -1, y = -1},
        [ChainElementDirections.forwardright] = {x = 1, y = -1},
        [ChainElementDirections.backwardleft] = {x = -1, y = 1},
        [ChainElementDirections.backwardright] = {x = 1, y = 1}
    },
    [DIRECTION_SOUTH] = {
        [ChainElementDirections.none] = {x = 0, y = 0},
        [ChainElementDirections.forward] = {x = 0, y = 1},
        [ChainElementDirections.left] = {x = 1, y = 0},
        [ChainElementDirections.right] = {x = -1, y = 0},
        [ChainElementDirections.backward] = {x = 0, y = -1},
        [ChainElementDirections.forwardleft] = {x = 1, y = 1},
        [ChainElementDirections.forwardright] = {x = -1, y = 1},
        [ChainElementDirections.backwardleft] = {x = 1, y = -1},
        [ChainElementDirections.backwardright] = {x = -1, y = -1}
    },
    [DIRECTION_WEST] = {
        [ChainElementDirections.none] = {x = 0, y = 0},
        [ChainElementDirections.forward] = {x = -1, y = 0},
        [ChainElementDirections.left] = {x = 0, y = 1},
        [ChainElementDirections.right] = {x = 0, y = -1},
        [ChainElementDirections.backward] = {x = 1, y = 0},
        [ChainElementDirections.forwardleft] = {x = -1, y = 1},
        [ChainElementDirections.forwardright] = {x = -1, y = -1},
        [ChainElementDirections.backwardleft] = {x = 1, y = 1},
        [ChainElementDirections.backwardright] = {x = 1, y = -1}
    },
    [DIRECTION_EAST] = {
        [ChainElementDirections.none] = {x = 0, y = 0},
        [ChainElementDirections.forward] = {x = 1, y = 0},
        [ChainElementDirections.left] = {x = 0, y = -1},
        [ChainElementDirections.right] = {x = 0, y = 1},
        [ChainElementDirections.backward] = {x = -1, y = 0},
        [ChainElementDirections.forwardleft] = {x = 1, y = -1},
        [ChainElementDirections.forwardright] = {x = 1, y = 1},
        [ChainElementDirections.backwardleft] = {x = -1, y = -1},
        [ChainElementDirections.backwardright] = {x = -1, y = 1}
    }
}

ChainElement = {
    direction = ChainElementDirections.forward,
    delay = 0,
    steps = 1,
    getPositionAdjustment = function(self, casterDirection)
        local coordinates = PositionAdjustmentMap[casterDirection][self.direction]
        return {x = coordinates.x * self.steps, y = coordinates.y * self.steps}
    end,

    new = function(self, args)
        local steps = args.steps or self.steps
        local ignore = false
        local direction = args.direction or self.direction
        local delay = args.delay or self.delay
        if args.ignore then ignore = true end
        local instance = {direction = direction, delay = delay, steps = steps, ignore = ignore}
        setmetatable(instance, self)
        self.__index = self
        return instance
    end
}

other examples:
Lua:
    local skillShotCircle = SkillShot:new({
        animation = CONST_ANI_ENERGY,
        effect = CONST_ME_ENERGYAREA,
        casterId = creature:getId(),
        chain = {
            ChainElement:new({direction = ChainElementDirections.forward}),
            ChainElement:new({direction = ChainElementDirections.right, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.left, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.left, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 200})
        }
    }):fire()
 
Last edited:
Fixed the last example for you, incase someone doesn't understand why it deals no damage 👌

Lua:
    local skillShotCircle = SkillShot:new({
        animation = CONST_ANI_ENERGY,
        effect = CONST_ME_ENERGYAREA,
        onCreatureHit = onCreatureHit,
        casterId = creature:getId(),
        chain = {
            ChainElement:new({direction = ChainElementDirections.forward}),
            ChainElement:new({direction = ChainElementDirections.right, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.left, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.left, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 200})
        }
    }):fire()
 
Fixed the last example for you, incase someone doesn't understand why it deals no damage 👌

Lua:
    local skillShotCircle = SkillShot:new({
        animation = CONST_ANI_ENERGY,
        effect = CONST_ME_ENERGYAREA,
        onCreatureHit = onCreatureHit,
        casterId = creature:getId(),
        chain = {
            ChainElement:new({direction = ChainElementDirections.forward}),
            ChainElement:new({direction = ChainElementDirections.right, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.backward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.left, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.left, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 200}),
            ChainElement:new({direction = ChainElementDirections.forward, delay = 200})
        }
    }):fire()
I would think that is pretty obvious to anyone who actually reads the code they put on their servers, regardless of whether it was intentional or not(probably was intentional for this purpose...) it's still letting users know they don't need an onCreatureHit. Far from a fix bro :rolleyes:

@topic really fucking cool, the amount of easy and intuitive control over the spells has given me some really good ideas for custom spells that I can actually execute, thank you so much for sharing :D
 
@ Anyone who is using this:

Can someone please post a gif or vid of what this looks like
 
Very nice! Lots of effort put into making this I can see! Love the library! Thank you for the release!
 
sup @Azakelis

i just tried the script and im getting this nil value from doTargetCombat


Lua:
Lua Script Error: [Main Interface]
in a timer event called from:
(Unknown scriptfile)
data/spells/scripts/warden/earth_wave.lua:10: attempt to call global 'doTargetCombat' (a nil value)
stack traceback:
        [C]: in function 'doTargetCombat'
        data/spells/scripts/warden/earth_wave.lua:10: in function 'onCreatureHit'
        data/spells/scripts/warden/earth_wave.lua:88: in function 'onHit'
        data/spells/scripts/warden/earth_wave.lua:129: in function <data/spells/scripts/warden/earth_wave.lua:92>

i placed libs inside the same file for the meantime
 
you are right im using doTargetCombatHealth, that should be fine?
Post automatically merged:

well it ended up working thks mate, awesome spell btw
 
Last edited:
how can I make this into a "distances" effect instead?

and make it do damage upon sending it away in the faced direction, and do damage on the way back?
also return the animation to the player when returning.
 
Really nice system, but it needs something, at least for me really obvious. Walls/Objects projectile block :)

Lua:
if toTile:hasProperty(CONST_PROP_BLOCKPROJECTILE) or
            toTile:hasFlag(TILESTATE_PROTECTIONZONE) or toTile:hasFlag(TILESTATE_FLOORCHANGE)
            or toTile:hasFlag(TILESTATE_BLOCKSOLID) or toTile:hasFlag(TILESTATE_BLOCKPATH) or toTile:hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)
            or toTile:hasFlag(TILESTATE_IMMOVABLEBLOCKPATH) then return end

with this update is way more realisitc on my opinion. hope it helps
 
Last edited:
Back
Top