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

Feature [TFS 1.2] Load Vocations in Lua or XML (Optional from Config)

592160

Banned User
Joined
Oct 20, 2016
Messages
158
Reaction score
44
I don't know about you but I don't really care for loading content from xml.

Find this in vocation.cpp
Code:
#include "tools.h"
Place this below it.
Code:
#include "configmanager.h"
#include "luascript.h"
extern LuaEnvironment g_lua;
extern ConfigManager g_config;

Now look for this.
Code:
bool Vocations::loadFromXml()
Now place this above it.
Code:
static struct v {
    const char* name;
    int type;
}vocation[] = {
    { "id", LUA_TNUMBER },
    { "clientid", LUA_TNUMBER },
    { "name", LUA_TSTRING },
    { "description", LUA_TSTRING },
    { "gaincap", LUA_TNUMBER },
    { "gainhp", LUA_TNUMBER },
    { "gainmana", LUA_TNUMBER },
    { "gainhpticks", LUA_TNUMBER },
    { "gainhpamount", LUA_TNUMBER },
    { "gainmanaticks", LUA_TNUMBER },
    { "gainmanaamount", LUA_TNUMBER },
    { "manamultiplier", LUA_TNUMBER },
    { "attackspeed", LUA_TNUMBER },
    { "basespeed", LUA_TNUMBER },
    { "soulmax", LUA_TNUMBER },
    { "gainsoulticks", LUA_TNUMBER },
    { "fromvoc", LUA_TNUMBER },
    { "formula", LUA_TTABLE },
    { "skill", LUA_TTABLE },
    { NULL, 0 }
}, formula[] = {
    { "meleeDamage", LUA_TNUMBER },
    { "distDamage", LUA_TNUMBER },
    { "defense", LUA_TNUMBER },
    { "armor", LUA_TNUMBER },
    { NULL, 0 }
}, skill[] = {
    { "SKILL_FIST", LUA_TNUMBER },
    { "SKILL_CLUB", LUA_TNUMBER },
    { "SKILL_SWORD", LUA_TNUMBER },
    { "SKILL_AXE", LUA_TNUMBER },
    { "SKILL_DISTANCE", LUA_TNUMBER },
    { "SKILL_SHIELD", LUA_TNUMBER },
    { "SKILL_FISHING", LUA_TNUMBER },
    { NULL, 0 }
};

Next find this and make a backup copy of the entire method in block comments /* block comment */
Code:
bool Vocations::loadFromXml()
Replace it with this.
Code:
bool Vocations::loadFromXml()
{
    // this will give the option to load vocations as xml or lua
    bool useXML = g_config.getBoolean(ConfigManager::USE_XML);
    if (!useXML) {
        lua_State *L = luaL_newstate();
        std::string str;
        uint16_t id;

        if (!L) {
            throw std::runtime_error("Failed to allocate memory in vocations");
        }

        luaL_openlibs(L);

        if (luaL_dofile(L, "data/LUA/vocations.lua")) {
            std::cout << "[Error - Vocations] " << lua_tostring(L, -1) << std::endl;
            lua_close(L);
            return false;
        }

        lua_getglobal(L, "vocations");

        for (int i = 1; ; i++) {
            lua_rawgeti(L, -1, i);
            if (lua_isnil(L, -1)) {
                lua_pop(L, 1);
                break;
            }
            luaL_checktype(L, -1, LUA_TTABLE);
            id = (uint16_t)i;
            auto res = vocationsMap.emplace(std::piecewise_construct,
                std::forward_as_tuple(id), std::forward_as_tuple(id));
            Vocation& voc = res.first->second;
            for (int fields = 0; vocation[fields].name != NULL; fields++) {
                lua_getfield(L, -1, vocation[fields].name);
                luaL_checktype(L, -1, vocation[fields].type);
                str = vocation[fields].name;
                if (lua_isnumber(L, -1)) {
                    if (str == "id") {
                        //id = g_lua.getNumber<uint16_t>(L, -1);
                    }
                    else if (str == "clientid") {
                        voc.clientId = g_lua.getNumber<uint16_t>(L, -1);
                    }
                    else if (str == "gaincap") {
                        voc.gainCap = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "gainhp") {
                        voc.gainHP = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "gainmana") {
                        voc.gainMana = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "gainhpticks") {
                        voc.gainHealthTicks = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "gainhpamount") {
                        voc.gainHealthAmount = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "gainmanaticks") {
                        voc.gainManaTicks = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "gainmanaamount") {
                        voc.gainManaAmount = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "manamultiplier") {
                        voc.manaMultiplier = g_lua.getNumber<float>(L, -1);
                    }
                    else if (str == "attackspeed") {
                        voc.attackSpeed = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "basespeed") {
                        voc.baseSpeed = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "soulmax") {
                        voc.soulMax = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "gainsoulticks") {
                        voc.gainSoulTicks = g_lua.getNumber<uint32_t>(L, -1);
                    }
                    else if (str == "fromvoc") {
                        voc.fromVocation = g_lua.getNumber<uint32_t>(L, -1);
                    }
                }

                if (lua_isstring(L, -1)) {
                    if (str == "name") {
                        voc.name = lua_tostring(L, -1);
                    }
                    else if (str == "description") {
                        voc.description = lua_tostring(L, -1);
                    }
                }
                if (lua_istable(L, -1)) {
                    if (str == "formula") {
                        std::string formstr;
                        for (int n = 0; formula[n].name != NULL; n++) {
                            lua_getfield(L, -1, formula[n].name);
                            luaL_checktype(L, -1, formula[n].type);
                            formstr = formula[n].name;
                            switch (lua_type(L, -1)) {
                            case LUA_TNUMBER:
                                if (formstr == "meleeDamage") {
                                    voc.meleeDamageMultiplier = g_lua.getNumber<float>(L, -1);
                                }
                                else if (formstr == "distDamage") {
                                    voc.distDamageMultiplier = g_lua.getNumber<float>(L, -1);
                                }
                                else if (formstr == "defense") {
                                    voc.defenseMultiplier = g_lua.getNumber<float>(L, -1);
                                }
                                else if (formstr == "armor") {
                                    voc.armorMultiplier = g_lua.getNumber<float>(L, -1);
                                }
                                break;
                            }
                            lua_pop(L, 1);
                        }
                    }
                    else if (str == "skill") {
                        std::string skillstr;
                        for (int x = 0; skill[x].name != NULL; x++) {
                            lua_getfield(L, -1, skill[x].name);
                            luaL_checktype(L, -1, skill[x].type);
                            skillstr = skill[x].name;
                            switch (lua_type(L, -1)) {
                            case LUA_TNUMBER:
                                if (skillstr == "SKILL_FIST") {
                                    voc.skillMultipliers[SKILL_FIST] = g_lua.getNumber<float>(L, -1);
                                }
                                else if (skillstr == "SKILL_CLUB") {
                                    voc.skillMultipliers[SKILL_CLUB] = g_lua.getNumber<float>(L, -1);
                                }
                                else if (skillstr == "SKILL_SWORD") {
                                    voc.skillMultipliers[SKILL_SWORD] = g_lua.getNumber<float>(L, -1);
                                }
                                else if (skillstr == "SKILL_AXE") {
                                    voc.skillMultipliers[SKILL_AXE] = g_lua.getNumber<float>(L, -1);
                                }
                                else if (skillstr == "SKILL_DISTANCE") {
                                    voc.skillMultipliers[SKILL_DISTANCE] = g_lua.getNumber<float>(L, -1);
                                }
                                else if (skillstr == "SKILL_SHIELD") {
                                    voc.skillMultipliers[SKILL_SHIELD] = g_lua.getNumber<float>(L, -1);
                                }
                                else if (skillstr == "SKILL_FISHING") {
                                    voc.skillMultipliers[SKILL_FISHING] = g_lua.getNumber<float>(L, -1);
                                }
                                break;
                            }
                            lua_pop(L, 1);
                        }
                    }
                }
                lua_pop(L, 1);
            }
            lua_pop(L, 1);
        }
        lua_close(L);
        return true;
    }
    else {
        pugi::xml_document doc;
        pugi::xml_parse_result result = doc.load_file("data/XML/vocations.xml");
        if (!result) {
            printXMLError("Error - Vocations::loadFromXml", "data/XML/vocations.xml", result);
            return false;
        }

        for (auto vocationNode : doc.child("vocations").children()) {
            pugi::xml_attribute attr;
            if (!(attr = vocationNode.attribute("id"))) {
                std::cout << "[Warning - Vocations::loadFromXml] Missing vocation id" << std::endl;
                continue;
            }


            uint16_t id = pugi::cast<uint16_t>(attr.value());

                auto res = vocationsMap.emplace(std::piecewise_construct,
                    std::forward_as_tuple(id), std::forward_as_tuple(id));
            Vocation& voc = res.first->second;


            if ((attr = vocationNode.attribute("name"))) {
                voc.name = attr.as_string();
            }

            if ((attr = vocationNode.attribute("clientid"))) {
                voc.clientId = pugi::cast<uint16_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("description"))) {
                voc.description = attr.as_string();
            }

            if ((attr = vocationNode.attribute("gaincap"))) {
                voc.gainCap = pugi::cast<uint32_t>(attr.value()) * 100;
            }

            if ((attr = vocationNode.attribute("gainhp"))) {
                voc.gainHP = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("gainmana"))) {
                voc.gainMana = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("gainhpticks"))) {
                voc.gainHealthTicks = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("gainhpamount"))) {
                voc.gainHealthAmount = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("gainmanaticks"))) {
                voc.gainManaTicks = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("gainmanaamount"))) {
                voc.gainManaAmount = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("manamultiplier"))) {
                voc.manaMultiplier = pugi::cast<float>(attr.value());
            }

            if ((attr = vocationNode.attribute("attackspeed"))) {
                voc.attackSpeed = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("basespeed"))) {
                voc.baseSpeed = pugi::cast<uint32_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("soulmax"))) {
                voc.soulMax = pugi::cast<uint16_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("gainsoulticks"))) {
                voc.gainSoulTicks = pugi::cast<uint16_t>(attr.value());
            }

            if ((attr = vocationNode.attribute("fromvoc"))) {
                voc.fromVocation = pugi::cast<uint32_t>(attr.value());
            }


            for (auto childNode : vocationNode.children()) {
                if (strcasecmp(childNode.name(), "skill") == 0) {
                    pugi::xml_attribute skillIdAttribute = childNode.attribute("id");
                    if (skillIdAttribute) {
                        uint16_t skill_id = pugi::cast<uint16_t>(skillIdAttribute.value());
                        if (skill_id <= SKILL_LAST) {
                            voc.skillMultipliers[skill_id] = pugi::cast<float>(childNode.attribute("multiplier").value());
                        }
                        else {
                            std::cout << "[Notice - Vocations::loadFromXml] No valid skill id: " << skill_id << " for vocation: " << voc.id << std::endl;
                        }
                    }
                    else {
                        std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id << std::endl;
                    }
                }
                else if (strcasecmp(childNode.name(), "formula") == 0) {
                    pugi::xml_attribute meleeDamageAttribute = childNode.attribute("meleeDamage");
                    if (meleeDamageAttribute) {
                        voc.meleeDamageMultiplier = pugi::cast<float>(meleeDamageAttribute.value());
                    }

                    pugi::xml_attribute distDamageAttribute = childNode.attribute("distDamage");
                    if (distDamageAttribute) {
                        voc.distDamageMultiplier = pugi::cast<float>(distDamageAttribute.value());
                    }

                    pugi::xml_attribute defenseAttribute = childNode.attribute("defense");
                    if (defenseAttribute) {
                        voc.defenseMultiplier = pugi::cast<float>(defenseAttribute.value());
                    }

                    pugi::xml_attribute armorAttribute = childNode.attribute("armor");
                    if (armorAttribute) {
                        voc.armorMultiplier = pugi::cast<float>(armorAttribute.value());
                    }
                }
            }
        }
        return true;
    }
}

In configmanager.cpp look for this
Code:
boolean[CLASSIC_EQUIPMENT_SLOTS] = getGlobalBoolean(L, "classicEquipmentSlots", false);
Place this right underneath
Code:
boolean[USE_XML] = getGlobalBoolean(L, "useXML", false);

Find this in configmanager.h
Code:
CLASSIC_EQUIPMENT_SLOTS,
Put this underneath.
Code:
USE_XML,

In luascript.cpp find this
Code:
registerEnumIn("configKeys", ConfigManager::CLASSIC_EQUIPMENT_SLOTS)
Add this below it.
Code:
registerEnumIn("configKeys", ConfigManager::USE_XML)

Place this anywhere in config.lua
Code:
useXML = false

Now create a new folder in data called LUA and save this as vocations.lua
Code:
local classes = {
    ['None'] = {
        hp = 5, mana = 0, desc = "none.",
        clientid = 0,
        gaincap = 0,
        gainhpticks = 5,
        gainhpamount = 5,
        gainmanaticks = 5,
        gainmanaamount = 5,
        manamultiplier = 5,
        attackspeed = 2000,
        basespeed = 220,
        soulmax = 100,
        gainsoulticks = 5,
        fromvoc = 0,
        formula = {
            meleeDamage = 1.0,
            distDamage = 1.0,
            defense = 1.0,
            armor = 1.0
        },
        skill = {
            SKILL_FIST = 1.1,
            SKILL_CLUB = 1.1,
            SKILL_SWORD = 1.1,
            SKILL_AXE = 1.1,
            SKILL_DISTANCE = 1.1,
            SKILL_SHIELD = 1.1,
            SKILL_FISHING = 1.1,
        }
    }
}
vocationsTable = {}
local i = 1
for vocationName, attributes in pairs(classes) do
    vocationsTable[i] = {
        id = i,
        name = vocationName,
        clientid = attributes.clientid,
        description = attributes.desc,
        gaincap = attributes.gaincap,
        gainhp = attributes.hp,
        gainmana = attributes.mana,
        gainhpticks = attributes.gainhpticks,
        gainhpamount = attributes.gainhpamount,
        gainmanaticks = attributes.gainmanaticks,
        gainmanaamount = attributes.gainmanaamount,
        manamultiplier = attributes.manamultiplier,
        attackspeed = attributes.attackspeed,
        basespeed = attributes.basespeed,
        soulmax = attributes.soulmax,
        gainsoulticks = attributes.gainsoulticks,
        fromvoc = attributes.fromvoc,
   
        formula = {
            meleeDamage = attributes.formula.meleeDamage,
            distDamage = attributes.formula.distDamage,
            defense = attributes.formula.defense,
            armor = attributes.formula.armor
        },
        skill = {
            SKILL_FIST = attributes.skill.SKILL_FIST,
            SKILL_CLUB = attributes.skill.SKILL_CLUB,
            SKILL_SWORD = attributes.skill.SKILL_SWORD,
            SKILL_AXE = attributes.skill.SKILL_AXE,
            SKILL_DISTANCE = attributes.skill.SKILL_DISTANCE,
            SKILL_SHIELD = attributes.skill.SKILL_SHIELD,
            SKILL_FISHING = attributes.skill.SKILL_FISHING,
        }
    }
    i = i + 1
end

vocations = vocationsTable

In otserv.cpp look for this.
Code:
//load vocations

std::cout << ">> Loading vocations" << std::endl;

if (!g_vocations.loadFromXml()) {

startupErrorMessage("Unable to load vocations!");

return;

}
Replace it with this
Code:
    //load vocations
   bool useXML = g_config.getBoolean(ConfigManager::USE_XML);
   std::string from = (useXML ? "from xml" : "from lua");
   std::cout << ">> Loading vocations " << from << std::endl;
   if (!g_vocations.loadFromXml()) {
       if (useXML) {
           startupErrorMessage("Unable to load vocations.xml!");
       }
       else {
           startupErrorMessage("Unable to load vocations.lua!");
       }
       return;
   }
 
Last edited:
I did the same thing for everything else which is read from xml, Monsters, Items, Spells etc.. but the implementation is too complex to post here on the forums.
 
Don't worry, it's not like OT users want to make their servers more complex than these are.
 
Don't worry, it's not like OT users want to make their servers more complex than these are.
I don't know if this statement is going against or for the code I posted (it sounds like its going against the code), loading these files as lua rather than xml simplifies things for a developer, because xml is static where as lua is not.

I am sure this can be improved upon after all some of the first bits of code ever produced on these forums from you seasoned professionals were redundant and bulky. But over time we all have learned ways to optimize our code.
 
I personally wouldn't care for choosing between XML and Lua. Either I apply this patch and fuck XML or I don't even care for it.

Lua is much better as you could have functions instead of plain values, e.g. if you want a player to get 10 extra cap every 5 levels you could use
Code:
  gaincap = function(level)
    if math.mod(level, 5) then
      return 5
    end
    return 10
  end,

Nice feature!
 
I personally wouldn't care for choosing between XML and Lua. Either I apply this patch and fuck XML or I don't even care for it.

Lua is much better as you could have functions instead of plain values, e.g. if you want a player to get 10 extra cap every 5 levels you could use
Code:
  gaincap = function(level)
    if math.mod(level, 5) then
      return 5
    end
    return 10
  end,

Nice feature!
Well there is a reason for this, not in the case of vocations but for items, I wrote code in the sources which builds the items.lua file from items.xml, same thing with monsters, which if done manually would take eons :p
 
maybe u can fix in our post too, one code improvation for reload vocations.lua.
anyway nice feature!
 
Back
Top