diff --git a/data-otxserver/scripts/weapons/scripts/diamond_arrow.lua b/data-otxserver/scripts/weapons/scripts/diamond_arrow.lua
index 619842092..36be8dc38 100644
--- a/data-otxserver/scripts/weapons/scripts/diamond_arrow.lua
+++ b/data-otxserver/scripts/weapons/scripts/diamond_arrow.lua
@@ -16,7 +16,7 @@ combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true)
function onGetFormulaValues(player, skill, attack, factor)
local distanceSkill = player:getEffectiveSkillLevel(SKILL_DISTANCE)
local min = (player:getLevel() / 5)
- local max = (0.09 * factor) * distanceSkill * 37 + (player:getLevel() / 5)
+ local max = (0.09 * factor) * distanceSkill * attack + (player:getLevel() / 5)
return -min, -max
end
diff --git a/data/items/items.xml b/data/items/items.xml
index 103bb11ed..3cc002cf1 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -53105,6 +53105,9 @@ hands of its owner. Granted by TibiaRoyal.com"/>
+
+
+
-
diff --git a/data/scripts/eventcallbacks/README.md b/data/scripts/eventcallbacks/README.md
index bdafcb41b..601653574 100644
--- a/data/scripts/eventcallbacks/README.md
+++ b/data/scripts/eventcallbacks/README.md
@@ -47,6 +47,7 @@ Event callbacks are available for several categories of game entities, such as `
- `(void)` `playerOnCombat`
- `(void)` `playerOnInventoryUpdate`
- `(bool)` `playerOnRotateItem`
+- `(void)` `playerOnWalk`
- `(void)` `monsterOnDropLoot`
- `(void)` `monsterPostDropLoot`
- `(void)` `monsterOnSpawn`
diff --git a/data/scripts/runes/destroy_field_rune.lua b/data/scripts/runes/destroy_field_rune.lua
index 024dbc1bc..8752be7eb 100644
--- a/data/scripts/runes/destroy_field_rune.lua
+++ b/data/scripts/runes/destroy_field_rune.lua
@@ -4,6 +4,13 @@ local fields = { 105, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2132
local rune = Spell("rune")
function rune.onCastSpell(creature, variant, isHotkey)
+ local inPz = creature:getTile():hasFlag(TILESTATE_PROTECTIONZONE)
+ if inPz then
+ creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE)
+ creature:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+
local position = Variant.getPosition(variant)
local tile = Tile(position)
local field = tile and tile:getItemByType(ITEM_TYPE_MAGICFIELD)
diff --git a/data/scripts/talkactions/gm/afk.lua b/data/scripts/talkactions/gm/afk.lua
new file mode 100644
index 000000000..6167a2b60
--- /dev/null
+++ b/data/scripts/talkactions/gm/afk.lua
@@ -0,0 +1,94 @@
+local afk = TalkAction("/afk")
+
+playersAFKs = {}
+
+local function checkIsAFK(id)
+ for index, item in pairs(playersAFKs) do
+ if id == item.id then
+ return { afk = true, index = index }
+ end
+ end
+ return { afk = false }
+end
+
+local function showAfkMessage(playerPosition)
+ local spectators = Game.getSpectators(playerPosition, false, true, 8, 8, 8, 8)
+ if #spectators > 0 then
+ for _, spectator in ipairs(spectators) do
+ spectator:say("AFK !", TALKTYPE_MONSTER_SAY, false, spectator, playerPosition)
+ end
+ end
+end
+
+function afk.onSay(player, words, param)
+ if param == "" then
+ player:sendCancelMessage("You need to specify on/off param.")
+ return true
+ end
+
+ local id, playerPosition = player:getId(), player:getPosition()
+ local isAfk = checkIsAFK(id)
+ if param == "on" then
+ if isAfk.afk then
+ player:sendCancelMessage("You are already AFK!")
+ return true
+ end
+
+ table.insert(playersAFKs, { id = id, position = playerPosition })
+ if player:isInGhostMode() then
+ player:setGhostMode(false)
+ end
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are now AFK!")
+ playerPosition:sendMagicEffect(CONST_ME_REDSMOKE)
+ showAfkMessage(playerPosition)
+ elseif param == "off" then
+ if isAfk.afk then
+ table.remove(playersAFKs, isAfk.index)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!")
+ playerPosition:sendMagicEffect(CONST_ME_REDSMOKE)
+ end
+ end
+
+ return true
+end
+
+afk:separator(" ")
+afk:groupType("gamemaster")
+afk:register()
+
+------------------ AFK Effect Message ------------------
+local afkEffect = GlobalEvent("GodAfkEffect")
+function afkEffect.onThink(interval)
+ for _, player in ipairs(playersAFKs) do
+ showAfkMessage(player.position)
+ end
+ return true
+end
+
+afkEffect:interval(5000)
+afkEffect:register()
+
+------------------ Stop AFK Message when moves ------------------
+local callback = EventCallback()
+function callback.playerOnWalk(player, creature, creaturePos, toPos)
+ local isAfk = checkIsAFK(player:getId())
+ if isAfk.afk then
+ table.remove(playersAFKs, isAfk.index)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are no longer AFK!")
+ end
+ return true
+end
+
+callback:register()
+
+------------------ Player Logout ------------------
+local godAfkLogout = CreatureEvent("GodAfkLogout")
+function godAfkLogout.onLogout(player)
+ local isAfk = checkIsAFK(player:getId())
+ if isAfk.afk then
+ table.remove(playersAFKs, isAfk.index)
+ end
+ return true
+end
+
+godAfkLogout:register()
diff --git a/data/scripts/talkactions/god/reload.lua b/data/scripts/talkactions/god/reload.lua
index 5bf728683..20b9431db 100644
--- a/data/scripts/talkactions/god/reload.lua
+++ b/data/scripts/talkactions/god/reload.lua
@@ -30,6 +30,7 @@ local reloadTypes = {
["scripts"] = RELOAD_TYPE_SCRIPTS,
["stage"] = RELOAD_TYPE_CORE,
["stages"] = RELOAD_TYPE_CORE,
+ ["vocations"] = RELOAD_TYPE_VOCATIONS,
}
local reload = TalkAction("/reload")
diff --git a/src/canary_server.cpp b/src/canary_server.cpp
index 6b737df35..e21691ba0 100644
--- a/src/canary_server.cpp
+++ b/src/canary_server.cpp
@@ -390,4 +390,5 @@ void CanaryServer::shutdown() {
g_dispatcher().shutdown();
g_metrics().shutdown();
inject().shutdown();
+ std::exit(0);
}
diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp
index bcb31a4ab..516ed7b5c 100644
--- a/src/creatures/players/player.cpp
+++ b/src/creatures/players/player.cpp
@@ -72,7 +72,7 @@ Player::~Player() {
}
bool Player::setVocation(uint16_t vocId) {
- Vocation* voc = g_vocations().getVocation(vocId);
+ const auto &voc = g_vocations().getVocation(vocId);
if (!voc) {
return false;
}
@@ -1926,6 +1926,8 @@ void Player::onWalk(Direction &dir) {
Creature::onWalk(dir);
setNextActionTask(nullptr);
setNextAction(OTSYS_TIME() + getStepDuration(dir));
+
+ g_callbacks().executeCallback(EventCallback_t::playerOnWalk, &EventCallback::playerOnWalk, getPlayer(), dir);
}
void Player::onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) {
@@ -2393,7 +2395,7 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool
++level;
// Player stats gain for vocations level <= 8
if (vocation->getId() != VOCATION_NONE && level <= 8) {
- const Vocation* noneVocation = g_vocations().getVocation(VOCATION_NONE);
+ const auto &noneVocation = g_vocations().getVocation(VOCATION_NONE);
healthMax += noneVocation->getHPGain();
health += noneVocation->getHPGain();
manaMax += noneVocation->getManaGain();
@@ -2488,7 +2490,7 @@ void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) {
--level;
// Player stats loss for vocations level <= 8
if (vocation->getId() != VOCATION_NONE && level <= 8) {
- const Vocation* noneVocation = g_vocations().getVocation(VOCATION_NONE);
+ const auto &noneVocation = g_vocations().getVocation(VOCATION_NONE);
healthMax = std::max(0, healthMax - noneVocation->getHPGain());
manaMax = std::max(0, manaMax - noneVocation->getManaGain());
capacity = std::max(0, capacity - noneVocation->getCapGain());
@@ -2918,6 +2920,7 @@ bool Player::spawn() {
getParent()->postAddNotification(static_self_cast(), nullptr, 0);
g_game().addCreatureCheck(static_self_cast());
g_game().addPlayer(static_self_cast());
+ static_self_cast()->onChangeZone(static_self_cast()->getZoneType());
return true;
}
@@ -4673,6 +4676,8 @@ void Player::onPlacedCreature() {
removePlayer(true);
}
+ this->onChangeZone(this->getZoneType());
+
sendUnjustifiedPoints();
}
@@ -6905,8 +6910,9 @@ std::shared_ptr
- Player::getItemFromDepotSearch(uint16_t itemId, const Posi
return nullptr;
}
-std::pair>, std::map>> Player::requestLockerItems(std::shared_ptr depotLocker, bool sendToClient /*= false*/, uint8_t tier /*= 0*/) const {
- if (depotLocker == nullptr) {
+std::pair>, std::map>>
+Player::requestLockerItems(std::shared_ptr depotLocker, bool sendToClient /*= false*/, uint8_t tier /*= 0*/) const {
+ if (!depotLocker) {
g_logger().error("{} - Depot locker is nullptr", __FUNCTION__);
return {};
}
@@ -6915,24 +6921,18 @@ std::pair>, std::map> itemVector;
std::vector> containers { depotLocker };
- size_t size = 0;
- do {
- std::shared_ptr container = containers[size];
- size++;
+ for (size_t i = 0; i < containers.size(); ++i) {
+ std::shared_ptr container = containers[i];
- for (std::shared_ptr
- item : container->getItemList()) {
+ for (const auto &item : container->getItemList()) {
std::shared_ptr lockerContainers = item->getContainer();
if (lockerContainers && !lockerContainers->empty()) {
containers.push_back(lockerContainers);
continue;
}
- if (item->isStoreItem()) {
- continue;
- }
-
const ItemType &itemType = Item::items[item->getID()];
- if (itemType.wareId == 0) {
+ if (item->isStoreItem() || itemType.wareId == 0) {
continue;
}
@@ -6940,37 +6940,24 @@ std::pair>, std::maphasMarketAttributes()) {
- continue;
- }
-
- if (!sendToClient && item->getTier() != tier) {
+ if (!item->hasMarketAttributes() || (!sendToClient && item->getTier() != tier)) {
continue;
}
- (lockerItems[itemType.wareId])[item->getTier()] += Item::countByType(item, -1);
+ lockerItems[itemType.wareId][item->getTier()] += Item::countByType(item, -1);
itemVector.push_back(item);
}
- } while (size < containers.size());
- StashItemList stashToSend = getStashItems();
- uint32_t countSize = 0;
- for (auto [itemId, itemCount] : stashToSend) {
- countSize += itemCount;
}
- do {
- for (auto [itemId, itemCount] : stashToSend) {
- const ItemType &itemType = Item::items[itemId];
- if (itemType.wareId == 0) {
- continue;
- }
-
- countSize = countSize - itemCount;
- (lockerItems[itemType.wareId])[0] += itemCount;
+ StashItemList stashToSend = getStashItems();
+ for (const auto &[itemId, itemCount] : stashToSend) {
+ const ItemType &itemType = Item::items[itemId];
+ if (itemType.wareId != 0) {
+ lockerItems[itemType.wareId][0] += itemCount;
}
- } while (countSize > 0);
+ }
- return std::make_pair(itemVector, lockerItems);
+ return { itemVector, lockerItems };
}
std::pair>, uint16_t> Player::getLockerItemsAndCountById(const std::shared_ptr &depotLocker, uint8_t tier, uint16_t itemId) {
diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp
index 855b48a78..70c8c05ae 100644
--- a/src/creatures/players/player.hpp
+++ b/src/creatures/players/player.hpp
@@ -334,7 +334,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
bool isBossOnBosstiaryTracker(const std::shared_ptr &monsterType) const;
- Vocation* getVocation() const {
+ std::shared_ptr getVocation() const {
return vocation;
}
@@ -2522,7 +2522,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
// Concoction system
void updateConcoction(uint16_t itemId, uint16_t timeLeft) {
- if (timeLeft < 0) {
+ if (timeLeft == 0) {
activeConcoctions.erase(itemId);
} else {
activeConcoctions[itemId] = timeLeft;
@@ -2773,7 +2773,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
ProtocolGame_ptr client;
std::shared_ptr walkTask;
std::shared_ptr town;
- Vocation* vocation = nullptr;
+ std::shared_ptr vocation = nullptr;
std::shared_ptr rewardChest = nullptr;
uint32_t inventoryWeight = 0;
diff --git a/src/creatures/players/vocations/vocation.cpp b/src/creatures/players/vocations/vocation.cpp
index c9a276a64..98dbaeb1b 100644
--- a/src/creatures/players/vocations/vocation.cpp
+++ b/src/creatures/players/vocations/vocation.cpp
@@ -14,6 +14,11 @@
#include "utils/pugicast.hpp"
#include "utils/tools.hpp"
+bool Vocations::reload() {
+ vocationsMap.clear();
+ return loadFromXml();
+}
+
bool Vocations::loadFromXml() {
pugi::xml_document doc;
auto folder = g_configManager().getString(CORE_DIRECTORY, __FUNCTION__) + "/XML/vocations.xml";
@@ -32,87 +37,87 @@ bool Vocations::loadFromXml() {
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 res = vocationsMap.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(std::make_shared(id)));
+ auto voc = res.first->second;
if ((attr = vocationNode.attribute("name"))) {
- voc.name = attr.as_string();
+ voc->name = attr.as_string();
}
if ((attr = vocationNode.attribute("clientid"))) {
- voc.clientId = pugi::cast(attr.value());
+ voc->clientId = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("baseid"))) {
- voc.baseId = pugi::cast(attr.value());
+ voc->baseId = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("description"))) {
- voc.description = attr.as_string();
+ voc->description = attr.as_string();
}
if ((attr = vocationNode.attribute("magicshield"))) {
- voc.magicShield = attr.as_bool();
+ voc->magicShield = attr.as_bool();
}
if ((attr = vocationNode.attribute("gaincap"))) {
- voc.gainCap = pugi::cast(attr.value()) * 100;
+ voc->gainCap = pugi::cast(attr.value()) * 100;
}
if ((attr = vocationNode.attribute("gainhp"))) {
- voc.gainHP = pugi::cast(attr.value());
+ voc->gainHP = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("gainmana"))) {
- voc.gainMana = pugi::cast(attr.value());
+ voc->gainMana = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("gainhpticks"))) {
- voc.gainHealthTicks = pugi::cast(attr.value());
+ voc->gainHealthTicks = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("gainhpamount"))) {
- voc.gainHealthAmount = pugi::cast(attr.value());
+ voc->gainHealthAmount = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("gainmanaticks"))) {
- voc.gainManaTicks = pugi::cast(attr.value());
+ voc->gainManaTicks = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("gainmanaamount"))) {
- voc.gainManaAmount = pugi::cast(attr.value());
+ voc->gainManaAmount = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("manamultiplier"))) {
- voc.manaMultiplier = pugi::cast(attr.value());
+ voc->manaMultiplier = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("attackspeed"))) {
- voc.attackSpeed = pugi::cast(attr.value());
+ voc->attackSpeed = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("basespeed"))) {
- voc.baseSpeed = pugi::cast(attr.value());
+ voc->baseSpeed = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("soulmax"))) {
- voc.soulMax = pugi::cast(attr.value());
+ voc->soulMax = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("gainsoulticks"))) {
- voc.gainSoulTicks = pugi::cast(attr.value());
+ voc->gainSoulTicks = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("fromvoc"))) {
- voc.fromVocation = pugi::cast(attr.value());
+ voc->fromVocation = pugi::cast(attr.value());
}
if ((attr = vocationNode.attribute("canCombat"))) {
- voc.combat = attr.as_bool();
+ voc->combat = attr.as_bool();
}
if ((attr = vocationNode.attribute("avatarlooktype"))) {
- voc.avatarLookType = pugi::cast(attr.value());
+ voc->avatarLookType = pugi::cast(attr.value());
}
for (auto childNode : vocationNode.children()) {
@@ -121,75 +126,75 @@ bool Vocations::loadFromXml() {
if (skillIdAttribute) {
uint16_t skill_id = pugi::cast(skillIdAttribute.value());
if (skill_id <= SKILL_LAST) {
- voc.skillMultipliers[skill_id] = pugi::cast(childNode.attribute("multiplier").value());
+ voc->skillMultipliers[skill_id] = pugi::cast(childNode.attribute("multiplier").value());
} else {
g_logger().warn("[Vocations::loadFromXml] - "
"No valid skill id: {} for vocation: {}",
- skill_id, voc.id);
+ skill_id, voc->id);
}
} else {
g_logger().warn("[Vocations::loadFromXml] - "
"Missing skill id for vocation: {}",
- voc.id);
+ voc->id);
}
} else if (strcasecmp(childNode.name(), "mitigation") == 0) {
pugi::xml_attribute factorAttribute = childNode.attribute("multiplier");
if (factorAttribute) {
- voc.mitigationFactor = pugi::cast(factorAttribute.value());
+ voc->mitigationFactor = pugi::cast(factorAttribute.value());
}
pugi::xml_attribute primaryShieldAttribute = childNode.attribute("primaryShield");
if (primaryShieldAttribute) {
- voc.mitigationPrimaryShield = pugi::cast(primaryShieldAttribute.value());
+ voc->mitigationPrimaryShield = pugi::cast(primaryShieldAttribute.value());
}
pugi::xml_attribute secondaryShieldAttribute = childNode.attribute("secondaryShield");
if (secondaryShieldAttribute) {
- voc.mitigationSecondaryShield = pugi::cast(secondaryShieldAttribute.value());
+ voc->mitigationSecondaryShield = pugi::cast(secondaryShieldAttribute.value());
}
} else if (strcasecmp(childNode.name(), "formula") == 0) {
pugi::xml_attribute meleeDamageAttribute = childNode.attribute("meleeDamage");
if (meleeDamageAttribute) {
- voc.meleeDamageMultiplier = pugi::cast(meleeDamageAttribute.value());
+ voc->meleeDamageMultiplier = pugi::cast(meleeDamageAttribute.value());
}
pugi::xml_attribute distDamageAttribute = childNode.attribute("distDamage");
if (distDamageAttribute) {
- voc.distDamageMultiplier = pugi::cast(distDamageAttribute.value());
+ voc->distDamageMultiplier = pugi::cast(distDamageAttribute.value());
}
pugi::xml_attribute defenseAttribute = childNode.attribute("defense");
if (defenseAttribute) {
- voc.defenseMultiplier = pugi::cast(defenseAttribute.value());
+ voc->defenseMultiplier = pugi::cast(defenseAttribute.value());
}
pugi::xml_attribute armorAttribute = childNode.attribute("armor");
if (armorAttribute) {
- voc.armorMultiplier = pugi::cast(armorAttribute.value());
+ voc->armorMultiplier = pugi::cast(armorAttribute.value());
}
} else if (strcasecmp(childNode.name(), "pvp") == 0) {
pugi::xml_attribute pvpDamageReceivedMultiplier = childNode.attribute("damageReceivedMultiplier");
if (pvpDamageReceivedMultiplier) {
- voc.pvpDamageReceivedMultiplier = pugi::cast(pvpDamageReceivedMultiplier.value());
+ voc->pvpDamageReceivedMultiplier = pugi::cast(pvpDamageReceivedMultiplier.value());
}
pugi::xml_attribute pvpDamageDealtMultiplier = childNode.attribute("damageDealtMultiplier");
if (pvpDamageDealtMultiplier) {
- voc.pvpDamageDealtMultiplier = pugi::cast(pvpDamageDealtMultiplier.value());
+ voc->pvpDamageDealtMultiplier = pugi::cast(pvpDamageDealtMultiplier.value());
}
} else if (strcasecmp(childNode.name(), "gem") == 0) {
pugi::xml_attribute qualityAttr = childNode.attribute("quality");
pugi::xml_attribute nameAttr = childNode.attribute("name");
auto quality = pugi::cast(qualityAttr.value());
auto name = nameAttr.as_string();
- voc.wheelGems[static_cast(quality)] = name;
+ voc->wheelGems[static_cast(quality)] = name;
}
}
}
return true;
}
-Vocation* Vocations::getVocation(uint16_t id) {
+std::shared_ptr Vocations::getVocation(uint16_t id) {
auto it = vocationsMap.find(id);
if (it == vocationsMap.end()) {
g_logger().warn("[Vocations::getVocation] - "
@@ -197,12 +202,12 @@ Vocation* Vocations::getVocation(uint16_t id) {
id);
return nullptr;
}
- return &it->second;
+ return it->second;
}
uint16_t Vocations::getVocationId(const std::string &name) const {
for (const auto &it : vocationsMap) {
- if (strcasecmp(it.second.name.c_str(), name.c_str()) == 0) {
+ if (strcasecmp(it.second->name.c_str(), name.c_str()) == 0) {
return it.first;
}
}
@@ -211,7 +216,7 @@ uint16_t Vocations::getVocationId(const std::string &name) const {
uint16_t Vocations::getPromotedVocation(uint16_t vocationId) const {
for (const auto &it : vocationsMap) {
- if (it.second.fromVocation == vocationId && it.first != vocationId) {
+ if (it.second->fromVocation == vocationId && it.first != vocationId) {
return it.first;
}
}
@@ -292,7 +297,7 @@ std::vector Vocation::getSupremeGemModifiers() {
auto allModifiers = magic_enum::enum_entries();
g_logger().debug("Loading supreme gem modifiers for vocation: {}", vocationName);
for (const auto &[value, modifierName] : allModifiers) {
- std::string targetVocation(modifierName.substr(0, modifierName.find("_")));
+ std::string targetVocation(modifierName.substr(0, modifierName.find('_')));
toLowerCaseString(targetVocation);
g_logger().debug("Checking supreme gem modifier: {}, targetVocation: {}", modifierName, targetVocation);
if (targetVocation == "general" || targetVocation.find(vocationName) != std::string::npos) {
diff --git a/src/creatures/players/vocations/vocation.hpp b/src/creatures/players/vocations/vocation.hpp
index a658e21ec..0ad95ac0f 100644
--- a/src/creatures/players/vocations/vocation.hpp
+++ b/src/creatures/players/vocations/vocation.hpp
@@ -179,16 +179,17 @@ class Vocations {
}
bool loadFromXml();
+ bool reload();
- Vocation* getVocation(uint16_t id);
- const std::map &getVocations() const {
+ std::shared_ptr getVocation(uint16_t id);
+ const std::map> &getVocations() const {
return vocationsMap;
}
uint16_t getVocationId(const std::string &name) const;
uint16_t getPromotedVocation(uint16_t vocationId) const;
private:
- std::map vocationsMap;
+ std::map> vocationsMap;
};
constexpr auto g_vocations = Vocations::getInstance;
diff --git a/src/game/functions/game_reload.cpp b/src/game/functions/game_reload.cpp
index 887877bb1..9a7246a86 100644
--- a/src/game/functions/game_reload.cpp
+++ b/src/game/functions/game_reload.cpp
@@ -52,6 +52,8 @@ bool GameReload::init(Reload_t reloadTypes) const {
return reloadScripts();
case Reload_t::RELOAD_TYPE_GROUPS:
return reloadGroups();
+ case Reload_t::RELOAD_TYPE_VOCATIONS:
+ return reloadVocations();
default:
return false;
}
@@ -201,3 +203,9 @@ bool GameReload::reloadGroups() const {
logReloadStatus("Groups", result);
return result;
}
+
+bool GameReload::reloadVocations() const {
+ const bool result = g_vocations().reload();
+ logReloadStatus("Vocations", result);
+ return result;
+}
diff --git a/src/game/functions/game_reload.hpp b/src/game/functions/game_reload.hpp
index e2f3789cf..45ff35c01 100644
--- a/src/game/functions/game_reload.hpp
+++ b/src/game/functions/game_reload.hpp
@@ -29,6 +29,7 @@ enum class Reload_t : uint8_t {
RELOAD_TYPE_RAIDS,
RELOAD_TYPE_SCRIPTS,
RELOAD_TYPE_GROUPS,
+ RELOAD_TYPE_VOCATIONS,
// Every is last
RELOAD_TYPE_LAST
@@ -65,6 +66,7 @@ class GameReload : public Game {
bool reloadRaids() const;
bool reloadScripts() const;
bool reloadGroups() const;
+ bool reloadVocations() const;
};
constexpr auto g_gameReload = GameReload::getInstance;
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 0dfba6f67..4e83325b6 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -8330,12 +8330,12 @@ std::string Game::generateVocationConditionHighscore(uint32_t vocation) {
const auto vocationsMap = g_vocations().getVocations();
for (const auto &it : vocationsMap) {
const auto &voc = it.second;
- if (voc.getFromVocation() == vocation) {
+ if (voc->getFromVocation() == vocation) {
if (firstVocation) {
- queryPart << " WHERE `vocation` = " << voc.getId();
+ queryPart << " WHERE `vocation` = " << voc->getId();
firstVocation = false;
} else {
- queryPart << " OR `vocation` = " << voc.getId();
+ queryPart << " OR `vocation` = " << voc->getId();
}
}
}
diff --git a/src/items/item.cpp b/src/items/item.cpp
index b51c076d8..eea07a4e5 100644
--- a/src/items/item.cpp
+++ b/src/items/item.cpp
@@ -2315,7 +2315,7 @@ std::string Item::getDescription(const ItemType &it, int32_t lookDistance, std::
s << " (\"" << it.runeSpellName << "\"). " << (it.stackable && tmpSubType > 1 ? "They" : "It") << " can only be used by ";
const VocSpellMap &vocMap = rune->getVocMap();
- std::vector showVocMap;
+ std::vector> showVocMap;
// vocations are usually listed with the unpromoted and promoted version, the latter being
// hidden from description, so `total / 2` is most likely the amount of vocations to be shown.
diff --git a/src/lib/thread/thread_pool.cpp b/src/lib/thread/thread_pool.cpp
index e01028810..3971d3a6a 100644
--- a/src/lib/thread/thread_pool.cpp
+++ b/src/lib/thread/thread_pool.cpp
@@ -8,7 +8,10 @@
*/
#include "pch.hpp"
+
#include "lib/thread/thread_pool.hpp"
+
+#include "game/game.hpp"
#include "utils/tools.hpp"
#ifndef DEFAULT_NUMBER_OF_THREADS
@@ -65,7 +68,6 @@ void ThreadPool::shutdown() {
while (status == std::future_status::timeout && std::chrono::steady_clock::now() - start < timeout) {
tries++;
if (tries > 5) {
- logger.error("Thread pool shutdown timed out.");
break;
}
for (auto &future : futures) {
@@ -84,7 +86,9 @@ asio::io_context &ThreadPool::getIoContext() {
void ThreadPool::addLoad(const std::function &load) {
asio::post(ioService, [this, load]() {
if (ioService.stopped()) {
- logger.error("Shutting down, cannot execute task.");
+ if (g_game().getGameState() != GAME_STATE_SHUTDOWN) {
+ logger.error("Shutting down, cannot execute task.");
+ }
return;
}
diff --git a/src/lua/callbacks/callbacks_definitions.hpp b/src/lua/callbacks/callbacks_definitions.hpp
index 521a2c4cd..3b8016f5f 100644
--- a/src/lua/callbacks/callbacks_definitions.hpp
+++ b/src/lua/callbacks/callbacks_definitions.hpp
@@ -56,6 +56,7 @@ enum class EventCallback_t : uint16_t {
playerOnCombat,
playerOnInventoryUpdate,
playerOnRotateItem,
+ playerOnWalk,
// Monster
monsterOnDropLoot,
monsterPostDropLoot,
diff --git a/src/lua/callbacks/event_callback.cpp b/src/lua/callbacks/event_callback.cpp
index 51c03131f..d2b88c30f 100644
--- a/src/lua/callbacks/event_callback.cpp
+++ b/src/lua/callbacks/event_callback.cpp
@@ -985,6 +985,29 @@ bool EventCallback::playerOnRotateItem(std::shared_ptr player, std::shar
return getScriptInterface()->callFunction(3);
}
+void EventCallback::playerOnWalk(std::shared_ptr player, Direction &dir) const {
+ if (!getScriptInterface()->reserveScriptEnv()) {
+ g_logger().error("[EventCallback::eventOnWalk - "
+ "Player {}] "
+ "Call stack overflow. Too many lua script calls being nested.",
+ player->getName());
+ return;
+ }
+
+ ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv();
+ scriptEnvironment->setScriptId(getScriptId(), getScriptInterface());
+
+ lua_State* L = getScriptInterface()->getLuaState();
+ getScriptInterface()->pushFunction(getScriptId());
+
+ LuaScriptInterface::pushUserdata(L, player);
+ LuaScriptInterface::setMetatable(L, -1, "Player");
+
+ lua_pushnumber(L, dir);
+
+ getScriptInterface()->callVoidFunction(2);
+}
+
void EventCallback::playerOnStorageUpdate(std::shared_ptr player, const uint32_t key, const int32_t value, int32_t oldValue, uint64_t currentTime) const {
if (!getScriptInterface()->reserveScriptEnv()) {
g_logger().error("[EventCallback::eventOnStorageUpdate - "
diff --git a/src/lua/callbacks/event_callback.hpp b/src/lua/callbacks/event_callback.hpp
index d9dc4a9b1..9141235a0 100644
--- a/src/lua/callbacks/event_callback.hpp
+++ b/src/lua/callbacks/event_callback.hpp
@@ -116,6 +116,7 @@ class EventCallback : public Script {
void playerOnCombat(std::shared_ptr player, std::shared_ptr target, std::shared_ptr
- item, CombatDamage &damage) const;
void playerOnInventoryUpdate(std::shared_ptr player, std::shared_ptr
- item, Slots_t slot, bool equip) const;
bool playerOnRotateItem(std::shared_ptr player, std::shared_ptr
- item, const Position &position) const;
+ void playerOnWalk(std::shared_ptr player, Direction &dir) const;
// Monster
void monsterOnDropLoot(std::shared_ptr monster, std::shared_ptr corpse) const;
diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp
index 621023e5c..94da8f4ff 100644
--- a/src/lua/functions/creatures/player/player_functions.cpp
+++ b/src/lua/functions/creatures/player/player_functions.cpp
@@ -1384,13 +1384,13 @@ int PlayerFunctions::luaPlayerSetVocation(lua_State* L) {
return 1;
}
- Vocation* vocation;
+ std::shared_ptr vocation;
if (isNumber(L, 2)) {
vocation = g_vocations().getVocation(getNumber(L, 2));
} else if (isString(L, 2)) {
vocation = g_vocations().getVocation(g_vocations().getVocationId(getString(L, 2)));
} else if (isUserdata(L, 2)) {
- vocation = getUserdata(L, 2);
+ vocation = getUserdataShared(L, 2);
} else {
vocation = nullptr;
}
diff --git a/src/lua/functions/creatures/player/vocation_functions.cpp b/src/lua/functions/creatures/player/vocation_functions.cpp
index 5e95a827f..15cf888bf 100644
--- a/src/lua/functions/creatures/player/vocation_functions.cpp
+++ b/src/lua/functions/creatures/player/vocation_functions.cpp
@@ -21,7 +21,7 @@ int VocationFunctions::luaVocationCreate(lua_State* L) {
vocationId = g_vocations().getVocationId(getString(L, 2));
}
- Vocation* vocation = g_vocations().getVocation(vocationId);
+ std::shared_ptr vocation = g_vocations().getVocation(vocationId);
if (vocation) {
pushUserdata(L, vocation);
setMetatable(L, -1, "Vocation");
@@ -33,7 +33,7 @@ int VocationFunctions::luaVocationCreate(lua_State* L) {
int VocationFunctions::luaVocationGetId(lua_State* L) {
// vocation:getId()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getId());
} else {
@@ -44,7 +44,7 @@ int VocationFunctions::luaVocationGetId(lua_State* L) {
int VocationFunctions::luaVocationGetClientId(lua_State* L) {
// vocation:getClientId()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getClientId());
} else {
@@ -55,7 +55,7 @@ int VocationFunctions::luaVocationGetClientId(lua_State* L) {
int VocationFunctions::luaVocationGetBaseId(lua_State* L) {
// vocation:getBaseId()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getBaseId());
} else {
@@ -66,7 +66,7 @@ int VocationFunctions::luaVocationGetBaseId(lua_State* L) {
int VocationFunctions::luaVocationGetName(lua_State* L) {
// vocation:getName()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
pushString(L, vocation->getVocName());
} else {
@@ -77,7 +77,7 @@ int VocationFunctions::luaVocationGetName(lua_State* L) {
int VocationFunctions::luaVocationGetDescription(lua_State* L) {
// vocation:getDescription()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
pushString(L, vocation->getVocDescription());
} else {
@@ -88,7 +88,7 @@ int VocationFunctions::luaVocationGetDescription(lua_State* L) {
int VocationFunctions::luaVocationGetRequiredSkillTries(lua_State* L) {
// vocation:getRequiredSkillTries(skillType, skillLevel)
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
skills_t skillType = getNumber(L, 2);
uint16_t skillLevel = getNumber(L, 3);
@@ -101,7 +101,7 @@ int VocationFunctions::luaVocationGetRequiredSkillTries(lua_State* L) {
int VocationFunctions::luaVocationGetRequiredManaSpent(lua_State* L) {
// vocation:getRequiredManaSpent(magicLevel)
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
uint32_t magicLevel = getNumber(L, 2);
lua_pushnumber(L, vocation->getReqMana(magicLevel));
@@ -113,7 +113,7 @@ int VocationFunctions::luaVocationGetRequiredManaSpent(lua_State* L) {
int VocationFunctions::luaVocationGetCapacityGain(lua_State* L) {
// vocation:getCapacityGain()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getCapGain());
} else {
@@ -124,7 +124,7 @@ int VocationFunctions::luaVocationGetCapacityGain(lua_State* L) {
int VocationFunctions::luaVocationGetHealthGain(lua_State* L) {
// vocation:getHealthGain()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getHPGain());
} else {
@@ -135,7 +135,7 @@ int VocationFunctions::luaVocationGetHealthGain(lua_State* L) {
int VocationFunctions::luaVocationGetHealthGainTicks(lua_State* L) {
// vocation:getHealthGainTicks()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getHealthGainTicks());
} else {
@@ -146,7 +146,7 @@ int VocationFunctions::luaVocationGetHealthGainTicks(lua_State* L) {
int VocationFunctions::luaVocationGetHealthGainAmount(lua_State* L) {
// vocation:getHealthGainAmount()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getHealthGainAmount());
} else {
@@ -157,7 +157,7 @@ int VocationFunctions::luaVocationGetHealthGainAmount(lua_State* L) {
int VocationFunctions::luaVocationGetManaGain(lua_State* L) {
// vocation:getManaGain()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getManaGain());
} else {
@@ -168,7 +168,7 @@ int VocationFunctions::luaVocationGetManaGain(lua_State* L) {
int VocationFunctions::luaVocationGetManaGainTicks(lua_State* L) {
// vocation:getManaGainTicks()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getManaGainTicks());
} else {
@@ -179,7 +179,7 @@ int VocationFunctions::luaVocationGetManaGainTicks(lua_State* L) {
int VocationFunctions::luaVocationGetManaGainAmount(lua_State* L) {
// vocation:getManaGainAmount()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getManaGainAmount());
} else {
@@ -190,7 +190,7 @@ int VocationFunctions::luaVocationGetManaGainAmount(lua_State* L) {
int VocationFunctions::luaVocationGetMaxSoul(lua_State* L) {
// vocation:getMaxSoul()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getSoulMax());
} else {
@@ -201,7 +201,7 @@ int VocationFunctions::luaVocationGetMaxSoul(lua_State* L) {
int VocationFunctions::luaVocationGetSoulGainTicks(lua_State* L) {
// vocation:getSoulGainTicks()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getSoulGainTicks());
} else {
@@ -212,7 +212,7 @@ int VocationFunctions::luaVocationGetSoulGainTicks(lua_State* L) {
int VocationFunctions::luaVocationGetBaseAttackSpeed(lua_State* L) {
// vocation:getBaseAttackSpeed()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getBaseAttackSpeed());
} else {
@@ -223,7 +223,7 @@ int VocationFunctions::luaVocationGetBaseAttackSpeed(lua_State* L) {
int VocationFunctions::luaVocationGetAttackSpeed(lua_State* L) {
// vocation:getAttackSpeed()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getAttackSpeed());
} else {
@@ -234,7 +234,7 @@ int VocationFunctions::luaVocationGetAttackSpeed(lua_State* L) {
int VocationFunctions::luaVocationGetBaseSpeed(lua_State* L) {
// vocation:getBaseSpeed()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (vocation) {
lua_pushnumber(L, vocation->getBaseSpeed());
} else {
@@ -245,7 +245,7 @@ int VocationFunctions::luaVocationGetBaseSpeed(lua_State* L) {
int VocationFunctions::luaVocationGetDemotion(lua_State* L) {
// vocation:getDemotion()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (!vocation) {
lua_pushnil(L);
return 1;
@@ -257,7 +257,7 @@ int VocationFunctions::luaVocationGetDemotion(lua_State* L) {
return 1;
}
- Vocation* demotedVocation = g_vocations().getVocation(fromId);
+ std::shared_ptr demotedVocation = g_vocations().getVocation(fromId);
if (demotedVocation && demotedVocation != vocation) {
pushUserdata(L, demotedVocation);
setMetatable(L, -1, "Vocation");
@@ -269,7 +269,7 @@ int VocationFunctions::luaVocationGetDemotion(lua_State* L) {
int VocationFunctions::luaVocationGetPromotion(lua_State* L) {
// vocation:getPromotion()
- Vocation* vocation = getUserdata(L, 1);
+ std::shared_ptr vocation = getUserdataShared(L, 1);
if (!vocation) {
lua_pushnil(L);
return 1;
@@ -281,7 +281,7 @@ int VocationFunctions::luaVocationGetPromotion(lua_State* L) {
return 1;
}
- Vocation* promotedVocation = g_vocations().getVocation(promotedId);
+ std::shared_ptr promotedVocation = g_vocations().getVocation(promotedId);
if (promotedVocation && promotedVocation != vocation) {
pushUserdata(L, promotedVocation);
setMetatable(L, -1, "Vocation");
diff --git a/src/lua/functions/creatures/player/vocation_functions.hpp b/src/lua/functions/creatures/player/vocation_functions.hpp
index 7205580f9..0895f6ac8 100644
--- a/src/lua/functions/creatures/player/vocation_functions.hpp
+++ b/src/lua/functions/creatures/player/vocation_functions.hpp
@@ -14,7 +14,7 @@
class VocationFunctions final : LuaScriptInterface {
public:
static void init(lua_State* L) {
- registerClass(L, "Vocation", "", VocationFunctions::luaVocationCreate);
+ registerSharedClass(L, "Vocation", "", VocationFunctions::luaVocationCreate);
registerMetaMethod(L, "Vocation", "__eq", VocationFunctions::luaUserdataCompare);
registerMethod(L, "Vocation", "getId", VocationFunctions::luaVocationGetId);
diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp
index 0d0d8e79f..22a5ae614 100644
--- a/src/server/network/protocol/protocolgame.cpp
+++ b/src/server/network/protocol/protocolgame.cpp
@@ -2144,12 +2144,12 @@ void ProtocolGame::sendHighscores(const std::vector &charact
uint32_t selectedVocation = 0xFFFFFFFF;
const auto vocationsMap = g_vocations().getVocations();
for (const auto &it : vocationsMap) {
- const Vocation &vocation = it.second;
- if (vocation.getFromVocation() == static_cast(vocation.getId())) {
- msg.add(vocation.getFromVocation()); // Vocation Id
- msg.addString(vocation.getVocName(), "ProtocolGame::sendHighscores - vocation.getVocName()"); // Vocation Name
+ const auto &vocation = it.second;
+ if (vocation->getFromVocation() == static_cast(vocation->getId())) {
+ msg.add(vocation->getFromVocation()); // Vocation Id
+ msg.addString(vocation->getVocName(), "ProtocolGame::sendHighscores - vocation.getVocName()"); // Vocation Name
++vocations;
- if (vocation.getFromVocation() == vocationId) {
+ if (vocation->getFromVocation() == vocationId) {
selectedVocation = vocationId;
}
}