Leo32
Getting back into it...
- Joined
- Sep 21, 2007
- Messages
- 987
- Solutions
- 14
- Reaction score
- 532
I know spell animations are nothing new, but the way they've been done in the past calls multiple combat objects.
This can be cool in how each square / animation has its own damage and the damage is applied with the animation, but this can introduce issues where; if the creature moves to a non-damaging square between the animation delays, then they won't receive damage at all.
Here is an example of an animated spell using this dynamic, multiple combat object method:
fire nova (animated with dynamic damage)
We can achieve the same animation effect, but have the damage roll behave normally.
This guarantees that nothing misses.
The cons of this setup are:
You may want to use both of these systems.
Maybe you want to animate fire wave or a stoneshower/gfb area, but you want the damage to apply immediately just like in vanilla tibia:
Well here is how you would do that:
fire wave (animated):
With fire wave (or any directional spell), the direction of the wave needs to be rotated depending on the direction you are facing.
This is done automatically for the combat object/area but for the animation logic you'd need to do this again.
Here is the rotation code I use, you'll need to add the rotate function to the bottom of your spells\lib\spells.lua file if you want to use the fire wave script from this thread:
Here is the animated stoneshower script too:
rock slide.lua
Basically what you're doing is casting the spell with NO animation parameter:
And dealing with the animations yourself:
Using either of these methods, and by cutting and splicing this code, you should be able to make more creative and interesting spells
This can be cool in how each square / animation has its own damage and the damage is applied with the animation, but this can introduce issues where; if the creature moves to a non-damaging square between the animation delays, then they won't receive damage at all.
Here is an example of an animated spell using this dynamic, multiple combat object method:
fire nova (animated with dynamic damage)
Lua:
-- Delay between animations.
local animationDelay = 200
local combat = {}
-- Frames (1 = Area, 2 = Player, 3 = Player + Self Damaging)
local area = {
{
{0, 1, 0},
{1, 2, 1},
{0, 1, 0}
},
{
{1, 0, 1},
{0, 2, 0},
{1, 0, 1}
},
{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 2, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
}
}
for i = 1, #area do
combat[i] = Combat()
combat[i]:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
combat[i]:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA)
end
for x, _ in ipairs(area) do
combat[x]:setArea(createCombatArea(area[x]))
end
function executeCombat(p, i)
if not p.player then
return false
end
if not p.player:isPlayer() then
return false
end
p.combat[i]:execute(p.player, p.var)
end
function onCastSpell(player, var)
local p = {player = player, var = var, combat = combat}
-- Damage formula
local level = player:getLevel()
local maglevel = player:getMagicLevel()
local min = (level / 5) + (maglevel * 1.4) + 8
local max = (level / 5) + (maglevel * 2.2) + 14
for i = 1, #area do
combat[i]:setFormula(COMBAT_FORMULA_LEVELMAGIC, 0, -min, 0, -max)
if i == 1 then
combat[i]:execute(player, var)
else
addEvent(executeCombat, (animationDelay * i) - animationDelay, p, i)
end
end
return true
end
We can achieve the same animation effect, but have the damage roll behave normally.
This guarantees that nothing misses.
The cons of this setup are:
- The damage won't apply in a delayed fashion, synchronized with the animation - it applies immediately.
- Each animation frame doesn't have separate combat rolls, so all squares will do the same damage (like vanilla tibia spells)
Lua:
local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
-- Set area
local arr = {
{0, 1, 1, 1, 0},
{1, 1, 1, 1, 1},
{1, 1, 2, 1, 1},
{1, 1, 1, 1, 1},
{0, 1, 1, 1, 0}
}
local animationarea = {
{
{0, 0, 0, 0, 0},
{0, 0, 1, 0, 0},
{0, 1, 2, 1, 0},
{0, 0, 1, 0, 0},
{0, 0, 0, 0, 0}
},
{
{0, 0, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 0, 0}
},
{
{0, 1, 1, 1, 0},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 1, 1, 1, 0}
}
}
local area = createCombatArea(arr)
combat:setArea(area)
function onGetFormulaValues(player, level, maglevel)
local min = (level / 5) + (maglevel * 1.4) + 8
local max = (level / 5) + (maglevel * 2.2) + 14
return -min, -max
end
combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")
local function animation(pos, playerpos)
--if not Tile(Position(pos)):hasProperty(CONST_PROP_BLOCKPROJECTILE) then
if Position(pos):isSightClear(playerpos) then
Position(pos):sendMagicEffect(CONST_ME_FIREAREA)
end
--end
end
function onCastSpell(creature, var)
local creaturepos = creature:getPosition()
local playerpos = Position(creaturepos)
local delay = 200
local centre = {}
local damagearea = {}
for j = 1, #animationarea do
for k,v in ipairs(animationarea[j]) do
for i = 1, #v do
--print(v[i])
if v[i] == 3 or v[i] == 2 then
centre.Row = k
centre.Column = i
table.insert(damagearea, centre)
elseif v[i] == 1 then
local darea = {}
darea.Row = k
darea.Column = i
darea.Delay = j * delay
table.insert(damagearea, darea)
end
end
end
--print(centre.Column .. "," .. centre.Row)
end
for i = 1,#damagearea do
--print(damagearea[i].Column .. "," .. damagearea[i].Row)
local modifierx = damagearea[i].Column - centre.Column
local modifiery = damagearea[i].Row - centre.Row
--print("x " .. modifierx .. " " .. "y " .. modifiery)
local damagepos = Position(creaturepos)
damagepos.x = damagepos.x + modifierx
damagepos.y = damagepos.y + modifiery
--print("Damage: " .. damagepos.x .. "," .. damagepos.y .. "," .. damagepos.z)
local animationDelay = damagearea[i].Delay or 0
addEvent(animation, animationDelay, damagepos, playerpos)
end
return combat:execute(creature, var)
end
You may want to use both of these systems.
Maybe you want to animate fire wave or a stoneshower/gfb area, but you want the damage to apply immediately just like in vanilla tibia:
Well here is how you would do that:
fire wave (animated):
Lua:
local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
-- Set area
local arr = AREA_WAVE4
local area = createCombatArea(arr)
combat:setArea(area)
function onGetFormulaValues(player, level, maglevel)
local min = (level / 5) + (maglevel * 1.2) + 7
local max = (level / 5) + (maglevel * 2) + 12
return -min, -max
end
combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")
local function animation(pos, playerpos)
if not Tile(Position(pos)):hasProperty(CONST_PROP_BLOCKPROJECTILE) then
if Position(pos):isSightClear(playerpos) then
Position(pos):sendMagicEffect(CONST_ME_HITBYFIRE)
end
end
end
function onCastSpell(creature, var)
local animationarea = arr
local creaturepos = creature:getPosition()
local playerpos = Position(creaturepos)
local directional = true -- is spell directional?
local directionalx = 0
local directionaly = 0
local delay = 100
-- Rotate based on direction
if directional then
local direction = creature:getDirection()
if direction == 1 then
animationarea = rotate_CW_90(animationarea)
directionalx = 1
elseif direction == 2 then
animationarea = rotate_180(animationarea)
directionaly = 1
elseif direction == 3 then
animationarea = rotate_CCW_90(animationarea)
directionalx = -1
else
directionaly = -1
end
end
local centre = {}
local damagearea = {}
for k,v in ipairs(animationarea) do
for i = 1, #v do
--print(v[i])
if v[i] == 3 or v[i] == 2 then
centre.Row = k
centre.Column = i
table.insert(damagearea, centre)
elseif v[i] == 1 then
local darea = {}
darea.Row = k
darea.Column = i
table.insert(damagearea, darea)
end
end
end
--print(centre.Column .. "," .. centre.Row)
for i = 1,#damagearea do
local animationDelay = delay
--print(damagearea[i].Column .. "," .. damagearea[i].Row)
local modifierx = damagearea[i].Column - centre.Column
local modifiery = damagearea[i].Row - centre.Row
--print("x " .. modifierx .. " " .. "y " .. modifiery)
local damagepos = Position(creaturepos)
damagepos.x = damagepos.x + modifierx + directionalx
damagepos.y = damagepos.y + modifiery + directionaly
--print("Damage: " .. damagepos.x .. "," .. damagepos.y .. "," .. damagepos.z)
if directional then
local direction = creature:getDirection()
if direction == 1 then
animationDelay = (modifierx + directionalx) * delay
elseif direction == 2 then
animationDelay = (modifiery + directionaly) * delay
elseif direction == 3 then
animationDelay = ((modifierx + directionalx) * -1) * delay
else
animationDelay = ((modifiery + directionaly) * -1) * delay
end
animationDelay = animationDelay - delay
--print(animationDelay)
end
addEvent(animation, animationDelay, damagepos, playerpos)
end
return combat:execute(creature, var)
end
With fire wave (or any directional spell), the direction of the wave needs to be rotated depending on the direction you are facing.
This is done automatically for the combat object/area but for the animation logic you'd need to do this again.
Here is the rotation code I use, you'll need to add the rotate function to the bottom of your spells\lib\spells.lua file if you want to use the fire wave script from this thread:
Lua:
function rotate_CCW_90(m)
local rotated = {}
for c, m_1_c in ipairs(m[1]) do
local col = {m_1_c}
for r = 2, #m do
col[r] = m[r][c]
end
table.insert(rotated, 1, col)
end
return rotated
end
function rotate_180(m)
return rotate_CCW_90(rotate_CCW_90(m))
end
function rotate_CW_90(m)
return rotate_CCW_90(rotate_CCW_90(rotate_CCW_90(m)))
end
Here is the animated stoneshower script too:
rock slide.lua
Lua:
-- Frames (1 = Area, 2 = Player, 3 = Player + Self Damaging)
local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE)
local arr = {
{0, 1, 1, 1, 0},
{1, 1, 1, 1, 1},
{1, 1, 3, 1, 1},
{1, 1, 1, 1, 1},
{0, 1, 1, 1, 0}
}
local area = createCombatArea(arr)
combat:setArea(area)
function onGetFormulaValues(player, level, maglevel)
local min = (level / 5) + (maglevel * 1.6) + 16
local max = (level / 5) + (maglevel * 3.0) + 28
return -min, -max
end
combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")
local function animation(pos, playerpos)
if not Tile(Position(pos)):hasProperty(CONST_PROP_BLOCKPROJECTILE) then
-- This is only applicable for directional spells
--if Position(pos):isSightClear(playerpos) then
Position(pos):sendMagicEffect(CONST_ME_STONES)
Position(pos):sendMagicEffect(CONST_ME_GROUNDSHAKER)
--end
end
end
function onCastSpell(creature, var)
local animationarea = arr
local creaturepos = creature:getPosition()
local playerpos = Position(creaturepos)
local targeted = true -- is this a targettable spell? (strike or gfb-like spell etc)
local delay = 100
if targeted then
creaturepos = creature:getTarget():getPosition()
end
local centre = {}
local damagearea = {}
for k,v in ipairs(animationarea) do
for i = 1, #v do
if v[i] == 3 or v[i] == 2 then
centre.Row = k
centre.Column = i
table.insert(damagearea, centre)
elseif v[i] == 1 then
local darea = {}
darea.Row = k
darea.Column = i
table.insert(damagearea, darea)
end
end
end
for i = 1,#damagearea do
-- adjust delay randomizer here, different animations have different "sweet-spot" delays
local animationDelay = math.random(1,6) * delay
local modifierx = damagearea[i].Column - centre.Column
local modifiery = damagearea[i].Row - centre.Row
local damagepos = Position(creaturepos)
damagepos.x = damagepos.x + modifierx
damagepos.y = damagepos.y + modifiery
addEvent(animation, animationDelay, damagepos, playerpos)
end
return combat:execute(creature, var)
end
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE)
And dealing with the animations yourself:
Lua:
addEvent(animation, animationDelay, damagepos, playerpos)
local function animation(pos, playerpos)
if not Tile(Position(pos)):hasProperty(CONST_PROP_BLOCKPROJECTILE) then
if Position(pos):isSightClear(playerpos) then
Position(pos):sendMagicEffect(CONST_ME_HITBYFIRE)
end
end
end
Using either of these methods, and by cutting and splicing this code, you should be able to make more creative and interesting spells
Last edited: