From 955fddea27c32c67710bdb51da1bd6c78423fba0 Mon Sep 17 00:00:00 2001 From: Karin Date: Fri, 19 Apr 2024 17:26:31 -0300 Subject: [PATCH 01/14] fix: block gold pouch using in the obtain method (#2559) --- src/game/game.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/game/game.cpp b/src/game/game.cpp index cac0b596ebf..a11fde62c2c 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5384,6 +5384,11 @@ void Game::playerSetManagedContainer(uint32_t playerId, ObjectCategory_t categor return; } + if (container->getID() == ITEM_GOLD_POUCH && !isLootContainer) { + player->sendTextMessage(MESSAGE_FAILURE, "You can only set the gold pouch as a loot container."); + return; + } + if (container->getHoldingPlayer() != player) { player->sendCancelMessage("You must be holding the container to set it as a loot container."); return; From b73df39ce9832e5c000906e8c757272ca7ce02e7 Mon Sep 17 00:00:00 2001 From: Eduardo Augusto <38956084+duuh30@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:56:23 -0300 Subject: [PATCH 02/14] fix: two handed weapons (#2570) --- src/creatures/players/player.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 75e5b5ef2af..618ffea1081 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -3271,13 +3271,6 @@ ReturnValue Player::queryAdd(int32_t index, const std::shared_ptr &thing, case CONST_SLOT_LEFT: { if (item->isQuiver()) { ret = RETURNVALUE_CANNOTBEDRESSED; - } else if (slotPosition & SLOTP_LEFT) { - WeaponType_t type = item->getWeaponType(); - if (type == WEAPON_NONE || type == WEAPON_SHIELD || type == WEAPON_AMMO) { - ret = RETURNVALUE_CANNOTBEDRESSED; - } else { - ret = RETURNVALUE_NOERROR; - } } else if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_RIGHT]) { WeaponType_t type = item->getWeaponType(); @@ -3290,6 +3283,13 @@ ReturnValue Player::queryAdd(int32_t index, const std::shared_ptr &thing, } else { ret = RETURNVALUE_NOERROR; } + } else if (slotPosition & SLOTP_LEFT) { + WeaponType_t type = item->getWeaponType(); + if (type == WEAPON_NONE || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RETURNVALUE_CANNOTBEDRESSED; + } else { + ret = RETURNVALUE_NOERROR; + } } else if (inventory[CONST_SLOT_RIGHT]) { std::shared_ptr rightItem = inventory[CONST_SLOT_RIGHT]; WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType(); From 3fcf5856002a021820f1aec2341108bf5e2b04c7 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Tue, 23 Apr 2024 09:07:35 -0300 Subject: [PATCH 03/14] fix: improves on gamestore, fix on gitignore and items (#2415) --- .gitignore | 12 +++-- data/items/items.xml | 4 +- data/modules/scripts/gamestore/gamestore.lua | 2 +- data/modules/scripts/gamestore/init.lua | 46 +++++++++++--------- src/game/game.cpp | 8 ++-- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index d233ae17409..a73a8c3022c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ bld/ [Ll]og/ build/ vcproj/ +!vcproj/canary.sln +!vcproj/canary.vcxproj +!vcproj/settings.props # Visual Studio 2015/2017 cache/options directory .vs/ @@ -372,9 +375,7 @@ config.lua config_canary.lua client_assertions.txt .env -otservbr.otbm -canary.otbm -otservbr-custom.otbm +data-otservbr-global/world/otservbr.otbm # Extensions *.ini @@ -382,9 +383,6 @@ otservbr-custom.otbm *.exe *.manifest *.rar -*-house.xml -*-monster.xml -*-npc.xml monster_count.txt # SFTP for Sublime @@ -398,4 +396,4 @@ canary.old vcpkg_installed # CLION -cmake-build-debug* +cmake-build-* diff --git a/data/items/items.xml b/data/items/items.xml index 765c6e9db51..0bc1c782452 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -75427,7 +75427,7 @@ Granted by TibiaGoals.com"/> - + @@ -75455,7 +75455,7 @@ Granted by TibiaGoals.com"/> - + diff --git a/data/modules/scripts/gamestore/gamestore.lua b/data/modules/scripts/gamestore/gamestore.lua index 852bd4d4405..f000c4079af 100644 --- a/data/modules/scripts/gamestore/gamestore.lua +++ b/data/modules/scripts/gamestore/gamestore.lua @@ -6770,7 +6770,7 @@ for k, category in ipairs(GameStore.Categories) do offer.type = GameStore.OfferTypes.OFFER_TYPE_NONE end if not offer.coinType then - offer.coinType = GameStore.CoinType.Coin + offer.coinType = GameStore.CoinType.Transferable end end end diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index 6146f562307..e53bab813f0 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -206,6 +206,13 @@ GameStore.DefaultDescriptions = { TEMPLE = { "Need a quick way home? Buy this transportation service to get instantly teleported to your home temple. \n\nNote, you cannot use this service while having a battle sign or a protection zone block. Further, the service will not work in no-logout zones or close to your home temple." }, } +GameStore.ItemLimit = { + PREY_WILDCARD = 50, + INSTANT_REWARD_ACCESS = 90, + EXPBOOST = 6, + HIRELING = 10, +} + --==Parsing==-- GameStore.isItsPacket = function(byte) for k, v in pairs(GameStore.RecivedPackets) do @@ -507,8 +514,8 @@ function parseBuyStoreOffer(playerId, msg) if not pcallOk then local alertMessage = pcallError.code and pcallError.message or "Something went wrong. Your purchase has been cancelled." - if not pcallError.code then -- unhandled error - -- log some debugging info + -- unhandled error + if not pcallError.code then logger.warn("[parseBuyStoreOffer] - Purchase failed due to an unhandled script error. Stacktrace: {}", pcallError) end @@ -618,7 +625,6 @@ function sendOfferDescription(player, offerId, description) end function Player.canBuyOffer(self, offer) - local playerId = self:getId() local disabled, disabledReason = 0, "" if offer.disabled or not offer.type then disabled = 1 @@ -697,12 +703,12 @@ function Player.canBuyOffer(self, offer) disabledReason = "You already have this mount." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS then - if self:getCollectionTokens() >= 90 then + if self:getCollectionTokens() >= GameStore.ItemLimit.INSTANT_REWARD_ACCESS then disabled = 1 disabledReason = "You already have maximum of reward tokens." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYBONUS then - if self:getPreyCards() >= 50 then + if self:getPreyCards() >= GameStore.ItemLimit.PREY_WILDCARD then disabled = 1 disabledReason = "You already have maximum of prey wildcards." end @@ -722,7 +728,7 @@ function Player.canBuyOffer(self, offer) disabledReason = "You already have 3 slots released." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST then - if self:getStorageValue(GameStore.Storages.expBoostCount) == 6 then + if self:getStorageValue(GameStore.Storages.expBoostCount) == GameStore.ItemLimit.EXPBOOST then disabled = 1 disabledReason = "You can't buy XP Boost for today." end @@ -731,7 +737,7 @@ function Player.canBuyOffer(self, offer) disabledReason = "You already have an active XP boost." end elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HIRELING then - if self:getHirelingsCount() >= 10 then + if self:getHirelingsCount() >= GameStore.ItemLimit.HIRELING then disabled = 1 disabledReason = "You already have bought the maximum number of allowed hirelings." end @@ -1565,8 +1571,9 @@ function GameStore.processAllBlessingsPurchase(player, count) end function GameStore.processInstantRewardAccess(player, offerCount) - if player:getCollectionTokens() + offerCount >= 91 then - return error({ code = 1, message = "You cannot own more than 90 reward tokens." }) + local limit = GameStore.ItemLimit.INSTANT_REWARD_ACCESS + if player:getCollectionTokens() + offerCount >= limit + 1 then + return error({ code = 1, message = "You cannot own more than " .. limit .. " reward tokens." }) end player:setCollectionTokens(player:getCollectionTokens() + offerCount) end @@ -1641,7 +1648,6 @@ function GameStore.processHouseRelatedPurchase(player, offer) decoKit:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) end end - player:sendUpdateContainer(inbox) else for i = 1, offer.count do local decoKit = inbox:addItem(ITEM_DECORATION_KIT, 1) @@ -1653,10 +1659,10 @@ function GameStore.processHouseRelatedPurchase(player, offer) decoKit:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime()) end end - player:sendUpdateContainer(inbox) end end end + player:sendUpdateContainer(inbox) end end @@ -1677,11 +1683,7 @@ function GameStore.processOutfitPurchase(player, offerSexIdTable, addon) elseif player:hasOutfit(looktype, _addon) then return error({ code = 0, message = "You already own this outfit." }) else - if - not (player:addOutfitAddon(looktype, _addon)) -- TFS call failed - or (not player:hasOutfit(looktype, _addon)) -- Additional check; if the looktype doesn't match player sex for example, - -- then the TFS check will still pass... bug? (TODO) - then + if not player:addOutfitAddon(looktype, _addon) or not player:hasOutfit(looktype, _addon) then error({ code = 0, message = "There has been an issue with your outfit purchase. Your purchase has been cancelled." }) else player:addOutfitAddon(offerSexIdTable.male, _addon) @@ -1769,8 +1771,9 @@ function GameStore.processTaskHuntingThirdSlot(player) end function GameStore.processPreyBonusReroll(player, offerCount) - if player:getPreyCards() + offerCount >= 51 then - return error({ code = 1, message = "You cannot own more than 50 prey wildcards." }) + local limit = GameStore.ItemLimit.PREY_WILDCARD + if player:getPreyCards() + offerCount >= limit + 1 then + return error({ code = 1, message = "You cannot own more than " .. limit .. " prey wildcards." }) end player:addPreyCards(offerCount) end @@ -1812,8 +1815,8 @@ function GameStore.processHirelingPurchase(player, offer, productType, hirelingN return addPlayerEvent(sendStorePurchaseSuccessful, 650, player:getId(), message) -- If not, we ask him to do! else - if player:getHirelingsCount() >= 10 then - return error({ code = 1, message = "You cannot have more than 10 hirelings." }) + if player:getHirelingsCount() >= GameStore.ItemLimit.HIRELING then + return error({ code = 1, message = "You cannot have more than " .. GameStore.ItemLimit.HIRELING .. " hirelings." }) end -- TODO: Use the correct dialog (byte 0xDB) on client 1205+ -- for compatibility, request name using the change name dialog @@ -2219,7 +2222,8 @@ function sendHomePage(playerId) msg:sendToPlayer(player) end -function Player:openStore(serviceName) --exporting the method so other scripts can use to open store +--exporting the method so other scripts can use to open store +function Player:openStore(serviceName) local playerId = self:getId() openStore(playerId) diff --git a/src/game/game.cpp b/src/game/game.cpp index a11fde62c2c..0dfba6f6724 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -7841,9 +7841,11 @@ void Game::addBestiaryList(uint16_t raceid, std::string name) { } void Game::broadcastMessage(const std::string &text, MessageClasses type) const { - g_logger().info("Broadcasted message: {}", text); - for (const auto &it : players) { - it.second->sendTextMessage(type, text); + if (!text.empty()) { + g_logger().info("Broadcasted message: {}", text); + for (const auto &it : players) { + it.second->sendTextMessage(type, text); + } } } From bd72f6618e11221e2d8b8d58c3436e34e44fe0d9 Mon Sep 17 00:00:00 2001 From: Felipe Paluco <87909998+FelipePaluco@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:20:33 -0300 Subject: [PATCH 04/14] fix: transcendence applies correct status without unlocked stages (#2566) Fix the logic to ensure correct status application when using tiered legs and undergoing transcendence without any avatar stages unlocked. Remove the early return that blocks code execution in this scenario. Ensure proper application of damage reduction, 100% critical chance, and 15% critical damage. --- src/creatures/players/player.cpp | 2 +- src/creatures/players/wheel/player_wheel.cpp | 24 +++++++++++-------- .../players/wheel/wheel_definitions.hpp | 5 ++-- .../creatures/player/player_functions.cpp | 4 ++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 618ffea1081..bcb31a4abfa 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -6688,7 +6688,7 @@ void Player::triggerTranscendance() { outfit.lookType = getVocation()->getAvatarLookType(); outfitCondition->setOutfit(outfit); addCondition(outfitCondition); - wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR, OTSYS_TIME() + duration); + wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR_FORGE, OTSYS_TIME() + duration); g_game().addMagicEffect(getPosition(), CONST_ME_AVATAR_APPEAR); sendTextMessage(MESSAGE_ATTENTION, "Transcendance was triggered."); sendSkills(); diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index 77a15f7cbf7..20e8f683bf9 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -2410,21 +2410,25 @@ int32_t PlayerWheel::checkBattleHealingAmount() const { } int32_t PlayerWheel::checkAvatarSkill(WheelAvatarSkill_t skill) const { - if (skill == WheelAvatarSkill_t::NONE || getOnThinkTimer(WheelOnThink_t::AVATAR) <= OTSYS_TIME()) { + if (skill == WheelAvatarSkill_t::NONE || (getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL) <= OTSYS_TIME() && getOnThinkTimer(WheelOnThink_t::AVATAR_FORGE) <= OTSYS_TIME())) { return 0; } uint8_t stage = 0; - if (getInstant("Avatar of Light")) { - stage = getStage(WheelStage_t::AVATAR_OF_LIGHT); - } else if (getInstant("Avatar of Steel")) { - stage = getStage(WheelStage_t::AVATAR_OF_STEEL); - } else if (getInstant("Avatar of Nature")) { - stage = getStage(WheelStage_t::AVATAR_OF_NATURE); - } else if (getInstant("Avatar of Storm")) { - stage = getStage(WheelStage_t::AVATAR_OF_STORM); + if (getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL) > OTSYS_TIME()) { + if (getInstant("Avatar of Light")) { + stage = getStage(WheelStage_t::AVATAR_OF_LIGHT); + } else if (getInstant("Avatar of Steel")) { + stage = getStage(WheelStage_t::AVATAR_OF_STEEL); + } else if (getInstant("Avatar of Nature")) { + stage = getStage(WheelStage_t::AVATAR_OF_NATURE); + } else if (getInstant("Avatar of Storm")) { + stage = getStage(WheelStage_t::AVATAR_OF_STORM); + } else { + return 0; + } } else { - return 0; + stage = 3; } if (skill == WheelAvatarSkill_t::DAMAGE_REDUCTION) { diff --git a/src/creatures/players/wheel/wheel_definitions.hpp b/src/creatures/players/wheel/wheel_definitions.hpp index 8432e9dca21..c23d2adf53f 100644 --- a/src/creatures/players/wheel/wheel_definitions.hpp +++ b/src/creatures/players/wheel/wheel_definitions.hpp @@ -105,9 +105,10 @@ enum class WheelOnThink_t : uint8_t { FOCUS_MASTERY = 4, GIFT_OF_LIFE = 5, DIVINE_EMPOWERMENT = 6, - AVATAR = 7, + AVATAR_SPELL = 7, + AVATAR_FORGE = 8, - TOTAL_COUNT = 8 + TOTAL_COUNT = 9 }; enum class WheelStat_t : uint8_t { diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 3ba2c43841d..621023e5c52 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -3997,9 +3997,9 @@ int PlayerFunctions::luaPlayerAvatarTimer(lua_State* L) { } if (lua_gettop(L) == 1) { - lua_pushnumber(L, (lua_Number)player->wheel()->getOnThinkTimer(WheelOnThink_t::AVATAR)); + lua_pushnumber(L, (lua_Number)player->wheel()->getOnThinkTimer(WheelOnThink_t::AVATAR_SPELL)); } else { - player->wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR, getNumber(L, 2)); + player->wheel()->setOnThinkTimer(WheelOnThink_t::AVATAR_SPELL, getNumber(L, 2)); pushBoolean(L, true); } return 1; From e60612e9ad6da36c2de1824e597e5c3b6efd7615 Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 25 Apr 2024 10:12:36 -0300 Subject: [PATCH 05/14] fix: alana sio only to "aleta som" users (#2564) --- data/scripts/spells/house/kick.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/scripts/spells/house/kick.lua b/data/scripts/spells/house/kick.lua index b4b583c1007..265ac48f796 100644 --- a/data/scripts/spells/house/kick.lua +++ b/data/scripts/spells/house/kick.lua @@ -4,6 +4,7 @@ function spell.onCastSpell(player, variant) local targetPlayer = Player(variant:getString()) or player local guest = targetPlayer:getTile():getHouse() local owner = player:getTile():getHouse() + -- Owner kick yourself from house if targetPlayer == player then player:getPosition():sendMagicEffect(CONST_ME_POFF) @@ -11,6 +12,13 @@ function spell.onCastSpell(player, variant) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) return true end + + if not owner:canEditAccessList(GUEST_LIST, player) then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + player:getPosition():sendMagicEffect(CONST_ME_POFF) + return false + end + if not owner or not guest or not guest:kickPlayer(player, targetPlayer) then player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) player:getPosition():sendMagicEffect(CONST_ME_POFF) From 72794c40f1d13ffe64613cf67bae6c7bb1208281 Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 25 Apr 2024 10:12:52 -0300 Subject: [PATCH 06/14] fix: destroy field is working inside pz (#2558) --- data/scripts/runes/destroy_field_rune.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/scripts/runes/destroy_field_rune.lua b/data/scripts/runes/destroy_field_rune.lua index 024dbc1bcd7..8752be7eb51 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) From 69b25aa431c869e8758a2624fd9dd18de8508b87 Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 25 Apr 2024 10:13:12 -0300 Subject: [PATCH 07/14] fix: stamina not recovering when dead (#2557) --- src/creatures/players/player.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index bcb31a4abfa..39a8841fdf2 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -2918,6 +2918,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 +4674,8 @@ void Player::onPlacedCreature() { removePlayer(true); } + this->onChangeZone(this->getZoneType()); + sendUnjustifiedPoints(); } From 38651ca103ee8674bdd6bcb7a994e2cea2220afd Mon Sep 17 00:00:00 2001 From: Aluisio Penna Date: Thu, 25 Apr 2024 10:15:52 -0300 Subject: [PATCH 08/14] fix: kill count of each type of minotaur in 'Turmoil of War' quest log (#2569) --- .../quests/killing_in_the_name_of/monster_kill.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua index b2ba681d251..f8f7ddc5c6b 100644 --- a/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua +++ b/data-otservbr-global/scripts/creaturescripts/quests/killing_in_the_name_of/monster_kill.lua @@ -57,7 +57,7 @@ function deathEvent.onDeath(creature, _corpse, _lastHitKiller, mostDamageKiller) end end -- Minotaurs - killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, nil, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount) + killCheck(player, targetName, Storage.KillingInTheNameOf.BudrikMinos, 0, tasks.Budrik[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.MinotaurCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.MinotaurCount) -- Necromancers and Priestesses killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 0, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount) killCheck(player, targetName, Storage.KillingInTheNameOf.LugriNecromancers, 3, tasks.Lugri[1].creatures, Storage.Quest.U8_5.KillingInTheNameOf.AltKillCount.NecromancerCount, Storage.Quest.U8_5.KillingInTheNameOf.MonsterKillCount.NecromancerCount) From dd8cdde213f4258f8f577462d12622bb88872e3d Mon Sep 17 00:00:00 2001 From: svetrey <51045033+svetrey@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:17:02 +0100 Subject: [PATCH 09/14] fix: diamond arrow static attack points (#2560) --- data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua b/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua index 61984209287..36be8dc38b4 100644 --- a/data-otservbr-global/scripts/weapons/scripts/diamond_arrow.lua +++ b/data-otservbr-global/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 From 0f91db0e379714e20652fda85714a4259e7a2bba Mon Sep 17 00:00:00 2001 From: Jeswill David Bolivar Mendoza <76903590+jeswilldbm@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:17:53 +0200 Subject: [PATCH 10/14] fix: missing attribute in bone fiddle (#2531) --- data/items/items.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/items/items.xml b/data/items/items.xml index 0bc1c782452..421e981ad02 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -53105,6 +53105,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + From 13798e59702e8de713ae8f78fded17e2e5de35e2 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 25 Apr 2024 10:18:20 -0300 Subject: [PATCH 11/14] feat: include day count in getTimeInWords function (#2525) --- data/libs/functions/functions.lua | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/data/libs/functions/functions.lua b/data/libs/functions/functions.lua index 9eb1b0b57b5..c2349ce7fd6 100644 --- a/data/libs/functions/functions.lua +++ b/data/libs/functions/functions.lua @@ -67,17 +67,28 @@ end function getTimeInWords(secsParam) local secs = tonumber(secsParam) + local days = math.floor(secs / (24 * 3600)) + secs = secs - (days * 24 * 3600) local hours, minutes, seconds = getHours(secs), getMinutes(secs), getSeconds(secs) local timeStr = "" + if days > 0 then + timeStr = days .. (days > 1 and " days" or " day") + end + if hours > 0 then - timeStr = hours .. (hours > 1 and " hours" or " hour") + if timeStr ~= "" then + timeStr = timeStr .. ", " + end + + timeStr = timeStr .. hours .. (hours > 1 and " hours" or " hour") end if minutes > 0 then if timeStr ~= "" then timeStr = timeStr .. ", " end + timeStr = timeStr .. minutes .. (minutes > 1 and " minutes" or " minute") end @@ -85,9 +96,9 @@ function getTimeInWords(secsParam) if timeStr ~= "" then timeStr = timeStr .. " and " end + timeStr = timeStr .. seconds .. (seconds > 1 and " seconds" or " second") end - return timeStr end From 09984296e7172f550d3889670205bc7f0bda5bca Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 25 Apr 2024 10:18:39 -0300 Subject: [PATCH 12/14] feat: playerOnWalk event callback and afk talkaction (#2518) --- data/scripts/eventcallbacks/README.md | 1 + data/scripts/talkactions/gm/afk.lua | 94 +++++++++++++++++++++ src/creatures/players/player.cpp | 2 + src/lua/callbacks/callbacks_definitions.hpp | 1 + src/lua/callbacks/event_callback.cpp | 23 +++++ src/lua/callbacks/event_callback.hpp | 1 + 6 files changed, 122 insertions(+) create mode 100644 data/scripts/talkactions/gm/afk.lua diff --git a/data/scripts/eventcallbacks/README.md b/data/scripts/eventcallbacks/README.md index bdafcb41b33..601653574bd 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/talkactions/gm/afk.lua b/data/scripts/talkactions/gm/afk.lua new file mode 100644 index 00000000000..6167a2b6068 --- /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/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 39a8841fdf2..7e53f476683 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -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) { diff --git a/src/lua/callbacks/callbacks_definitions.hpp b/src/lua/callbacks/callbacks_definitions.hpp index 521a2c4cda7..3b8016f5f5b 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 51c03131fa9..d2b88c30f3a 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 d9dc4a9b110..9141235a028 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; 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 13/14] 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 14/14] 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; } }