From f219099d65746d3b0047d10692d06000524363d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Lu=C3=ADs=20Lucarelo=20Lamonato?= Date: Thu, 19 Oct 2023 01:45:55 -0300 Subject: [PATCH 01/14] fix: stack overflow in bug report (#1704) fix the FS.mkdir_p(path) function that was causing stackoverflow exception I made a change so that the code works as follows: The path is divided into parts, then the function goes through the components, creating the directory if it does not exist. The process repeats until all components of the path have been successfully completed, avoiding stackoverflow exception. At the end, the function returns true to indicate that the operation was successful. --- data/libs/functions/fs.lua | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/data/libs/functions/fs.lua b/data/libs/functions/fs.lua index 46e81a7352f..f1a98d66f06 100644 --- a/data/libs/functions/fs.lua +++ b/data/libs/functions/fs.lua @@ -24,9 +24,27 @@ function FS.mkdir_p(path) if path == "" then return true end - if FS.exists(path) then - return true + + local components = {} + for component in path:gmatch("[^/\\]+") do + table.insert(components, component) end - FS.mkdir_p(path:match("(.*[/\\])")) - return FS.mkdir(path) + + local currentPath = "" + for i, component in ipairs(components) do + currentPath = currentPath .. component + + if not FS.exists(currentPath) then + local success, err = FS.mkdir(currentPath) + if not success then + return false, err + end + end + + if i < #components then + currentPath = currentPath .. "/" + end + end + + return true end From 7f5fce13ef08a02ad3af409f7ca96c5f8942984c Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Thu, 19 Oct 2023 11:23:55 -0300 Subject: [PATCH 02/14] feat: vector-sort (#1690) vector_sort is a container that contains sorted objects. --- src/pch.hpp | 1 + src/utils/vectorsort.hpp | 178 +++++++++++++++++++++++++++++++++++++++ vcproj/canary.vcxproj | 1 + 3 files changed, 180 insertions(+) create mode 100644 src/utils/vectorsort.hpp diff --git a/src/pch.hpp b/src/pch.hpp index c2308989b8f..84d07897ba1 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -18,6 +18,7 @@ #include "utils/definitions.hpp" #include "utils/simd.hpp" #include "utils/vectorset.hpp" +#include "utils/vectorsort.hpp" // -------------------- // STL Includes diff --git a/src/utils/vectorsort.hpp b/src/utils/vectorsort.hpp new file mode 100644 index 00000000000..7069e5463c8 --- /dev/null +++ b/src/utils/vectorsort.hpp @@ -0,0 +1,178 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include +#include + +// # Mehah +// vector_sort is a container that contains sorted objects. + +namespace stdext { + template + class vector_sort { + public: + bool contains(const T &v) { + update(); + return std::ranges::binary_search(container, v); + } + + bool erase(const T &v) { + update(); + + const auto &it = std::ranges::lower_bound(container, v); + if (it == container.end()) { + return false; + } + container.erase(it); + + return true; + } + + bool erase(const size_t begin, const size_t end) { + update(); + + if (begin > size() || end > size()) { + return false; + } + + container.erase(container.begin() + begin, container.begin() + end); + return true; + } + + template + bool erase_if(F fnc) { + update(); + return std::erase_if(container, std::move(fnc)) > 0; + } + + auto &front() { + update(); + return container.front(); + } + + void pop_back() { + update(); + container.pop_back(); + } + + auto &back() { + update(); + return container.back(); + } + + void push_back(const T &v) { + needUpdate = true; + container.push_back(v); + } + + void push_back(T &&_Val) { + needUpdate = true; + container.push_back(std::move(_Val)); + } + + // Copy all content list to this + auto insert_all(const vector_sort &list) { + needUpdate = true; + return container.insert(container.end(), list.begin(), list.end()); + } + + // Copy all content list to this + auto insert_all(const std::vector &list) { + needUpdate = true; + return container.insert(container.end(), list.begin(), list.end()); + } + + // Move all content list to this + auto join(vector_sort &list) { + needUpdate = true; + auto res = container.insert(container.end(), make_move_iterator(list.begin()), make_move_iterator(list.end())); + list.clear(); + return res; + } + + // Move all content list to this + auto join(std::vector &list) { + needUpdate = true; + auto res = container.insert(container.end(), make_move_iterator(list.begin()), make_move_iterator(list.end())); + list.clear(); + return res; + } + + template + decltype(auto) emplace_back(_Valty &&... v) { + needUpdate = true; + return container.emplace_back(std::forward<_Valty>(v)...); + } + + void partial_sort(size_t begin, size_t end = 0) { + partial_begin = begin; + if (end > begin) { + partial_end = size() - end; + } + } + + void notify_sort() { + needUpdate = true; + } + + bool empty() const noexcept { + return container.empty(); + } + + size_t size() const noexcept { + return container.size(); + } + + auto begin() noexcept { + update(); + return container.begin(); + } + + auto end() noexcept { + return container.end(); + } + + void clear() noexcept { + partial_begin = partial_end = 0; + return container.clear(); + } + + void reserve(size_t newCap) noexcept { + container.reserve(newCap); + } + + const auto &data() noexcept { + update(); + return container.data(); + } + + T &operator[](const size_t i) { + update(); + return container[i]; + } + + private: + inline void update() noexcept { + if (!needUpdate) { + return; + } + + needUpdate = false; + std::ranges::sort(container.begin() + partial_begin, container.end() - partial_end, std::less()); + } + + std::vector container; + + bool needUpdate = false; + size_t partial_begin { 0 }; + size_t partial_end { 0 }; + }; +} diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index e7fa84338e0..67268dff0f7 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -220,6 +220,7 @@ + From 51eb0b29017ebebfec2c2ede2b27083d806f77c4 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 19 Oct 2023 11:14:12 -0700 Subject: [PATCH 03/14] feat: fully async saves (#1560) Credits: @beats-dh. This is highly inspired and done with Beats' help. Save intervals are pretty frustrating, they lock the server up and no one is excited to see them come. This makes that whole process asynchronous and speeds everything up. Only saves that happen during the interval will be async, everything else stays the same. This means that logging out, using the market, etc, will still directly save the player. When that happens, the player is then automatically removed from the save queue. --- src/creatures/creature.cpp | 4 +- src/creatures/creature.hpp | 1 + src/creatures/monsters/monster.cpp | 5 +- src/creatures/players/player.cpp | 22 +-- src/creatures/players/player.hpp | 21 +++ src/creatures/players/wheel/player_wheel.cpp | 2 +- src/game/CMakeLists.txt | 1 + src/game/bank/bank.cpp | 5 +- src/game/game.cpp | 49 ++---- src/game/game.hpp | 11 +- src/game/scheduling/save_manager.cpp | 144 ++++++++++++++++++ src/game/scheduling/save_manager.hpp | 46 ++++++ src/io/iologindata.cpp | 8 +- src/io/iomap.cpp | 2 +- src/io/iomapserialize.cpp | 5 +- src/io/iomarket.cpp | 3 +- src/items/bed.cpp | 3 +- src/items/containers/container.cpp | 2 +- src/items/containers/mailbox/mailbox.cpp | 3 +- .../functions/core/game/global_functions.cpp | 3 +- .../creatures/player/player_functions.cpp | 11 +- src/lua/functions/items/item_functions.cpp | 1 + src/map/house/house.cpp | 5 +- src/server/signals.cpp | 3 +- src/utils/benchmark.hpp | 2 +- vcproj/canary.vcxproj | 4 +- 26 files changed, 288 insertions(+), 78 deletions(-) create mode 100644 src/game/scheduling/save_manager.cpp create mode 100644 src/game/scheduling/save_manager.hpp diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 9601af20e18..9825f50076a 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -738,8 +738,8 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared dropLoot(corpse->getContainer(), lastHitCreature); corpse->startDecaying(); bool corpses = corpse->isRewardCorpse() || (corpse->getID() == ITEM_MALE_CORPSE || corpse->getID() == ITEM_FEMALE_CORPSE); - if (corpse->getContainer() && mostDamageCreature && mostDamageCreature->getPlayer() && !corpses) { - const auto player = mostDamageCreature->getPlayer(); + const auto player = mostDamageCreature ? mostDamageCreature->getPlayer() : nullptr; + if (corpse->getContainer() && player && !corpses) { auto monster = getMonster(); if (monster && !monster->isRewardBoss()) { std::ostringstream lootMessage; diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index b6a8df61a8f..9dd5745c353 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -493,6 +493,7 @@ class Creature : virtual public Thing, public SharedObject { std::shared_ptr getParent() override final { return getTile(); } + void setParent(std::weak_ptr cylinder) override final { auto lockedCylinder = cylinder.lock(); if (lockedCylinder) { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 013c5412137..9bef75a5ecb 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -225,6 +225,9 @@ void Monster::onCreatureMove(std::shared_ptr creature, std::shared_ptr } updateIdleStatus(); + if (!m_attackedCreature.expired()) { + return; + } if (!isSummon()) { auto followCreature = getFollowCreature(); @@ -792,7 +795,7 @@ void Monster::onThink(uint32_t interval) { // This happens just after a master orders an attack, so lets follow it aswell. setFollowCreature(attackedCreature); } - } else if (!targetIDList.empty()) { + } else if (!attackedCreature && !targetIDList.empty()) { if (!followCreature || !hasFollowPath) { searchTarget(TARGETSEARCH_NEAREST); } else if (isFleeing()) { diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index b596e65e529..996c8de5d41 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -19,6 +19,7 @@ #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" #include "game/scheduling/task.hpp" +#include "game/scheduling/save_manager.hpp" #include "grouping/familiars.hpp" #include "lua/creature/creatureevent.hpp" #include "lua/creature/events.hpp" @@ -1715,17 +1716,18 @@ void Player::onAttackedCreatureChangeZone(ZoneType_t zone) { void Player::onRemoveCreature(std::shared_ptr creature, bool isLogout) { Creature::onRemoveCreature(creature, isLogout); + auto player = getPlayer(); - if (creature == getPlayer()) { + if (creature == player) { if (isLogout) { if (party) { - party->leaveParty(static_self_cast()); + party->leaveParty(player); } if (guild) { - guild->removeMember(static_self_cast()); + guild->removeMember(player); } - g_game().removePlayerUniqueLogin(static_self_cast()); + g_game().removePlayerUniqueLogin(player); loginPosition = getPosition(); lastLogout = time(nullptr); g_logger().info("{} has logged out", getName()); @@ -1739,16 +1741,12 @@ void Player::onRemoveCreature(std::shared_ptr creature, bool isLogout) } if (tradePartner) { - g_game().internalCloseTrade(static_self_cast()); + g_game().internalCloseTrade(player); } closeShopWindow(); - for (uint32_t tries = 0; tries < 3; ++tries) { - if (IOLoginData::savePlayer(static_self_cast())) { - break; - } - } + g_saveManager().savePlayer(player); } if (creature == shopOwner) { @@ -4048,7 +4046,9 @@ void Player::postRemoveNotification(std::shared_ptr thing, std::shared_pt assert(i ? i->getContainer() != nullptr : true); if (i) { - requireListUpdate = i->getContainer()->getHoldingPlayer() != getPlayer(); + if (auto container = i->getContainer()) { + requireListUpdate = container->getHoldingPlayer() != getPlayer(); + } } else { requireListUpdate = newParent != getPlayer(); } diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 19614aa3d7e..306fed3f2b3 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -94,6 +94,23 @@ static constexpr int32_t PLAYER_SOUND_HEALTH_CHANGE = 10; class Player final : public Creature, public Cylinder, public Bankable { public: + class PlayerLock { + public: + explicit PlayerLock(const std::shared_ptr &p) : + player(p) { + player->mutex.lock(); + } + + PlayerLock(const PlayerLock &) = delete; + + ~PlayerLock() { + player->mutex.unlock(); + } + + private: + const std::shared_ptr &player; + }; + explicit Player(ProtocolGame_ptr p); ~Player(); @@ -2504,6 +2521,9 @@ class Player final : public Creature, public Cylinder, public Bankable { std::shared_ptr getLootPouch(); private: + friend class PlayerLock; + std::mutex mutex; + static uint32_t playerFirstID; static uint32_t playerLastID; @@ -2862,6 +2882,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void clearCooldowns(); friend class Game; + friend class SaveManager; friend class Npc; friend class PlayerFunctions; friend class NetworkMessageFunctions; diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index dc7d0ee77f6..e6b02777828 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -849,7 +849,7 @@ void PlayerWheel::saveSlotPointsOnPressSaveButton(NetworkMessage &msg) { initializePlayerData(); registerPlayerBonusData(); - g_logger().debug("Player: {} is saved the all slots info in: {} seconds", m_player.getName(), bm_saveSlot.duration()); + g_logger().debug("Player: {} is saved the all slots info in: {} milliseconds", m_player.getName(), bm_saveSlot.duration()); } /* diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index fdf967662a7..dc7758f996d 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -7,5 +7,6 @@ target_sources(${PROJECT_NAME}_lib PRIVATE scheduling/events_scheduler.cpp scheduling/dispatcher.cpp scheduling/task.cpp + scheduling/save_manager.cpp zones/zone.cpp ) diff --git a/src/game/bank/bank.cpp b/src/game/bank/bank.cpp index bab052b8a38..63952e917b8 100644 --- a/src/game/bank/bank.cpp +++ b/src/game/bank/bank.cpp @@ -13,6 +13,7 @@ #include "game/game.hpp" #include "creatures/players/player.hpp" #include "io/iologindata.hpp" +#include "game/scheduling/save_manager.hpp" Bank::Bank(const std::shared_ptr bankable) : m_bankable(bankable) { @@ -25,14 +26,14 @@ Bank::~Bank() { } std::shared_ptr player = bankable->getPlayer(); if (player && !player->isOnline()) { - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); return; } if (bankable->isGuild()) { const auto guild = static_self_cast(bankable); if (guild && !guild->isOnline()) { - IOGuild::saveGuild(guild); + g_saveManager().saveGuild(guild); } } } diff --git a/src/game/game.cpp b/src/game/game.cpp index 474e73dee3c..cec2e9ab3e2 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -27,6 +27,7 @@ #include "creatures/monsters/monster.hpp" #include "lua/creature/movement.hpp" #include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" #include "server/server.hpp" #include "creatures/combat/spells.hpp" #include "lua/creature/talkaction.hpp" @@ -358,7 +359,7 @@ void Game::setGameState(GameState_t newState) { } saveMotdNum(); - saveGameState(); + g_saveManager().saveAll(); g_dispatcher().addEvent(std::bind(&Game::shutdown, this), "Game::shutdown"); @@ -377,7 +378,7 @@ void Game::setGameState(GameState_t newState) { } } - saveGameState(); + g_saveManager().saveAll(); break; } @@ -386,31 +387,6 @@ void Game::setGameState(GameState_t newState) { } } -void Game::saveGameState() { - if (gameState == GAME_STATE_NORMAL) { - setGameState(GAME_STATE_MAINTAIN); - } - - g_logger().info("Saving server..."); - - for (const auto &it : players) { - it.second->loginPosition = it.second->getPosition(); - IOLoginData::savePlayer(it.second); - } - - for (const auto &it : guilds) { - IOGuild::saveGuild(it.second); - } - - Map::save(); - - g_kv().saveAll(); - - if (gameState == GAME_STATE_MAINTAIN) { - setGameState(GAME_STATE_NORMAL); - } -} - bool Game::loadItemsPrice() { itemsSaleCount = 0; std::ostringstream query, marketQuery; @@ -3839,9 +3815,10 @@ std::shared_ptr Game::wrapItem(std::shared_ptr item, std::shared_ptr house->removeBed(item->getBed()); } uint16_t oldItemID = item->getID(); + auto itemName = item->getName(); std::shared_ptr newItem = transformItem(item, ITEM_DECORATION_KIT); newItem->setCustomAttribute("unWrapId", static_cast(oldItemID)); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap it in your own house to create a <" + item->getName() + ">."); + item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap it in your own house to create a <" + itemName + ">."); if (hiddenCharges > 0) { newItem->setAttribute(DATE, hiddenCharges); } @@ -4764,7 +4741,7 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item return; } - std::lock_guard lock(player->quickLootMutex); + Player::PlayerLock lock(player); if (!autoLoot) { player->setNextActionTask(nullptr); } @@ -8333,7 +8310,7 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t ite // Exhausted for create offert in the market player->updateUIExhausted(); - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter) { @@ -8414,7 +8391,7 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 player->sendMarketEnter(player->getLastDepotId()); // Exhausted for cancel offer in the market player->updateUIExhausted(); - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) { @@ -8564,7 +8541,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 } if (buyerPlayer->isOffline()) { - IOLoginData::savePlayer(buyerPlayer); + g_saveManager().savePlayer(buyerPlayer); } } else if (offer.type == MARKETACTION_SELL) { std::shared_ptr sellerPlayer = getPlayerByGUID(offer.playerId); @@ -8658,7 +8635,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 } if (sellerPlayer->isOffline()) { - IOLoginData::savePlayer(sellerPlayer); + g_saveManager().savePlayer(sellerPlayer); } } @@ -8689,7 +8666,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 player->sendMarketAcceptOffer(offer); // Exhausted for accept offer in the market player->updateUIExhausted(); - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string &buffer) { @@ -9172,7 +9149,7 @@ void Game::addGuild(const std::shared_ptr guild) { void Game::removeGuild(uint32_t guildId) { auto it = guilds.find(guildId); if (it != guilds.end()) { - IOGuild::saveGuild(it->second); + g_saveManager().saveGuild(it->second); } guilds.erase(guildId); } @@ -9729,7 +9706,7 @@ void Game::playerRewardChestCollect(uint32_t playerId, const Position &pos, uint // Updates the parent of the reward chest and reward containers to avoid memory usage after cleaning auto playerRewardChest = player->getRewardChest(); - if (playerRewardChest->empty()) { + if (playerRewardChest && playerRewardChest->empty()) { player->sendCancelMessage(RETURNVALUE_REWARDCHESTISEMPTY); return; } diff --git a/src/game/game.hpp b/src/game/game.hpp index 8f73ff17977..f94e456819c 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -427,7 +427,6 @@ class Game { GameState_t getGameState() const; void setGameState(GameState_t newState); - void saveGameState(); // Events void checkCreatureWalk(uint32_t creatureId); @@ -497,7 +496,10 @@ class Game { const std::map> &getItemsPrice() const { return itemsPriceMap; } - const phmap::flat_hash_map> &getPlayers() const { + const phmap::parallel_flat_hash_map> &getGuilds() const { + return guilds; + } + const phmap::parallel_flat_hash_map> &getPlayers() const { return players; } const std::map> &getMonsters() const { @@ -770,10 +772,11 @@ class Game { phmap::flat_hash_map highscoreCache; phmap::flat_hash_map> m_uniqueLoginPlayerNames; - phmap::flat_hash_map> players; + phmap::parallel_flat_hash_map> players; phmap::flat_hash_map> mappedPlayerNames; - phmap::flat_hash_map> guilds; + phmap::parallel_flat_hash_map> guilds; phmap::flat_hash_map> uniqueItems; + phmap::parallel_flat_hash_map m_playerNameCache; std::map stages; /* Items stored from the lua scripts positions diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp new file mode 100644 index 00000000000..9a5d0114544 --- /dev/null +++ b/src/game/scheduling/save_manager.cpp @@ -0,0 +1,144 @@ +#include "pch.hpp" + +#include "game/game.hpp" +#include "game/scheduling/save_manager.hpp" +#include "io/iologindata.hpp" + +SaveManager::SaveManager(ThreadPool &threadPool, KVStore &kvStore, Logger &logger, Game &game) : + threadPool(threadPool), kv(kvStore), logger(logger), game(game) { } + +SaveManager &SaveManager::getInstance() { + return inject(); +} + +void SaveManager::saveAll() { + Benchmark bm_saveAll; + logger.info("Saving server..."); + const auto players = game.getPlayers(); + + for (const auto &[_, player] : players) { + player->loginPosition = player->getPosition(); + doSavePlayer(player); + } + + auto guilds = game.getGuilds(); + for (const auto &[_, guild] : guilds) { + saveGuild(guild); + } + + saveMap(); + saveKV(); + logger.info("Server saved in {} milliseconds.", bm_saveAll.duration()); +} + +void SaveManager::scheduleAll() { + auto scheduledAt = std::chrono::steady_clock::now(); + m_scheduledAt = scheduledAt; + + threadPool.addLoad([this, scheduledAt]() { + if (m_scheduledAt.load() != scheduledAt) { + logger.warn("Skipping save for server because another save has been scheduled."); + return; + } + saveAll(); + }); +} + +void SaveManager::schedulePlayer(std::weak_ptr playerPtr) { + auto playerToSave = playerPtr.lock(); + if (!playerToSave) { + logger.debug("Skipping save for player because player is no longer online."); + return; + } + logger.debug("Scheduling player {} for saving.", playerToSave->getName()); + auto scheduledAt = std::chrono::steady_clock::now(); + m_playerMap[playerToSave->getGUID()] = scheduledAt; + threadPool.addLoad([this, playerPtr, scheduledAt]() { + auto player = playerPtr.lock(); + if (!player) { + logger.debug("Skipping save for player because player is no longer online."); + return; + } + if (m_playerMap[player->getGUID()] != scheduledAt) { + logger.warn("Skipping save for player because another save has been scheduled."); + return; + } + doSavePlayer(player); + }); +} + +bool SaveManager::doSavePlayer(std::shared_ptr player) { + if (!player) { + logger.debug("Failed to save player because player is null."); + return false; + } + Benchmark bm_savePlayer; + Player::PlayerLock lock(player); + m_playerMap.erase(player->getGUID()); + logger.debug("Saving player {}...", player->getName()); + bool saveSuccess = IOLoginData::savePlayer(player); + if (!saveSuccess) { + logger.error("Failed to save player {}.", player->getName()); + } + auto duration = bm_savePlayer.duration(); + if (duration > 100) { + logger.warn("Saving player {} took {} milliseconds.", player->getName(), duration); + } else { + logger.debug("Saving player {} took {} milliseconds.", player->getName(), duration); + } + return saveSuccess; +} + +bool SaveManager::savePlayer(std::shared_ptr player) { + if (player->isOnline()) { + schedulePlayer(player); + return true; + } + return doSavePlayer(player); +} + +void SaveManager::saveGuild(std::shared_ptr guild) { + if (!guild) { + logger.debug("Failed to save guild because guild is null."); + return; + } + Benchmark bm_saveGuild; + logger.debug("Saving guild {}...", guild->getName()); + IOGuild::saveGuild(guild); + auto duration = bm_saveGuild.duration(); + if (duration > 100) { + logger.warn("Saving guild {} took {} milliseconds.", guild->getName(), duration); + } else { + logger.debug("Saving guild {} took {} milliseconds.", guild->getName(), duration); + } +} + +void SaveManager::saveMap() { + Benchmark bm_saveMap; + logger.debug("Saving map..."); + bool saveSuccess = Map::save(); + if (!saveSuccess) { + logger.error("Failed to save map."); + } + auto duration = bm_saveMap.duration(); + if (duration > 100) { + logger.warn("Map saved in {} milliseconds.", bm_saveMap.duration()); + } else { + logger.debug("Map saved in {} milliseconds.", bm_saveMap.duration()); + } +} + +void SaveManager::saveKV() { + Benchmark bm_saveKV; + logger.debug("Saving key-value store..."); + bool saveSuccess = kv.saveAll(); + if (!saveSuccess) { + logger.error("Failed to save key-value store."); + } + auto duration = bm_saveKV.duration(); + if (duration > 100) { + logger.warn("Key-value store saved in {} milliseconds.", bm_saveKV.duration()); + } else { + logger.debug("Key-value store saved in {} milliseconds.", bm_saveKV.duration()); + } +} diff --git a/src/game/scheduling/save_manager.hpp b/src/game/scheduling/save_manager.hpp new file mode 100644 index 00000000000..a809dd73f6a --- /dev/null +++ b/src/game/scheduling/save_manager.hpp @@ -0,0 +1,46 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include "lib/thread/thread_pool.hpp" +#include "kv/kv.hpp" + +class SaveManager { +public: + explicit SaveManager(ThreadPool &threadPool, KVStore &kvStore, Logger &logger, Game &game); + + SaveManager(const SaveManager &) = delete; + void operator=(const SaveManager &) = delete; + + static SaveManager &getInstance(); + + void saveAll(); + void scheduleAll(); + + bool savePlayer(std::shared_ptr player); + void saveGuild(std::shared_ptr guild); + +private: + void saveMap(); + void saveKV(); + + void schedulePlayer(std::weak_ptr player); + bool doSavePlayer(std::shared_ptr player); + + std::atomic m_scheduledAt; + phmap::parallel_flat_hash_map m_playerMap; + + ThreadPool &threadPool; + KVStore &kv; + Logger &logger; + Game &game; +}; + +constexpr auto g_saveManager = SaveManager::getInstance; diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index f32b90568a4..d454cd32bdb 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -362,7 +362,9 @@ void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::stri std::ostringstream query; query << "INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES (" << accountId << ',' << guid << ',' << db.escapeString(description) << ',' << icon << ',' << notify << ')'; - db.executeQuery(query.str()); + if (!db.executeQuery(query.str())) { + g_logger().error("Failed to add VIP entry for account %u. QUERY: %s", accountId, query.str().c_str()); + } } void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string &description, uint32_t icon, bool notify) { @@ -370,7 +372,9 @@ void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::str std::ostringstream query; query << "UPDATE `account_viplist` SET `description` = " << db.escapeString(description) << ", `icon` = " << icon << ", `notify` = " << notify << " WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; - db.executeQuery(query.str()); + if (!db.executeQuery(query.str())) { + g_logger().error("Failed to edit VIP entry for account %u. QUERY: %s", accountId, query.str().c_str()); + } } void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) { diff --git a/src/io/iomap.cpp b/src/io/iomap.cpp index 72eaed123ae..51306b7f756 100644 --- a/src/io/iomap.cpp +++ b/src/io/iomap.cpp @@ -77,7 +77,7 @@ void IOMap::loadMap(Map* map, const Position &pos) { map->flush(); - g_logger().info("Map Loaded {} ({}x{}) in {} seconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); + g_logger().info("Map Loaded {} ({}x{}) in {} milliseconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); } void IOMap::parseMapDataAttributes(FileStream &stream, Map* map) { diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index 94531a61228..c089fe022db 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -49,7 +49,7 @@ void IOMapSerialize::loadHouseItems(Map* map) { loadItem(propStream, tile, true); } } while (result->next()); - g_logger().info("Loaded house items in {} seconds", bm_context.duration()); + g_logger().info("Loaded house items in {} milliseconds", bm_context.duration()); } bool IOMapSerialize::saveHouseItems() { bool success = DBTransaction::executeWithinTransaction([]() { @@ -64,8 +64,6 @@ bool IOMapSerialize::saveHouseItems() { } bool IOMapSerialize::SaveHouseItemsGuard() { - Benchmark bm_context; - Database &db = Database::getInstance(); std::ostringstream query; @@ -98,7 +96,6 @@ bool IOMapSerialize::SaveHouseItemsGuard() { return false; } - g_logger().info("Saved house items in {} seconds", bm_context.duration()); return true; } diff --git a/src/io/iomarket.cpp b/src/io/iomarket.cpp index d7edca1c51e..d6b6109309c 100644 --- a/src/io/iomarket.cpp +++ b/src/io/iomarket.cpp @@ -14,6 +14,7 @@ #include "io/iologindata.hpp" #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" uint8_t IOMarket::getTierFromDatabaseTable(const std::string &string) { auto tier = static_cast(std::atoi(string.c_str())); @@ -177,7 +178,7 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) { } if (player->isOffline()) { - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } } else { uint64_t totalPrice = result->getNumber("price") * amount; diff --git a/src/items/bed.cpp b/src/items/bed.cpp index d8a246ec8b7..289ae210a6a 100644 --- a/src/items/bed.cpp +++ b/src/items/bed.cpp @@ -13,6 +13,7 @@ #include "game/game.hpp" #include "io/iologindata.hpp" #include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" BedItem::BedItem(uint16_t id) : Item(id) { @@ -178,7 +179,7 @@ void BedItem::wakeUp(std::shared_ptr player) { auto regenPlayer = std::make_shared(nullptr); if (IOLoginData::loadPlayerById(regenPlayer, sleeperGUID)) { regeneratePlayer(regenPlayer); - IOLoginData::savePlayer(regenPlayer); + g_saveManager().savePlayer(regenPlayer); } } else { regeneratePlayer(player); diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index 67884eb4fcc..0800f032adb 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -58,7 +58,7 @@ std::shared_ptr Container::create(std::shared_ptr tile) { Container::~Container() { if (getID() == ITEM_BROWSEFIELD) { for (std::shared_ptr item : itemlist) { - item->setParent(m_parent); + item->setParent(getParent()); } } } diff --git a/src/items/containers/mailbox/mailbox.cpp b/src/items/containers/mailbox/mailbox.cpp index 959ca818bb5..9253bdd7417 100644 --- a/src/items/containers/mailbox/mailbox.cpp +++ b/src/items/containers/mailbox/mailbox.cpp @@ -12,6 +12,7 @@ #include "items/containers/mailbox/mailbox.hpp" #include "game/game.hpp" #include "io/iologindata.hpp" +#include "game/scheduling/save_manager.hpp" #include "map/spectators.hpp" ReturnValue Mailbox::queryAdd(int32_t, const std::shared_ptr &thing, uint32_t, uint32_t, std::shared_ptr) { @@ -107,7 +108,7 @@ bool Mailbox::sendItem(std::shared_ptr item) const { if (player->isOnline()) { player->onReceiveMail(); } else { - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } return true; } diff --git a/src/lua/functions/core/game/global_functions.cpp b/src/lua/functions/core/game/global_functions.cpp index 1188233dd83..7abf3b2c6e6 100644 --- a/src/lua/functions/core/game/global_functions.cpp +++ b/src/lua/functions/core/game/global_functions.cpp @@ -12,6 +12,7 @@ #include "creatures/interactions/chat.hpp" #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" #include "lua/functions/core/game/global_functions.hpp" #include "lua/scripts/lua_environment.hpp" #include "lua/scripts/script_environment.hpp" @@ -722,7 +723,7 @@ int GlobalFunctions::luaStopEvent(lua_State* L) { } int GlobalFunctions::luaSaveServer(lua_State* L) { - g_game().saveGameState(); + g_saveManager().scheduleAll(); pushBoolean(L, true); return 1; } diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 7145525c3bd..6779101380d 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -19,6 +19,7 @@ #include "io/ioprey.hpp" #include "items/item.hpp" #include "lua/functions/creatures/player/player_functions.hpp" +#include "game/scheduling/save_manager.hpp" #include "map/spectators.hpp" int PlayerFunctions::luaPlayerSendInventory(lua_State* L) { @@ -1379,6 +1380,11 @@ int PlayerFunctions::luaPlayerSetVocation(lua_State* L) { } player->setVocation(vocation->getId()); + player->sendSkills(); + player->sendStats(); + player->sendBasicData(); + player->wheel()->sendGiftOfLifeCooldown(); + g_game().reloadCreature(player); pushBoolean(L, true); return 1; } @@ -2824,10 +2830,7 @@ int PlayerFunctions::luaPlayerSave(lua_State* L) { if (!player->isOffline()) { player->loginPosition = player->getPosition(); } - pushBoolean(L, IOLoginData::savePlayer(player)); - if (player->isOffline()) { - // avoiding memory leak - } + pushBoolean(L, g_saveManager().savePlayer(player)); } else { lua_pushnil(L); } diff --git a/src/lua/functions/items/item_functions.cpp b/src/lua/functions/items/item_functions.cpp index f943c52c8fc..c1f25361253 100644 --- a/src/lua/functions/items/item_functions.cpp +++ b/src/lua/functions/items/item_functions.cpp @@ -14,6 +14,7 @@ #include "game/game.hpp" #include "items/item.hpp" #include "items/decay/decay.hpp" +#include "game/scheduling/save_manager.hpp" class Imbuement; diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index a623a3e523a..e1225aa9370 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -14,6 +14,7 @@ #include "io/iologindata.hpp" #include "game/game.hpp" #include "items/bed.hpp" +#include "game/scheduling/save_manager.hpp" House::House(uint32_t houseId) : id(houseId) { } @@ -285,7 +286,7 @@ bool House::transferToDepot(std::shared_ptr player) const { g_logger().debug("[{}] moving item '{}' to depot", __FUNCTION__, item->getName()); g_game().internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); } - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); return true; } @@ -821,7 +822,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const { } } - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } } diff --git a/src/server/signals.cpp b/src/server/signals.cpp index d70f65e06a9..bb40bbcd859 100644 --- a/src/server/signals.cpp +++ b/src/server/signals.cpp @@ -11,6 +11,7 @@ #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" #include "lib/thread/thread_pool.hpp" #include "lua/creature/events.hpp" #include "lua/scripts/lua_environment.hpp" @@ -91,7 +92,7 @@ void Signals::sigtermHandler() { void Signals::sigusr1Handler() { // Dispatcher thread g_logger().info("SIGUSR1 received, saving the game state..."); - g_game().saveGameState(); + g_saveManager().scheduleAll(); } void Signals::sighupHandler() { diff --git a/src/utils/benchmark.hpp b/src/utils/benchmark.hpp index 8d968ca708b..7ec007ceaeb 100644 --- a/src/utils/benchmark.hpp +++ b/src/utils/benchmark.hpp @@ -75,7 +75,7 @@ class Benchmark { private: int64_t time() const noexcept { - return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); } int64_t startTime = -1; diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index 67268dff0f7..ed1217302f2 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -61,6 +61,7 @@ + @@ -258,6 +259,7 @@ + @@ -548,4 +550,4 @@ - \ No newline at end of file + From afcae2c369e7893ea38ecbb531c4dda0dac071f2 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Thu, 19 Oct 2023 20:00:44 -0300 Subject: [PATCH 04/14] feat: Arraylist, fast new container to add front and back (#1677) Arraylist is a very fast container for adding to the front and back, it uses two vectors to do this juggling and doesn't allow you to remove the front, as it is slow, use std::list for this case. **Benchmark** loop: 99999 ![image](https://github.com/opentibiabr/canary/assets/2267386/ebdc1883-0249-413a-8553-8c5dab0b7ac8) loop: 999999 ![image](https://github.com/opentibiabr/canary/assets/2267386/6d0c46b3-ff6e-429a-8ae7-9cf015ed142e)
```c++ constexpr size_t LOOP = 999999; Benchmark bm; for (size_t i = 0; i < 999; i++) { stdext::arraylist list; list.reserve(LOOP); for (size_t d = 0; d < LOOP; d++) { if (d % 2) { list.emplace_front(d); } else { list.emplace_back(d); } } for (const auto &v : list) { } } g_logger().info("stdext::arraylist + reserve: {}ms", bm.duration()); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { stdext::arraylist list; for (size_t d = 0; d < LOOP; d++) { if (d % 2) { list.emplace_front(d); } else { list.emplace_back(d); } } for (const auto &v : list) { } } g_logger().info("stdext::arraylist: {}ms", bm.duration()); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { std::list list; for (size_t d = 0; d < LOOP; d++) { if (d % 2) { list.emplace_front(d); } else { list.emplace_back(d); } } for (const auto &v : list) { } } g_logger().info("std::list: {}ms", bm.duration()); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { std::deque list; for (size_t d = 0; d < LOOP; d++) { if (d % 2) { list.emplace_front(d); } else { list.emplace_back(d); } } for (const auto &v : list) { } } g_logger().info("std::deque: {}ms", bm.duration()); g_logger().info("--- stdext::arraylist vs std::forward_list vs std::list vs std::deque --- (push_front)"); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { stdext::arraylist list; list.reserve(LOOP); for (size_t d = 0; d < LOOP; d++) { list.emplace_front(d); } for (const auto &v : list) { } } g_logger().info("stdext::arraylist + reserve: {}ms", bm.duration()); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { stdext::arraylist arraylist; for (size_t d = 0; d < LOOP; d++) { arraylist.emplace_front(d); } for (const auto &v : arraylist) { } } g_logger().info("stdext::arraylist: {}ms", bm.duration()); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { std::forward_list list; for (size_t d = 0; d < LOOP; d++) { list.emplace_front(d); } for (const auto &v : list) { } } g_logger().info("std::forward_list: {}ms", bm.duration()); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { std::list list; for (size_t d = 0; d < LOOP; d++) { list.emplace_front(d); } for (const auto &v : list) { } } g_logger().info("std::list: {}ms", bm.duration()); bm.reset(); bm.start(); for (size_t i = 0; i < 999; i++) { std::deque list; for (size_t d = 0; d < LOOP; d++) { list.emplace_front(d); } for (const auto &v : list) { } } g_logger().info("std::deque: {}ms", bm.duration()); ```
**Validating Content**
```c++ constexpr size_t LOOP = 999999; stdext::arraylist arraylist; arraylist.reserve(LOOP); for (size_t d = 0; d < LOOP; d++) { if (d % 2) { arraylist.emplace_front(d); } else { arraylist.emplace_back(d); } } std::deque deque; for (size_t d = 0; d < LOOP; d++) { if (d % 2) { deque.emplace_front(d); } else { deque.emplace_back(d); } } for (size_t i = 0; i < LOOP; i++) { if (arraylist[i] != deque[i]) { g_logger().info("NOT EQUAL"); } } g_logger().info("FINISHED"); ```
--- src/pch.hpp | 1 + src/utils/arraylist.hpp | 158 ++++++++++++++++++++++++++++++++++++++++ vcproj/canary.vcxproj | 1 + 3 files changed, 160 insertions(+) create mode 100644 src/utils/arraylist.hpp diff --git a/src/pch.hpp b/src/pch.hpp index 84d07897ba1..ab57f178e84 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -18,6 +18,7 @@ #include "utils/definitions.hpp" #include "utils/simd.hpp" #include "utils/vectorset.hpp" +#include "utils/arraylist.hpp" #include "utils/vectorsort.hpp" // -------------------- diff --git a/src/utils/arraylist.hpp b/src/utils/arraylist.hpp new file mode 100644 index 00000000000..ea803ab8cf0 --- /dev/null +++ b/src/utils/arraylist.hpp @@ -0,0 +1,158 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include +#include + +// # Mehah +// Arraylist is a very fast container for adding to the front and back, +// it uses two vectors to do this juggling and doesn't allow you to remove the front, as it is slow, +// use std::list for this case. + +namespace stdext { + template + class arraylist { + public: + bool contains(const T &v) { + update(); + return std::ranges::find(backContainer, v) != backContainer.end(); + } + + bool erase(const T &v) { + update(); + + const auto &it = std::ranges::find(backContainer, v); + if (it == backContainer.end()) { + return false; + } + backContainer.erase(it); + + return true; + } + + bool erase(const size_t begin, const size_t end) { + update(); + + if (begin > size() || end > size()) { + return false; + } + + backContainer.erase(backContainer.begin() + begin, backContainer.begin() + end); + return true; + } + + template + bool erase_if(F fnc) { + update(); + return std::erase_if(backContainer, std::move(fnc)) > 0; + } + + auto &front() { + update(); + return backContainer.front(); + } + + void pop_back() { + update(); + backContainer.pop_back(); + } + + auto &back() { + update(); + return backContainer.back(); + } + + void push_front(const T &v) { + needUpdate = true; + frontContainer.push_back(v); + } + + void push_front(T &&_Val) { + needUpdate = true; + frontContainer.push_back(std::move(_Val)); + } + + template + decltype(auto) emplace_front(_Valty &&... v) { + needUpdate = true; + return frontContainer.emplace_back(std::forward<_Valty>(v)...); + } + + void push_back(const T &v) { + backContainer.push_back(v); + } + + void push_back(T &&_Val) { + backContainer.push_back(std::move(_Val)); + } + + template + decltype(auto) emplace_back(_Valty &&... v) { + return backContainer.emplace_back(std::forward<_Valty>(v)...); + } + + bool empty() const noexcept { + return backContainer.empty() && frontContainer.empty(); + } + + size_t size() const noexcept { + return backContainer.size() + frontContainer.size(); + } + + auto begin() noexcept { + update(); + return backContainer.begin(); + } + + auto end() noexcept { + return backContainer.end(); + } + + void clear() noexcept { + frontContainer.clear(); + return backContainer.clear(); + } + + void reserve(size_t newCap) noexcept { + backContainer.reserve(newCap); + frontContainer.reserve(newCap); + } + + const auto &data() noexcept { + update(); + return backContainer.data(); + } + + T &operator[](const size_t i) { + update(); + return backContainer[i]; + } + + private: + inline void update() noexcept { + if (!needUpdate) { + return; + } + + needUpdate = false; + std::ranges::reverse(frontContainer); + frontContainer.insert(frontContainer.end(), make_move_iterator(backContainer.begin()), make_move_iterator(backContainer.end())); + backContainer.clear(); + backContainer.insert(backContainer.end(), make_move_iterator(frontContainer.begin()), make_move_iterator(frontContainer.end())); + frontContainer.clear(); + } + + std::vector backContainer; + std::vector frontContainer; + + bool needUpdate = false; + }; +} diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index ed1217302f2..7b467346d39 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -212,6 +212,7 @@ + From 10d6002556900b722ae7f7d035ed58f37e5ca74d Mon Sep 17 00:00:00 2001 From: Luan Colombo <94877887+luancolombo@users.noreply.github.com> Date: Fri, 20 Oct 2023 03:36:09 +0100 Subject: [PATCH 05/14] fix: fire axe quest (#1717) Added fire axe to quest chest. --- data-otservbr-global/startup/tables/chest.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/startup/tables/chest.lua b/data-otservbr-global/startup/tables/chest.lua index 2ca7c272215..f5049f93f61 100644 --- a/data-otservbr-global/startup/tables/chest.lua +++ b/data-otservbr-global/startup/tables/chest.lua @@ -653,7 +653,7 @@ ChestUnique = { itemId = 2472, itemPos = { x = 33078, y = 31656, z = 11 }, container = 2853, - reward = { { 3098, 1 }, { 3085, 200 }, { 3028, 7 } }, + reward = { { 3098, 1 }, { 3085, 200 }, { 3028, 7 }, { 3320, 1 } }, weight = 27, storage = Storage.Quest.U6_4.FireAxe.Rewards.Bag, }, From 450e04bee279ab626abb68099886b9ef19cf2e91 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Fri, 20 Oct 2023 10:10:43 -0700 Subject: [PATCH 06/14] improve: createItemBatch (#1714) Makes bulk purchases from NPCs _way_ faster and less prone to exploits (players will no longer be able to abuse NPCs by buying a lot of items at once). --- src/game/game.cpp | 100 ++++++++++++++++++ src/game/game.hpp | 3 + src/items/thing.hpp | 4 + .../functions/creatures/npc/npc_functions.cpp | 82 +------------- 4 files changed, 109 insertions(+), 80 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index cec2e9ab3e2..2e5620d0e9d 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -2022,6 +2022,106 @@ ReturnValue Game::internalRemoveItem(std::shared_ptr item, int32_t count / return RETURNVALUE_NOERROR; } +std::tuple Game::addItemBatch(const std::shared_ptr &toCylinder, const std::vector> &items, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + const auto player = toCylinder->getPlayer(); + bool dropping = false; + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; + uint32_t totalAdded = 0; + uint32_t containersCreated = 0; + + auto setupDestination = [&]() -> std::shared_ptr { + if (autoContainerId == 0) { + return toCylinder; + } + auto autoContainer = Item::CreateItem(autoContainerId); + if (!autoContainer) { + g_logger().error("[{}] Failed to create auto container", __FUNCTION__); + return toCylinder; + } + if (internalAddItem(toCylinder, autoContainer, CONST_SLOT_WHEREEVER, flags) != RETURNVALUE_NOERROR) { + if (internalAddItem(toCylinder->getTile(), autoContainer, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + g_logger().error("[{}] Failed to add auto container", __FUNCTION__); + return toCylinder; + } + } + auto container = autoContainer->getContainer(); + if (!container) { + g_logger().error("[{}] Failed to get auto container", __FUNCTION__); + return toCylinder; + } + containersCreated++; + return container; + }; + auto destination = setupDestination(); + + for (const auto &item : items) { + auto container = destination->getContainer(); + if (container && container->getFreeSlots() == 0) { + destination = setupDestination(); + } + if (!dropping) { + uint32_t remainderCount = 0; + ret = internalAddItem(destination, item, CONST_SLOT_WHEREEVER, flags, false, remainderCount); + if (remainderCount != 0) { + std::shared_ptr remainderItem = Item::CreateItem(item->getID(), remainderCount); + ReturnValue remaindRet = internalAddItem(destination->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (player && remaindRet != RETURNVALUE_NOERROR) { + player->sendLootStats(item, static_cast(item->getItemCount())); + } + } + } + + if (dropping || ret != RETURNVALUE_NOERROR && dropOnMap) { + dropping = true; + ret = internalAddItem(destination->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + if (player && ret == RETURNVALUE_NOERROR) { + player->sendForgingData(); + } + if (ret != RETURNVALUE_NOERROR) { + break; + } else { + totalAdded += item->getItemCount(); + } + } + + return std::make_tuple(ret, totalAdded, containersCreated); +} + +std::tuple Game::createItemBatch(const std::shared_ptr &toCylinder, const std::vector> &itemCounts, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + std::vector> items; + for (const auto &[itemId, count, subType] : itemCounts) { + const auto &itemType = Item::items[itemId]; + if (itemType.id <= 0) { + continue; + } + if (count == 0) { + continue; + } + uint32_t countPerItem = itemType.stackable ? itemType.stackSize : 1; + for (uint32_t i = 0; i < count; ++i) { + std::shared_ptr item; + if (itemType.isWrappable()) { + countPerItem = 1; + item = Item::CreateItem(ITEM_DECORATION_KIT, subType); + item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + itemType.name + ">."); + item->setCustomAttribute("unWrapId", static_cast(itemId)); + } else { + item = Item::CreateItem(itemId, itemType.stackable ? std::min(countPerItem, count - i) : subType); + } + items.push_back(item); + i += countPerItem - 1; + } + } + + return addItemBatch(toCylinder, items, flags, dropOnMap, autoContainerId); +} + +std::tuple Game::createItem(const std::shared_ptr &toCylinder, uint16_t itemId, uint32_t count, uint16_t subType, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + return createItemBatch(toCylinder, { std::make_tuple(itemId, count, subType) }, flags, dropOnMap, autoContainerId); +} + ReturnValue Game::internalPlayerAddItem(std::shared_ptr player, std::shared_ptr item, bool dropOnMap /*= true*/, Slots_t slot /*= CONST_SLOT_WHEREEVER*/) { uint32_t remainderCount = 0; ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); diff --git a/src/game/game.hpp b/src/game/game.hpp index f94e456819c..aa2d81ba831 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -210,6 +210,9 @@ class Game { ReturnValue checkMoveItemToCylinder(std::shared_ptr player, std::shared_ptr fromCylinder, std::shared_ptr toCylinder, std::shared_ptr item, Position toPos); ReturnValue internalMoveItem(std::shared_ptr fromCylinder, std::shared_ptr toCylinder, int32_t index, std::shared_ptr item, uint32_t count, std::shared_ptr* movedItem, uint32_t flags = 0, std::shared_ptr actor = nullptr, std::shared_ptr tradeItem = nullptr, bool checkTile = true); + std::tuple addItemBatch(const std::shared_ptr &toCylinder, const std::vector> &items, uint32_t flags = 0, bool dropOnMap = true, uint32_t autoContainerId = 0); + std::tuple createItemBatch(const std::shared_ptr &toCylinder, const std::vector> &itemCounts, uint32_t flags = 0, bool dropOnMap = true, uint32_t autoContainerId = 0); + std::tuple createItem(const std::shared_ptr &toCylinder, uint16_t itemId, uint32_t count, uint16_t subType, uint32_t flags = 0, bool dropOnMap = true, uint32_t autoContainerId = 0); ReturnValue internalAddItem(std::shared_ptr toCylinder, std::shared_ptr item, int32_t index = INDEX_WHEREEVER, uint32_t flags = 0, bool test = false); ReturnValue internalAddItem(std::shared_ptr toCylinder, std::shared_ptr item, int32_t index, uint32_t flags, bool test, uint32_t &remainderCount); ReturnValue internalRemoveItem(std::shared_ptr item, int32_t count = -1, bool test = false, uint32_t flags = 0, bool force = false); diff --git a/src/items/thing.hpp b/src/items/thing.hpp index 0a6161783b3..5b75e716cae 100644 --- a/src/items/thing.hpp +++ b/src/items/thing.hpp @@ -16,6 +16,7 @@ class Cylinder; class Item; class Creature; class Container; +class Player; class Thing { public: @@ -47,6 +48,9 @@ class Thing { virtual int32_t getThrowRange() const = 0; virtual bool isPushable() = 0; + virtual std::shared_ptr getPlayer() { + return nullptr; + } virtual std::shared_ptr getContainer() { return nullptr; } diff --git a/src/lua/functions/creatures/npc/npc_functions.cpp b/src/lua/functions/creatures/npc/npc_functions.cpp index fc8c8272c68..e0ae3c2bfaf 100644 --- a/src/lua/functions/creatures/npc/npc_functions.cpp +++ b/src/lua/functions/creatures/npc/npc_functions.cpp @@ -524,85 +524,7 @@ int NpcFunctions::luaNpcSellItem(lua_State* L) { } } - uint32_t itemsPurchased = 0; - uint8_t backpacksPurchased = 0; - uint8_t internalCount = it.stackable ? it.stackSize : 1; - auto remainingAmount = static_cast(amount); - if (inBackpacks) { - while (remainingAmount > 0) { - std::shared_ptr container = Item::CreateItem(ITEM_SHOPPING_BAG); - if (!container) { - break; - } - - if (g_game().internalPlayerAddItem(player, container, ignoreCap, CONST_SLOT_WHEREEVER) != RETURNVALUE_NOERROR) { - break; - } - - backpacksPurchased++; - uint8_t internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - const ItemType &iType = Item::items[itemId]; - std::shared_ptr item; - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - if (actionId != 0) { - item->setAttribute(ItemAttribute_t::ACTIONID, actionId); - } - - while (remainingAmount > 0) { - if (g_game().internalAddItem(container->getContainer(), item, INDEX_WHEREEVER, 0) != RETURNVALUE_NOERROR) { - break; - } - - itemsPurchased += internalAmount; - remainingAmount -= internalAmount; - internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - } - } - } else { - uint8_t internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - const ItemType &iType = Item::items[itemId]; - std::shared_ptr item; - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - if (actionId != 0) { - item->setAttribute(ItemAttribute_t::ACTIONID, actionId); - } - - while (remainingAmount > 0) { - if (g_game().internalPlayerAddItem(player, item, ignoreCap, CONST_SLOT_WHEREEVER) != RETURNVALUE_NOERROR) { - break; - } - - itemsPurchased += internalAmount; - remainingAmount -= internalAmount; - internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - } - } + const auto &[_, itemsPurchased, backpacksPurchased] = g_game().createItem(player, itemId, amount, subType, actionId, ignoreCap, inBackpacks ? ITEM_SHOPPING_BAG : 0); std::stringstream ss; uint64_t itemCost = itemsPurchased * pricePerUnit; @@ -610,7 +532,7 @@ int NpcFunctions::luaNpcSellItem(lua_State* L) { if (npc->getCurrency() == ITEM_GOLD_COIN) { if (!g_game().removeMoney(player, itemCost + backpackCost, 0, true)) { g_logger().error("[NpcFunctions::luaNpcSellItem (removeMoney)] - Player {} have a problem for buy item {} on shop for npc {}", player->getName(), itemId, npc->getName()); - g_logger().debug("[Information] Player {} buyed item {} on shop for npc {}, at position {}", player->getName(), itemId, npc->getName(), player->getPosition().toString()); + g_logger().debug("[Information] Player {} bought {} x item {} on shop for npc {}, at position {}", player->getName(), itemsPurchased, itemId, npc->getName(), player->getPosition().toString()); } else if (backpacksPurchased > 0) { ss << "Bought " << std::to_string(itemsPurchased) << "x " << it.name << " and " << std::to_string(backpacksPurchased); if (backpacksPurchased > 1) { From b8e668880f2925a3dcf7d69807f407d8fc6c1724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Lu=C3=ADs=20Lucarelo=20Lamonato?= Date: Fri, 20 Oct 2023 14:18:49 -0300 Subject: [PATCH 07/14] fix: house_lists version column in database with wrong type (#1716) The column is defined with the type int, which receives a maximum value of 4.294.967.295. Therefore, it was showing an error when saving the database table. The solution I found was to change the type of the version column from INT to BIGINT. --- data-otservbr-global/migrations/40.lua | 9 ++++++--- schema.sql | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/data-otservbr-global/migrations/40.lua b/data-otservbr-global/migrations/40.lua index 6e9828d66b7..22bfea0da95 100644 --- a/data-otservbr-global/migrations/40.lua +++ b/data-otservbr-global/migrations/40.lua @@ -1,8 +1,5 @@ function onUpdateDatabase() logger.info("Updating database to version 41 (optimize house_lists)") - db.query([[ - ALTER TABLE `house_lists` DROP COLUMN `id`; - ]]) db.query([[ ALTER TABLE `house_lists` @@ -10,5 +7,11 @@ function onUpdateDatabase() ADD INDEX `version` (`version`), ADD PRIMARY KEY (`house_id`, `listid`); ]]) + + db.query([[ + ALTER TABLE `house_lists` + MODIFY `version` bigint(20) NOT NULL DEFAULT '0'; + ]]) + return true end diff --git a/schema.sql b/schema.sql index b54d9e2990a..dc58d3a71ac 100644 --- a/schema.sql +++ b/schema.sql @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS `server_config` ( CONSTRAINT `server_config_pk` PRIMARY KEY (`config`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '38'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '41'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); -- Table structure `accounts` CREATE TABLE IF NOT EXISTS `accounts` ( @@ -438,7 +438,7 @@ DELIMITER ; CREATE TABLE IF NOT EXISTS `house_lists` ( `house_id` int NOT NULL, `listid` int NOT NULL, - `version` int NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0', `list` text NOT NULL, PRIMARY KEY (`house_id`, `listid`), KEY `house_id_index` (`house_id`), From 4c32854236183cd4341ad38fc8a078ad13918f70 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Fri, 20 Oct 2023 10:20:26 -0700 Subject: [PATCH 08/14] feat: datapack improvements (#1713) Another small collection of datapack improvements. These are small quest fixes, coordinate fixes, etc. --- data-otservbr-global/npc/....lua | 4 +- data-otservbr-global/npc/battlemart.lua | 3 ++ data-otservbr-global/npc/john_bounac.lua | 0 data-otservbr-global/npc/the_bone_master.lua | 2 +- data-otservbr-global/npc/the_dream_master.lua | 2 +- .../scripts/actions/addons/addons.lua | 3 ++ .../scripts/actions/other/bag_you_desire.lua | 39 ++++++++++--------- .../actions/other/exercise_training.lua | 2 +- .../actions/other/others/quest_system1.lua | 4 ++ .../quests/a_pirates_tail/cheesy_key.lua | 39 +++++++++++++++++++ .../feaster_of_souls/dread_maiden_lever.lua | 10 ++--- .../feaster_of_souls/fear_feaster_lever.lua | 10 ++--- .../feaster_of_souls/pale_worm_lever.lua | 20 +++++----- .../feaster_of_souls/unwelcome_lever.lua | 10 ++--- .../actions/quests/first_dragon/rewards.lua | 8 +++- .../quests/first_dragon/treasure_chest.lua | 3 +- .../quests/grimvale/portal_minis_grimvale.lua | 8 ++-- .../actions/quests/lions_rock/lions_rock.lua | 2 +- .../quests/secret_library/ghulosh_lever.lua | 10 ++--- .../quests/secret_library/gorzindel_lever.lua | 4 +- .../quests/secret_library/mazzinor_lever.lua | 10 ++--- .../quests/soul_war/soulwar_entrances.lua | 2 +- .../scripts/actions/tools/rust_remover.lua | 9 +++++ .../quests/a_pirates_tail/ratmiral_death.lua | 20 ++++++++++ .../quests/a_pirates_tail/tentugly_death.lua | 19 +++++++++ .../druid_outfits_blooming_griffinclaw.lua | 2 + .../quests/pits_of_inferno/throne.lua | 1 + data-otservbr-global/world/otservbr-npc.xml | 6 +++ data/libs/achievements_lib.lua | 4 ++ data/libs/functions/player.lua | 10 ++++- data/libs/functions/tile.lua | 10 ++++- .../monster/ondroploot__base.lua | 1 + data/scripts/reward_chest/boss_death.lua | 14 +++---- data/scripts/talkactions/player/bank.lua | 23 +++++------ data/scripts/talkactions/player/bless.lua | 3 +- data/scripts/talkactions/player/buy_house.lua | 14 +++---- .../talkactions/player/leave_house.lua | 6 +-- .../scripts/talkactions/player/sell_house.lua | 6 +-- 38 files changed, 240 insertions(+), 103 deletions(-) create mode 100644 data-otservbr-global/npc/john_bounac.lua create mode 100644 data-otservbr-global/scripts/actions/quests/a_pirates_tail/cheesy_key.lua create mode 100644 data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/ratmiral_death.lua create mode 100644 data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/tentugly_death.lua diff --git a/data-otservbr-global/npc/....lua b/data-otservbr-global/npc/....lua index 8555383b67e..38222c4534a 100644 --- a/data-otservbr-global/npc/....lua +++ b/data-otservbr-global/npc/....lua @@ -14,11 +14,11 @@ npcConfig.outfit = { lookBody = 0, lookLegs = 0, lookFeet = 0, - lookAddons = 0 + lookAddons = 0, } npcConfig.flags = { - floorchange = false + floorchange = false, } local keywordHandler = KeywordHandler:new() diff --git a/data-otservbr-global/npc/battlemart.lua b/data-otservbr-global/npc/battlemart.lua index d8109854bc0..ec668947aa9 100644 --- a/data-otservbr-global/npc/battlemart.lua +++ b/data-otservbr-global/npc/battlemart.lua @@ -86,6 +86,7 @@ npcConfig.shop = { { itemName = "enhanced exercise bow", clientId = 35282, buy = 2340000 }, { itemName = "enhanced exercise club", clientId = 35281, buy = 2340000 }, { itemName = "enhanced exercise rod", clientId = 35283, buy = 2340000 }, + { itemName = "enhanced exercise shield", clientId = 44066, buy = 2340000 }, { itemName = "enhanced exercise sword", clientId = 35279, buy = 2340000 }, { itemName = "enhanced exercise wand", clientId = 35284, buy = 2340000 }, { itemName = "envenomed arrow", clientId = 16143, buy = 12 }, @@ -124,10 +125,12 @@ npcConfig.shop = { { itemName = "magic wall rune", clientId = 3180, buy = 116 }, { itemName = "magma amulet", clientId = 817, buy = 15000 }, { itemName = "mana potion", clientId = 268, buy = 56 }, + { itemName = "mana shield potion", clientId = 35563, buy = 200000 }, { itemName = "masterful exercise axe", clientId = 35286, buy = 2700000 }, { itemName = "masterful exercise bow", clientId = 35288, buy = 2700000 }, { itemName = "masterful exercise club", clientId = 35287, buy = 2700000 }, { itemName = "masterful exercise rod", clientId = 35289, buy = 2700000 }, + { itemName = "masterful exercise shield", clientId = 44067, buy = 2700000 }, { itemName = "masterful exercise sword", clientId = 35285, buy = 2700000 }, { itemName = "masterful exercise wand", clientId = 35290, buy = 2700000 }, { itemName = "might ring", clientId = 3048, buy = 5000 }, diff --git a/data-otservbr-global/npc/john_bounac.lua b/data-otservbr-global/npc/john_bounac.lua new file mode 100644 index 00000000000..e69de29bb2d diff --git a/data-otservbr-global/npc/the_bone_master.lua b/data-otservbr-global/npc/the_bone_master.lua index 0d94d2bac86..67a6322f299 100644 --- a/data-otservbr-global/npc/the_bone_master.lua +++ b/data-otservbr-global/npc/the_bone_master.lua @@ -68,7 +68,7 @@ local function creatureSayCallback(npc, creature, type, message) }, npc, creature) npcHandler:setTopic(playerId, 1) end - elseif MsgContains(message, "advancement") then + elseif MsgContains(message, "advancement") or MsgContains(message, "demonic") or MsgContains(message, "essence") then if player:getStorageValue(Storage.OutfitQuest.BrotherhoodOutfit) == 1 then npcHandler:say("So you want to advance to a {Hyaena} rank? Did you bring 500 demonic essences with you?", npc, creature) npcHandler:setTopic(playerId, 3) diff --git a/data-otservbr-global/npc/the_dream_master.lua b/data-otservbr-global/npc/the_dream_master.lua index cee652f28ee..b282908b375 100644 --- a/data-otservbr-global/npc/the_dream_master.lua +++ b/data-otservbr-global/npc/the_dream_master.lua @@ -67,7 +67,7 @@ local function creatureSayCallback(npc, creature, type, message) }, npc, creature) npcHandler:setTopic(playerId, 1) end - elseif MsgContains(message, "advancement") then + elseif MsgContains(message, "advancement") or MsgContains(message, "demonic") or MsgContains(message, "essence") then if player:getStorageValue(Storage.OutfitQuest.NightmareOutfit) == 1 then npcHandler:say("So you want to advance to a {Initiate} rank? Did you bring 500 demonic essences with you?", npc, creature) npcHandler:setTopic(playerId, 3) diff --git a/data-otservbr-global/scripts/actions/addons/addons.lua b/data-otservbr-global/scripts/actions/addons/addons.lua index 157187d189f..8fea3d0b074 100644 --- a/data-otservbr-global/scripts/actions/addons/addons.lua +++ b/data-otservbr-global/scripts/actions/addons/addons.lua @@ -17,6 +17,9 @@ local config = { -- poltergeist [32630] = { female = 1271, male = 1270, addon = 1, effect = CONST_ME_BLUE_GHOST, achievement = "Mainstreet Nightmare" }, [32631] = { female = 1271, male = 1270, addon = 2, effect = CONST_ME_BLUE_GHOST, achievement = "Mainstreet Nightmare" }, + -- rascoohan + [35595] = { female = 1372, male = 1371, addon = 1, achievement = "Honorary Rascoohan" }, + [35695] = { female = 1372, male = 1371, addon = 2, achievement = "Honorary Rascoohan" }, } local addons = Action() diff --git a/data-otservbr-global/scripts/actions/other/bag_you_desire.lua b/data-otservbr-global/scripts/actions/other/bag_you_desire.lua index 247651b38a0..7cf5357fa57 100644 --- a/data-otservbr-global/scripts/actions/other/bag_you_desire.lua +++ b/data-otservbr-global/scripts/actions/other/bag_you_desire.lua @@ -1,27 +1,30 @@ local rewards = { - { id = 34082, name = "Soulcutter" }, - { id = 34083, name = "Soulshredder" }, - { id = 34084, name = "Soulbiter" }, - { id = 34085, name = "Souleater" }, - { id = 34086, name = "Soulcrusher" }, - { id = 34087, name = "Soulmaimer" }, - { id = 34088, name = "Soulbleeder" }, - { id = 34089, name = "Soulpiercer" }, - { id = 34090, name = "Soultainter" }, - { id = 34091, name = "Soulhexer" }, - { id = 34092, name = "Soulshanks" }, - { id = 34093, name = "Soulstrider" }, - { id = 34094, name = "Soulshell" }, - { id = 34095, name = "Soulmantle" }, - { id = 34096, name = "Soulshroud" }, - { id = 34097, name = "Pair of Soulwalkers" }, - { id = 34098, name = "Pair of Soulstalkers" }, - { id = 34099, name = "Soulbastion" }, + { id = 34082, name = "soulcutter" }, + { id = 34083, name = "soulshredder" }, + { id = 34084, name = "soulbiter" }, + { id = 34085, name = "souleater" }, + { id = 34086, name = "soulcrusher" }, + { id = 34087, name = "soulmaimer" }, + { id = 34097, name = "pair of soulwalkers" }, + { id = 34099, name = "soulbastion" }, + { id = 34088, name = "soulbleeder" }, + { id = 34089, name = "soulpiercer" }, + { id = 34094, name = "soulshell" }, + { id = 34098, name = "pair of soulstalkers" }, + { id = 34090, name = "soultainter" }, + { id = 34092, name = "soulshanks" }, + { id = 34095, name = "soulmantle" }, + { id = 34091, name = "soulhexer" }, + { id = 34093, name = "soulstrider" }, + { id = 34096, name = "soulshroud" }, } local bagyouDesire = Action() function bagyouDesire.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if not player then + return false + end local randId = math.random(1, #rewards) local rewardItem = rewards[randId] diff --git a/data-otservbr-global/scripts/actions/other/exercise_training.lua b/data-otservbr-global/scripts/actions/other/exercise_training.lua index d03dc1cb928..353c1459c21 100644 --- a/data-otservbr-global/scripts/actions/other/exercise_training.lua +++ b/data-otservbr-global/scripts/actions/other/exercise_training.lua @@ -48,7 +48,7 @@ function exerciseTraining.onUse(player, item, fromPosition, target, toPosition, playersOnDummy = playersOnDummy + 1 end - if playersOnDummy == maxAllowedOnADummy then + if playersOnDummy >= maxAllowedOnADummy then player:sendTextMessage(MESSAGE_FAILURE, "That exercise dummy is busy.") return true end diff --git a/data-otservbr-global/scripts/actions/other/others/quest_system1.lua b/data-otservbr-global/scripts/actions/other/others/quest_system1.lua index 7fe74d5f729..836ce8b20f5 100644 --- a/data-otservbr-global/scripts/actions/other/others/quest_system1.lua +++ b/data-otservbr-global/scripts/actions/other/others/quest_system1.lua @@ -88,6 +88,10 @@ function questSystem1.onUse(player, item, fromPosition, target, toPosition, isHo end else local container = Container(item.uid) + if not container then + logger.error("[questSystem1.onUse] failed to create container") + return false + end for i = 0, container:getSize() - 1 do local originalItem = container:getItem(i) local newItem = Game.createItem(originalItem.itemid, originalItem.type) diff --git a/data-otservbr-global/scripts/actions/quests/a_pirates_tail/cheesy_key.lua b/data-otservbr-global/scripts/actions/quests/a_pirates_tail/cheesy_key.lua new file mode 100644 index 00000000000..aea03186fa6 --- /dev/null +++ b/data-otservbr-global/scripts/actions/quests/a_pirates_tail/cheesy_key.lua @@ -0,0 +1,39 @@ +local config = { + sorcerer = { + id = 1367, + name = "Bladespark", + }, + druid = { + id = 1364, + name = "Mossmasher", + }, + paladin = { + id = 1366, + name = "Sandscourge", + }, + knight = { + id = 1365, + name = "Snowbash", + }, +} + +local action = Action() + +function action.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local vocation = config[player:getVocation():getBase():getName():lower()] + if not vocation then + return true + end + if player:hasFamiliar(vocation.id) then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You already have the " .. vocation.name .. " familiar.") + return false + end + + player:addFamiliar(vocation.id) + item:remove() + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You obtained the " .. vocation.name .. " familiar.") + return true +end + +action:id(35508) +action:register() diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/dread_maiden_lever.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/dread_maiden_lever.lua index 1f398adc276..92ad5202162 100644 --- a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/dread_maiden_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/dread_maiden_lever.lua @@ -6,11 +6,11 @@ local config = { requiredLevel = 250, playerPositions = { - { pos = Position(33739, 31506, 14), teleport = Position(33711, 31510, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33740, 31506, 14), teleport = Position(33711, 31510, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33741, 31506, 14), teleport = Position(33711, 31510, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33742, 31506, 14), teleport = Position(33711, 31510, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33743, 31506, 14), teleport = Position(33711, 31510, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33739, 31506, 14), teleport = Position(33712, 31509, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33740, 31506, 14), teleport = Position(33712, 31509, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33741, 31506, 14), teleport = Position(33712, 31509, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33742, 31506, 14), teleport = Position(33712, 31509, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33743, 31506, 14), teleport = Position(33712, 31509, 14), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(33703, 31494, 14), diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/fear_feaster_lever.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/fear_feaster_lever.lua index ffb9a98df82..f671cb1e136 100644 --- a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/fear_feaster_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/fear_feaster_lever.lua @@ -6,11 +6,11 @@ local config = { requiredLevel = 250, playerPositions = { - { pos = Position(33734, 31471, 14), teleport = Position(33711, 31476, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33735, 31471, 14), teleport = Position(33711, 31476, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33736, 31471, 14), teleport = Position(33711, 31476, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33737, 31471, 14), teleport = Position(33711, 31476, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33738, 31471, 14), teleport = Position(33711, 31476, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33734, 31471, 14), teleport = Position(33711, 31474, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33735, 31471, 14), teleport = Position(33711, 31474, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33736, 31471, 14), teleport = Position(33711, 31474, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33737, 31471, 14), teleport = Position(33711, 31474, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33738, 31471, 14), teleport = Position(33711, 31474, 14), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(33705, 31463, 14), diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/pale_worm_lever.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/pale_worm_lever.lua index 537bbf2aa32..74b49470618 100644 --- a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/pale_worm_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/pale_worm_lever.lua @@ -7,16 +7,16 @@ local config = { timeToDefeat = 25 * 60, playerPositions = { - { pos = Position(33772, 31504, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33773, 31504, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33774, 31504, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33775, 31504, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33773, 31503, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33774, 31503, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33775, 31503, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33773, 31505, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33774, 31505, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33775, 31505, 14), teleport = Position(33808, 31515, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33772, 31504, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33773, 31504, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33774, 31504, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33775, 31504, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33773, 31503, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33774, 31503, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33775, 31503, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33773, 31505, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33774, 31505, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33775, 31505, 14), teleport = Position(33808, 31513, 14), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(33793, 31496, 14), diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/unwelcome_lever.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/unwelcome_lever.lua index 59f1665eacf..93c4e833b9e 100644 --- a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/unwelcome_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/unwelcome_lever.lua @@ -6,11 +6,11 @@ local config = { requiredLevel = 250, playerPositions = { - { pos = Position(33736, 31537, 14), teleport = Position(33708, 31547, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33737, 31537, 14), teleport = Position(33708, 31547, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33738, 31537, 14), teleport = Position(33708, 31547, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33739, 31537, 14), teleport = Position(33708, 31547, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33740, 31537, 14), teleport = Position(33708, 31547, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33736, 31537, 14), teleport = Position(33707, 31545, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33737, 31537, 14), teleport = Position(33707, 31545, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33738, 31537, 14), teleport = Position(33707, 31545, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33739, 31537, 14), teleport = Position(33707, 31545, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33740, 31537, 14), teleport = Position(33707, 31545, 14), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(33699, 31529, 14), diff --git a/data-otservbr-global/scripts/actions/quests/first_dragon/rewards.lua b/data-otservbr-global/scripts/actions/quests/first_dragon/rewards.lua index c855009543e..2d1c8ac2a90 100644 --- a/data-otservbr-global/scripts/actions/quests/first_dragon/rewards.lua +++ b/data-otservbr-global/scripts/actions/quests/first_dragon/rewards.lua @@ -6,7 +6,7 @@ local bpItems = { { name = "gold token", count = 3 }, { name = "blue gem", count = 1 }, { name = "yellow gem", count = 1 }, - { name = "red gem", count = 1 }, + { id = 3039, count = 1 }, -- red gem { name = "demon horn", count = 2 }, { name = "slime heart", count = 2 }, { name = "energy vein", count = 2 }, @@ -46,7 +46,11 @@ function finalReward.onUse(player, item, fromPosition, target, toPosition, isHot local bp = Game.createItem("Backpack", 1) if bp then for i = 1, #bpItems do - bp:addItem(bpItems[i].name, count) + if bpItems[i].id then + bp:addItem(bpItems[i].id, count) + else + bp:addItem(bpItems[i].name, count) + end end bp:moveTo(player) end diff --git a/data-otservbr-global/scripts/actions/quests/first_dragon/treasure_chest.lua b/data-otservbr-global/scripts/actions/quests/first_dragon/treasure_chest.lua index b82c52cce56..d40aec690d2 100644 --- a/data-otservbr-global/scripts/actions/quests/first_dragon/treasure_chest.lua +++ b/data-otservbr-global/scripts/actions/quests/first_dragon/treasure_chest.lua @@ -4,7 +4,8 @@ local UniqueTable = { count = 1, }, [14002] = { - name = "gold nugget", + -- gold nugget, + itemId = 3040, count = 2, }, [14003] = { diff --git a/data-otservbr-global/scripts/actions/quests/grimvale/portal_minis_grimvale.lua b/data-otservbr-global/scripts/actions/quests/grimvale/portal_minis_grimvale.lua index 109c04189fe..09c464fc7f6 100644 --- a/data-otservbr-global/scripts/actions/quests/grimvale/portal_minis_grimvale.lua +++ b/data-otservbr-global/scripts/actions/quests/grimvale/portal_minis_grimvale.lua @@ -4,7 +4,7 @@ local config = { bossName = "Bloodback", timeToFightAgain = 10, -- In hour timeToDefeat = 10, -- In minutes - destination = Position(33180, 32012, 8), + destination = Position(33182, 32012, 8), bossPosition = Position(33184, 32016, 8), specPos = { from = Position(33174, 32007, 8), @@ -32,7 +32,7 @@ local config = { bossName = "Sharpclaw", timeToFightAgain = 10, -- In hour timeToDefeat = 10, -- In minutes - destination = Position(33120, 31997, 9), + destination = Position(33121, 31998, 9), bossPosition = Position(33120, 32002, 9), specPos = { from = Position(33113, 31994, 9), @@ -60,7 +60,7 @@ local config = { bossName = "Black Vixen", timeToFightAgain = 10, -- In hour timeToDefeat = 10, -- In minutes - destination = Position(33447, 32040, 9), + destination = Position(33448, 32038, 9), bossPosition = Position(33450, 32034, 9), specPos = { from = Position(33442, 32027, 9), @@ -74,7 +74,7 @@ local config = { exitPosition = Position(33167, 31978, 8), }, [7] = { - teleportPosition = { x = 33055, y = 31888, z = 9 }, + teleportPosition = { x = 33056, y = 31890, z = 9 }, exitPosition = Position(33055, 31911, 9), }, [8] = { diff --git a/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua b/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua index cb945df5a47..652ba60f1ef 100644 --- a/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua +++ b/data-otservbr-global/scripts/actions/quests/lions_rock/lions_rock.lua @@ -5,7 +5,7 @@ local rewards = { "giant shimmering pearl", "gold ingot", "green gem", - "red gem", + 3039, -- red gem "lion's heart", "yellow gem", } diff --git a/data-otservbr-global/scripts/actions/quests/secret_library/ghulosh_lever.lua b/data-otservbr-global/scripts/actions/quests/secret_library/ghulosh_lever.lua index dcf90e0a0ed..7e7e3d57c3c 100644 --- a/data-otservbr-global/scripts/actions/quests/secret_library/ghulosh_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/secret_library/ghulosh_lever.lua @@ -6,11 +6,11 @@ local config = { requiredLevel = 250, playerPositions = { - { pos = Position(32747, 32773, 10), teleport = Position(32756, 32729, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32748, 32773, 10), teleport = Position(32756, 32729, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32749, 32773, 10), teleport = Position(32756, 32729, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32750, 32773, 10), teleport = Position(32756, 32729, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32751, 32773, 10), teleport = Position(32756, 32729, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32747, 32773, 10), teleport = Position(32757, 32727, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32748, 32773, 10), teleport = Position(32757, 32727, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32749, 32773, 10), teleport = Position(32757, 32727, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32750, 32773, 10), teleport = Position(32757, 32727, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32751, 32773, 10), teleport = Position(32757, 32727, 10), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(32748, 32713, 10), diff --git a/data-otservbr-global/scripts/actions/quests/secret_library/gorzindel_lever.lua b/data-otservbr-global/scripts/actions/quests/secret_library/gorzindel_lever.lua index 082f94ef8a5..fe2fcd74cda 100644 --- a/data-otservbr-global/scripts/actions/quests/secret_library/gorzindel_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/secret_library/gorzindel_lever.lua @@ -9,8 +9,8 @@ local config = { { pos = Position(32747, 32749, 10), teleport = Position(32686, 32721, 10), effect = CONST_ME_TELEPORT }, { pos = Position(32748, 32749, 10), teleport = Position(32686, 32721, 10), effect = CONST_ME_TELEPORT }, { pos = Position(32749, 32749, 10), teleport = Position(32686, 32721, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32747, 32750, 10), teleport = Position(32686, 32721, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32747, 32751, 10), teleport = Position(32686, 32721, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32750, 32749, 10), teleport = Position(32686, 32721, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32751, 32749, 10), teleport = Position(32686, 32721, 10), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(32680, 32711, 10), diff --git a/data-otservbr-global/scripts/actions/quests/secret_library/mazzinor_lever.lua b/data-otservbr-global/scripts/actions/quests/secret_library/mazzinor_lever.lua index 4071002a863..eb7afac5509 100644 --- a/data-otservbr-global/scripts/actions/quests/secret_library/mazzinor_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/secret_library/mazzinor_lever.lua @@ -6,11 +6,11 @@ local config = { requiredLevel = 250, playerPositions = { - { pos = Position(32721, 32773, 10), teleport = Position(32725, 32728, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32721, 32773, 10), teleport = Position(32725, 32728, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32721, 32773, 10), teleport = Position(32725, 32728, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32721, 32773, 10), teleport = Position(32725, 32728, 10), effect = CONST_ME_TELEPORT }, - { pos = Position(32721, 32773, 10), teleport = Position(32725, 32728, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32721, 32773, 10), teleport = Position(32726, 32726, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32722, 32773, 10), teleport = Position(32726, 32726, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32723, 32773, 10), teleport = Position(32726, 32726, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32724, 32773, 10), teleport = Position(32726, 32726, 10), effect = CONST_ME_TELEPORT }, + { pos = Position(32725, 32773, 10), teleport = Position(32726, 32726, 10), effect = CONST_ME_TELEPORT }, }, specPos = { from = Position(32716, 32713, 10), diff --git a/data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua b/data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua index fc9f5929854..51f8281a04d 100644 --- a/data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua +++ b/data-otservbr-global/scripts/actions/quests/soul_war/soulwar_entrances.lua @@ -9,7 +9,7 @@ local config = { { position = { x = 34022, y = 31091, z = 11 }, destination = { x = 33685, y = 31599, z = 14 } }, -- goshnar's malice entrance { position = { x = 33856, y = 31884, z = 5 }, destination = { x = 33857, y = 31865, z = 6 } }, -- goshnar's cruelty entrance { position = { x = 33889, y = 31873, z = 3 }, destination = { x = 33830, y = 31881, z = 4 } }, -- 1st to 2nd floor cloak - { position = { x = 33829, y = 31880, z = 4 }, destination = { x = 33856, y = 31890, z = 5 } }, -- 2nd to 3rd floor cloak + { position = { x = 33829, y = 31880, z = 4 }, destination = { x = 33856, y = 31889, z = 5 } }, -- 2nd to 3rd floor cloak } local portal = { position = { x = 33914, y = 31032, z = 12 }, destination = { x = 33780, y = 31601, z = 14 } } -- goshnar's hatred entrance diff --git a/data-otservbr-global/scripts/actions/tools/rust_remover.lua b/data-otservbr-global/scripts/actions/tools/rust_remover.lua index f5adad4511f..93e64a66292 100644 --- a/data-otservbr-global/scripts/actions/tools/rust_remover.lua +++ b/data-otservbr-global/scripts/actions/tools/rust_remover.lua @@ -1,6 +1,7 @@ local CHAIN_ARMOR, SCALE_ARMOR, BRASS_ARMOR, PLATE_ARMOR, KNIGHT_ARMOR, PALADIN_ARMOR, CROWN_ARMOR, GOLDEN_ARMOR, DRAGON_SCALE_MAIL, MAGIC_PLATE_ARMOR = 3358, 3377, 3359, 3357, 3370, 8063, 3381, 3360, 3386, 3366 local STUDDED_LEGS, CHAIN_LEGS, BRASS_LEGS, PLATE_LEGS, KNIGHT_LEGS, CROWN_LEGS, GOLDEN_LEGS = 3362, 3558, 3372, 3557, 3371, 3382, 3364 local BRASS_HELMET, IRON_HELMET, STEEL_HELMET, CROWN_HELMET, CRUSADER_HELMET, ROYAL_HELMET = 3354, 3353, 3351, 3385, 3391, 3392 +local PLATE_SHIELD, ANCIENT_SHIELD, NORSE_SHIELD, CROWN_SHIELD, VAMPIRE_SHIELD = 3410, 3432, 7460, 3419, 3434 local config = { [8894] = { -- common rusty armor @@ -71,6 +72,14 @@ local config = { { { 97501, 99000 }, CROWN_LEGS }, { { 99001, 100000 }, GOLDEN_LEGS }, }, + [8902] = { -- slightly rusted shield + { { 1, 28070 } }, + { { 28071, 59440 }, PLATE_SHIELD }, + { { 59441, 88310 }, ANCIENT_SHIELD }, + { { 88311, 97601 }, NORSE_SHIELD }, + { { 97602, 99901 }, CROWN_SHIELD }, + { { 99902, 100000 }, VAMPIRE_SHIELD }, + }, } local rustRemover = Action() diff --git a/data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/ratmiral_death.lua b/data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/ratmiral_death.lua new file mode 100644 index 00000000000..ea00d6ead07 --- /dev/null +++ b/data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/ratmiral_death.lua @@ -0,0 +1,20 @@ +local event = CreatureEvent("RatmiralBlackwhiskersDeath") +local outfits = { 1371, 1372 } + +function event.onDeath(creature, corpse, killer, mostDamage, unjustified, mostDamage_unjustified) + if not creature or not creature:getMonster() then + return + end + local damageMap = creature:getMonster():getDamageMap() + + for key, _ in pairs(damageMap) do + local player = Player(key) + if player and not player:hasOutfit(outfits[1]) and not player:hasOutfit(outfits[2]) then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations you received the Rascoohan Outfit.") + player:addOutfit(1371, 0) + player:addOutfit(1372, 0) + end + end +end + +event:register() diff --git a/data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/tentugly_death.lua b/data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/tentugly_death.lua new file mode 100644 index 00000000000..58eeed5e10d --- /dev/null +++ b/data-otservbr-global/scripts/creaturescripts/quests/a_pirates_tail/tentugly_death.lua @@ -0,0 +1,19 @@ +local tentuglysHeadDeath = CreatureEvent("TentuglysHeadDeath") + +function tentuglysHeadDeath.onDeath(creature, corpse, killer, mostDamage, unjustified, mostDamage_unjustified) + if not creature or not creature:getMonster() then + return + end + local damageMap = creature:getMonster():getDamageMap() + + for key, value in pairs(damageMap) do + local player = Player(key) + if player then + player:setStorageValue(Storage.Quest.U12_60.APiratesTail.TentuglyKilled, 1) -- Access to wreckoning + player:addAchievement("Release the Kraken") + player:addMount(175) + end + end +end + +tentuglysHeadDeath:register() diff --git a/data-otservbr-global/scripts/globalevents/quests/druid_outfits_blooming_griffinclaw.lua b/data-otservbr-global/scripts/globalevents/quests/druid_outfits_blooming_griffinclaw.lua index 0943af42595..fbb1dc44d05 100644 --- a/data-otservbr-global/scripts/globalevents/quests/druid_outfits_blooming_griffinclaw.lua +++ b/data-otservbr-global/scripts/globalevents/quests/druid_outfits_blooming_griffinclaw.lua @@ -8,6 +8,7 @@ local function decayFlower(tile) end local function bloom() + -- 1/7 chance of blooming if math.random(7) ~= 1 then addEvent(bloom, 60 * 60 * 1000) return @@ -17,6 +18,7 @@ local function bloom() if not tile then return false end + local item = tile:getItemById(5687) if item then item:transform(5658) diff --git a/data-otservbr-global/scripts/movements/quests/pits_of_inferno/throne.lua b/data-otservbr-global/scripts/movements/quests/pits_of_inferno/throne.lua index c910622a5ab..92e1c656673 100644 --- a/data-otservbr-global/scripts/movements/quests/pits_of_inferno/throne.lua +++ b/data-otservbr-global/scripts/movements/quests/pits_of_inferno/throne.lua @@ -58,6 +58,7 @@ function throne.onStepIn(creature, item, position, fromPosition) if player:getStorageValue(throne.storage) ~= 1 then player:setStorageValue(throne.storage, 1) + player:setStorageValue(Storage.PitsOfInferno.ShortcutHubDoor, 1) player:getPosition():sendMagicEffect(throne.effect) player:say(throne.text, TALKTYPE_MONSTER_SAY) else diff --git a/data-otservbr-global/world/otservbr-npc.xml b/data-otservbr-global/world/otservbr-npc.xml index 68721530346..b82ea1ad59a 100644 --- a/data-otservbr-global/world/otservbr-npc.xml +++ b/data-otservbr-global/world/otservbr-npc.xml @@ -117,6 +117,9 @@ + + + @@ -2223,6 +2226,9 @@ + + + diff --git a/data/libs/achievements_lib.lua b/data/libs/achievements_lib.lua index 2ec9b5ad318..49318843c1c 100644 --- a/data/libs/achievements_lib.lua +++ b/data/libs/achievements_lib.lua @@ -574,6 +574,10 @@ achievements = { [477] = { name = "Waypoint Explorer", grade = 1, points = 1, description = "You've explored all the towns of Tibia and discovered each town's waypoint." }, [478] = { name = "Up the Molehill", grade = 1, points = 3, description = "Putting this candle stump on your new mount was kind of a waiting game. You're even tempted to call it whack-a-mole. But in the end you found a loyal companion for your journeys into the depths." }, [479] = { name = "Inquisition's Arm", grade = 1, points = 2, description = "Your special outfit, bestowed exclusively on a dedicated hand of the Inquisition, is now complete." }, + + --12.60 + [480] = { name = "Honorary Rascoohan", grade = 1, points = 2, description = "When in Rascacoon, do as the Rascoohans do!" }, + [481] = { name = "Release the Kraken", grade = 1, points = 3, description = "Riding around on this squishy companion gives you the feeling of flying through the air... uhm... swimming through the seven seas!" }, } ACHIEVEMENT_FIRST = 1 diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index c2ae610cfd2..18737d66f3e 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -203,7 +203,15 @@ function Player.transferMoneyTo(self, target, amount) if not target then return false end - return Bank.transfer(self, target, amount) + if not Bank.transfer(self, target, amount) then + return false + end + + local targetPlayer = Player(target) + if targetPlayer then + targetPlayer:sendTextMessage(MESSAGE_LOOK, self:getName() .. " has transferred " .. FormatNumber(amount) .. " gold coins to you.") + end + return true end function Player.withdrawMoney(self, amount) diff --git a/data/libs/functions/tile.lua b/data/libs/functions/tile.lua index 0201459018c..0e35d24afbd 100644 --- a/data/libs/functions/tile.lua +++ b/data/libs/functions/tile.lua @@ -46,6 +46,9 @@ function Tile:isWalkable(pz, creature, floorchange, block, proj) if self:hasProperty(CONST_PROP_BLOCKSOLID) or self:hasProperty(CONST_PROP_BLOCKPROJECTILE) then return false end + if self:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) then + return false + end if pz and (self:hasFlag(TILESTATE_HOUSE) or self:hasFlag(TILESTATE_PROTECTIONZONE)) then return false end @@ -56,6 +59,9 @@ function Tile:isWalkable(pz, creature, floorchange, block, proj) return false end if block then + if self:hasProperty(CONST_PROP_BLOCKPATH) or self:hasProperty(CONST_PROP_IMMOVABLEBLOCKPATH) or self:hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) or self:hasProperty(CONST_PROP_NOFIELDBLOCKPATH) then + return false + end local topStackItem = self:getTopTopItem() if topStackItem and topStackItem:hasProperty(CONST_PROP_BLOCKPATH) then return false @@ -66,7 +72,9 @@ function Tile:isWalkable(pz, creature, floorchange, block, proj) if #items > 0 then for i = 1, #items do local itemType = ItemType(items[i]) - if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and items[i]:hasProperty(CONST_PROP_BLOCKSOLID) then + local blockSolid = items[i]:hasProperty(CONST_PROP_BLOCKSOLID) + local blockProjectile = items[i]:hasProperty(CONST_PROP_BLOCKPROJECTILE) + if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and (blockSolid or blockProjectile) then return false end end diff --git a/data/scripts/eventcallbacks/monster/ondroploot__base.lua b/data/scripts/eventcallbacks/monster/ondroploot__base.lua index 5cdf62b5893..c30f0026316 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot__base.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot__base.lua @@ -11,6 +11,7 @@ function callback.monsterOnDropLoot(monster, corpse) end local mType = monster:getType() if not mType then + logger.warning("monsterOnDropLoot: monster has no type") return end diff --git a/data/scripts/reward_chest/boss_death.lua b/data/scripts/reward_chest/boss_death.lua index 9e33d0bebd7..7b3f992b384 100644 --- a/data/scripts/reward_chest/boss_death.lua +++ b/data/scripts/reward_chest/boss_death.lua @@ -13,7 +13,7 @@ function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUn local monsterType = creature:getType() -- Make sure it is a boss if monsterType and monsterType:isRewardBoss() then - if not corpse:isContainer() then + if not corpse.isContainer or not corpse:isContainer() then logger.warn("[bossDeath.onDeath] Corpse (id: {}) for reward boss {} is not a container.", corpse:getId(), creature:getName()) end corpse:registerReward() @@ -83,12 +83,12 @@ function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUn local rolls = 1 local isBoostedBoss = creature:getName():lower() == (Game.getBoostedBoss()):lower() local bossRaceIds = { player:getSlotBossId(1), player:getSlotBossId(2) } - local isBoss = table.contains(bossRaceIds, monsterType:bossRaceId()) or isBoostedBoss - if isBoss and monsterType:bossRaceId() ~= 0 then - if monsterType:bossRaceId() == player:getSlotBossId(1) then - rolls = rolls + player:getBossBonus(1) / 100 - elseif monsterType:bossRaceId() == player:getSlotBossId(2) then - rolls = rolls + player:getBossBonus(2) / 100 + local isBoss = table.contains(bossRaceIds, monsterType:raceId()) or isBoostedBoss + if isBoss and monsterType:raceId() ~= 0 then + if monsterType:raceId() == player:getSlotBossId(1) then + rolls = rolls + player:getBossBonus(1) / 100.0 + elseif monsterType:raceId() == player:getSlotBossId(2) then + rolls = rolls + player:getBossBonus(2) / 100.0 else rolls = rolls + configManager.getNumber(configKeys.BOOSTED_BOSS_LOOT_BONUS) / 100 end diff --git a/data/scripts/talkactions/player/bank.lua b/data/scripts/talkactions/player/bank.lua index b25c2811938..19acaf4a239 100644 --- a/data/scripts/talkactions/player/bank.lua +++ b/data/scripts/talkactions/player/bank.lua @@ -11,6 +11,7 @@ local balance = TalkAction("!balance") function balance.onSay(player, words, param) player:sendTextMessage(config.messageStyle, "Your current bank balance is " .. FormatNumber(Bank.balance(player)) .. ".") + return true end balance:separator(" ") @@ -27,17 +28,17 @@ function deposit.onSay(player, words, param) amount = tonumber(param) if not amount or amount <= 0 and isValidMoney(amount) then player:sendTextMessage(config.messageStyle, "Invalid amount.") - return false + return true end end if not Bank.deposit(player, amount) then player:sendTextMessage(config.messageStyle, "You don't have enough money.") - return false + return true end player:sendTextMessage(config.messageStyle, "You have deposited " .. FormatNumber(amount) .. " gold coins.") - return false + return true end deposit:separator(" ") @@ -50,16 +51,16 @@ function withdraw.onSay(player, words, param) local amount = tonumber(param) if not amount or amount <= 0 and isValidMoney(amount) then player:sendTextMessage(config.messageStyle, "Invalid amount.") - return false + return true end if not Bank.withdraw(player, amount) then player:sendTextMessage(config.messageStyle, "You don't have enough money.") - return false + return true end player:sendTextMessage(config.messageStyle, "You have withdrawn " .. FormatNumber(amount) .. " gold coins.") - return false + return true end withdraw:separator(" ") @@ -74,23 +75,23 @@ function transfer.onSay(player, words, param) local amount = tonumber(split[2]) if not amount or amount <= 0 and isValidMoney(amount) then player:sendTextMessage(config.messageStyle, "Invalid amount.") - return false + return true end local normalizedName = Game.getNormalizedPlayerName(name) if not normalizedName then player:sendTextMessage(config.messageStyle, "A player with name " .. name .. " does not exist.") - return false + return true end name = normalizedName - if not Bank.transfer(player, name, amount) then + if not player:transferMoneyTo(name, amount) then player:sendTextMessage(config.messageStyle, "You don't have enough money.") - return false + return true end player:sendTextMessage(config.messageStyle, "You have transferred " .. FormatNumber(amount) .. " gold coins to " .. name .. ".") - return false + return true end transfer:separator(" ") diff --git a/data/scripts/talkactions/player/bless.lua b/data/scripts/talkactions/player/bless.lua index 0f91986f556..9e4c60825ce 100644 --- a/data/scripts/talkactions/player/bless.lua +++ b/data/scripts/talkactions/player/bless.lua @@ -1,7 +1,8 @@ local bless = TalkAction("!bless") function bless.onSay(player, words, param) - return Blessings.BuyAllBlesses(player) + Blessings.BuyAllBlesses(player) + return true end bless:groupType("normal") diff --git a/data/scripts/talkactions/player/buy_house.lua b/data/scripts/talkactions/player/buy_house.lua index 0d32e233d87..90b33aef735 100644 --- a/data/scripts/talkactions/player/buy_house.lua +++ b/data/scripts/talkactions/player/buy_house.lua @@ -8,13 +8,13 @@ function buyHouse.onSay(player, words, param) if not player:isPremium() then player:sendCancelMessage("You need a premium account.") - return false + return true end local houseBuyLevel = configManager.getNumber(configKeys.HOUSE_BUY_LEVEL) if player:getLevel() < houseBuyLevel then player:sendCancelMessage("You need to be level " .. houseBuyLevel .. " to buy a house.") - return false + return true end local position = player:getPosition() @@ -27,17 +27,17 @@ function buyHouse.onSay(player, words, param) if not house or playerPos ~= houseEntry then player:sendCancelMessage("You have to be looking at the door of the house you would like to buy.") - return false + return true end if house:getOwnerGuid() > 0 then player:sendCancelMessage("This house already has an owner.") - return false + return true end if player:getHouse() then player:sendCancelMessage("You are already the owner of a house.") - return false + return true end if house:hasItemOnTile() then @@ -48,12 +48,12 @@ function buyHouse.onSay(player, words, param) local price = house:getPrice() if not player:removeMoneyBank(price) then player:sendCancelMessage("You do not have enough money.") - return false + return true end house:setHouseOwner(player:getGuid()) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have successfully bought this house, be sure to have the money for the rent in the bank.") - return false + return true end buyHouse:separator(" ") diff --git a/data/scripts/talkactions/player/leave_house.lua b/data/scripts/talkactions/player/leave_house.lua index 694dbdb2f5b..8743ed17a23 100644 --- a/data/scripts/talkactions/player/leave_house.lua +++ b/data/scripts/talkactions/player/leave_house.lua @@ -7,13 +7,13 @@ function leaveHouse.onSay(player, words, param) if not house then player:sendCancelMessage("You are not inside a house.") playerPosition:sendMagicEffect(CONST_ME_POFF) - return false + return true end if house:getOwnerGuid() ~= player:getGuid() then player:sendCancelMessage("You are not the owner of this house.") playerPosition:sendMagicEffect(CONST_ME_POFF) - return false + return true end if house:hasNewOwnership() then @@ -39,7 +39,7 @@ function leaveHouse.onSay(player, words, param) house:setNewOwnerGuid(0) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have successfully left your house.") playerPosition:sendMagicEffect(CONST_ME_POFF) - return false + return true end leaveHouse:separator(" ") diff --git a/data/scripts/talkactions/player/sell_house.lua b/data/scripts/talkactions/player/sell_house.lua index 27ad85e61ad..c96cb5f71c3 100644 --- a/data/scripts/talkactions/player/sell_house.lua +++ b/data/scripts/talkactions/player/sell_house.lua @@ -4,20 +4,20 @@ function sellHouse.onSay(player, words, param) local tradePartner = Player(param) if not tradePartner or tradePartner == player then player:sendCancelMessage("Trade player not found.") - return false + return true end local house = player:getTile():getHouse() if not house then player:sendCancelMessage("You must stand in your house to initiate the trade.") - return false + return true end local returnValue = house:startTrade(player, tradePartner) if returnValue ~= RETURNVALUE_NOERROR then player:sendCancelMessage(returnValue) end - return false + return true end sellHouse:separator(" ") From 14c7500d9d149ce24f624a3db5092a05e4e83ae4 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Fri, 20 Oct 2023 14:24:18 -0300 Subject: [PATCH 09/14] improve: ip cache (#1691) The getIP is used a lot in the project and is a value that doesn't change, so we can cache it to avoid unnecessary lock. --- src/server/network/connection/connection.cpp | 12 +++++++----- src/server/network/connection/connection.hpp | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index 204ff83834e..8e637c053ee 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -54,6 +54,7 @@ void Connection::close(bool force) { ConnectionManager::getInstance().releaseConnection(shared_from_this()); std::lock_guard lockClass(connectionLock); + ip = 0; if (connectionState == CONNECTION_STATE_CLOSED) { return; } @@ -319,16 +320,17 @@ void Connection::internalWorker() { } uint32_t Connection::getIP() { + if (ip != 1) { + return ip; + } + std::lock_guard lockClass(connectionLock); // IP-address is expressed in network byte order std::error_code error; const asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error); - if (error) { - return 0; - } - - return htonl(endpoint.address().to_v4().to_ulong()); + ip = error ? 0 : htonl(endpoint.address().to_v4().to_uint()); + return ip; } void Connection::internalSend(const OutputMessage_ptr &outputMessage) { diff --git a/src/server/network/connection/connection.hpp b/src/server/network/connection/connection.hpp index c240046a01c..3a80bf6ca54 100644 --- a/src/server/network/connection/connection.hpp +++ b/src/server/network/connection/connection.hpp @@ -101,6 +101,7 @@ class Connection : public std::enable_shared_from_this { time_t timeConnected; uint32_t packetsSent = 0; + uint32_t ip = 1; std::underlying_type_t connectionState = CONNECTION_STATE_OPEN; bool receivedFirst = false; From 85bc99751541ab2fd42889634b75360228d70c46 Mon Sep 17 00:00:00 2001 From: Majesty <32709570+majestyotbr@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:01:07 -0300 Subject: [PATCH 10/14] build: fix visual studio solution (#1708) --- vcproj/canary.vcxproj | 2 +- vcproj/settings.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index 7b467346d39..bdb891be8df 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -473,7 +473,7 @@ $(ProjectName)-sln - false + true false diff --git a/vcproj/settings.props b/vcproj/settings.props index aa958c24b0b..9f01a78902b 100644 --- a/vcproj/settings.props +++ b/vcproj/settings.props @@ -51,7 +51,7 @@ Level3 true true - MultiThreaded + MultiThreadedDLL
$(CANARY_LIBDEPS) From 8e3d4a060e5a802eacf1e3870c7a100416d64b35 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Fri, 20 Oct 2023 16:20:11 -0700 Subject: [PATCH 11/14] fix: divine grenade (#1715) --- src/creatures/players/wheel/player_wheel.cpp | 56 ++++++++++++++++--- .../players/wheel/wheel_definitions.hpp | 4 +- src/game/game.cpp | 7 +++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index e6b02777828..7f97edbb54b 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -1146,6 +1146,14 @@ void PlayerWheel::registerPlayerBonusData() { setSpellInstant("Divine Empowerment", false); } + if (m_playerBonusData.stages.divineGrenade > 0) { + for (int i = 0; i < m_playerBonusData.stages.divineGrenade; ++i) { + setSpellInstant("Divine Grenade", true); + } + } else { + setSpellInstant("Divine Grenade", false); + } + if (m_playerBonusData.stages.drainBody > 0) { for (int i = 0; i < m_playerBonusData.stages.drainBody; ++i) { setSpellInstant("Drain Body", true); @@ -1328,6 +1336,9 @@ void PlayerWheel::printPlayerWheelMethodsBonusData(const PlayerWheelMethodsBonus if (bonusData.stages.divineEmpowerment > 0) { g_logger().debug(" divineEmpowerment: {}", bonusData.stages.divineEmpowerment); } + if (bonusData.stages.divineGrenade > 0) { + g_logger().debug(" divineGrenade: {}", bonusData.stages.divineGrenade); + } if (bonusData.stages.blessingOfTheGrove > 0) { g_logger().debug(" blessingOfTheGrove: {}", bonusData.stages.blessingOfTheGrove); } @@ -1435,6 +1446,7 @@ void PlayerWheel::loadRevelationPerks() { addSpellToVector("Great Death Beam"); } } else if (vocationEnum == Vocation_t::VOCATION_PALADIN_CIP) { + m_playerBonusData.stages.divineGrenade = redStageValue; for (uint8_t i = 0; i < redStageValue; ++i) { addSpellToVector("Divine Grenade"); } @@ -1791,7 +1803,7 @@ bool PlayerWheel::checkCombatMastery() { bool PlayerWheel::checkDivineEmpowerment() { bool updateClient = false; - setOnThinkTimer(WheelOnThink_t::DIVINE_EMPOWERMENT, OTSYS_TIME() + 2000); + setOnThinkTimer(WheelOnThink_t::DIVINE_EMPOWERMENT, OTSYS_TIME() + 1000); const auto tile = m_player.getTile(); if (!tile) { @@ -1821,16 +1833,34 @@ bool PlayerWheel::checkDivineEmpowerment() { } else if (stage >= 1) { damageBonus = 8; } - - if (damageBonus != getMajorStat(WheelMajor_t::DAMAGE)) { - setMajorStat(WheelMajor_t::DAMAGE, damageBonus); - updateClient = true; - } + } + if (damageBonus != getMajorStat(WheelMajor_t::DAMAGE)) { + setMajorStat(WheelMajor_t::DAMAGE, damageBonus); + updateClient = true; } return updateClient; } +int32_t PlayerWheel::checkDivineGrenade(std::shared_ptr target) const { + if (!target || target == m_player.getPlayer()) { + return 0; + } + + int32_t damageBonus = 0; + uint8_t stage = getStage(WheelStage_t::DIVINE_GRENADE); + + if (stage >= 3) { + damageBonus = 100; + } else if (stage >= 2) { + damageBonus = 60; + } else if (stage >= 1) { + damageBonus = 30; + } + + return damageBonus; +} + void PlayerWheel::checkGiftOfLife() { // Healing CombatDamage giftDamage; @@ -2055,7 +2085,9 @@ void PlayerWheel::onThink(bool force /* = false*/) { m_player.sendStats(); g_game().reloadCreature(m_player.getPlayer()); } - return; + if (!force) { + return; + } } // Battle Instinct if (getInstant("Battle Instinct") && (force || getOnThinkTimer(WheelOnThink_t::BATTLE_INSTINCT) < OTSYS_TIME()) && checkBattleInstinct()) { @@ -2297,6 +2329,12 @@ void PlayerWheel::setSpellInstant(const std::string &name, bool value) { } else { setStage(WheelStage_t::DIVINE_EMPOWERMENT, 0); } + } else if (name == "Divine Grenade") { + if (value) { + setStage(WheelStage_t::DIVINE_GRENADE, getStage(WheelStage_t::DIVINE_GRENADE) + 1); + } else { + setStage(WheelStage_t::DIVINE_GRENADE, 0); + } } else if (name == "Twin Burst") { if (value) { setStage(WheelStage_t::TWIN_BURST, getStage(WheelStage_t::TWIN_BURST) + 1); @@ -2380,6 +2418,8 @@ uint8_t PlayerWheel::getStage(const std::string name) const { return PlayerWheel::getStage(WheelStage_t::DRAIN_BODY); } else if (name == "Divine Empowerment") { return PlayerWheel::getStage(WheelStage_t::DIVINE_EMPOWERMENT); + } else if (name == "Divine Grenade") { + return PlayerWheel::getStage(WheelStage_t::DIVINE_GRENADE); } else if (name == "Twin Burst") { return PlayerWheel::getStage(WheelStage_t::TWIN_BURST); } else if (name == "Executioner's Throw") { @@ -2502,6 +2542,8 @@ bool PlayerWheel::getInstant(const std::string name) const { return PlayerWheel::getStage(WheelStage_t::DRAIN_BODY); } else if (name == "Divine Empowerment") { return PlayerWheel::getStage(WheelStage_t::DIVINE_EMPOWERMENT); + } else if (name == "Divine Grenade") { + return PlayerWheel::getStage(WheelStage_t::DIVINE_GRENADE); } else if (name == "Twin Burst") { return PlayerWheel::getStage(WheelStage_t::TWIN_BURST); } else if (name == "Executioner's Throw") { diff --git a/src/creatures/players/wheel/wheel_definitions.hpp b/src/creatures/players/wheel/wheel_definitions.hpp index 6bcf9d50805..91666a8f1be 100644 --- a/src/creatures/players/wheel/wheel_definitions.hpp +++ b/src/creatures/players/wheel/wheel_definitions.hpp @@ -92,8 +92,9 @@ enum class WheelStage_t : uint8_t { AVATAR_OF_NATURE = 9, AVATAR_OF_STEEL = 10, AVATAR_OF_STORM = 11, + DIVINE_GRENADE = 12, - TOTAL_COUNT = 12 + TOTAL_COUNT = 13 }; enum class WheelOnThink_t : uint8_t { @@ -226,6 +227,7 @@ struct PlayerWheelMethodsBonusData { int combatMastery = 0; // Knight int giftOfLife = 0; // Knight/Paladin/Druid/Sorcerer int divineEmpowerment = 0; // Paladin + int divineGrenade = 0; // Paladin int blessingOfTheGrove = 0; // Druid int drainBody = 0; // Sorcerer int beamMastery = 0; // Sorcerer diff --git a/src/game/game.cpp b/src/game/game.cpp index 2e5620d0e9d..776a1483bfd 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6294,6 +6294,13 @@ void Game::applyWheelOfDestinyEffectsToDamage(CombatDamage &damage, std::shared_ damage.secondary.value += (damage.secondary.value * damageBonus) / 100.; } } + if (damage.instantSpellName == "Divine Grenade") { + int32_t damageBonus = attackerPlayer->wheel()->checkDivineGrenade(target); + if (damageBonus != 0) { + damage.primary.value += (damage.primary.value * damageBonus) / 100.; + damage.secondary.value += (damage.secondary.value * damageBonus) / 100.; + } + } } } From 400a0c39a7710a3ad743b49e64fd1f42b65ac993 Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Fri, 20 Oct 2023 16:43:02 -0700 Subject: [PATCH 12/14] fix: add missing john (bounac) script (#1722) --- data-otservbr-global/npc/john_bounac.lua | 74 ++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/data-otservbr-global/npc/john_bounac.lua b/data-otservbr-global/npc/john_bounac.lua index e69de29bb2d..7cc4c1d4a6e 100644 --- a/data-otservbr-global/npc/john_bounac.lua +++ b/data-otservbr-global/npc/john_bounac.lua @@ -0,0 +1,74 @@ +local npcName = "John" +local npcType = Game.createNpcType("John (Bounac)") +local npcConfig = {} + +npcConfig.name = npcName +npcConfig.description = npcName + +npcConfig.health = 100 +npcConfig.maxHealth = npcConfig.health +npcConfig.walkInterval = 2000 +npcConfig.walkRadius = 2 + +npcConfig.outfit = { + lookType = 1245, + lookHead = 40, + lookBody = 0, + lookLegs = 99, + lookFeet = 28, + lookAddons = 3, +} + +npcConfig.flags = { + floorchange = false, +} + +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) + +npcType.onThink = function(npc, interval) + npcHandler:onThink(npc, interval) +end + +npcType.onAppear = function(npc, creature) + npcHandler:onAppear(npc, creature) +end + +npcType.onDisappear = function(npc, creature) + npcHandler:onDisappear(npc, creature) +end + +npcType.onMove = function(npc, creature, fromPosition, toPosition) + npcHandler:onMove(npc, creature, fromPosition, toPosition) +end + +npcType.onSay = function(npc, creature, type, message) + npcHandler:onSay(npc, creature, type, message) +end + +npcType.onCloseChannel = function(npc, creature) + npcHandler:onCloseChannel(npc, creature) +end + +npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) + +npcConfig.shop = { + { clientId = 36951, buy = 10000 }, + { clientId = 36952, buy = 10000 }, + { clientId = 36953, buy = 10000 }, + { clientId = 36954, buy = 10000 }, + { clientId = 36955, buy = 10000 }, + { clientId = 36956, buy = 10000 }, +} +-- On buy npc shop message +npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) + npc:sellItem(player, itemId, amount, subType, 0, ignore, inBackpacks) +end +-- On sell npc shop message +npcType.onSellItem = function(npc, player, itemId, subtype, amount, ignore, name, totalCost) + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("Sold %ix %s for %i gold.", amount, name, totalCost)) +end +-- On check npc shop message (look item) +npcType.onCheckItem = function(npc, player, clientId, subType) end + +npcType:register(npcConfig) From 834741205062b50d55d78480fcc789fedec9cfc8 Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Fri, 20 Oct 2023 20:50:43 -0300 Subject: [PATCH 13/14] =?UTF-8?q?improve:=20removing=20-1=20values=20?= =?UTF-8?q?=E2=80=8B=E2=80=8Bin=20gameStorages=20from=20cache=20(#1670)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When someone sets the value -1 they will be clearing the cache of that gameStorages key --- data/libs/functions/game.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/libs/functions/game.lua b/data/libs/functions/game.lua index f9242c32d88..4be079586d2 100644 --- a/data/libs/functions/game.lua +++ b/data/libs/functions/game.lua @@ -136,5 +136,12 @@ function Game.setStorageValue(key, value) return end + if value == -1 then + if globalStorageTable[key] then + table.remove(globalStorageTable, key) + end + return + end + globalStorageTable[key] = value end From e3675f54343d3e740fcf40b8bb2f4cacb8909d82 Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Fri, 20 Oct 2023 21:00:25 -0300 Subject: [PATCH 14/14] improve: remove unused attribute targetTicks from Monster class (#1671) The unused attribute causes some PRs to fail due to code quality checks. --- src/creatures/monsters/monster.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 858b3ac8dd8..adcb0258503 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -357,7 +357,6 @@ class Monster final : public Creature { int64_t lastMeleeAttack = 0; uint32_t attackTicks = 0; - uint32_t targetTicks = 0; uint32_t targetChangeTicks = 0; uint32_t defenseTicks = 0; uint32_t yellTicks = 0;