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

lua > xml ? get finaly rid of xml!

What do you think about this?


  • Total voters
    23

Evil Hero

Legacy Member
TFS Developer
Joined
Dec 12, 2007
Messages
1,254
Solutions
27
Reaction score
721
Location
Germany
Hello folks,

Today I'm here to talk about a certain topic which has been more important than ever since TFS 1.x series came out.
As TFS 1.x keeps evolving and getting better and better with every update, it still keeps slacking behind when it comes to modularity.
Why do I think so? well that's certainly easily explained.
XML as a config for our events and such is restricting us in such a big way you can't even imagine at the first look.
Every developer who has worked on a really custom server before knows exactly what I'm talking about, for those who don't there is a certain issue on github which can explain most of it... https://github.com/otland/forgottenserver/issues/1057

I've been working towards that topic in the past already regarding monsters beeing solely scripted in lua, no XML files needed, drag and drop installation.
Yet you might think, what "real" benefits do we get out of this, if it's just drag and drop installation then it's certainly nothing special...
1) We can customize monsters through lua commands on the fly without the need to reload all monsters.
2) We can even create new monsters while the server is running

Those are just 2 prime examples of what benefits it brings.
Now let me show you how an example script of a monster would look like
Code:
local mType = Game.createMonsterType("Demon")
local monster = {}
monster.description = "a demon"
monster.experience = 6000
monster.outfit = {
  lookType = 35
}

monster.health = 8200
monster.maxHealth = monster.health
monster.race = "fire"
monster.corpse = 5995
monster.speed = 280
monster.maxSummons = 1

monster.changeTarget = {
   interval = 4*1000,
   chance = 20
}

monster.flags = {
   summonable = false,
  attackable = true,
   hostile = true,
  convinceable = false,
   illusionable = false,
   canPushItems = true,
   canPushCreatures = true,
   targetDistance = 1,
   staticAttack = 70
}

monster.summons = {
   {name = "fire elemental", chance = 10, interval = 2*1000}
}

monster.voices = {
   interval = 5000,
   chance = 10,
   {text = "Your soul will be mine!", yell = false},
   {text = "MUHAHAHAHA!", yell = false},
   {text = "CHAMEK ATH UTHUL ARAK!", yell = false},
   {text = "I SMELL FEEEEAAAAAR!", yell = false},
   {text = "Your resistance is futile!", yell = false}
}

monster.loot = {
   {id = "gold coin", chance = 60000, maxCount = 100},
   {id = "gold coin", chance = 60000, maxCount = 99},
   {id = "platinum coin", chance = 100000, maxCount = 6},
   {id = "fire mushroom", chance = 20740, maxCount = 6},
   {id = 8473, chance = 20000, maxCount = 3},
   {id = 7590, chance = 14285, maxCount = 3},
   {id = "double axe", chance = 14285},
   {id = "small emerald", chance = 10000},
   {id = "assassin star", chance = 5263, maxCount = 5},
   {id = "fire axe", chance = 3703},
   {id = "talon", chance = 3571},
   {id = "orb", chance = 2854},
   {id = "giant sword", chance = 2000},
   {id = "golden sickle", chance = 1428},
   {id = "stealth ring", chance = 1333},
   {id = "devil helmet", chance = 1204},
   {id = "purple tome", chance = 1190},
   {id = "gold ring", chance = 1010},
   {id = "platinum amulet", chance = 813},
   {id = "ice rapier", chance = 666},
   {id = "demon shield", chance = 649},
   {id = "ring of healing", chance = 473},
   {id = "demon horn", chance = 467},
   {id = "golden legs", chance = 413},
   {id = "mastermind shield", chance = 389},
   {id = "might ring", chance = 170},
   {id = "demon trophy", chance = 100},
   {id = "demonrage sword", chance = 80},
   {id = "magic plate armor", chance = 70}
}

monster.attacks = {
   {name = "melee", attack = 130, skill = 70, effect = CONST_ME_DRAWBLOOD, interval = 2*1000},
   {name = "energy strike", range = 1, chance = 10, interval = 2*1000, minDamage = -210, maxDamage = -300, target = true},
   {name = "combat", type = COMBAT_MANADRAIN, chance = 10, interval = 2*1000, minDamage = 0, maxDamage = -120, target = true, range = 7, effect = CONST_ME_MAGIC_BLUE},
   {name = "combat", type = COMBAT_FIREDAMAGE, chance = 20, interval = 2*1000, minDamage = -150, maxDamage = -250, radius = 1, target = true, effect = CONST_ME_FIREAREA, shootEffect = CONST_ANI_FIRE},
   {name = "speed", chance = 15, interval = 2*1000, speed = -700, radius = 1, target = true, duration = 30*1000, effect = CONST_ME_MAGIC_RED},
   {name = "firefield", chance = 10, interval = 2*1000, range = 7, radius = 1, target = true, shootEffect = CONST_ANI_FIRE},
   {name = "combat", type = COMBAT_LIFEDRAIN, chance = 10, interval = 2*1000, length = 8, spread = 0, minDamage = -300, maxDamage = -490, effect = CONST_ME_PURPLEENERGY}
}

monster.defenses = {
   defense = 55,
   armor = 55,
   {name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2*1000, minDamage = 180, maxDamage = 250, effect = CONST_ME_MAGIC_BLUE},
   {name = "speed", chance = 15, interval = 2*1000, speed = 320, effect = CONST_ME_MAGIC_RED}
}

monster.elements = {
   {type = COMBAT_PHYSICALDAMAGE, percent = 30},
   {type = COMBAT_DEATHDAMAGE, percent = 30},
   {type = COMBAT_ENERGYDAMAGE, percent = 50},
   {type = COMBAT_EARTHDAMAGE, percent = 40},
   {type = COMBAT_ICEDAMAGE, percent = -10},
   {type = COMBAT_HOLYDAMAGE, percent = -10}
}

monster.immunities = {
   {type = "fire", combat = true, condition = true},
   {type = "drown", condition = true},
   {type = "lifedrain", combat = true},
   {type = "paralyze", condition = true},
   {type = "invisible", condition = true}
}

mType:register(monster)

You stil might be thinking "what's so special about it?"
Here's some funny examples.
Code:
monster.outfit = {
  lookType = math.random(35,120)
}
Give your monster everytime you start your server another looktype without doing basicly anything.
Same can be done with basicly everything experience / health and all that stuff, making monsters less predictable.
Here's the link to my old branch containing the changes (it's messy and I havn't worked on it for quite a while) https://github.com/EvilHero90/forgottenserver

However monsters are not the only topic which limit us, basicly everything involved with xml still holds us back and I'd really would love to have some feedback regarding this topic as it's for me one of the most important and depending on the outcome I might work more towards it.
 
i can fully agree with this however for testing purposes xml is usefull..

pull out some lines from the xml files and see if the server runs "bug-fee".
But yes i have seen the limitation of attributes in different sections..

would like to see more of this!
 
yes, xml is annoying. Lets please loose it xD

Part of my actions.xml file, not everything is updated to actionSystem, but over 50% of my actions scripts run trough same function.
same with movements.xml over 90% of stepIn/out scripts run trough same function.
Code:
<!-- ROOMS -->
        <!-- ghoul room -->
            <action actionid="2054"                     script="AIDItems.lua"/>                 <!-- rail -->
           
        <!-- shadow room -->
            <action uniqueid="1175"                     script="UIDItems.lua"/>                 <!-- door -->
           
        <!-- ghost room -->
            <action actionid="2031"                     script="AIDItems.lua"/>                 <!-- rail -->
           
        <!-- bandit mountain -->
            <action actionid="2042"                     script="AIDItems.lua"/>                 <!-- vertical Leather Stand -->
            <action actionid="2043"                     script="AIDItems.lua"/>                 <!-- horizontal Leather Stand -->
            <action actionid="2044"                     script="AIDItems.lua"/>                 <!-- horizontal Wolf Fur Stand -->
            <action actionid="2045"                     script="AIDItems.lua"/>                 <!-- vertical Wolf Fur Stand -->
            <action actionid="2046"                     script="AIDItems.lua"/>                 <!-- hand Axe stump -->
            <action actionid="2047"                     script="AIDItems.lua"/>                 <!-- loot chests -->
            <action actionid="2048"                     script="AIDItems.lua"/>                 <!-- loot boxes -->
            <action actionid="2049"                     script="AIDItems.lua"/>                 <!-- loot creates -->
            <action actionid="2051"                     script="AIDItems.lua"/>                 <!-- loot creates -->
            <action itemid="3871"                       script="IDItems.lua"/>                  <!-- stack of logs -->
           
           
       
    <!-- KEYS -->
        <action itemid="2086"       script="keys.lua"/>
        <action itemid="2088"       script="keys.lua"/>
        <action itemid="2091"       script="keys.lua"/>
        <action itemid="2092"       script="keys.lua"/>
        <action itemid="2089"       script="keys.lua"/>
   
    <!-- Teleports -->
        <action itemid="11793"      script="IDItems.lua"/>                  <!-- teleport charge pillar -->
        <action itemid="11795"      script="IDItems.lua"/>                  <!-- teleport charge pillar -->
        <action uniqueid="1070"     script="UIDItems.lua"/>                 <!-- bandit Mountain -->
        <action uniqueid="1071"     script="UIDItems.lua"/>                 <!-- Cyclops Dungeon -->
        <action uniqueid="1122"     script="UIDItems.lua"/>                 <!-- Bandit Mountain Level 2 entrance LEAVE -->

Example how i register my items in Lua
Code:
AIDItems = {
    [AID.herbs.flysh] = {text = {msg = "Too fragile, requires tool to pick it up"}},
    [AID.herbs.shadily_gloomy_shroom] = {text = {msg = "Too shady for you, requires tool to pick it up"}},
    [AID.rooms.ghoulRoom.rail] = {funcSTR = "ghoulRoom_resetFloor8"},
    [AID.loot.BM_randomLoot_crates] = {funcSTR = "randomLoot"},
    [AID.loot.BM_randomLoot_boxes] = {funcSTR = "randomLoot"},
    [AID.loot.BM_randomLoot_chests] = {funcSTR = "randomLoot"},
    [AID.other.BM_handAxeStump] = {
        createItems = {
            {delay = 120*60*1000},
            {itemID = 8786}
        },
        rewardItems = {{itemID = 2380}},
        removeItems = {{itemID = 8786, delay = 120*60*1000}, {}}
    },

If there could be a way to load the events/actions to game with lua that would be epic.
At least for me, because for my server xml's are just double registering. Which is useless.
 
yes, xml is annoying. Lets please loose it xD
At least for me, because for my server xml's are just double registering. Which is useless.
Can't you use something like <action fromid="8838" toid="8845" .... />?
 
Hello folks,

Today I'm here to talk about a certain topic which has been more important than ever since TFS 1.x series came out.
As TFS 1.x keeps evolving and getting better and better with every update, it still keeps slacking behind when it comes to modularity.
Why do I think so? well that's certainly easily explained.
XML as a config for our events and such is restricting us in such a big way you can't even imagine at the first look.
Every developer who has worked on a really custom server before knows exactly what I'm talking about, for those who don't there is a certain issue on github which can explain most of it... https://github.com/otland/forgottenserver/issues/1057

I've been working towards that topic in the past already regarding monsters beeing solely scripted in lua, no XML files needed, drag and drop installation.
Yet you might think, what "real" benefits do we get out of this, if it's just drag and drop installation then it's certainly nothing special...
1) We can customize monsters through lua commands on the fly without the need to reload all monsters.
2) We can even create new monsters while the server is running

Those are just 2 prime examples of what benefits it brings.
Now let me show you how an example script of a monster would look like
Code:
local mType = Game.createMonsterType("Demon")
local monster = {}
monster.description = "a demon"
monster.experience = 6000
monster.outfit = {
  lookType = 35
}

monster.health = 8200
monster.maxHealth = monster.health
monster.race = "fire"
monster.corpse = 5995
monster.speed = 280
monster.maxSummons = 1

monster.changeTarget = {
   interval = 4*1000,
   chance = 20
}

monster.flags = {
   summonable = false,
  attackable = true,
   hostile = true,
  convinceable = false,
   illusionable = false,
   canPushItems = true,
   canPushCreatures = true,
   targetDistance = 1,
   staticAttack = 70
}

monster.summons = {
   {name = "fire elemental", chance = 10, interval = 2*1000}
}

monster.voices = {
   interval = 5000,
   chance = 10,
   {text = "Your soul will be mine!", yell = false},
   {text = "MUHAHAHAHA!", yell = false},
   {text = "CHAMEK ATH UTHUL ARAK!", yell = false},
   {text = "I SMELL FEEEEAAAAAR!", yell = false},
   {text = "Your resistance is futile!", yell = false}
}

monster.loot = {
   {id = "gold coin", chance = 60000, maxCount = 100},
   {id = "gold coin", chance = 60000, maxCount = 99},
   {id = "platinum coin", chance = 100000, maxCount = 6},
   {id = "fire mushroom", chance = 20740, maxCount = 6},
   {id = 8473, chance = 20000, maxCount = 3},
   {id = 7590, chance = 14285, maxCount = 3},
   {id = "double axe", chance = 14285},
   {id = "small emerald", chance = 10000},
   {id = "assassin star", chance = 5263, maxCount = 5},
   {id = "fire axe", chance = 3703},
   {id = "talon", chance = 3571},
   {id = "orb", chance = 2854},
   {id = "giant sword", chance = 2000},
   {id = "golden sickle", chance = 1428},
   {id = "stealth ring", chance = 1333},
   {id = "devil helmet", chance = 1204},
   {id = "purple tome", chance = 1190},
   {id = "gold ring", chance = 1010},
   {id = "platinum amulet", chance = 813},
   {id = "ice rapier", chance = 666},
   {id = "demon shield", chance = 649},
   {id = "ring of healing", chance = 473},
   {id = "demon horn", chance = 467},
   {id = "golden legs", chance = 413},
   {id = "mastermind shield", chance = 389},
   {id = "might ring", chance = 170},
   {id = "demon trophy", chance = 100},
   {id = "demonrage sword", chance = 80},
   {id = "magic plate armor", chance = 70}
}

monster.attacks = {
   {name = "melee", attack = 130, skill = 70, effect = CONST_ME_DRAWBLOOD, interval = 2*1000},
   {name = "energy strike", range = 1, chance = 10, interval = 2*1000, minDamage = -210, maxDamage = -300, target = true},
   {name = "combat", type = COMBAT_MANADRAIN, chance = 10, interval = 2*1000, minDamage = 0, maxDamage = -120, target = true, range = 7, effect = CONST_ME_MAGIC_BLUE},
   {name = "combat", type = COMBAT_FIREDAMAGE, chance = 20, interval = 2*1000, minDamage = -150, maxDamage = -250, radius = 1, target = true, effect = CONST_ME_FIREAREA, shootEffect = CONST_ANI_FIRE},
   {name = "speed", chance = 15, interval = 2*1000, speed = -700, radius = 1, target = true, duration = 30*1000, effect = CONST_ME_MAGIC_RED},
   {name = "firefield", chance = 10, interval = 2*1000, range = 7, radius = 1, target = true, shootEffect = CONST_ANI_FIRE},
   {name = "combat", type = COMBAT_LIFEDRAIN, chance = 10, interval = 2*1000, length = 8, spread = 0, minDamage = -300, maxDamage = -490, effect = CONST_ME_PURPLEENERGY}
}

monster.defenses = {
   defense = 55,
   armor = 55,
   {name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2*1000, minDamage = 180, maxDamage = 250, effect = CONST_ME_MAGIC_BLUE},
   {name = "speed", chance = 15, interval = 2*1000, speed = 320, effect = CONST_ME_MAGIC_RED}
}

monster.elements = {
   {type = COMBAT_PHYSICALDAMAGE, percent = 30},
   {type = COMBAT_DEATHDAMAGE, percent = 30},
   {type = COMBAT_ENERGYDAMAGE, percent = 50},
   {type = COMBAT_EARTHDAMAGE, percent = 40},
   {type = COMBAT_ICEDAMAGE, percent = -10},
   {type = COMBAT_HOLYDAMAGE, percent = -10}
}

monster.immunities = {
   {type = "fire", combat = true, condition = true},
   {type = "drown", condition = true},
   {type = "lifedrain", combat = true},
   {type = "paralyze", condition = true},
   {type = "invisible", condition = true}
}

mType:register(monster)

You stil might be thinking "what's so special about it?"
Here's some funny examples.
Code:
monster.outfit = {
  lookType = math.random(35,120)
}
Give your monster everytime you start your server another looktype without doing basicly anything.
Same can be done with basicly everything experience / health and all that stuff, making monsters less predictable.
Here's the link to my old branch containing the changes (it's messy and I havn't worked on it for quite a while) https://github.com/EvilHero90/forgottenserver

However monsters are not the only topic which limit us, basicly everything involved with xml still holds us back and I'd really would love to have some feedback regarding this topic as it's for me one of the most important and depending on the outcome I might work more towards it.
Everything you just provided can be done now even in its present state using a script.. however monsters can't be edited ingame, because they are loaded into memory as the server starts up..

So xml or lua wouldn't make a big difference, TFS needs a complete re-write on how it handles/references it resources.
 
Can't you use something like <action fromid="8838" toid="8845" .... />?
I rarely use itemID as script executor.
If tehre would be way to do:
fromAID=2000 toAID=3000
I would use it xD
 
There is. Always.
https://github.com/otland/forgottenserver/blob/master/src/actions.cpp#L168-L173

@Topic
Bad idea, slow and would not be changeable anytime without reloading anyway, unless you get rid of all the pointers in classes and make them index LUA which is also slow af and a bad idea.
I don't believe its slow.
I have build this to make a remake of realots 7.7 action engine.

the actions.lua:
http://pastebin.com/5gS36XeD

the lib file that makes everything works:
http://pastebin.com/YDUx9TX0

and the event handler in events/player.lua:

Code:
function Player:onUseItem(...)
    return ActionSystem:onUseEventCallback(self, ...)
end

remember, its a prototype. It give us a lot of powe as i have show in action.lua,we can register actions based on a certain item property and even more. XML is bad.
 
I don't believe its slow.
I have build this to make a remake of realots 7.7 action engine.

the actions.lua:
http://pastebin.com/5gS36XeD

the lib file that makes everything works:
http://pastebin.com/YDUx9TX0

and the event handler in events/player.lua:

Code:
function Player:onUseItem(...)
    return ActionSystem:onUseEventCallback(self, ...)
end

remember, its a prototype. It give us a lot of powe as i have show in action.lua,we can register actions based on a certain item property and even more. XML is bad.
Do you really think that this is faster than C++ handling of events loaded on the startup from the XML's?

Registering events with lua however as Mark said in that github issue is a good idea, but getting rid of XML is not a good idea.
 
Do you really think that this is faster than C++ handling of events loaded on the startup from the XML's?

Registering events with lua however as Mark said in that github issue is a good idea, but getting rid of XML is not a good idea.
No it would be never faster than C++, but it give us the power that C++ is not giving us. It makes us able to design our action system the way we want and even more cleaner than the current XML + C++ implementation.

@Mark
 
Are you kidding me!? How did I miss that when I first tried to do it.
Now I need reorganize my AID and UID table into events too.
I will wait on that a month or so, incase there is any progress towards loosing the xml

You think too much in linear terms :(
Well that would have been easier and faster to implement imo (and well seems it already was)

----------------
I approve @Yamaken solution, because it looks way too similar to mine xD (expect that I still need to use .xml for server to recognize that it should activate an actionSystem on this object)

same executActionSystem is applied to everything: aid items, uid items, onStepIn, onStepOut, etc
Code:
actionSystem = {}
actionSystem.__index = actionSystem

setmetatable(
    actionSystem, {
        __call = function(class, ...) return class(...) end,
    }
)

function actionSystem.new()
    return setmetatable({}, actionSystem)
end

function onUse(player, item, fromPosition, itemEx, toPosition, isHotkey)
local itemID = item:getId()
local t = IDitems[itemID]

    if t then return executeActionSystem(player, item, t, itemEx) end
end

No it would be never faster than C++, but it give us the power that C++ is not giving us. It makes us able to design our action system the way we want and even more cleaner than the current XML + C++ implementation.
I wouldn't mind loosing a bit server speed, when in return I can create and maintain game mechanics more easily and faster.

That speed issue can be solved with better CPU on host I assume?
 
There is. Always.
https://github.com/otland/forgottenserver/blob/master/src/actions.cpp#L168-L173

@Topic
Bad idea, slow and would not be changeable anytime without reloading anyway, unless you get rid of all the pointers in classes and make them index LUA which is also slow af and a bad idea.
There is no loss in speed, I still load everything through c on startup, I just erased xml as the bridge and instead load it directly through the lua files. This way I could decrease aprox 100-200 lines in c to get rid of xml handling and added a converter in lua which equals to around 100 lines.
Even tho if we did that with all events and such, it would just mean that your server in the end would take 1 or maybe 2 more seconds to load at startup and that basicly was it, therefore we are given a way more structured and modular environment to script in.
With only those changes in monsters I've showed I could basicly make a pet system in less then 200 lines which would require normaly to make 1000s of different monster xml files and all that, so don't get me wrong but the power difference between the 2 is quite obvious.
 
You can find my opinion on this matter here: https://github.com/otland/forgottenserver/pull/1296#issuecomment-94258993, I don't mind enabling such functionality through Lua, but we're not going to remove XML in TFS 1.x.
I am curious as to why every distro you keep putting out the same concepts as the previous, although tfs looks completely re-written internally you never include any of the more popular concepts which a vast majority of servers or just people in general implement on their own.

We use to see this in pre-1.x distros, but now its just this somewhat of an empty shell of tibia... isn't it time to break away from tibia itself given the new tools and technologies which are available to us today and form a new game?

On a final note can we get some proper documentation on the server functionality, interfaces, functions etc..

I started writing a tutorial on the interface matter because the wiki doesn't contain any relevant information for a new developer.. but is this really my job?

Surely staff or someone on your team has a weekend to spare.. right?
 
Last edited:
Hi, tryied to make my server load lua monsters aswell

getting this error


[100%] Linking CXX executable tfs
CMakeFiles/tfs.dir/src/otserv.cpp.o: In function `mainLoader(int, char**, ServiceManager*)':
otserv.cpp:(.text+0xad6): undefined reference to `loadLuaFilesInDirectory(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
collect2: error: ld returned 1 exit status
CMakeFiles/tfs.dir/build.make:2166: recipe for target 'tfs' failed
make[2]: *** [tfs] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/tfs.dir/all' failed
make[1]: *** [CMakeFiles/tfs.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2


do i need to define loadLuaFilesInDirectory in anymore file than tools.h? or did i just fuck something big up and should try from beginning again? :D
 
Hi, tryied to make my server load lua monsters aswell

getting this error


[100%] Linking CXX executable tfs
CMakeFiles/tfs.dir/src/otserv.cpp.o: In function `mainLoader(int, char**, ServiceManager*)':
otserv.cpp:(.text+0xad6): undefined reference to `loadLuaFilesInDirectory(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
collect2: error: ld returned 1 exit status
CMakeFiles/tfs.dir/build.make:2166: recipe for target 'tfs' failed
make[2]: *** [tfs] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/tfs.dir/all' failed
make[1]: *** [CMakeFiles/tfs.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2


do i need to define loadLuaFilesInDirectory in anymore file than tools.h? or did i just fuck something big up and should try from beginning again? :D
forward me your skype in a pm, will help you there then.
 
@Evil Hero ofc following this logic we could get rid of SQL and use lua tables as db instead, since sql is limiting us to do funny things like
Code:
Account = { [name] = "str" ... }
if os.time() == 123456789 then
Account = { [name] = "rts" ... }
 
Back
Top