diff --git a/data/cpplinter.lua b/data/cpplinter.lua index 17624824de..01efc54f71 100644 --- a/data/cpplinter.lua +++ b/data/cpplinter.lua @@ -1986,6 +1986,7 @@ RELOAD_TYPE_SCRIPTS = 14 RELOAD_TYPE_SPELLS = 15 RELOAD_TYPE_TALKACTIONS = 16 RELOAD_TYPE_WEAPONS = 17 +RELOAD_TYPE_VOCATIONS = 18 PlayerFlag_CannotUseCombat = 1 * 2 ^ 0 PlayerFlag_CannotAttackPlayer = 1 * 2 ^ 1 diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua index 35c8cabafa..9786de13c6 100644 --- a/data/talkactions/scripts/reload.lua +++ b/data/talkactions/scripts/reload.lua @@ -49,6 +49,9 @@ local reloadTypes = { ["weapon"] = RELOAD_TYPE_WEAPONS, ["weapons"] = RELOAD_TYPE_WEAPONS, + ["vocation"] = RELOAD_TYPE_VOCATIONS, + ["vocations"] = RELOAD_TYPE_VOCATIONS, + ["scripts"] = RELOAD_TYPE_SCRIPTS, ["libs"] = RELOAD_TYPE_GLOBAL } diff --git a/src/combat.cpp b/src/combat.cpp index 0f8e326270..aecded696b 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -276,7 +276,7 @@ bool Combat::isProtected(const Player* attacker, const Player* target) return true; } - if (!attacker->getVocation()->allowsPvp() || !target->getVocation()->allowsPvp()) { + if (!attacker->getVocation()->allowPvp || !target->getVocation()->allowPvp) { return true; } diff --git a/src/const.h b/src/const.h index c7dc5cfc55..19fc8812e8 100644 --- a/src/const.h +++ b/src/const.h @@ -681,6 +681,7 @@ enum ReloadTypes_t : uint8_t RELOAD_TYPE_SPELLS, RELOAD_TYPE_TALKACTIONS, RELOAD_TYPE_WEAPONS, + RELOAD_TYPE_VOCATIONS, }; enum MonsterIcon_t : uint8_t diff --git a/src/game.cpp b/src/game.cpp index 7b10f38afa..8c3919de01 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -35,11 +35,12 @@ #include "talkaction.h" #include "weapons.h" +#include + extern Actions* g_actions; extern Chat* g_chat; extern TalkActions* g_talkActions; extern Spells* g_spells; -extern Vocations g_vocations; extern GlobalEvents* g_globalEvents; extern CreatureEvents* g_creatureEvents; extern Events* g_events; @@ -5848,6 +5849,18 @@ bool Game::reload(ReloadTypes_t reloadType) return results; } + case RELOAD_TYPE_VOCATIONS: { + if (std::ifstream is{"data/XML/vocations.xml"}; + tfs::game::vocations::load_from_xml(is, "data/XML/vocations.xml", true)) { + // Reload players vocations + for (const auto& [_, player] : players) { + player->updateVocation(); + } + return true; + } + return false; + } + case RELOAD_TYPE_SCRIPTS: { // commented out stuff is TODO, once we approach further in revscriptsys g_actions->clear(true); diff --git a/src/http/login.cpp b/src/http/login.cpp index bd22458930..8797508707 100644 --- a/src/http/login.cpp +++ b/src/http/login.cpp @@ -9,7 +9,6 @@ #include extern Game g_game; -extern Vocations g_vocations; namespace beast = boost::beast; namespace json = boost::json; @@ -99,14 +98,14 @@ std::pair tfs::http::handle_login(const json::object& body, uint32_t lastLogin = 0; if (result) { do { - auto vocation = g_vocations.getVocation(result->getNumber("vocation")); + auto vocation = tfs::game::vocations::find_by_id(result->getNumber("vocation")); assert(vocation); characters.push_back({ {"worldid", 0}, // not implemented {"name", result->getString("name")}, {"level", result->getNumber("level")}, - {"vocation", vocation->getVocName()}, + {"vocation", vocation->name}, {"lastlogin", result->getNumber("lastlogin")}, {"ismale", result->getNumber("sex") == PLAYERSEX_MALE}, {"ishidden", false}, // not implemented diff --git a/src/http/tests/test_login.cpp b/src/http/tests/test_login.cpp index b35c00e8d1..b250de1445 100644 --- a/src/http/tests/test_login.cpp +++ b/src/http/tests/test_login.cpp @@ -10,8 +10,6 @@ #include -extern Vocations g_vocations; - auto vocationsXml = []() { return std::istringstream{R"( @@ -127,7 +125,7 @@ struct LoginFixture setNumber(ConfigManager::SQL_PORT, 3306); auto is = vocationsXml(); - g_vocations.loadFromXml(is, ":memory:"); + tfs::game::vocations::load_from_xml(is, ":memory:"); db.connect(); transaction.begin(); diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 4878b1ae01..d53e49abe8 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -204,11 +204,13 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) condition = Condition::createCondition(propStream); } - if (!player->setVocation(result->getNumber("vocation"))) { + auto vocation = tfs::game::vocations::find_by_id(result->getNumber("vocation")); + if (!vocation) { std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Vocation ID " << result->getNumber("vocation") << " which doesn't exist" << std::endl; return false; } + player->setVocation(vocation); player->mana = result->getNumber("mana"); player->manaMax = result->getNumber("manamax"); @@ -648,7 +650,7 @@ bool IOLoginData::savePlayer(Player* player) query << "UPDATE `players` SET "; query << "`level` = " << player->level << ','; query << "`group_id` = " << player->group->id << ','; - query << "`vocation` = " << player->getVocationId() << ','; + query << "`vocation` = " << player->getVocation()->id << ','; query << "`health` = " << player->health << ','; query << "`healthmax` = " << player->healthMax << ','; query << "`experience` = " << player->experience << ','; diff --git a/src/item.cpp b/src/item.cpp index 995f3cd5d5..e4b8e508fc 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -19,7 +19,6 @@ class Spells; extern Game g_game; extern Spells* g_spells; -extern Vocations g_vocations; Items Item::items; diff --git a/src/luascript.cpp b/src/luascript.cpp index 24b0d43d69..df3cc69f01 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -43,7 +43,6 @@ extern Chat* g_chat; extern Game g_game; extern GlobalEvents* g_globalEvents; extern Monsters g_monsters; -extern Vocations g_vocations; extern Spells* g_spells; extern Events* g_events; extern Actions* g_actions; @@ -2204,6 +2203,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(L, RELOAD_TYPE_SPELLS); registerEnum(L, RELOAD_TYPE_TALKACTIONS); registerEnum(L, RELOAD_TYPE_WEAPONS); + registerEnum(L, RELOAD_TYPE_VOCATIONS); registerEnum(L, ZONE_PROTECTION); registerEnum(L, ZONE_NOPVP); @@ -4877,12 +4877,12 @@ int LuaScriptInterface::luaGameGetMounts(lua_State* L) int LuaScriptInterface::luaGameGetVocations(lua_State* L) { // Game.getVocations() - const auto& vocations = g_vocations.getVocations(); + const auto& vocations = tfs::game::vocations::find_vocations(); lua_createtable(L, vocations.size(), 0); int index = 0; - for (const auto& [id, vocation] : vocations) { - tfs::lua::pushUserdata(L, &vocation); + for (const auto& vocation : vocations) { + pushSharedPtr(L, vocation); tfs::lua::setMetatable(L, -1, "Vocation"); lua_rawseti(L, -2, ++index); } @@ -9691,7 +9691,7 @@ int LuaScriptInterface::luaPlayerGetVocation(lua_State* L) // player:getVocation() Player* player = tfs::lua::getUserdata(L, 1); if (player) { - tfs::lua::pushUserdata(L, player->getVocation()); + pushSharedPtr(L, player->getVocation()); tfs::lua::setMetatable(L, -1, "Vocation"); } else { lua_pushnil(L); @@ -9708,24 +9708,22 @@ int LuaScriptInterface::luaPlayerSetVocation(lua_State* L) return 1; } - Vocation* vocation; + Vocation_ptr vocation = nullptr; if (isNumber(L, 2)) { - vocation = g_vocations.getVocation(tfs::lua::getNumber(L, 2)); + vocation = tfs::game::vocations::find_by_id(tfs::lua::getNumber(L, 2)); } else if (lua_isstring(L, 2)) { - vocation = g_vocations.getVocation(g_vocations.getVocationId(tfs::lua::getString(L, 2))); + vocation = tfs::game::vocations::find_by_name(tfs::lua::getString(L, 2)); } else if (lua_isuserdata(L, 2)) { - vocation = tfs::lua::getUserdata(L, 2); - } else { - vocation = nullptr; + vocation = getSharedPtr(L, 2); } - if (!vocation) { + if (vocation) { + player->setVocation(vocation); + tfs::lua::pushBoolean(L, true); + } else { + std::cout << "[Warning - Player::setVocation] Wrong vocation: " << tfs::lua::getString(L, 2) << std::endl; tfs::lua::pushBoolean(L, false); - return 1; } - - player->setVocation(vocation->getId()); - tfs::lua::pushBoolean(L, true); return 1; } @@ -9972,7 +9970,7 @@ int LuaScriptInterface::luaPlayerGetMaxSoul(lua_State* L) // player:getMaxSoul() Player* player = tfs::lua::getUserdata(L, 1); if (player && player->vocation) { - lua_pushnumber(L, player->vocation->getSoulMax()); + lua_pushnumber(L, player->vocation->soulMax); } else { lua_pushnil(L); } @@ -10738,7 +10736,7 @@ int LuaScriptInterface::luaPlayerCanLearnSpell(lua_State* L) return 1; } - if (!spell->hasVocationSpellMap(player->getVocationId())) { + if (!spell->hasVocation(player->getVocation())) { tfs::lua::pushBoolean(L, false); } else if (player->getLevel() < spell->getLevel()) { tfs::lua::pushBoolean(L, false); @@ -12480,18 +12478,18 @@ int LuaScriptInterface::luaGroupHasFlag(lua_State* L) int LuaScriptInterface::luaVocationCreate(lua_State* L) { // Vocation(id or name) - uint32_t id; + Vocation_ptr vocation = nullptr; if (isNumber(L, 2)) { - id = tfs::lua::getNumber(L, 2); - } else { - id = g_vocations.getVocationId(tfs::lua::getString(L, 2)); + vocation = tfs::game::vocations::find_by_id(tfs::lua::getNumber(L, 2)); + } else if (lua_isstring(L, 2)) { + vocation = tfs::game::vocations::find_by_name(tfs::lua::getString(L, 2)); } - Vocation* vocation = g_vocations.getVocation(id); if (vocation) { - tfs::lua::pushUserdata(L, vocation); + pushSharedPtr(L, vocation); tfs::lua::setMetatable(L, -1, "Vocation"); } else { + std::cout << "[Warning - Vocation] Wrong vocation: " << tfs::lua::getString(L, 2) << std::endl; lua_pushnil(L); } return 1; @@ -12500,9 +12498,8 @@ int LuaScriptInterface::luaVocationCreate(lua_State* L) int LuaScriptInterface::luaVocationGetId(lua_State* L) { // vocation:getId() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getId()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->id); } else { lua_pushnil(L); } @@ -12512,9 +12509,8 @@ int LuaScriptInterface::luaVocationGetId(lua_State* L) int LuaScriptInterface::luaVocationGetClientId(lua_State* L) { // vocation:getClientId() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getClientId()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->clientId); } else { lua_pushnil(L); } @@ -12524,9 +12520,8 @@ int LuaScriptInterface::luaVocationGetClientId(lua_State* L) int LuaScriptInterface::luaVocationGetName(lua_State* L) { // vocation:getName() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - tfs::lua::pushString(L, vocation->getVocName()); + if (auto vocation = getSharedPtr(L, 1)) { + tfs::lua::pushString(L, vocation->name); } else { lua_pushnil(L); } @@ -12536,9 +12531,8 @@ int LuaScriptInterface::luaVocationGetName(lua_State* L) int LuaScriptInterface::luaVocationGetDescription(lua_State* L) { // vocation:getDescription() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - tfs::lua::pushString(L, vocation->getVocDescription()); + if (auto vocation = getSharedPtr(L, 1)) { + tfs::lua::pushString(L, vocation->description); } else { lua_pushnil(L); } @@ -12548,8 +12542,7 @@ int LuaScriptInterface::luaVocationGetDescription(lua_State* L) int LuaScriptInterface::luaVocationGetRequiredSkillTries(lua_State* L) { // vocation:getRequiredSkillTries(skillType, skillLevel) - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { + if (auto vocation = getSharedPtr(L, 1)) { skills_t skillType = tfs::lua::getNumber(L, 2); uint16_t skillLevel = tfs::lua::getNumber(L, 3); lua_pushnumber(L, vocation->getReqSkillTries(skillType, skillLevel)); @@ -12562,8 +12555,7 @@ int LuaScriptInterface::luaVocationGetRequiredSkillTries(lua_State* L) int LuaScriptInterface::luaVocationGetRequiredManaSpent(lua_State* L) { // vocation:getRequiredManaSpent(magicLevel) - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { + if (auto vocation = getSharedPtr(L, 1)) { uint32_t magicLevel = tfs::lua::getNumber(L, 2); lua_pushnumber(L, vocation->getReqMana(magicLevel)); } else { @@ -12575,9 +12567,8 @@ int LuaScriptInterface::luaVocationGetRequiredManaSpent(lua_State* L) int LuaScriptInterface::luaVocationGetCapacityGain(lua_State* L) { // vocation:getCapacityGain() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getCapGain()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainCap); } else { lua_pushnil(L); } @@ -12587,9 +12578,8 @@ int LuaScriptInterface::luaVocationGetCapacityGain(lua_State* L) int LuaScriptInterface::luaVocationGetHealthGain(lua_State* L) { // vocation:getHealthGain() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getHPGain()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainHP); } else { lua_pushnil(L); } @@ -12599,9 +12589,8 @@ int LuaScriptInterface::luaVocationGetHealthGain(lua_State* L) int LuaScriptInterface::luaVocationGetHealthGainTicks(lua_State* L) { // vocation:getHealthGainTicks() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getHealthGainTicks()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainHealthTicks); } else { lua_pushnil(L); } @@ -12611,9 +12600,8 @@ int LuaScriptInterface::luaVocationGetHealthGainTicks(lua_State* L) int LuaScriptInterface::luaVocationGetHealthGainAmount(lua_State* L) { // vocation:getHealthGainAmount() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getHealthGainAmount()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainHealthAmount); } else { lua_pushnil(L); } @@ -12623,9 +12611,8 @@ int LuaScriptInterface::luaVocationGetHealthGainAmount(lua_State* L) int LuaScriptInterface::luaVocationGetManaGain(lua_State* L) { // vocation:getManaGain() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getManaGain()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainMana); } else { lua_pushnil(L); } @@ -12635,9 +12622,8 @@ int LuaScriptInterface::luaVocationGetManaGain(lua_State* L) int LuaScriptInterface::luaVocationGetManaGainTicks(lua_State* L) { // vocation:getManaGainTicks() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getManaGainTicks()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainManaTicks); } else { lua_pushnil(L); } @@ -12647,9 +12633,8 @@ int LuaScriptInterface::luaVocationGetManaGainTicks(lua_State* L) int LuaScriptInterface::luaVocationGetManaGainAmount(lua_State* L) { // vocation:getManaGainAmount() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getManaGainAmount()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainManaAmount); } else { lua_pushnil(L); } @@ -12659,9 +12644,8 @@ int LuaScriptInterface::luaVocationGetManaGainAmount(lua_State* L) int LuaScriptInterface::luaVocationGetMaxSoul(lua_State* L) { // vocation:getMaxSoul() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getSoulMax()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->soulMax); } else { lua_pushnil(L); } @@ -12671,9 +12655,8 @@ int LuaScriptInterface::luaVocationGetMaxSoul(lua_State* L) int LuaScriptInterface::luaVocationGetSoulGainTicks(lua_State* L) { // vocation:getSoulGainTicks() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getSoulGainTicks()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->gainSoulTicks); } else { lua_pushnil(L); } @@ -12683,9 +12666,8 @@ int LuaScriptInterface::luaVocationGetSoulGainTicks(lua_State* L) int LuaScriptInterface::luaVocationGetAttackSpeed(lua_State* L) { // vocation:getAttackSpeed() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getAttackSpeed()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->attackSpeed); } else { lua_pushnil(L); } @@ -12695,9 +12677,8 @@ int LuaScriptInterface::luaVocationGetAttackSpeed(lua_State* L) int LuaScriptInterface::luaVocationGetBaseSpeed(lua_State* L) { // vocation:getBaseSpeed() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getBaseSpeed()); + if (auto vocation = getSharedPtr(L, 1)) { + lua_pushnumber(L, vocation->baseSpeed); } else { lua_pushnil(L); } @@ -12707,21 +12688,15 @@ int LuaScriptInterface::luaVocationGetBaseSpeed(lua_State* L) int LuaScriptInterface::luaVocationGetDemotion(lua_State* L) { // vocation:getDemotion() - Vocation* vocation = tfs::lua::getUserdata(L, 1); + auto vocation = getSharedPtr(L, 1); if (!vocation) { lua_pushnil(L); return 1; } - uint16_t fromId = vocation->getFromVocation(); - if (fromId == VOCATION_NONE) { - lua_pushnil(L); - return 1; - } - - Vocation* demotedVocation = g_vocations.getVocation(fromId); - if (demotedVocation && demotedVocation != vocation) { - tfs::lua::pushUserdata(L, demotedVocation); + auto demoted_vocation = tfs::game::vocations::find_by_id(vocation->fromVocation); + if (demoted_vocation && !demoted_vocation->isNone() && demoted_vocation != vocation) { + pushSharedPtr(L, demoted_vocation); tfs::lua::setMetatable(L, -1, "Vocation"); } else { lua_pushnil(L); @@ -12732,21 +12707,15 @@ int LuaScriptInterface::luaVocationGetDemotion(lua_State* L) int LuaScriptInterface::luaVocationGetPromotion(lua_State* L) { // vocation:getPromotion() - Vocation* vocation = tfs::lua::getUserdata(L, 1); + auto vocation = getSharedPtr(L, 1); if (!vocation) { lua_pushnil(L); return 1; } - uint16_t promotedId = g_vocations.getPromotedVocation(vocation->getId()); - if (promotedId == VOCATION_NONE) { - lua_pushnil(L); - return 1; - } - - Vocation* promotedVocation = g_vocations.getVocation(promotedId); - if (promotedVocation && promotedVocation != vocation) { - tfs::lua::pushUserdata(L, promotedVocation); + auto promoted_vocation = tfs::game::vocations::find_by_promoted_id(vocation->id); + if (promoted_vocation && !promoted_vocation->isNone() && promoted_vocation != vocation) { + pushSharedPtr(L, promoted_vocation); tfs::lua::setMetatable(L, -1, "Vocation"); } else { lua_pushnil(L); @@ -12757,9 +12726,8 @@ int LuaScriptInterface::luaVocationGetPromotion(lua_State* L) int LuaScriptInterface::luaVocationAllowsPvp(lua_State* L) { // vocation:allowsPvp() - Vocation* vocation = tfs::lua::getUserdata(L, 1); - if (vocation) { - tfs::lua::pushBoolean(L, vocation->allowsPvp()); + if (auto vocation = getSharedPtr(L, 1)) { + tfs::lua::pushBoolean(L, vocation->allowPvp); } else { lua_pushnil(L); } @@ -17145,17 +17113,20 @@ int LuaScriptInterface::luaSpellVocation(lua_State* L) if (lua_gettop(L) == 1) { lua_createtable(L, 0, 0); int i = 0; - for (auto& vocation : spell->getVocationSpellMap()) { - std::string name = g_vocations.getVocation(vocation.first)->getVocName(); - tfs::lua::pushString(L, name); + for (auto [vocation, show_in_description] : spell->getVocations()) { + tfs::lua::pushString(L, vocation->name); lua_rawseti(L, -2, ++i); } } else { int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc for (int i = 0; i < parameters; ++i) { std::string vocStr = tfs::lua::getString(L, 2 + i); - auto vocList = explodeString(vocStr, ";"); - spell->addVocationSpellMap(vocList[0], vocList.size() > 1 ? booleanString(vocList[1]) : false); + auto vocations = explodeString(vocStr, ";"); + if (auto vocation = tfs::game::vocations::find_by_name(vocations[0])) { + spell->addVocation(vocation, vocations.size() > 1 ? booleanString(vocations[1]) : false); + } else { + std::cout << "[Warning - Spell::vocation] Wrong vocation name: " << vocations[0] << std::endl; + } } tfs::lua::pushBoolean(L, true); } @@ -18000,41 +17971,48 @@ int LuaScriptInterface::luaMoveEventPremium(lua_State* L) int LuaScriptInterface::luaMoveEventVocation(lua_State* L) { - // moveevent:vocation(vocName[, showInDescription = false, lastVoc = false]) + // moveevent:vocation(id or name or userdata[, showInDescription = false, lastVoc = false]) MoveEvent* moveevent = tfs::lua::getUserdata(L, 1); - if (moveevent) { - moveevent->addVocationEquipSet(tfs::lua::getString(L, 2)); - moveevent->setWieldInfo(WIELDINFO_VOCREQ); - std::string tmp; - bool showInDescription = false; - bool lastVoc = false; - if (tfs::lua::getBoolean(L, 3)) { - showInDescription = tfs::lua::getBoolean(L, 3); - } - if (tfs::lua::getBoolean(L, 4)) { - lastVoc = tfs::lua::getBoolean(L, 4); - } - if (showInDescription) { - if (moveevent->getVocationString().empty()) { - tmp = boost::algorithm::to_lower_copy(tfs::lua::getString(L, 2)); - tmp += "s"; - moveevent->setVocationString(tmp); - } else { - tmp = moveevent->getVocationString(); - if (lastVoc) { - tmp += " and "; - } else { - tmp += ", "; - } - tmp += boost::algorithm::to_lower_copy(tfs::lua::getString(L, 2)); - tmp += "s"; - moveevent->setVocationString(tmp); - } - } - tfs::lua::pushBoolean(L, true); - } else { + if (!moveevent) { + lua_pushnil(L); + return 1; + } + + Vocation_ptr vocation = nullptr; + if (isNumber(L, 2)) { + vocation = tfs::game::vocations::find_by_id(tfs::lua::getNumber(L, 2)); + } else if (lua_isstring(L, 2)) { + vocation = tfs::game::vocations::find_by_name(tfs::lua::getString(L, 2)); + } else if (lua_isuserdata(L, 2)) { + vocation = getSharedPtr(L, 2); + } + + if (!vocation) { + std::cout << "[Warning - Weapon::vocation] Wrong vocation: " << tfs::lua::getString(L, 2) << std::endl; lua_pushnil(L); + return 1; } + + moveevent->addVocation(vocation); + moveevent->setWieldInfo(WIELDINFO_VOCREQ); + + bool showInDescription = tfs::lua::getBoolean(L, 3, false); + bool lastVoc = tfs::lua::getBoolean(L, 4, false); + + if (showInDescription) { + std::string description = moveevent->getVocationString(); + if (description.empty()) { + description = boost::algorithm::to_lower_copy(vocation->name); + description += "s"; + } else { + description += lastVoc ? " and " : ", "; + description += boost::algorithm::to_lower_copy(vocation->name); + description += "s"; + } + moveevent->setVocationString(description); + } + + tfs::lua::pushBoolean(L, true); return 1; } @@ -18603,36 +18581,48 @@ int LuaScriptInterface::luaWeaponPremium(lua_State* L) int LuaScriptInterface::luaWeaponVocation(lua_State* L) { - // weapon:vocation(vocName[, showInDescription = false, lastVoc = false]) + // weapon:vocation(id or name or userdata[, showInDescription = false, lastVoc = false]) Weapon* weapon = tfs::lua::getUserdata(L, 1); - if (weapon) { - weapon->addVocationWeaponSet(tfs::lua::getString(L, 2)); - weapon->setWieldInfo(WIELDINFO_VOCREQ); - std::string tmp; - bool showInDescription = tfs::lua::getBoolean(L, 3, false); - bool lastVoc = tfs::lua::getBoolean(L, 4, false); - - if (showInDescription) { - if (weapon->getVocationString().empty()) { - tmp = boost::algorithm::to_lower_copy(tfs::lua::getString(L, 2)); - tmp += "s"; - weapon->setVocationString(tmp); - } else { - tmp = weapon->getVocationString(); - if (lastVoc) { - tmp += " and "; - } else { - tmp += ", "; - } - tmp += boost::algorithm::to_lower_copy(tfs::lua::getString(L, 2)); - tmp += "s"; - weapon->setVocationString(tmp); - } - } - tfs::lua::pushBoolean(L, true); - } else { + if (!weapon) { lua_pushnil(L); + return 1; + } + + Vocation_ptr vocation = nullptr; + if (isNumber(L, 2)) { + vocation = tfs::game::vocations::find_by_id(tfs::lua::getNumber(L, 2)); + } else if (lua_isstring(L, 2)) { + vocation = tfs::game::vocations::find_by_name(tfs::lua::getString(L, 2)); + } else if (lua_isuserdata(L, 2)) { + vocation = getSharedPtr(L, 2); } + + if (!vocation) { + std::cout << "[Warning - Weapon::vocation] Wrong vocation: " << tfs::lua::getString(L, 2) << std::endl; + lua_pushnil(L); + return 1; + } + + weapon->addVocation(vocation); + weapon->setWieldInfo(WIELDINFO_VOCREQ); + + bool showInDescription = tfs::lua::getBoolean(L, 3, false); + bool lastVoc = tfs::lua::getBoolean(L, 4, false); + + if (showInDescription) { + std::string description = weapon->getVocationString(); + if (description.empty()) { + description = boost::algorithm::to_lower_copy(vocation->name); + description += "s"; + } else { + description += lastVoc ? " and " : ", "; + description += boost::algorithm::to_lower_copy(vocation->name); + description += "s"; + } + weapon->setVocationString(description); + } + + tfs::lua::pushBoolean(L, true); return 1; } diff --git a/src/movement.cpp b/src/movement.cpp index 55412fac6c..b31f9ba433 100644 --- a/src/movement.cpp +++ b/src/movement.cpp @@ -10,7 +10,6 @@ #include "pugicast.h" extern Game g_game; -extern Vocations g_vocations; MoveEvents::MoveEvents() : scriptInterface("MoveEvents Interface") { scriptInterface.initState(); } @@ -510,13 +509,14 @@ std::string_view MoveEvent::getScriptEventName() const bool MoveEvent::configureEvent(const pugi::xml_node& node) { - pugi::xml_attribute eventAttr = node.attribute("event"); - if (!eventAttr) { + pugi::xml_attribute attr; + + if (!(attr = node.attribute("event"))) { std::cout << "[Error - MoveEvent::configureMoveEvent] Missing event" << std::endl; return false; } - std::string tmpStr = boost::algorithm::to_lower_copy(eventAttr.as_string()); + std::string tmpStr = boost::algorithm::to_lower_copy(attr.as_string()); if (tmpStr == "stepin") { eventType = MOVE_EVENT_STEP_IN; } else if (tmpStr == "stepout") { @@ -530,15 +530,13 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) } else if (tmpStr == "removeitem") { eventType = MOVE_EVENT_REMOVE_ITEM; } else { - std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << eventAttr.as_string() - << std::endl; + std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << attr.as_string() << std::endl; return false; } if (eventType == MOVE_EVENT_EQUIP || eventType == MOVE_EVENT_DEEQUIP) { - pugi::xml_attribute slotAttribute = node.attribute("slot"); - if (slotAttribute) { - tmpStr = boost::algorithm::to_lower_copy(slotAttribute.as_string()); + if ((attr = node.attribute("slot"))) { + tmpStr = boost::algorithm::to_lower_copy(attr.as_string()); if (tmpStr == "head") { slot = SLOTP_HEAD; } else if (tmpStr == "necklace") { @@ -562,32 +560,29 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) } else if (tmpStr == "ammo") { slot = SLOTP_AMMO; } else { - std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " - << slotAttribute.as_string() << std::endl; + std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << attr.as_string() + << std::endl; } } wieldInfo = 0; - pugi::xml_attribute levelAttribute = node.attribute("level"); - if (levelAttribute) { - reqLevel = pugi::cast(levelAttribute.value()); + if ((attr = node.attribute("level"))) { + reqLevel = pugi::cast(attr.value()); if (reqLevel > 0) { wieldInfo |= WIELDINFO_LEVEL; } } - pugi::xml_attribute magLevelAttribute = node.attribute("maglevel"); - if (magLevelAttribute) { - reqMagLevel = pugi::cast(magLevelAttribute.value()); + if ((attr = node.attribute("maglevel"))) { + reqMagLevel = pugi::cast(attr.value()); if (reqMagLevel > 0) { wieldInfo |= WIELDINFO_MAGLV; } } - pugi::xml_attribute premiumAttribute = node.attribute("premium"); - if (premiumAttribute) { - premium = premiumAttribute.as_bool(); + if ((attr = node.attribute("premium"))) { + premium = attr.as_bool(); if (premium) { wieldInfo |= WIELDINFO_PREMIUM; } @@ -595,23 +590,24 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) // Gather vocation information std::list vocStringList; - for (auto vocationNode : node.children()) { - pugi::xml_attribute vocationNameAttribute = vocationNode.attribute("name"); - if (!vocationNameAttribute) { + for (auto& vocation_node : node.children()) { + if (!(attr = vocation_node.attribute("name"))) { continue; } - int32_t vocationId = g_vocations.getVocationId(vocationNameAttribute.as_string()); - if (vocationId != -1) { - vocationEquipSet.insert(vocationId); - if (vocationNode.attribute("showInDescription").as_bool(true)) { - vocStringList.push_back( - boost::algorithm::to_lower_copy(vocationNameAttribute.as_string())); + if (auto vocation = tfs::game::vocations::find_by_name(attr.as_string())) { + addVocation(vocation); + + if (vocation_node.attribute("showInDescription").as_bool(true)) { + vocStringList.push_back(boost::algorithm::to_lower_copy(attr.as_string())); } + } else { + std::cout << "[Warning - MoveEvent::configureMoveEvent] Wrong vocation name: " << attr.as_string() + << std::endl; } } - if (!vocationEquipSet.empty()) { + if (!vocations.empty()) { wieldInfo |= WIELDINFO_VOCREQ; } @@ -664,7 +660,7 @@ uint32_t MoveEvent::RemoveItemField(Item*, Item*, const Position&) { return 1; } ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck) { if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck) && moveEvent->getWieldInfo() != 0) { - if (!moveEvent->hasVocationEquipSet(player->getVocationId())) { + if (!moveEvent->hasVocation(player->getVocation())) { return RETURNVALUE_YOUDONTHAVEREQUIREDPROFESSION; } diff --git a/src/movement.h b/src/movement.h index ad8300b630..aba1f368ac 100644 --- a/src/movement.h +++ b/src/movement.h @@ -11,8 +11,6 @@ class MoveEvent; -extern Vocations g_vocations; - enum MoveEvent_t { MOVE_EVENT_STEP_IN, @@ -145,19 +143,18 @@ class MoveEvent final : public Event const std::string& getVocationString() const { return vocationString; } void setVocationString(const std::string& str) { vocationString = str; } uint32_t getWieldInfo() const { return wieldInfo; } - const auto& getVocationEquipSet() const { return vocationEquipSet; } - void addVocationEquipSet(const std::string& vocationName) + + void addVocation(Vocation_ptr vocation) { vocations.insert(vocation); } + + bool hasVocation(Vocation_ptr vocation) const { - int32_t vocationId = g_vocations.getVocationId(vocationName); - if (vocationId != -1) { - vocationEquipSet.insert(vocationId); + if (vocations.empty()) { + // If the set is empty, it is considered to be for all vocations. + return true; } + return vocations.find(vocation) != vocations.end(); } - // If the set is empty, it is considered to be for all vocations. - bool hasVocationEquipSet(uint16_t vocationId) const - { - return vocationEquipSet.empty() || vocationEquipSet.find(vocationId) != vocationEquipSet.end(); - } + bool getTileItem() const { return tileItem; } void setTileItem(bool b) { tileItem = b; } void setSlot(uint32_t s) { slot = s; } @@ -195,8 +192,9 @@ class MoveEvent final : public Event bool premium = false; std::string vocationString; uint32_t wieldInfo = 0; - std::unordered_set vocationEquipSet; bool tileItem = false; + + std::set vocations; }; #endif // FS_MOVEMENT_H diff --git a/src/otserv.cpp b/src/otserv.cpp index 88fa3a2f92..adb3a3eb6b 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -35,7 +35,6 @@ Scheduler g_scheduler; Game g_game; Monsters g_monsters; -Vocations g_vocations; extern Scripts* g_scripts; std::mutex g_loaderLock; @@ -139,7 +138,8 @@ void mainLoader(ServiceManager* services) // load vocations std::cout << ">> Loading vocations" << std::endl; - if (std::ifstream is{"data/XML/vocations.xml"}; !g_vocations.loadFromXml(is, "data/XML/vocations.xml")) { + if (std::ifstream is{"data/XML/vocations.xml"}; + !tfs::game::vocations::load_from_xml(is, "data/XML/vocations.xml")) { startupErrorMessage("Unable to load vocations!"); return; } diff --git a/src/player.cpp b/src/player.cpp index 755678dff5..6ab32575af 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -28,7 +28,6 @@ extern Game g_game; extern Chat* g_chat; -extern Vocations g_vocations; extern MoveEvents* g_moveEvents; extern Weapons* g_weapons; extern CreatureEvents* g_creatureEvents; @@ -91,19 +90,18 @@ void Player::setID() } } -bool Player::setVocation(uint16_t vocId) +void Player::setVocation(Vocation_ptr vocation) { - Vocation* voc = g_vocations.getVocation(vocId); - if (!voc) { - return false; - } - vocation = voc; + this->vocation = vocation; + updateVocation(); +} +void Player::updateVocation() +{ updateRegeneration(); - setBaseSpeed(voc->getBaseSpeed()); + setBaseSpeed(vocation->baseSpeed); updateBaseSpeed(); g_game.changeSpeed(this, 0); - return true; } bool Player::isPushable() const @@ -123,8 +121,8 @@ std::string Player::getDescription(int32_t lookDistance) const if (group->access) { s << " You are " << group->name << '.'; - } else if (vocation->getId() != VOCATION_NONE) { - s << " You are " << vocation->getVocDescription() << '.'; + } else if (!vocation->isNone()) { + s << " You are " << vocation->description << '.'; } else { s << " You have no vocation."; } @@ -143,8 +141,8 @@ std::string Player::getDescription(int32_t lookDistance) const if (group->access) { s << " is " << group->name << '.'; - } else if (vocation->getId() != VOCATION_NONE) { - s << " is " << vocation->getVocDescription() << '.'; + } else if (!vocation->isNone()) { + s << " is " << vocation->description << '.'; } else { s << " has no vocation."; } @@ -399,7 +397,7 @@ uint32_t Player::getAttackSpeed() const { const Item* weapon = getWeapon(true); if (!weapon || weapon->getAttackSpeed() == 0) { - return vocation->getAttackSpeed(); + return vocation->attackSpeed; } return weapon->getAttackSpeed(); @@ -885,7 +883,7 @@ void Player::sendPing() setAttackedCreature(nullptr); } - int32_t noPongKickTime = vocation->getNoPongKickTime(); + int32_t noPongKickTime = vocation->noPongKickTime; if (pzLocked && noPongKickTime < 60000) { noPongKickTime = 60000; } @@ -1753,11 +1751,11 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText /* = fa uint32_t prevLevel = level; while (experience >= nextLevelExp) { ++level; - healthMax += vocation->getHPGain(); - health += vocation->getHPGain(); - manaMax += vocation->getManaGain(); - mana += vocation->getManaGain(); - capacity += vocation->getCapGain(); + healthMax += vocation->gainHP; + health += vocation->gainHP; + manaMax += vocation->gainMana; + mana += vocation->gainMana; + capacity += vocation->gainCap; currLevelExp = nextLevelExp; nextLevelExp = Player::getExpForLevel(level + 1); @@ -1845,9 +1843,9 @@ void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) while (level > 1 && experience < currLevelExp) { --level; - healthMax = std::max(0, healthMax - vocation->getHPGain()); - manaMax = std::max(0, manaMax - vocation->getManaGain()); - capacity = std::max(0, capacity - vocation->getCapGain()); + healthMax = std::max(0, healthMax - vocation->gainHP); + manaMax = std::max(0, manaMax - vocation->gainMana); + capacity = std::max(0, capacity - vocation->gainCap); currLevelExp = Player::getExpForLevel(level); } @@ -2104,15 +2102,15 @@ void Player::death(Creature* lastHitCreature) if (expLoss != 0) { uint32_t oldLevel = level; - if (vocation->getId() == VOCATION_NONE || level > 7) { + if (vocation->isNone() || level > 7) { experience -= expLoss; } while (level > 1 && experience < Player::getExpForLevel(level)) { --level; - healthMax = std::max(0, healthMax - vocation->getHPGain()); - manaMax = std::max(0, manaMax - vocation->getManaGain()); - capacity = std::max(0, capacity - vocation->getCapGain()); + healthMax = std::max(0, healthMax - vocation->gainHP); + manaMax = std::max(0, manaMax - vocation->gainMana); + capacity = std::max(0, capacity - vocation->gainCap); } if (oldLevel != level) { @@ -3841,7 +3839,7 @@ void Player::changeMana(int32_t manaChange) void Player::changeSoul(int32_t soulChange) { if (soulChange > 0) { - soul += std::min(soulChange, vocation->getSoulMax() - soul); + soul += std::min(soulChange, vocation->soulMax - soul); } else { soul = std::max(0, soul + soulChange); } @@ -4064,8 +4062,8 @@ void Player::checkSkullTicks(int64_t ticks) bool Player::isPromoted() const { - uint16_t promotedVocation = g_vocations.getPromotedVocation(vocation->getId()); - return promotedVocation == VOCATION_NONE && vocation->getId() != promotedVocation; + auto promoted_vocation = tfs::game::vocations::find_by_promoted_id(vocation->id); + return promoted_vocation && promoted_vocation->isNone() && vocation != promoted_vocation; } double Player::getLostPercent() const @@ -4712,15 +4710,11 @@ void Player::setGuild(Guild_ptr guild) void Player::updateRegeneration() { - if (!vocation) { - return; - } - - Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); - if (condition) { - condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount()); - condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); - condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount()); - condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000); + if (auto condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT)) { + condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->gainHealthAmount); + condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->gainHealthAmount); + condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->gainHealthTicks * 1000); + condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->gainManaAmount); + condition->setParam(CONDITION_PARAM_MANATICKS, vocation->gainManaTicks * 1000); } } diff --git a/src/player.h b/src/player.h index be1c3a508c..c328bd1f9f 100644 --- a/src/player.h +++ b/src/player.h @@ -190,8 +190,6 @@ class Player final : public Creature, public Cylinder const GuildWarVector& getGuildWarVector() const { return guildWarVector; } - Vocation* getVocation() const { return vocation; } - OperatingSystem_t getOperatingSystem() const { return operatingSystem; } void setOperatingSystem(OperatingSystem_t clientos) { operatingSystem = clientos; } @@ -280,8 +278,9 @@ class Player final : public Creature, public Cylinder bool isPremium() const; void setPremiumTime(time_t premiumEndsAt); - bool setVocation(uint16_t vocId); - uint16_t getVocationId() const { return vocation->getId(); } + void setVocation(Vocation_ptr vocation); + void updateVocation(); + auto getVocation() const { return vocation; } PlayerSex_t getSex() const { return sex; } void setSex(PlayerSex_t); @@ -1220,7 +1219,7 @@ class Player final : public Creature, public Cylinder Player* tradePartner = nullptr; SchedulerTask* walkTask = nullptr; Town* town = nullptr; - Vocation* vocation = nullptr; + Vocation_ptr vocation = nullptr; StoreInbox* storeInbox = nullptr; DepotLocker_ptr depotLocker = nullptr; @@ -1297,7 +1296,7 @@ class Player final : public Creature, public Cylinder void updateBaseSpeed() { if (!hasFlag(PlayerFlag_SetMaxSpeed)) { - baseSpeed = vocation->getBaseSpeed() + (2 * (level - 1)); + baseSpeed = vocation->baseSpeed + (2 * (level - 1)); } else { baseSpeed = PLAYER_MAX_SPEED; } diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index 5edfbb3dcb..658ade0971 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1751,7 +1751,7 @@ void ProtocolGame::sendBasicData() msg.add(0); } - msg.addByte(player->getVocation()->getClientId()); + msg.addByte(player->getVocation()->clientId); msg.addByte(0x00); // is prey system enabled (bool) // unlock spells on action bar @@ -1760,7 +1760,9 @@ void ProtocolGame::sendBasicData() msg.add(spellId); } - msg.addByte(player->getVocation()->getMagicShield()); // is magic shield active (bool) + // is magic shield active ? + msg.addByte(getBoolean(ConfigManager::MANASHIELD_BREAKABLE) ? player->getVocation()->magicShield : false); + writeToOutputBuffer(msg); } @@ -3491,7 +3493,7 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo // Player vocation info if (creatureType == CREATURETYPE_PLAYER) { - msg.addByte(otherPlayer ? otherPlayer->getVocation()->getClientId() : 0x00); + msg.addByte(otherPlayer ? otherPlayer->getVocation()->clientId : 0x00); } uint8_t speechBubble = creature->getSpeechBubble(); diff --git a/src/spells.cpp b/src/spells.cpp index 19433cade3..37f69c1c56 100644 --- a/src/spells.cpp +++ b/src/spells.cpp @@ -500,15 +500,14 @@ bool Spell::configureSpell(const pugi::xml_node& node) group = (aggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING); } - for (auto vocationNode : node.children()) { - if (!(attr = vocationNode.attribute("name"))) { + for (auto& vocation_node : node.children()) { + if (!(attr = vocation_node.attribute("name"))) { continue; } - int32_t vocationId = g_vocations.getVocationId(attr.as_string()); - if (vocationId != -1) { - attr = vocationNode.attribute("showInDescription"); - vocationSpellMap[vocationId] = !attr || attr.as_bool(); + if (auto vocation = tfs::game::vocations::find_by_name(attr.as_string())) { + attr = vocation_node.attribute("showInDescription"); + addVocation(vocation, !attr || attr.as_bool()); } else { std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << attr.as_string() << std::endl; } @@ -594,7 +593,7 @@ bool Spell::playerSpellCheck(Player* player) const g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } - } else if (!hasVocationSpellMap(player->getVocationId())) { + } else if (!hasVocation(player->getVocation())) { player->sendCancelMessage(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -1055,12 +1054,9 @@ bool InstantSpell::canCast(const Player* player) const if (player->hasLearnedInstantSpell(getName())) { return true; } - } else { - if (vocationSpellMap.empty() || hasVocationSpellMap(player->getVocationId())) { - return true; - } + } else if (hasVocation(player->getVocation())) { + return true; } - return false; } diff --git a/src/spells.h b/src/spells.h index 85374342ea..bbe12a3f48 100644 --- a/src/spells.h +++ b/src/spells.h @@ -18,8 +18,6 @@ class Spell; using InstantSpell_ptr = std::unique_ptr; using RuneSpell_ptr = std::unique_ptr; -extern Vocations g_vocations; - class Spells final : public BaseEvents { public: @@ -133,18 +131,19 @@ class Spell : public BaseSpell bool isLearnable() const { return learnable; } void setLearnable(bool l) { learnable = l; } - const auto& getVocationSpellMap() const { return vocationSpellMap; } - void addVocationSpellMap(std::string_view vocationName, bool showInDescription) + const auto& getVocations() { return vocations; } + + void addVocation(Vocation_ptr vocation, bool show_in_description) { vocations[vocation] = show_in_description; } + + bool hasVocation(Vocation_ptr vocation) const { - int32_t vocationId = g_vocations.getVocationId(vocationName); - if (vocationId != -1) { - vocationSpellMap[vocationId] = showInDescription; + if (vocations.empty()) { + // If the set is empty, it is considered to be for all vocations. + return true; } - } - // If the set is empty, it is considered to be for all vocations. - bool hasVocationSpellMap(uint16_t vocationId) const - { - return vocationSpellMap.empty() || vocationSpellMap.find(vocationId) != vocationSpellMap.end(); + + auto it = std::find_if(vocations.begin(), vocations.end(), [=](auto it) { return it.first == vocation; }); + return it != vocations.end(); } SpellGroup_t getGroup() const { return group; } @@ -186,7 +185,7 @@ class Spell : public BaseSpell bool playerInstantSpellCheck(Player* player, const Position& toPos); bool playerRuneSpellCheck(Player* player, const Position& toPos); - std::map vocationSpellMap; + std::map vocations; SpellGroup_t group = SPELLGROUP_NONE; SpellGroup_t secondaryGroup = SPELLGROUP_NONE; diff --git a/src/vocation.cpp b/src/vocation.cpp index e0b87692d7..f3a1f2abe0 100644 --- a/src/vocation.cpp +++ b/src/vocation.cpp @@ -9,102 +9,135 @@ #include "pugicast.h" #include "tools.h" -bool Vocations::loadFromXml(std::istream& is, std::string_view filename) +namespace { + +uint32_t skillBase[SKILL_LAST + 1] = {50, 50, 50, 50, 30, 100, 20}; + +} // namespace + +uint64_t Vocation::getReqSkillTries(uint8_t skill, uint16_t level) +{ + if (skill > SKILL_LAST) { + return 0; + } + return skillBase[skill] * + std::pow(skillMultipliers[skill], static_cast(level - (MINIMUM_SKILL_LEVEL + 1))); +} + +uint64_t Vocation::getReqMana(uint32_t magLevel) +{ + if (magLevel == 0) { + return 0; + } + return 1600 * std::pow(manaMultiplier, static_cast(magLevel - 1)); +} + +bool tfs::game::vocations::load_from_xml(std::istream& is, std::string_view filename, bool reload) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load(is); if (!result) { - printXMLError("Error - Vocations::loadFromXml", filename, result); + printXMLError("Error - tfs::game::vocations::load_from_xml", filename, result); return false; } - for (auto vocationNode : doc.child("vocations").children()) { - pugi::xml_attribute attr = vocationNode.attribute("id"); - if (!attr) { - std::cout << "[Warning - Vocations::loadFromXml] Missing vocation id" << std::endl; + for (auto& node : doc.child("vocations").children()) { + pugi::xml_attribute attr; + if (!(attr = node.attribute("id"))) { + std::cout << "[Warning - tfs::game::vocations::load_from_xml] Missing vocation id" << std::endl; continue; } - uint16_t id = pugi::cast(attr.value()); - auto res = vocationsMap.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(id)); - Vocation& voc = res.first->second; + auto vocation_id = pugi::cast(attr.value()); + + Vocation_ptr vocation = nullptr; + if (reload) { + vocation = tfs::game::vocations::find_by_id(vocation_id); + } + + if (!vocation) { + vocation = std::make_shared(vocation_id); + loaded_vocations.emplace(vocation); + } + + node.remove_attribute("id"); - vocationNode.remove_attribute("id"); - for (auto attrNode : vocationNode.attributes()) { - const char* attrName = attrNode.name(); + for (auto& attr_node : node.attributes()) { + auto attrName = attr_node.name(); if (caseInsensitiveEqual(attrName, "name")) { - voc.name = attrNode.as_string(); + vocation->name = attr_node.as_string(); } else if (caseInsensitiveEqual(attrName, "allowpvp")) { - voc.allowPvp = attrNode.as_bool(); + vocation->allowPvp = attr_node.as_bool(); } else if (caseInsensitiveEqual(attrName, "clientid")) { - voc.clientId = pugi::cast(attrNode.value()); + vocation->clientId = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "description")) { - voc.description = attrNode.as_string(); + vocation->description = attr_node.as_string(); } else if (caseInsensitiveEqual(attrName, "magicshield")) { - voc.magicShield = attrNode.as_bool(); + vocation->magicShield = attr_node.as_bool(); } else if (caseInsensitiveEqual(attrName, "gaincap")) { - voc.gainCap = pugi::cast(attrNode.value()) * 100; + vocation->gainCap = pugi::cast(attr_node.value()) * 100; } else if (caseInsensitiveEqual(attrName, "gainhp")) { - voc.gainHP = pugi::cast(attrNode.value()); + vocation->gainHP = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "gainmana")) { - voc.gainMana = pugi::cast(attrNode.value()); + vocation->gainMana = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "gainhpticks")) { - voc.gainHealthTicks = pugi::cast(attrNode.value()); + vocation->gainHealthTicks = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "gainhpamount")) { - voc.gainHealthAmount = pugi::cast(attrNode.value()); + vocation->gainHealthAmount = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "gainmanaticks")) { - voc.gainManaTicks = pugi::cast(attrNode.value()); + vocation->gainManaTicks = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "gainmanaamount")) { - voc.gainManaAmount = pugi::cast(attrNode.value()); + vocation->gainManaAmount = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "manamultiplier")) { - voc.manaMultiplier = pugi::cast(attrNode.value()); + vocation->manaMultiplier = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "attackspeed")) { - voc.attackSpeed = pugi::cast(attrNode.value()); + vocation->attackSpeed = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "basespeed")) { - voc.baseSpeed = pugi::cast(attrNode.value()); + vocation->baseSpeed = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "soulmax")) { - voc.soulMax = pugi::cast(attrNode.value()); + vocation->soulMax = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "gainsoulticks")) { - voc.gainSoulTicks = pugi::cast(attrNode.value()); + vocation->gainSoulTicks = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "fromvoc")) { - voc.fromVocation = pugi::cast(attrNode.value()); + vocation->fromVocation = pugi::cast(attr_node.value()); } else if (caseInsensitiveEqual(attrName, "nopongkicktime")) { - voc.noPongKickTime = pugi::cast(attrNode.value()) * 1000; + vocation->noPongKickTime = pugi::cast(attr_node.value()) * 1000; } else { - std::cout << "[Notice - Vocations::loadFromXml] Unknown attribute: \"" << attrName - << "\" for vocation: " << voc.id << std::endl; + std::cout << "[Notice - tfs::game::vocations::load_from_xml] Unknown attribute: \"" << attrName + << "\" for vocation: " << vocation->id << std::endl; } } - for (auto childNode : vocationNode.children()) { - if (caseInsensitiveEqual(childNode.name(), "skill")) { - if ((attr = childNode.attribute("id"))) { - uint16_t skillId = pugi::cast(attr.value()); - if (skillId <= SKILL_LAST) { - voc.skillMultipliers[skillId] = pugi::cast(childNode.attribute("multiplier").value()); - } else { - std::cout << "[Notice - Vocations::loadFromXml] No valid skill id: " << skillId - << " for vocation: " << voc.id << std::endl; - } - } else { - std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id - << std::endl; + for (auto& child_node : node.children()) { + if (caseInsensitiveEqual(child_node.name(), "skill")) { + if (!(attr = child_node.attribute("id"))) { + std::cout << "[Notice - tfs::game::vocations::load_from_xml] Missing skill id for vocation: " + << vocation->id << std::endl; + continue; + } + + auto skillId = pugi::cast(attr.value()); + if (skillId > SKILL_LAST) { + std::cout << "[Notice - tfs::game::vocations::load_from_xml] No valid skill id: " << skillId + << " for vocation: " << vocation->id << std::endl; + continue; } - } else if (caseInsensitiveEqual(childNode.name(), "formula")) { - if ((attr = childNode.attribute("meleeDamage"))) { - voc.meleeDamageMultiplier = pugi::cast(attr.value()); + vocation->skillMultipliers[skillId] = pugi::cast(child_node.attribute("multiplier").value()); + } else if (caseInsensitiveEqual(child_node.name(), "formula")) { + if ((attr = child_node.attribute("meleeDamage"))) { + vocation->meleeDamageMultiplier = pugi::cast(attr.value()); } - if ((attr = childNode.attribute("distDamage"))) { - voc.distDamageMultiplier = pugi::cast(attr.value()); + if ((attr = child_node.attribute("distDamage"))) { + vocation->distDamageMultiplier = pugi::cast(attr.value()); } - if ((attr = childNode.attribute("defense"))) { - voc.defenseMultiplier = pugi::cast(attr.value()); + if ((attr = child_node.attribute("defense"))) { + vocation->defenseMultiplier = pugi::cast(attr.value()); } - if ((attr = childNode.attribute("armor"))) { - voc.armorMultiplier = pugi::cast(attr.value()); + if ((attr = child_node.attribute("armor"))) { + vocation->armorMultiplier = pugi::cast(attr.value()); } } } @@ -112,45 +145,35 @@ bool Vocations::loadFromXml(std::istream& is, std::string_view filename) return true; } -Vocation* Vocations::getVocation(uint16_t id) +Vocation_ptr tfs::game::vocations::find_by_id(uint16_t id) { - auto it = vocationsMap.find(id); - if (it == vocationsMap.end()) { - std::cout << "[Warning - Vocations::getVocation] Vocation " << id << " not found." << std::endl; + auto it = std::find_if(loaded_vocations.begin(), loaded_vocations.end(), [id](auto it) { return it->id == id; }); + if (it == loaded_vocations.end()) { return nullptr; } - return &it->second; -} - -int32_t Vocations::getVocationId(std::string_view name) const -{ - auto it = std::find_if(vocationsMap.begin(), vocationsMap.end(), - [=](auto it) { return caseInsensitiveEqual(name, it.second.name); }); - return it != vocationsMap.end() ? it->first : -1; + return *it; } -uint16_t Vocations::getPromotedVocation(uint16_t id) const +Vocation_ptr tfs::game::vocations::find_by_name(std::string_view name) { - auto it = std::find_if(vocationsMap.begin(), vocationsMap.end(), - [id](auto it) { return it.second.fromVocation == id && it.first != id; }); - return it != vocationsMap.end() ? it->first : VOCATION_NONE; -} - -static const uint32_t skillBase[SKILL_LAST + 1] = {50, 50, 50, 50, 30, 100, 20}; + auto it = std::find_if(loaded_vocations.begin(), loaded_vocations.end(), + [=](auto it) { return caseInsensitiveEqual(name, it->name); }); -uint64_t Vocation::getReqSkillTries(uint8_t skill, uint16_t level) -{ - if (skill > SKILL_LAST) { - return 0; + if (it == loaded_vocations.end()) { + return nullptr; } - return skillBase[skill] * - std::pow(skillMultipliers[skill], static_cast(level - (MINIMUM_SKILL_LEVEL + 1))); + return *it; } -uint64_t Vocation::getReqMana(uint32_t magLevel) +Vocation_ptr tfs::game::vocations::find_by_promoted_id(uint16_t id) { - if (magLevel == 0) { - return 0; + auto it = std::find_if(loaded_vocations.begin(), loaded_vocations.end(), + [id](auto it) { return it->fromVocation == id && it->id != id; }); + + if (it == loaded_vocations.end()) { + return nullptr; } - return 1600 * std::pow(manaMultiplier, static_cast(magLevel - 1)); + return *it; } + +const std::set& tfs::game::vocations::find_vocations() { return loaded_vocations; } diff --git a/src/vocation.h b/src/vocation.h index cf244bc10f..94d21b2e2f 100644 --- a/src/vocation.h +++ b/src/vocation.h @@ -7,57 +7,28 @@ #include "configmanager.h" #include "enums.h" -class Vocation +#include + +class Vocation final : public std::enable_shared_from_this { public: explicit Vocation(uint16_t id) : id(id) {} - const std::string& getVocName() const { return name; } - const std::string& getVocDescription() const { return description; } + /// Deleted copy constructor to ensure Vocation is non-copyable. + Vocation(const Vocation&) = delete; + /// Deleted assignment operator to ensure Vocation is non-copyable. + Vocation& operator=(const Vocation&) = delete; + uint64_t getReqSkillTries(uint8_t skill, uint16_t level); uint64_t getReqMana(uint32_t magLevel); - uint16_t getId() const { return id; } - - uint8_t getClientId() const { return clientId; } - - uint32_t getHPGain() const { return gainHP; } - uint32_t getManaGain() const { return gainMana; } - uint32_t getCapGain() const { return gainCap; } - - uint32_t getManaGainTicks() const { return gainManaTicks; } - uint32_t getManaGainAmount() const { return gainManaAmount; } - uint32_t getHealthGainTicks() const { return gainHealthTicks; } - uint32_t getHealthGainAmount() const { return gainHealthAmount; } - - uint8_t getSoulMax() const { return soulMax; } - uint16_t getSoulGainTicks() const { return gainSoulTicks; } - - uint32_t getAttackSpeed() const { return attackSpeed; } - uint32_t getBaseSpeed() const { return baseSpeed; } - - uint32_t getFromVocation() const { return fromVocation; } - - uint32_t getNoPongKickTime() const { return noPongKickTime; } - - bool allowsPvp() const { return allowPvp; } - - bool getMagicShield() const - { - if (!getBoolean(ConfigManager::MANASHIELD_BREAKABLE)) { - return false; - } - return magicShield; - } + bool isNone() const { return id == VOCATION_NONE; } float meleeDamageMultiplier = 1.0f; float distDamageMultiplier = 1.0f; float defenseMultiplier = 1.0f; float armorMultiplier = 1.0f; -private: - friend class Vocations; - std::string name = "none"; std::string description; @@ -83,24 +54,45 @@ class Vocation uint8_t clientId = 0; bool allowPvp = true; - bool magicShield = false; + + bool operator==(const Vocation& other) const { return id == other.id; } }; -using VocationMap = std::map; +using Vocation_ptr = std::shared_ptr; -class Vocations -{ -public: - bool loadFromXml(std::istream& is, std::string_view filename); +namespace { - Vocation* getVocation(uint16_t id); - int32_t getVocationId(std::string_view name) const; - uint16_t getPromotedVocation(uint16_t vocationId) const; - const VocationMap& getVocations() const { return vocationsMap; } +std::set loaded_vocations; -private: - VocationMap vocationsMap; -}; +} // namespace + +namespace tfs::game::vocations { + +/// @brief Loads vocation data from an XML file. +/// @param {reload} Specifies whether to reload existing data. +/// @return True if the vocations were loaded successfully, false otherwise. +bool load_from_xml(std::istream& is, std::string_view filename, bool reload = false); + +/// @brief Finds a vocation by its unique ID. +/// @param {id} The unique identifier of the vocation. +/// @return A shared pointer to the vocation, or nullptr if not found. +Vocation_ptr find_by_id(uint16_t id); + +/// @brief Finds a vocation by its name. +/// @param {name} The name of the vocation. +/// @return A shared pointer to the vocation, or nullptr if not found. +Vocation_ptr find_by_name(std::string_view name); + +/// @brief Finds a vocation by its promoted vocation ID. +/// @param {id} The promoted vocation ID. +/// @return A shared pointer to the vocation, or nullptr if not found. +Vocation_ptr find_by_promoted_id(uint16_t id); + +/// @brief Gets the set of all loaded vocations. +/// @return A constant reference to the set of loaded vocations. +const std::set& find_vocations(); + +} // namespace tfs::game::vocations #endif // FS_VOCATION_H diff --git a/src/weapons.cpp b/src/weapons.cpp index bed996c957..421432f70a 100644 --- a/src/weapons.cpp +++ b/src/weapons.cpp @@ -12,7 +12,6 @@ #include "pugicast.h" extern Game g_game; -extern Vocations g_vocations; extern Weapons* g_weapons; Weapons::Weapons() { scriptInterface.initState(); } @@ -179,22 +178,23 @@ bool Weapon::configureEvent(const pugi::xml_node& node) } std::list vocStringList; - for (auto vocationNode : node.children()) { - if (!(attr = vocationNode.attribute("name"))) { + for (auto& vocation_node : node.children()) { + if (!(attr = vocation_node.attribute("name"))) { continue; } - int32_t vocationId = g_vocations.getVocationId(attr.as_string()); - if (vocationId != -1) { - vocationWeaponSet.insert(vocationId); - int32_t promotedVocation = g_vocations.getPromotedVocation(vocationId); - if (promotedVocation != VOCATION_NONE) { - vocationWeaponSet.insert(promotedVocation); + if (auto vocation = tfs::game::vocations::find_by_name(attr.as_string())) { + addVocation(vocation); + + if (auto promoted_vocation = tfs::game::vocations::find_by_promoted_id(vocation->id)) { + addVocation(promoted_vocation); } - if (vocationNode.attribute("showInDescription").as_bool(true)) { + if (vocation_node.attribute("showInDescription").as_bool(true)) { vocStringList.push_back(boost::algorithm::to_lower_copy(attr.as_string())); } + } else { + std::cout << "[Warning - Weapon::configureEvent] Wrong vocation name: " << attr.as_string() << std::endl; } } @@ -280,12 +280,11 @@ int32_t Weapon::playerWeaponCheck(Player* player, Creature* target, uint8_t shoo return 0; } - if (!hasVocationWeaponSet(player->getVocationId())) { + if (!hasVocation(player->getVocation())) { return 0; } int32_t damageModifier = 100; - if (player->getLevel() < getReqLevel()) { damageModifier = (isWieldedUnproperly() ? damageModifier / 2 : 0); } @@ -331,10 +330,9 @@ bool Weapon::ammoCheck(const Player* player) const return false; } - if (!hasVocationWeaponSet(player->getVocationId())) { + if (!hasVocation(player->getVocation())) { return false; } - return true; } diff --git a/src/weapons.h b/src/weapons.h index b060bfe546..255e581e74 100644 --- a/src/weapons.h +++ b/src/weapons.h @@ -14,8 +14,6 @@ class Weapon; using Weapon_ptr = std::unique_ptr; -extern Vocations g_vocations; - class Weapons final : public BaseEvents { public: @@ -105,18 +103,17 @@ class Weapon : public Event uint32_t getWieldInfo() const { return wieldInfo; } void setWieldInfo(uint32_t info) { wieldInfo |= info; } - const auto& getVocationWeaponSet() const { return vocationWeaponSet; } - void addVocationWeaponSet(const std::string& vocationName) + const auto& getVocations() { return vocations; } + + void addVocation(Vocation_ptr vocation) { vocations.insert(vocation); } + + bool hasVocation(Vocation_ptr vocation) const { - int32_t vocationId = g_vocations.getVocationId(vocationName); - if (vocationId != -1) { - vocationWeaponSet.insert(vocationId); + if (vocations.empty()) { + // If the set is empty, it is considered to be for all vocations. + return true; } - } - // If the set is empty, it is considered to be for all vocations. - bool hasVocationWeaponSet(uint16_t vocationId) const - { - return vocationWeaponSet.empty() || vocationWeaponSet.find(vocationId) != vocationWeaponSet.end(); + return vocations.find(vocation) != vocations.end(); } const std::string& getVocationString() const { return vocationString; } @@ -125,7 +122,6 @@ class Weapon : public Event WeaponAction_t action = WEAPONACTION_NONE; CombatParams params; WeaponType_t weaponType; - std::unordered_set vocationWeaponSet; protected: void internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const; @@ -153,6 +149,8 @@ class Weapon : public Event bool wieldUnproperly = false; std::string vocationString = ""; + std::set vocations; + std::string_view getScriptEventName() const override final { return "onUseWeapon"; } bool executeUseWeapon(Player* player, const LuaVariant& var) const;