• 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] Flexible spells lib + Spell Level System (configurable with growing areas, damage etc)

Animera

* * * * *
Joined
Dec 9, 2008
Messages
2,429
Solutions
5
Reaction score
603
Location
ANIMERA.ONLINE
Dear folks!

For a long time i wanted to give something back to the community, as i've received many support here myself (from questions in my 'noob times', to the development of TFS in general).

But i didn't want to share just 'something'. But something that may be usefull in general (for all kinds of servers). I have some other plans of scripts that i gonna release in the near future (that i actually made for my server, but this i just scripted it out of boredom).



Spell Level system: How does it work ?
By casting spells, the individual spell will gain 'skilltries', if the spell levels up, it increases in damage and/or area. Optionable with a level cap. (The level system is storage based, so if you're a bit handy with scripts you could even make quests that upgrade a spell or something else).

To make use of the spell level system you would need to have the more flexible spell system setup of mine, which can be found here:

ice_video_20191221-052559.gif
Do not get confused with different text colors, spell cast effects, leech/crit effect, i used whirlwind blade lv1 as a example as it is the 'exori/berserk' spell compared to the vanilla servers...



Flexible Spells Lib How does it work ?
As many of us know that combat area's need to be set up on beforehand, which reduces the manipulation options if we want to change the size/shape of a certain spell by using factors that can only be calculated when it get's 'casted'.. Glad we can index areas beforehand in combination of the doAreaCombatHealth function to create a more flexible system (and it's more simple if you ask me...).

This does means if you want to use it you need to rewrite all your spells, or just the spells that you want to make use of this flexible spell system (e.g. the spell level system).

Because i was lazy i used codex NG custom skill system for the spells (which was just what i needed anyway lol, no reason to reinvent the wheel. RIP buddy).

Example of usage (This is a berserk spell):
Code:
local spellname, effect, area, damagetype = "Berserk", CONST_ME_HITAREA, "AREA_SQUARE1X1", COMBAT_PHYSICALDAMAGE

function onCastSpell(creature, variant)
    local lvl, mlvl, skill, attack = creature:getLevel(), creature:getMagicLevel(), creature:getWepSkill(), creature:getAtk()
    local min = (lvl / 5) + ((skill * 2) + attack) * 0.5
    local max = (lvl / 5) + ((skill * 2) + attack) * 1.5
    return creature:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
end

Create a new file -> data/lvlspellslib.lua

Code:
----------------------------------------------------------------------------------------------------------
-- Flexible Spells System  Written by @Animera from Otland.net                                          --
--                                                                                                      --
-- Want to see more awesome features? Check my server: Animera.Online                                    --
-- Want a awesome feature yourself? I also take occasionaly jobs as freelancer. PM @ Otland                --
----------------------------------------------------------------------------------------------------------

SPELLS_LEVEL_LIST = {
    ["Berserk"] = {lvlstorage = 1000, dmgmultiplier = 1.1, increaseSize = true, maxlvl = 5}, -- multiplier for every lvl!
    ["Energy Beam"] = {lvlstorage = 1001, dmgmultiplier = 1.5, increaseSize = false, maxlvl = 100},
    ["Eternal Winter"] = {lvlstorage = 1002, dmgmultiplier = 1.2, increaseSize = false, maxlvl = 200}
}

function Player:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
local spelllvl = SPELLS_LEVEL_LIST[spellname].lvlstorage
local mox = self:lvlDmg(spellname, SPELLS_LEVEL_LIST[spellname].lvlstorage, min, max)
        if doAreaCombatHealth(self, damagetype, variant:getNumber() > 0 and variant:getPosition() or self:getPosition(), getSpellArea(area, spellname, self:getStorageValue(spelllvl)), -mox, -mox, effect) == true then
            if SPELLS_LEVEL_LIST[spellname] then
                if self:getStorageValue(spelllvl) < SPELLS_LEVEL_LIST[spellname].maxlvl then
                    self:addCustomSkillTry(spellname, spelllvl)
                end
            end
        return true            
        end
    
return false    
end

function getSpellArea(defaultarea, spellname, level)
    level = level - 1

    for i = 1, #SPELLS_LEVEL_AREAS do
        if SPELLS_LEVEL_AREAS[i] == defaultarea then
            if SPELLS_LEVEL_LIST[spellname] then
                if not SPELLS_LEVEL_LIST[spellname].increaseSize then
                    return arrs[i]
                end
            end
                if i+level < #SPELLS_LEVEL_AREAS then -- to prevent bug if reached last array..
                    return arrs[i+level]
                else
                    print(">> Spell level system warning: Spell has reached maximum potential, create more areas or lower max level limit.")
                    return arrs[#arrs] -- just use biggest area then...
                end
        end
    end
end

function Player:lvlDmg(spellname, lvlstorage, min, max)
    if SPELLS_LEVEL_LIST[spellname] then
        local level, mult = self:getStorageValue(SPELLS_LEVEL_LIST[spellname].lvlstorage), (SPELLS_LEVEL_LIST[spellname].dmgmultiplier - 1)
            min = min * (1 + ((level * mult)))
            max = max * (1 + ((level * mult)))
        return math.random(min, max)
    end
    print(">> Spell level system warning: This spell isn't registered yet.")
    return math.random(min, max)
end

function Player:getAtk()
    local hands = {CONST_SLOT_LEFT, CONST_SLOT_RIGHT}
    for i = 1, #hands do
        local wep = self:getSlotItem(hands[i])
        if wep then
            if wep:getType():getAttack() > 0 and wep:getType():getWeaponType() > 0 then
            return wep:getType():getAttack(), wep:getType():getWeaponType()
            end
        end
    end
    return 0
end

function Player:getWepSkill()    
    local hands = {CONST_SLOT_LEFT, CONST_SLOT_RIGHT}
    for i = 1, #hands do
        local wep = self:getSlotItem(hands[i])
            if wep then
                if wep == 1 then return self:getEffectiveSkillLevel(SKILL_SWORD)
                elseif wep == 2 then return self:getEffectiveSkillLevel(SKILL_CLUB)
                elseif wep == 3 then return self:getEffectiveSkillLevel(SKILL_AXE)
                end
            end
    end
return 10
end


data/global.lua - Add this somewhere on the first lines.
Code:
dofile('data/lvlspellslib.lua')




data/global.lua - Add this somewhere in the file (Credits to codex NG for writing the custom skill functions!)

Code:
function Player:getCustomSkill(storage)
return self:getStorageValue(storage)
end


function Player:addCustomSkill(skillName, storage)
    local skillStorage = math.max(1, self:getStorageValue(storage))
    local skillTries =  math.max(0, self:getStorageValue(storage + 1))
    self:setStorageValue(storage, skillStorage + 1)
    self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You advanced to " .. string.lower(skillName) .. " level "..self:getCustomSkill(storage)..".")
    self:getPosition():sendMagicEffect(math.random(29,31))
    self:setStorageValue(storage + 1, 0)
end


function Player:addCustomSkillTry(skillName, storage)
    local skillStorage = math.max(1, self:getStorageValue(storage))
    local skillTries =  math.max(0, self:getStorageValue(storage + 1))
    self:setStorageValue(storage + 1, skillTries + 1)

    if skillTries > math.floor(20 * math.pow(1.1, (skillStorage - 11)) / 10) then
        self:addCustomSkill(skillName, storage)
    end
end


function Player:getCustomSkillPercent(storage)
    local skillStorage = math.max(1, self:getStorageValue(storage))
    local skillTries =  math.max(0, self:getStorageValue(storage + 1))
    local triesNeeded = math.floor(20 * math.pow(1.1, (skillStorage - 11)) / 10)
    local percent = math.floor(100 * (1 - skillTries / triesNeeded))
    if percent > 1 and percent <= 100 then
        return percent
    else
        percent = 1
        return percent
    end
end



data/globalevents/startup.lua - Add this somewhere before the startup function!
Code:
dofile('data/spells/lib/spells.lua')

SPELLS_LEVEL_AREAS = { -- areas.. higher number is higher level, where spell starts is based on 'base area' exori starts at 1, gfb at 4...
    [1] = "AREA_SQUARE1X1",
    [2] = "AREA_SQUARE2X2",
    [3] = "AREA_CIRCLE2X2",
    [4] = "AREA_CIRCLE3X3",
    [5] = "AREA_CROSS5X5"
}

arrs = {}

for y = 1, #SPELLS_LEVEL_AREAS do
    table.insert(arrs, createCombatArea(_G[SPELLS_LEVEL_AREAS[y]]))
end


Create a new file called creaturescripts/firstlogin.lua (or paste it wisely in login.lua, if you know the first login block)
Code:
function setSpellLvl(self, count) -- reduces pressure on server launch, if you have a lot of spells...
    if not count then count = 1 end
    local self = Player(self)

    if not self then return end
    local voc = self:getVocation:getId()

    if self:canLearnSpell(SPELLS_LEVEL_LIST[SPELLS[count]]) then
        player:setStorageValue(SPELLS[count].lvlstorage, 1)
    end

    if count < #SPELLS then addEvent(setSpellLvl, 10, self:getId(), count+1) end
end

function onLogin(player)
if player:getLastLoginSaved() <= 0 then setSpellLvl(player:getId()) end
return true
end

Animera
 
Last edited:
Please fix code indendation, that looks very ugly and your code loses in eyes a bit.
 
Please fix code indendation, that looks very ugly and your code loses in eyes a bit.

I've tried... but for some reasons my tabs gets converted to spaces... it messed it up here and there... and sometimes i like to have '1 liners'. (if that'swhat you meant?). Yesterday i also had to delete my thread once, as my codes got mixed with unerasable forum styling tags lol..


Btw: Added a small fix which should prevent getting a error log occasionally.
 
Nice system!
I was thinking to do something like that, i only don't like to hold that info in storages, i think using Storing Info(Players and Items) with a global table to hold the spells levels and tries is better, just need to load in login and save in logout.
Thanks for share!
 
Nice system!
I was thinking to do something like that, i only don't like to hold that info in storages, i think using Storing Info(Players and Items) with a global table to hold the spells levels and tries is better, just need to load in login and save in logout.
Thanks for share!
Storages are not loaded from the database every time they are called, that only happens when a player closes or starts a section.

I've tried... but for some reasons my tabs gets converted to spaces... it messed it up here and there... and sometimes i like to have '1 liners'. (if that'swhat you meant?). Yesterday i also had to delete my thread once, as my codes got mixed with unerasable forum styling tags lol..


Btw: Added a small fix which should prevent getting a error log occasionally.

Hello great friend, what a good spell.
 
Thank you :)

If there are any questions, bugs or suggestions feel free to post it here!
I made the system quite quickly, but the script itself can still be improved (which i will do when i have the time).

I was also thinking about more features:
  • adding some functions that would replace the need of certain xml attributes (e.g. needweapon, target, range, manacost, exhaust)...
  • adding more possible bonuses from leveling up, (e.g. range, manacost, cooldowns)
  • more flexibility in bonuses (e.g. only increase in size every 10 levels instead of 1?)
  • expanding the lib so it could also be written for other spells like healing or condition?
  • adding the possibility to customize spells effect in general...
  • maybe something for animated spells?

Nice system!
I was thinking to do something like that, i only don't like to hold that info in storages, i think using Storing Info(Players and Items) with a global table to hold the spells levels and tries is better, just need to load in login and save in logout.
Thanks for share!

Interesting system for sure, but i prefer not to use global tables as they take additional memory, also global tables gets 'flushed' when your reloading x... Also i saw some DB connections? that can be quite a pressure if people are spamming spells... (not sure how the system exactly works, didn't check in-depth). As my server is loaded with stuff i am a bit cautious... If you feel like i am wrong please elaborate...

Hello great friend, what a good spell.



Animera
 
Good Job @Animera ! I like your system!
The main problem i see, is having to modify every single spell individually (spells that you want to use this system with).
I, for example, have 200+ custom spells, with many conditions and effects that doesnt use simple "doCombat", applying this system to every spell, would be madness. (I had to do it before to add cooldown reduction for spells).
So maybe a way to address that?
 
Interesting system for sure, but i prefer not to use global tables as they take additional memory, also global tables gets 'flushed' when your reloading x... Also i saw some DB connections? that can be quite a pressure if people are spamming spells... (not sure how the system exactly works, didn't check in-depth). As my server is loaded with stuff i am a bit cautious... If you feel like i am wrong please elaborate...
There's a trick you can do in Lua to make tables persistent and un-reloadable: x = x or {}.
As for db connections, no, storages use an internal key:value map (like tables) then are written to db once the player is saved, any time you get/set storage values, it's to that internal map, not the database.
Also worrying about a small table like that is funny, because at most that would take a hundred or two kilobytes of memory depending on player count.
 
Good Job @Animera ! I like your system!
The main problem i see, is having to modify every single spell individually (spells that you want to use this system with).
I, for example, have 200+ custom spells, with many conditions and effects that doesnt use simple "doCombat", applying this system to every spell, would be madness. (I had to do it before to add cooldown reduction for spells).
So maybe a way to address that?

You are right, the problem is my system uses doareaCombatHealth() instead of the regular combats/docombat function...
The only way to prevent that is making changes in c++ how spells work in general / what options we can register / lua options / maybe even mess with how areas work etc.

Maybe show few scripts to me in privately maybe i can see a pattern that could help you.. whether it is a scripting loop / or the replace all function in text editors... maybe a tool that converts scripts... (but then i would need to expand the system i guess, conditions, healing, animated stuff etc.)

Even if there would be a event for like onCastSpell, it would still need areadata to pass in the doCombat(wild guess?)

Even if it was just for the sake of damage it would be a lot easier by using onHealthChange function but then we still do not know which spell was the 'source'...

TBH: I think atm with the limitations inside spells/combats, and the fact that scripts can be much shorter.. that tfs would need to have it rewritten... in the near future...

x = x or {} Gonna remember that, bad of me that i didn't thought of that earlier lol.

Code:
 As for db connections, no, storages use an internal key:value map (like tables) then are written to db once the player is saved, any time you get/set storage values, it's to that internal map, not the database.
Are you reffering here to the storage system by default or the system @zxmatzx provided?

Code:
 Also worrying about a small table like that is funny, because at most that would take a hundred or two kilobytes of memory depending on player count.

Good to know, but it's just not just for the sake of having 1 global table, but having a another global table. Like stamina does and other systems...

My server is loaded with stuff... and has a big map (aprox. 80mb). (Not to mention storages won't take a thing compared to it's system?, and won't be lost on crashes?)

Once i reached 'Allocation failed, server out of memory!' partially due to my errors in the past. But i also found out at the point when my server reached it, it wasn't even using all of it's memory. And then i've found out after googling and stuff that there is a another bottleneck which depends on the OS (i remember it was 2-4GB?, it's been a while ago since i had this issue).

Couldn't find a solution, except there was 1 tool that could remove the limits of the program(tfs) so it could use more resources. Unfortunatly the tool only is made for windows. While i was running on ubuntu (64 bit)...


Animera
 
Last edited:
Yeah, i totally miss being able to use a function like "onCastSpell" or "onCast" outside of spells, or even a function that let me cast spells without the normal spell. "player:castSpell("berserk")" <-- would be an awesome addition.
Something that let me check if a spell has been casted or whatever. But that its a source problem i guess..
 
Are you reffering here to the storage system by default or the system @zxmatzx provided?
Both, the link he provided does the same thing, it's stored in a global table for temporary retrieval then saved on logout.

Good to know, but it's just not just for the sake of having 1 global table, but having a another global table. Like stamina does and other systems...
What difference does it make? It's not like any other system is going to use it.
 
what should i change but my spells?

local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE)
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITAREA)
combat:setParameter(COMBAT_PARAM_BLOCKARMOR, 1)
combat:setParameter(COMBAT_PARAM_USECHARGES, 1)
combat:setArea(createCombatArea(AREA_SQUARE1X1))

function onGetFormulaValues(player, skill, attack, factor)
local skillTotal = skill * attack
local levelTotal = player:getLevel() / 5
return -(((skillTotal * 0.07) + 7) + (levelTotal)), -(((skillTotal * 0.09) + 11) + (levelTotal))
end

combat:setCallback(CALLBACK_PARAM_SKILLVALUE, "onGetFormulaValues")

function onCastSpell(creature, var)
return combat:execute(creature, var)
end
 
what should i change but my spells?

local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE)
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITAREA)
combat:setParameter(COMBAT_PARAM_BLOCKARMOR, 1)
combat:setParameter(COMBAT_PARAM_USECHARGES, 1)
combat:setArea(createCombatArea(AREA_SQUARE1X1))

function onGetFormulaValues(player, skill, attack, factor)
local skillTotal = skill * attack
local levelTotal = player:getLevel() / 5
return -(((skillTotal * 0.07) + 7) + (levelTotal)), -(((skillTotal * 0.09) + 11) + (levelTotal))
end

combat:setCallback(CALLBACK_PARAM_SKILLVALUE, "onGetFormulaValues")

function onCastSpell(creature, var)
return combat:execute(creature, var)
end

Well i used an example of the 'berserk' spell in the post, compare it with that script...
or i do not know what you try to accomplish? shorter code or lvl up system in area/dmg or ?

Animera
 
Well i used an example of the 'berserk' spell in the post, compare it with that script...
or i do not know what you try to accomplish? shorter code or lvl up system in area/dmg or ?

Animera
lvl up system in area/dmg
I could edit only this script the rest I can manage?
 
lvl up system in area/dmg
I could edit only this script the rest I can manage?

To apply the spell level system you would need to change every spell script that is influenced by this system.
Check all the codes and install them properly.

If you want to config what the effect of levels are you have to modify this table
Code:
SPELLS_LEVEL_LIST = {
    ["Berserk"] = {lvlstorage = 1000, dmgmultiplier = 1.1, increaseSize = true, maxlvl = 5}, -- multiplier for every lvl!
    ["Energy Beam"] = {lvlstorage = 1001, dmgmultiplier = 1.5, increaseSize = false, maxlvl = 100},
    ["Eternal Winter"] = {lvlstorage = 1002, dmgmultiplier = 1.2, increaseSize = false, maxlvl = 200}
}



Animera
 
To apply the spell level system you would need to change every spell script that is influenced by this system.
Check all the codes and install them properly.

If you want to config what the effect of levels are you have to modify this table
Code:
SPELLS_LEVEL_LIST = {
    ["Berserk"] = {lvlstorage = 1000, dmgmultiplier = 1.1, increaseSize = true, maxlvl = 5}, -- multiplier for every lvl!
    ["Energy Beam"] = {lvlstorage = 1001, dmgmultiplier = 1.5, increaseSize = false, maxlvl = 100},
    ["Eternal Winter"] = {lvlstorage = 1002, dmgmultiplier = 1.2, increaseSize = false, maxlvl = 200}
}



Animera
how do I change my spells?
could i give an example with this script?

local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE)
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITAREA)
combat:setParameter(COMBAT_PARAM_BLOCKARMOR, 1)
combat:setParameter(COMBAT_PARAM_USECHARGES, 1)
combat:setArea(createCombatArea(AREA_SQUARE1X1))

function onGetFormulaValues(player, skill, attack, factor)
local skillTotal = skill * attack
local levelTotal = player:getLevel() / 5
return -(((skillTotal * 0.07) + 7) + (levelTotal)), -(((skillTotal * 0.09) + 11) + (levelTotal))
end

combat:setCallback(CALLBACK_PARAM_SKILLVALUE, "onGetFormulaValues")

function onCastSpell(creature, var)
return combat:execute(creature, var)
end
 
how do I change my spells?
could i give an example with this script?

local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE)
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITAREA)
combat:setParameter(COMBAT_PARAM_BLOCKARMOR, 1)
combat:setParameter(COMBAT_PARAM_USECHARGES, 1)
combat:setArea(createCombatArea(AREA_SQUARE1X1))

function onGetFormulaValues(player, skill, attack, factor)
local skillTotal = skill * attack
local levelTotal = player:getLevel() / 5
return -(((skillTotal * 0.07) + 7) + (levelTotal)), -(((skillTotal * 0.09) + 11) + (levelTotal))
end

combat:setCallback(CALLBACK_PARAM_SKILLVALUE, "onGetFormulaValues")

function onCastSpell(creature, var)
return combat:execute(creature, var)
end

This is the example i used for berserk spell, as you can see the combat info (effect/area/type/spellname) is configured below, with the damage formula inside the onCastSpell function.

Code:
local spellname, effect, area, damagetype = "Berserk", CONST_ME_HITAREA, "AREA_SQUARE1X1", COMBAT_PHYSICALDAMAGE

function onCastSpell(creature, variant)
    local lvl, mlvl, skill, attack = creature:getLevel(), creature:getMagicLevel(), creature:getWepSkill(), creature:getAtk()
    local min = (lvl / 5) + ((skill * 2) + attack) * 0.5
    local max = (lvl / 5) + ((skill * 2) + attack) * 1.5
    return creature:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
end


Animera
 
This is the example i used for berserk spell, as you can see the combat info (effect/area/type/spellname) is configured below, with the damage formula inside the onCastSpell function.

Code:
local spellname, effect, area, damagetype = "Berserk", CONST_ME_HITAREA, "AREA_SQUARE1X1", COMBAT_PHYSICALDAMAGE

function onCastSpell(creature, variant)
    local lvl, mlvl, skill, attack = creature:getLevel(), creature:getMagicLevel(), creature:getWepSkill(), creature:getAtk()
    local min = (lvl / 5) + ((skill * 2) + attack) * 0.5
    local max = (lvl / 5) + ((skill * 2) + attack) * 1.5
    return creature:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
end


Animera

in the case of this spell how would it look?
dofile('data/lib/miscellaneous/warPrivate_lib.lua')
local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICETORNADO)
combat:setArea(createCombatArea(AREA_CROSS5X5))

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

combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")

function onCastSpell(creature, var)
if creature:getStorageValue(warPrivate_UE) > 0 then
return false
else
return combat:execute(creature, var)
end
end
 
in the case of this spell how would it look?
dofile('data/lib/miscellaneous/warPrivate_lib.lua')
local combat = Combat()
combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICETORNADO)
combat:setArea(createCombatArea(AREA_CROSS5X5))

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

combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues")

function onCastSpell(creature, var)
if creature:getStorageValue(warPrivate_UE) > 0 then
return false
else
return combat:execute(creature, var)
end
end


Code:
local spellname, effect, area, damagetype = "Eternal Winter", CONST_ME_ICETORNADO, "AREA_CROSS5X5", COMBAT_ICEDAMAGE

function onCastSpell(creature, variant)
    local lvl, mlvl, skill, attack = creature:getLevel(), creature:getMagicLevel(), creature:getWepSkill(), creature:getAtk()
    local min = (level / 5) + (maglevel * 9.5) + 25
    local max = (level / 5) + (maglevel * 15) + 50
    return creature:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
end

Just like that :)
It's pretty easy, once you see the pattern.


Animera
 
Dear folks!

For a long time i wanted to give something back to the community, as i've received many support here myself (from questions in my 'noob times', to the development of TFS in general).

But i didn't want to share just 'something'. But something that may be usefull in general (for all kinds of servers). I have some other plans of scripts that i gonna release in the near future (that i actually made for my server, but this i just scripted it out of boredom).



Spell Level system: How does it work ?
By casting spells, the individual spell will gain 'skilltries', if the spell levels up, it increases in damage and/or area. Optionable with a level cap. (The level system is storage based, so if you're a bit handy with scripts you could even make quests that upgrade a spell or something else).

To make use of the spell level system you would need to have the more flexible spell system setup of mine, which can be found here:

View attachment 41211
Do not get confused with different text colors, spell cast effects, leech/crit effect, i used whirlwind blade lv1 as a example as it is the 'exori/berserk' spell compared to the vanilla servers...



Flexible Spells Lib How does it work ?
As many of us know that combat area's need to be set up on beforehand, which reduces the manipulation options if we want to change the size/shape of a certain spell by using factors that can only be calculated when it get's 'casted'.. Glad we can index areas beforehand in combination of the doAreaCombatHealth function to create a more flexible system (and it's more simple if you ask me...).

This does means if you want to use it you need to rewrite all your spells, or just the spells that you want to make use of this flexible spell system (e.g. the spell level system).

Because i was lazy i used codex NG custom skill system for the spells (which was just what i needed anyway lol, no reason to reinvent the wheel. RIP buddy).

Example of usage (This is a berserk spell):
Code:
local spellname, effect, area, damagetype = "Berserk", CONST_ME_HITAREA, "AREA_SQUARE1X1", COMBAT_PHYSICALDAMAGE

function onCastSpell(creature, variant)
    local lvl, mlvl, skill, attack = creature:getLevel(), creature:getMagicLevel(), creature:getWepSkill(), creature:getAtk()
    local min = (lvl / 5) + ((skill * 2) + attack) * 0.5
    local max = (lvl / 5) + ((skill * 2) + attack) * 1.5
    return creature:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
end

Create a new file -> data/lvlspellslib.lua

Code:
----------------------------------------------------------------------------------------------------------
-- Flexible Spells System  Written by @Animera from Otland.net                                          --
--                                                                                                      --
-- Want to see more awesome features? Check my server: Animera.Online                                    --
-- Want a awesome feature yourself? I also take occasionaly jobs as freelancer. PM @ Otland                --
----------------------------------------------------------------------------------------------------------

SPELLS_LEVEL_LIST = {
    ["Berserk"] = {lvlstorage = 1000, dmgmultiplier = 1.1, increaseSize = true, maxlvl = 5}, -- multiplier for every lvl!
    ["Energy Beam"] = {lvlstorage = 1001, dmgmultiplier = 1.5, increaseSize = false, maxlvl = 100},
    ["Eternal Winter"] = {lvlstorage = 1002, dmgmultiplier = 1.2, increaseSize = false, maxlvl = 200}
}

function Player:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
local spelllvl = SPELLS_LEVEL_LIST[spellname].lvlstorage
local mox = self:lvlDmg(spellname, SPELLS_LEVEL_LIST[spellname].lvlstorage, min, max)
        if doAreaCombatHealth(self, damagetype, variant:getNumber() > 0 and variant:getPosition() or self:getPosition(), getSpellArea(area, spellname, self:getStorageValue(spelllvl)), -mox, -mox, effect) == true then
            if SPELLS_LEVEL_LIST[spellname] then
                if self:getStorageValue(spelllvl) < SPELLS_LEVEL_LIST[spellname].maxlvl then
                    self:addCustomSkillTry(spellname, spelllvl)
                end
            end
        return true           
        end
   
return false   
end

function getSpellArea(defaultarea, spellname, level)
    level = level - 1

    for i = 1, #SPELLS_LEVEL_AREAS do
        if SPELLS_LEVEL_AREAS[i] == defaultarea then
            if SPELLS_LEVEL_LIST[spellname] then
                if not SPELLS_LEVEL_LIST[spellname].increaseSize then
                    return arrs[i]
                end
            end
                if i+level < #SPELLS_LEVEL_AREAS then -- to prevent bug if reached last array..
                    return arrs[i+level]
                else
                    print(">> Spell level system warning: Spell has reached maximum potential, create more areas or lower max level limit.")
                    return arrs[#arrs] -- just use biggest area then...
                end
        end
    end
end

function Player:lvlDmg(spellname, lvlstorage, min, max)
    if SPELLS_LEVEL_LIST[spellname] then
        local level, mult = self:getStorageValue(SPELLS_LEVEL_LIST[spellname].lvlstorage), (SPELLS_LEVEL_LIST[spellname].dmgmultiplier - 1)
            min = min * (1 + ((level * mult)))
            max = max * (1 + ((level * mult)))
        return math.random(min, max)
    end
    print(">> Spell level system warning: This spell isn't registered yet.")
    return math.random(min, max)
end

function Player:getAtk()
    local hands = {CONST_SLOT_LEFT, CONST_SLOT_RIGHT}
    for i = 1, #hands do
        local wep = self:getSlotItem(hands[i])
        if wep then
            if wep:getType():getAttack() > 0 and wep:getType():getWeaponType() > 0 then
            return wep:getType():getAttack(), wep:getType():getWeaponType()
            end
        end
    end
    return 0
end

function Player:getWepSkill()   
    local hands = {CONST_SLOT_LEFT, CONST_SLOT_RIGHT}
    for i = 1, #hands do
        local wep = self:getSlotItem(hands[i])
            if wep then
                if wep == 1 then return self:getEffectiveSkillLevel(SKILL_SWORD)
                elseif wep == 2 then return self:getEffectiveSkillLevel(SKILL_CLUB)
                elseif wep == 3 then return self:getEffectiveSkillLevel(SKILL_AXE)
                end
            end
    end
return 10
end


data/global.lua - Add this somewhere on the first lines.
Code:
dofile('data/lvlspellslib.lua')




data/global.lua - Add this somewhere in the file (Credits to codex NG for writing the custom skill functions!)

Code:
function Player:getCustomSkill(storage)
return self:getStorageValue(storage)
end


function Player:addCustomSkill(skillName, storage)
    local skillStorage = math.max(1, self:getStorageValue(storage))
    local skillTries =  math.max(0, self:getStorageValue(storage + 1))
    self:setStorageValue(storage, skillStorage + 1)
    self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You advanced to " .. string.lower(skillName) .. " level "..self:getCustomSkill(storage)..".")
    self:getPosition():sendMagicEffect(math.random(29,31))
    self:setStorageValue(storage + 1, 0)
end


function Player:addCustomSkillTry(skillName, storage)
    local skillStorage = math.max(1, self:getStorageValue(storage))
    local skillTries =  math.max(0, self:getStorageValue(storage + 1))
    self:setStorageValue(storage + 1, skillTries + 1)

    if skillTries > math.floor(20 * math.pow(1.1, (skillStorage - 11)) / 10) then
        self:addCustomSkill(skillName, storage)
    end
end


function Player:getCustomSkillPercent(storage)
    local skillStorage = math.max(1, self:getStorageValue(storage))
    local skillTries =  math.max(0, self:getStorageValue(storage + 1))
    local triesNeeded = math.floor(20 * math.pow(1.1, (skillStorage - 11)) / 10)
    local percent = math.floor(100 * (1 - skillTries / triesNeeded))
    if percent > 1 and percent <= 100 then
        return percent
    else
        percent = 1
        return percent
    end
end



data/globalevents/startup.lua - Add this somewhere before the startup function!
Code:
dofile('data/spells/lib/spells.lua')

SPELLS_LEVEL_AREAS = { -- areas.. higher number is higher level, where spell starts is based on 'base area' exori starts at 1, gfb at 4...
    [1] = "AREA_SQUARE1X1",
    [2] = "AREA_SQUARE2X2",
    [3] = "AREA_CIRCLE2X2",
    [4] = "AREA_CIRCLE3X3",
    [5] = "AREA_CROSS5X5"
}

arrs = {}

for y = 1, #SPELLS_LEVEL_AREAS do
    table.insert(arrs, createCombatArea(_G[SPELLS_LEVEL_AREAS[y]]))
end


Create a new file called creaturescripts/firstlogin.lua (or paste it wisely in login.lua, if you know the first login block)
Code:
function setSpellLvl(self, count) -- reduces pressure on server launch, if you have a lot of spells...
    if not count then count = 1 end
    local self = Player(self)

    if not self then return end
    local voc = self:getVocation:getId()

    if self:canLearnSpell(SPELLS_LEVEL_LIST[SPELLS[count]]) then
        player:setStorageValue(SPELLS[count].lvlstorage, 1)
    end

    if count < #SPELLS then addEvent(setSpellLvl, 10, self:getId(), count+1) end
end

function onLogin(player)
if player:getLastLoginSaved() <= 0 then setSpellLvl(player:getId()) end
return true
end

Animera


I wanted to use the system, without having anything to do with the areas, I am only interested in the increase in damage per spell

I was trying to add to the strikes

exori flam
exori vis
exori gran con
exori gran ico

and others with area
exori
exori gran
exori san

how can i add it to exori vis?

i try but dont works:

Lua:
local spellname, effect, area, damagetype = "Energy Strike", CONST_ME_ENERGYAREA, "CONST_ANI_ENERGY", COMBAT_ENERGYDAMAGE
    
function onCastSpell(creature, variant)
    local level, maglevel = creature:getLevel(), creature:getMagicLevel()
    local min = (level / 5) + (maglevel * 1.403) + 8
    local max = (level / 5) + (maglevel * 2.203) + 13
    return creature:doLevelCombat(variant, spellname, effect, area, damagetype, min, max)
end
 
Back
Top