From 5718a4d4f6cfa74296ca3789eb14ad9613bc937f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Lu=C3=ADs=20Lucarelo=20Lamonato?= Date: Thu, 25 Apr 2024 17:14:00 -0300 Subject: [PATCH 1/6] fix: brain head can be killed more than once (#2536) --- .../actions/quests/feaster_of_souls/portal_brain_head.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua index 725359eb64a..d06ccbefb70 100644 --- a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua +++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua @@ -103,26 +103,26 @@ function teleportBoss.onStepIn(creature, item, position, fromPosition) end local player = creature if player:getLevel() < config.requiredLevel then - player:teleportTo(fromPosition, true) + player:teleportTo(exitPosition, true) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to be level " .. config.requiredLevel .. " or higher.") return true end if locked then - player:teleportTo(fromPosition, true) + player:teleportTo(exitPosition, true) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. config.bossName .. ".") return false end if zone:countPlayers(IgnoredByMonsters) >= 5 then - player:teleportTo(fromPosition, true) + player:teleportTo(exitPosition, true) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The boss room is full.") return false end local timeLeft = player:getBossCooldown(config.bossName) - os.time() if timeLeft > 0 then - player:teleportTo(fromPosition, true) + player:teleportTo(exitPosition, true) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. getTimeInWords(timeLeft) .. " to face " .. config.bossName .. " again!") player:getPosition():sendMagicEffect(CONST_ME_POFF) From bf8aa79f2b7d234d12ee2123a7edae87f152b5d5 Mon Sep 17 00:00:00 2001 From: Beats Date: Thu, 25 Apr 2024 16:15:05 -0400 Subject: [PATCH 2/6] feat: shared_ptr vocation and vocation reload (#2555) --- data/scripts/talkactions/god/reload.lua | 1 + src/creatures/players/player.cpp | 6 +- src/creatures/players/player.hpp | 6 +- src/creatures/players/vocations/vocation.cpp | 85 ++++++++++--------- src/creatures/players/vocations/vocation.hpp | 7 +- src/game/functions/game_reload.cpp | 8 ++ src/game/functions/game_reload.hpp | 2 + src/game/game.cpp | 6 +- src/items/item.cpp | 2 +- .../creatures/player/player_functions.cpp | 4 +- .../creatures/player/vocation_functions.cpp | 48 +++++------ .../creatures/player/vocation_functions.hpp | 2 +- src/server/network/protocol/protocolgame.cpp | 10 +-- 13 files changed, 102 insertions(+), 85 deletions(-) diff --git a/data/scripts/talkactions/god/reload.lua b/data/scripts/talkactions/god/reload.lua index 5bf72868320..20b9431dbba 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/creatures/players/player.cpp b/src/creatures/players/player.cpp index 7e53f476683..92285c2bca6 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; } @@ -2395,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(); @@ -2490,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()); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 855b48a782b..70c8c05aecd 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 c9a276a641d..98dbaeb1bea 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 a658e21ece7..0ad95ac0fc0 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 887877bb1eb..9a7246a86ec 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 e2f3789cfde..45ff35c01a2 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 0dfba6f6724..4e83325b63d 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 b51c076d80b..eea07a4e5dd 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/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 621023e5c52..94da8f4ffbc 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 5e95a827ff9..15cf888bf8a 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 7205580f90f..0895f6ac8d6 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 0d0d8e79f17..22a5ae614ba 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; } } From 77368ae224f6eaf6e3e7f58fe62a644e2efc929f Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 25 Apr 2024 19:03:34 -0300 Subject: [PATCH 3/6] refactor: "requestLockerItems" for improved safety and clarity (#2565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This introduces a refactoring of the requestLockerItems function to enhance its safety, prevent potential infinite loops, and improve code clarity. Key changes include: • Loop Structure: Replaced the original do-while loop with a safer for loop that ensures all accesses are within vector bounds. • Loop Removal: Eliminated the second do-while loop that manipulated countSize, which could lead to infinite loops and overflow issues. • Simplified Item Handling: Streamlined the handling of items from stashToSend, directly updating lockerItems without unnecessary intermediate steps. • General Cleanup: Improved overall readability and maintainability of the code, making it easier to understand and less prone to errors. These modifications ensure that the function behaves as intended while being more robust and easier to debug. Additionally, a small problem was fixed that prevented the server from shutting down. Resolves #2561 --- src/canary_server.cpp | 1 + src/creatures/players/player.cpp | 50 ++++++++++---------------------- src/lib/thread/thread_pool.cpp | 8 +++-- 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/canary_server.cpp b/src/canary_server.cpp index 0961bc93fde..c40534deb89 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -389,4 +389,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 92285c2bca6..516ed7b5cfa 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -6910,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 {}; } @@ -6920,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; } @@ -6945,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/lib/thread/thread_pool.cpp b/src/lib/thread/thread_pool.cpp index e01028810bc..3971d3a6a02 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; } From 142066af378319684f7c9e980c020b021a64a579 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 25 Apr 2024 22:49:56 -0300 Subject: [PATCH 4/6] feat: not show royal costume or golden outfit if player doesn't has (#2527) Check if the player already has the royal or golden outfit to show in customize character windows. --- src/server/network/protocol/protocolgame.cpp | 24 ++++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 22a5ae614ba..13b328dd2cf 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -6707,17 +6707,21 @@ void ProtocolGame::sendOutfitWindow() { msg.addByte(0x00); ++outfitSize; } else if (outfit->lookType == 1210 || outfit->lookType == 1211) { - msg.add(outfit->lookType); - msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); - msg.addByte(3); - msg.addByte(0x02); - ++outfitSize; + if (player->canWear(1210, 0) || player->canWear(1211, 0)) { + msg.add(outfit->lookType); + msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); + msg.addByte(3); + msg.addByte(0x02); + ++outfitSize; + } } else if (outfit->lookType == 1456 || outfit->lookType == 1457) { - msg.add(outfit->lookType); - msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); - msg.addByte(3); - msg.addByte(0x03); - ++outfitSize; + if (player->canWear(1456, 0) || player->canWear(1457, 0)) { + msg.add(outfit->lookType); + msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); + msg.addByte(3); + msg.addByte(0x03); + ++outfitSize; + } } else if (outfit->from == "store") { msg.add(outfit->lookType); msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); From 56793a0a6514ad3fb09771f22cd3b5f452ea7c76 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Silva <104630060+CarlosE-Dev@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:54:10 -0300 Subject: [PATCH 5/6] feat: The Lost Brother Quest (#2454) Implementation of the quest "The Lost Brother". It's a small quest initiated with the NPC Tarun, with one of the rewards being able to negotiate with the NPC. So, this part was also modified so that the negotiation can only be done once this quest is complete. --- data-otservbr-global/lib/core/quests.lua | 14 +++- data-otservbr-global/lib/core/storages.lua | 3 +- data-otservbr-global/npc/tarun.lua | 68 +++++++++++++++++++ .../adventurers_guild/warrior_skeleton.lua | 5 ++ .../creaturescripts/customs/freequests.lua | 2 + .../movement-find-remains.lua | 19 ++++++ 6 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 data-otservbr-global/scripts/quests/the_lost_brother/movement-find-remains.lua diff --git a/data-otservbr-global/lib/core/quests.lua b/data-otservbr-global/lib/core/quests.lua index ef61ee2549b..a35dcb15dea 100644 --- a/data-otservbr-global/lib/core/quests.lua +++ b/data-otservbr-global/lib/core/quests.lua @@ -5592,7 +5592,7 @@ if not Quests then }, [41] = { name = "Adventurers Guild", - startStorageId = Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton, + startStorageId = Storage.AdventurersGuild.QuestLine, startStorageValue = 1, missions = { [1] = { @@ -5606,6 +5606,18 @@ if not Quests then But the dragon hoards might justify the risks. You killed %d/50 dragons and dragon lords."):format(math.max(player:getStorageValue(Storage.AdventurersGuild.GreatDragonHunt.DragonCounter), 0)) end, }, + [2] = { + name = "The Lost Brother", + storageId = Storage.AdventurersGuild.TheLostBrother, + missionId = 11000, + startValue = 1, + endValue = 3, + states = { + [1] = "At the Kha'zeel Mountains you met the merchant Tarun. His brother has gone missing and was last seen following a beautiful woman into a palace. Tarun fears this woman might have been a demon.", + [2] = "You found the remains of Tarun's brother containing a message. Go back to Tarun and report his brother's last words.", + [3] = "You told Tarun about his brother's sad fate. He was very downhearted but gave you his sincere thanks. The beautiful asuri have taken a young man's life and the happiness of another one.", + }, + }, }, }, [42] = { diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index 6ce29967451..fce8059f7d1 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -1570,6 +1570,8 @@ Storage = { WarriorSkeleton = 52146, DragonCounter = 52147, }, + QuestLine = 52148, + TheLostBrother = 52149, }, DreamersChallenge = { -- Reserved storage from 52160 - 52199 @@ -2534,7 +2536,6 @@ Storage = { NightmareTeddy = {}, PoacherCavesMiniWorldChange = {}, TheGreatDragonHunt = {}, - TheLostBrother = {}, TheTaintedSouls = {}, }, U10_90 = { -- update 10.90 - Reserved Storages 45201 - 45350 diff --git a/data-otservbr-global/npc/tarun.lua b/data-otservbr-global/npc/tarun.lua index 31f56602f81..9f0b8be5883 100644 --- a/data-otservbr-global/npc/tarun.lua +++ b/data-otservbr-global/npc/tarun.lua @@ -51,6 +51,74 @@ npcType.onCloseChannel = function(npc, creature) npcHandler:onCloseChannel(npc, creature) end +local function creatureSayCallback(npc, creature, type, message) + local player = Player(creature) + if not player then + return false + end + local playerId = player:getId() + + if not npcHandler:checkInteraction(npc, creature) then + return false + end + + local theLostBrotherStorage = player:getStorageValue(Storage.AdventurersGuild.TheLostBrother) + if MsgContains(message, "mission") then + if theLostBrotherStorage < 1 then + npcHandler:say({ + "My brother is missing. I fear, he went to this evil palace north of here. A place of great beauty, certainly filled with riches and luxury. But in truth it is a threshold to hell and demonesses are after his blood. ...", + "He is my brother, and I am deeply ashamed to admit but I don't dare to go there. Perhaps your heart is more courageous than mine. Would you go to see this place and search for my brother?", + }, npc, creature) + npcHandler:setTopic(playerId, 1) + elseif theLostBrotherStorage == 1 then + npcHandler:say("I hope you will find my brother.", npc, creature) + npcHandler:setTopic(playerId, 0) + elseif theLostBrotherStorage == 2 then + npcHandler:say({ + "So, he is dead as I feared. I warned him not to go with this woman, but he gave in to temptation. My heart darkens and moans. But you have my sincere thanks. ...", + "Without your help I would have stayed in the dark about his fate. Please, take this as a little recompense.", + }, npc, creature) + player:addItem(3039, 1) + player:addExperience(3000, true) + player:setStorageValue(Storage.AdventurersGuild.TheLostBrother, 3) + npcHandler:setTopic(playerId, 0) + end + elseif npcHandler:getTopic(playerId) == 1 then + if MsgContains(message, "yes") then + npcHandler:say("I thank you! This is more than I could hope!", npc, creature) + if theLostBrotherStorage < 1 then + player:setStorageValue(Storage.AdventurersGuild.QuestLine, 1) + end + player:setStorageValue(Storage.AdventurersGuild.TheLostBrother, 1) + elseif MsgContains(message, "no") then + npcHandler:say("As you wish.", npc, creature) + end + npcHandler:setTopic(playerId, 0) + end + + return true +end + +local function onTradeRequest(npc, creature) + local player = Player(creature) + if not player then + return false + end + local playerId = player:getId() + + if player:getStorageValue(Storage.AdventurersGuild.TheLostBrother) ~= 3 then + return false + end + + return true +end + +npcHandler:setMessage(MESSAGE_GREET, "Greetings!") +npcHandler:setMessage(MESSAGE_FAREWELL, "Farewell.") +npcHandler:setMessage(MESSAGE_SENDTRADE, "Of course, just have a look.") +npcHandler:setCallback(CALLBACK_ON_TRADE_REQUEST, onTradeRequest) +npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) +npcHandler:setMessage(MESSAGE_WALKAWAY, "Farewell.") npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) npcConfig.shop = { diff --git a/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua b/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua index a1aec80aaee..73a5d3f8acb 100644 --- a/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua +++ b/data-otservbr-global/scripts/actions/quests/adventurers_guild/warrior_skeleton.lua @@ -3,6 +3,11 @@ function adventurersWarriorSkeleton.onUse(player, item, fromPosition, target, to if player:getStorageValue(Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton) < 1 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have discovered a deceased warrior's skeleton. It seems he tried to hunt the dragons around here - and failed.") player:addItem(5882, 1) -- red dragon scale + + if player:getStorageValue(Storage.AdventurersGuild.QuestLine) < 1 then + player:setStorageValue(Storage.AdventurersGuild.QuestLine, 1) + end + player:setStorageValue(Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton, 1) player:setStorageValue(Storage.AdventurersGuild.GreatDragonHunt.DragonCounter, 0) else diff --git a/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua b/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua index ac8f4506c70..ad0e8d49b73 100644 --- a/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua +++ b/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua @@ -275,8 +275,10 @@ local questTable = { { storage = Storage.Quest.U11_40.ThreatenedDreams.QuestLine, storageValue = 1 }, { storage = Storage.Quest.U11_40.ThreatenedDreams.Mission01[1], storageValue = 16 }, { storage = Storage.Quest.U11_40.ThreatenedDreams.Mission02.KroazurAccess, storageValue = 1 }, + { storage = Storage.AdventurersGuild.QuestLine, storageValue = 1 }, { storage = Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton, storageValue = 1 }, { storage = Storage.AdventurersGuild.GreatDragonHunt.WarriorSkeleton, storageValue = 2 }, + { storage = Storage.AdventurersGuild.TheLostBrother, storageValue = 3 }, { storage = Storage.Quest.U10_55.Dawnport.Questline, storageValue = 1 }, { storage = Storage.Quest.U10_55.Dawnport.GoMain, storageValue = 1 }, { storage = Storage.ForgottenKnowledge.AccessDeath, storageValue = 1 }, diff --git a/data-otservbr-global/scripts/quests/the_lost_brother/movement-find-remains.lua b/data-otservbr-global/scripts/quests/the_lost_brother/movement-find-remains.lua new file mode 100644 index 00000000000..c2ddb8efcb7 --- /dev/null +++ b/data-otservbr-global/scripts/quests/the_lost_brother/movement-find-remains.lua @@ -0,0 +1,19 @@ +local findRemains = MoveEvent() + +function findRemains.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return true + end + + if player:getStorageValue(Storage.AdventurersGuild.TheLostBrother) == 1 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You stumble over some old bones. Something is carved into the stone wall here: 'Tarun, my brother, you were right. She's evil.'") + player:setStorageValue(Storage.AdventurersGuild.TheLostBrother, 2) + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + end + + return true +end + +findRemains:position(Position(32959, 32674, 4)) +findRemains:register() From e133c33b9a43ed7a3c413ee844120133a7e201fa Mon Sep 17 00:00:00 2001 From: Eduardo Augusto <38956084+duuh30@users.noreply.github.com> Date: Fri, 26 Apr 2024 08:45:25 -0300 Subject: [PATCH 6/6] fix: use flask on familiar (#2571) --- src/game/game.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/game/game.cpp b/src/game/game.cpp index 4e83325b63d..6670575d991 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3744,6 +3744,19 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uin return; } } + + const std::shared_ptr monster = creature->getMonster(); + if (monster && monster->isFamiliar() && creature->getMaster()->getPlayer() == player && (it.isRune() || it.type == ITEM_TYPE_POTION)) { + player->setNextPotionAction(OTSYS_TIME() + g_configManager().getNumber(EX_ACTIONS_DELAY_INTERVAL, __FUNCTION__)); + + if (it.isMultiUse()) { + player->sendUseItemCooldown(g_configManager().getNumber(EX_ACTIONS_DELAY_INTERVAL, __FUNCTION__)); + } + + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + Position toPos = creature->getPosition(); Position walkToPos = fromPos; ReturnValue ret = g_actions().canUse(player, fromPos);