• 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!
  • 2026 staff recruitment is open! Check it out and consider applying!

TFS 1.4.2 Cast spell through another spell

lukatxd

Well-Known Member
Joined
Dec 9, 2010
Messages
139
Solutions
1
Reaction score
63
Is there a way -preferably without source edit- that I can cast an spell X and call the spell Y script? Almost like the pokemon servers and stuff, but using my own character.
I'm building my server around the idea of piloting robots, and each robot has a specific set of spells, but I would like that I could just "cast a spell" by calling a command like "command1" and then look through a table of spells available for that robot and use it.
That way players dont need to keep adjusting their hotkeys all the time, because weapons will also make players learn spells

LUA:
function onCastSpell(creature, variant)
    local player = Player(creature:getId())
    local vocationId = player:getVocation()
   
    -- instead of this
    local spellCombat = VOCATION_COMMANDS_TABLE[vocationId][1]
    return spellCombat:execute(creature,variant)
   
    -- instead of the above use something like this
    return -cast exori_frigo.lua
end
 
Last edited:
Solution
Do it in 2 files.

Trying to "cast spell Y from spell X" is the wrong approach in TFS 1.4.2.
There is no clean Lua spell:cast() for that.
The clean way is:
spell X and spell Y both call the same shared handler.

Step 1

Create this file:

Code:
data/lib/robot_spells.lua

Paste this there:

LUA:
RobotSpellHandlers = RobotSpellHandlers or {}

-- Put the real logic for each robot spell here.
-- This can be Combat() objects, helper functions, conditions, etc.

local robotCombat1 = Combat()
robotCombat1:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
robotCombat1:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEATTACK)

function onGetRobotCombat1Values(player, level, maglevel)
    local min = (level / 5) + (maglevel * 5) + 25...
Do I have to resort to talkactions then? Having it as spell was nice because it ommitted the "player says: exori frigo" thing.
 
Do it in 2 files.

Trying to "cast spell Y from spell X" is the wrong approach in TFS 1.4.2.
There is no clean Lua spell:cast() for that.
The clean way is:
spell X and spell Y both call the same shared handler.

Step 1

Create this file:

Code:
data/lib/robot_spells.lua

Paste this there:

LUA:
RobotSpellHandlers = RobotSpellHandlers or {}

-- Put the real logic for each robot spell here.
-- This can be Combat() objects, helper functions, conditions, etc.

local robotCombat1 = Combat()
robotCombat1:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
robotCombat1:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEATTACK)

function onGetRobotCombat1Values(player, level, maglevel)
    local min = (level / 5) + (maglevel * 5) + 25
    local max = (level / 5) + (maglevel * 9) + 40
    return -min, -max
end

robotCombat1:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetRobotCombat1Values")

RobotSpellHandlers["robot_spell_1"] = function(creature, variant)
    return robotCombat1:execute(creature, variant)
end

Step 2

Create your wrapper spell here:

Code:
data/scripts/spells/support/robot_command_1.lua

Paste this:

LUA:
local spell = Spell(SPELL_INSTANT)

local RobotSlot1 = {
    [1] = "robot_spell_1", -- robot/vocation id 1
    [2] = "robot_spell_1", -- robot/vocation id 2
}

function spell.onCastSpell(creature, variant)
    local player = Player(creature)
    local vocationId = player:getVocation():getId()

    local spellKey = RobotSlot1[vocationId]
    if not spellKey then
        player:sendCancelMessage("This robot has no spell in slot 1.")
        return false
    end

    local handler = RobotSpellHandlers[spellKey]
    if not handler then
        player:sendCancelMessage("Spell handler not found.")
        return false
    end

    return handler(creature, variant)
end

spell:group("attack")
spell:id(2000) -- use a free spell id
spell:name("Robot Command 1")
spell:words("command1")
spell:level(1)
spell:mana(0)
spell:cooldown(1000)
spell:groupCooldown(1000)
spell:register()

Step 3

If you still want the original spell to exist too, then edit that spell file and make it call the same handler.

Example:

Code:
data/scripts/spells/attack/exori_frigo.lua

LUA:
local spell = Spell(SPELL_INSTANT)

function spell.onCastSpell(creature, variant)
    return RobotSpellHandlers["robot_spell_1"](creature, variant)
end

spell:group("attack")
spell:id(2001) -- use a free spell id
spell:name("Exori Frigo")
spell:words("exori frigo")
spell:level(20)
spell:mana(50)
spell:cooldown(4000)
spell:groupCooldown(2000)
spell:register()

Step 4

Restart the server or reload scripts.

Step 5

Also fix this in your current code:

LUA:
local vocationId = player:getVocation():getId()

not this:

LUA:
local vocationId = player:getVocation()

So short version:
  • do not try -cast exori_frigo.lua
  • do not move it to talkaction just for this
  • put the real spell logic in data/lib/robot_spells.lua
  • make your wrapper spell in data/scripts/spells/support/robot_command_1.lua
  • if needed, let the original spell file call the same handler too

That is the clean 1.4.2 way to do it.
 
Solution
Do it in 2 files.

Trying to "cast spell Y from spell X" is the wrong approach in TFS 1.4.2.
There is no clean Lua spell:cast() for that.
The clean way is:
spell X and spell Y both call the same shared handler.

Step 1

Create this file:

Code:
data/lib/robot_spells.lua

Paste this there:

LUA:
RobotSpellHandlers = RobotSpellHandlers or {}

-- Put the real logic for each robot spell here.
-- This can be Combat() objects, helper functions, conditions, etc.

local robotCombat1 = Combat()
robotCombat1:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
robotCombat1:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEATTACK)

function onGetRobotCombat1Values(player, level, maglevel)
    local min = (level / 5) + (maglevel * 5) + 25
    local max = (level / 5) + (maglevel * 9) + 40
    return -min, -max
end

robotCombat1:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetRobotCombat1Values")

RobotSpellHandlers["robot_spell_1"] = function(creature, variant)
    return robotCombat1:execute(creature, variant)
end

Step 2

Create your wrapper spell here:

Code:
data/scripts/spells/support/robot_command_1.lua

Paste this:

LUA:
local spell = Spell(SPELL_INSTANT)

local RobotSlot1 = {
    [1] = "robot_spell_1", -- robot/vocation id 1
    [2] = "robot_spell_1", -- robot/vocation id 2
}

function spell.onCastSpell(creature, variant)
    local player = Player(creature)
    local vocationId = player:getVocation():getId()

    local spellKey = RobotSlot1[vocationId]
    if not spellKey then
        player:sendCancelMessage("This robot has no spell in slot 1.")
        return false
    end

    local handler = RobotSpellHandlers[spellKey]
    if not handler then
        player:sendCancelMessage("Spell handler not found.")
        return false
    end

    return handler(creature, variant)
end

spell:group("attack")
spell:id(2000) -- use a free spell id
spell:name("Robot Command 1")
spell:words("command1")
spell:level(1)
spell:mana(0)
spell:cooldown(1000)
spell:groupCooldown(1000)
spell:register()

Step 3

If you still want the original spell to exist too, then edit that spell file and make it call the same handler.

Example:

Code:
data/scripts/spells/attack/exori_frigo.lua

LUA:
local spell = Spell(SPELL_INSTANT)

function spell.onCastSpell(creature, variant)
    return RobotSpellHandlers["robot_spell_1"](creature, variant)
end

spell:group("attack")
spell:id(2001) -- use a free spell id
spell:name("Exori Frigo")
spell:words("exori frigo")
spell:level(20)
spell:mana(50)
spell:cooldown(4000)
spell:groupCooldown(2000)
spell:register()

Step 4

Restart the server or reload scripts.

Step 5

Also fix this in your current code:

LUA:
local vocationId = player:getVocation():getId()

not this:

LUA:
local vocationId = player:getVocation()

So short version:
  • do not try -cast exori_frigo.lua
  • do not move it to talkaction just for this
  • put the real spell logic in data/lib/robot_spells.lua
  • make your wrapper spell in data/scripts/spells/support/robot_command_1.lua
  • if needed, let the original spell file call the same handler too

That is the clean 1.4.2 way to do it.
Thank you! This handler thing in the lib folder, does the server automatically reads it? I feel like I missed where it is initialized or something.

Would it work if I separated each spell in a file and added them to the spell_command.lua as a require? like
LUA:
-- this is the file in the lib folder---
----------------------------------------
require('spell_1') -- combat1 is defined here
require('spell_2') -- combat2 is defined here

-- spell wrapper already had the vocationId and the right spell index
-- pointing to the table below
-- these indexes are not the vocations, just an index
local RobotSpellsCompendium = {
[1] = combat1
[2] = combat2
[3] = combat3
}
my concern is that the combatFormulaValues callback wouldnt be called here as it would be unreachable
 
Yes, but small correction on my side:

data/scripts/lib is auto loaded in TFS 1.4.2
data/lib is not, unless you manually dofile(...) it from data/lib/lib.lua or data/global.lua.

So the clean path is:

Code:
data/scripts/lib/robot_spells.lua

not:

Code:
data/lib/robot_spells.lua

About your example: yes, that approach works too.

You can split each robot spell/combat into separate files and then build a compendium table from them.
The important part is just this:

  • the files must be loaded before your wrapper spell tries to use them
  • the callback formula function must exist in the Lua environment
  • the wrapper spell only chooses which combat/handler to execute

So in practice I would do it like this:

1. put shared files in:
Code:
data/scripts/lib/robot/

example:
Code:
data/scripts/lib/robot/spell_1.lua
data/scripts/lib/robot/spell_2.lua
data/scripts/lib/robot/compendium.lua

2. inside spell_1.lua define the combat and formula there

LUA:
combat1 = Combat()
combat1:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
combat1:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEATTACK)

function combat1Formula(player, level, maglevel)
    local min = (level / 5) + (maglevel * 5) + 25
    local max = (level / 5) + (maglevel * 9) + 40
    return -min, -max
end

combat1:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "combat1Formula")

3. inside compendium.lua mount them into a table

LUA:
RobotSpellsCompendium = {
    [1] = combat1,
    [2] = combat2
}

4. then your wrapper spell only does this

LUA:
function spell.onCastSpell(creature, variant)
    local player = Player(creature)
    local vocationId = player:getVocation():getId()
    local spellIndex = 1

    local combat = RobotSpellsCompendium[spellIndex]
    if not combat then
        return false
    end

    return combat:execute(creature, variant)
end

So yes:
the callback still works.
It does not need to be in the same file as the wrapper spell.
It only needs to be loaded before combat:execute() happens.

Only thing I would avoid is relying on require() unless you already know your environment is set up for it.
In TFS scripts, dofile() or just letting data/scripts/lib autoload the files is usually the safer route.
 
Back
Top