From 9d0b803f5cca7c6ac178a1b92405bea32c016e5a Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Mon, 23 Oct 2023 23:32:30 -0300 Subject: [PATCH 01/12] fix: valid names in house list (#1738) Fixing to match with: https://tibia.fandom.com/wiki/House --- src/map/house/house.cpp | 15 ++++++++++++++- src/utils/tools.cpp | 11 ----------- src/utils/tools.hpp | 1 - 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index e1225aa9370..8bb26bcfb6b 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -502,7 +502,20 @@ bool House::executeTransfer(std::shared_ptr item, std::shared } void AccessList::parseList(const std::string &list) { - std::string validList = validateNameHouse(list); + std::regex regexValidChars("[^a-zA-Z' \n*!@#]+"); + std::string validList = std::regex_replace(list, regexValidChars, ""); + + // Remove empty lines + std::istringstream iss(validList); + std::ostringstream oss; + std::string line; + while (std::getline(iss, line)) { + if (!line.empty()) { + oss << line << '\n'; + } + } + validList = oss.str(); + playerList.clear(); guildRankList.clear(); allowEveryone = false; diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index f5a8597eb9c..ea927bc5189 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -1511,17 +1511,6 @@ void consoleHandlerExit() { return; } -std::string validateNameHouse(const std::string &list) { - std::string result; - for (char c : list) { - if (isalpha(c) || c == ' ' || c == '\'' || c == '!' || c == '\n' - || c == '?' || c == '#' || c == '@' || c == '*') { - result += c; - } - } - return result; -} - NameEval_t validateName(const std::string &name) { StringVector prohibitedWords = { "owner", "gamemaster", "hoster", "admin", "staff", "tibia", "account", "god", "anal", "ass", "fuck", "sex", "hitler", "pussy", "dick", "rape", "cm", "gm", "tutor", "counsellor", "god" }; StringVector toks; diff --git a/src/utils/tools.hpp b/src/utils/tools.hpp index fd6b41d1f3c..14e9fd39416 100644 --- a/src/utils/tools.hpp +++ b/src/utils/tools.hpp @@ -126,7 +126,6 @@ const char* getReturnMessage(ReturnValue value); void sleep_for(uint64_t ms); void capitalizeWords(std::string &source); void consoleHandlerExit(); -std::string validateNameHouse(const std::string &name); NameEval_t validateName(const std::string &name); bool isCaskItem(uint16_t itemId); From cc7aeedbae355de8cc81fcf35350578f8aeaf50f Mon Sep 17 00:00:00 2001 From: Beats Date: Tue, 24 Oct 2023 01:13:52 -0300 Subject: [PATCH 02/12] fix: highscore client pages (#1735) --- src/creatures/players/player.hpp | 4 +- src/game/game.cpp | 53 +++++++++----------- src/game/game.hpp | 9 ++-- src/server/network/protocol/protocolgame.cpp | 5 +- src/server/network/protocol/protocolgame.hpp | 2 +- 5 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 6cefb184e9e..791f696148c 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -1605,9 +1605,9 @@ class Player final : public Creature, public Cylinder, public Bankable { client->sendHighscoresNoData(); } } - void sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages) { + void sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer) { if (client) { - client->sendHighscores(characters, categoryId, vocationId, page, pages); + client->sendHighscores(characters, categoryId, vocationId, page, pages, updateTimer); } } void addAsyncOngoingTask(uint64_t flags) { diff --git a/src/game/game.cpp b/src/game/game.cpp index 046de46a20f..b3706b7bf38 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -7968,14 +7968,18 @@ void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8 pages /= entriesPerPage; std::ostringstream cacheKeyStream; - cacheKeyStream << "Highscore_" << static_cast(category) << "_" << static_cast(vocation) << "_" << static_cast(entriesPerPage); + cacheKeyStream << "Highscore_" << static_cast(category) << "_" << static_cast(vocation) << "_" << static_cast(entriesPerPage) << "_" << page; std::string cacheKey = cacheKeyStream.str(); auto it = highscoreCache.find(cacheKey); - auto now = std::chrono::steady_clock::now(); + auto now = std::chrono::system_clock::now(); if (it != highscoreCache.end() && (now - it->second.timestamp < HIGHSCORE_CACHE_EXPIRATION_TIME)) { auto &cacheEntry = it->second; - player->sendHighscores(cacheEntry.characters, category, vocation, page, static_cast(pages)); + auto cachedTime = it->second.timestamp; + auto durationSinceEpoch = cachedTime.time_since_epoch(); + auto secondsSinceEpoch = std::chrono::duration_cast(durationSinceEpoch).count(); + auto updateTimer = static_cast(secondsSinceEpoch); + player->sendHighscores(cacheEntry.characters, category, vocation, cacheEntry.page, static_cast(cacheEntry.entriesPerPage), updateTimer); } else { std::vector characters; characters.reserve(result->countResults()); @@ -7992,25 +7996,14 @@ void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8 } while (result->next()); } - player->sendHighscores(characters, category, vocation, page, static_cast(pages)); - highscoreCache[cacheKey] = { characters, now }; + player->sendHighscores(characters, category, vocation, page, static_cast(pages), getTimeNow()); + highscoreCache[cacheKey] = { characters, page, pages, now }; } } -std::string Game::getCachedQueryHighscore(const std::string &key) { - auto it = queryCache.find(key); - if (it != queryCache.end()) { - auto now = std::chrono::steady_clock::now(); - if (now - it->second.timestamp < CACHE_EXPIRATION_TIME) { - return it->second.query; - } - } - return ""; -} - -void Game::cacheQueryHighscore(const std::string &key, const std::string &query) { - auto now = std::chrono::steady_clock::now(); - queryCache[key] = { query, now }; +void Game::cacheQueryHighscore(const std::string &key, const std::string &query, uint32_t page, uint8_t entriesPerPage) { + QueryHighscoreCacheEntry queryEntry { query, page, entriesPerPage, std::chrono::steady_clock::now() }; + queryCache[key] = queryEntry; } std::string Game::generateHighscoreOrGetCachedQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation) { @@ -8018,13 +8011,15 @@ std::string Game::generateHighscoreOrGetCachedQueryForEntries(const std::string cacheKeyStream << "Entries_" << categoryName << "_" << page << "_" << static_cast(entriesPerPage) << "_" << vocation; std::string cacheKey = cacheKeyStream.str(); - std::string cachedQuery = getCachedQueryHighscore(cacheKey); - if (!cachedQuery.empty()) { - return cachedQuery; + if (queryCache.find(cacheKey) != queryCache.end()) { + const QueryHighscoreCacheEntry &cachedEntry = queryCache[cacheKey]; + if (cachedEntry.page == page) { + return cachedEntry.query; + } } std::string newQuery = generateHighscoreQueryForEntries(categoryName, page, entriesPerPage, vocation); - cacheQueryHighscore(cacheKey, newQuery); + cacheQueryHighscore(cacheKey, newQuery, page, entriesPerPage); return newQuery; } @@ -8034,13 +8029,15 @@ std::string Game::generateHighscoreOrGetCachedQueryForOurRank(const std::string cacheKeyStream << "OurRank_" << categoryName << "_" << static_cast(entriesPerPage) << "_" << playerGUID << "_" << vocation; std::string cacheKey = cacheKeyStream.str(); - std::string cachedQuery = getCachedQueryHighscore(cacheKey); - if (!cachedQuery.empty()) { - return cachedQuery; + if (queryCache.find(cacheKey) != queryCache.end()) { + const QueryHighscoreCacheEntry &cachedEntry = queryCache[cacheKey]; + if (cachedEntry.page == entriesPerPage) { + return cachedEntry.query; + } } std::string newQuery = generateHighscoreQueryForOurRank(categoryName, entriesPerPage, playerGUID, vocation); - cacheQueryHighscore(cacheKey, newQuery); + cacheQueryHighscore(cacheKey, newQuery, entriesPerPage, entriesPerPage); return newQuery; } @@ -8092,7 +8089,7 @@ void Game::playerHighscores(std::shared_ptr player, HighscoreType_t type uint32_t playerID = player->getID(); std::function callback = [this, playerID, category, vocation, entriesPerPage](DBResult_ptr result, bool) { - processHighscoreResults(result, playerID, category, vocation, entriesPerPage); + processHighscoreResults(std::move(result), playerID, category, vocation, entriesPerPage); }; g_databaseTasks().store(query, callback); diff --git a/src/game/game.hpp b/src/game/game.hpp index e84cb4a513b..a5c7cd116c0 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -50,12 +50,16 @@ static constexpr std::chrono::minutes HIGHSCORE_CACHE_EXPIRATION_TIME { 10 }; // struct QueryHighscoreCacheEntry { std::string query; + uint32_t page; + uint8_t entriesPerPage; std::chrono::time_point timestamp; }; struct HighscoreCacheEntry { std::vector characters; - std::chrono::time_point timestamp; + uint32_t page; + uint32_t entriesPerPage; + std::chrono::time_point timestamp; }; class Game { @@ -900,10 +904,9 @@ class Game { // Variable members (m_) std::unique_ptr m_IOWheel; - void cacheQueryHighscore(const std::string &key, const std::string &query); + void cacheQueryHighscore(const std::string &key, const std::string &query, uint32_t page, uint8_t entriesPerPage); void processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8_t category, uint32_t vocation, uint8_t entriesPerPage); - std::string getCachedQueryHighscore(const std::string &key); std::string generateVocationConditionHighscore(uint32_t vocation); std::string generateHighscoreQueryForEntries(const std::string &categoryName, uint32_t page, uint8_t entriesPerPage, uint32_t vocation); std::string generateHighscoreQueryForOurRank(const std::string &categoryName, uint8_t entriesPerPage, uint32_t playerGUID, uint32_t vocation); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 53aedeb1c88..a2466038c99 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -2066,7 +2066,7 @@ void ProtocolGame::sendHighscoresNoData() { writeToOutputBuffer(msg); } -void ProtocolGame::sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages) { +void ProtocolGame::sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer) { if (oldProtocol) { return; } @@ -2145,8 +2145,7 @@ void ProtocolGame::sendHighscores(const std::vector &charact msg.addByte(0xFF); // ?? msg.addByte(0); // ?? msg.addByte(1); // ?? - msg.add(time(nullptr)); // Last Update - + msg.add(updateTimer); // Last Update msg.setBufferPosition(vocationPosition); msg.addByte(vocations); writeToOutputBuffer(msg); diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index df7a9f53a35..2295c1036ea 100644 --- a/src/server/network/protocol/protocolgame.hpp +++ b/src/server/network/protocol/protocolgame.hpp @@ -132,7 +132,7 @@ class ProtocolGame final : public Protocol { void parseHighscores(NetworkMessage &msg); void parseTaskHuntingAction(NetworkMessage &msg); void sendHighscoresNoData(); - void sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages); + void sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages, uint32_t updateTimer); void parseGreet(NetworkMessage &msg); void parseBugReport(NetworkMessage &msg); From 2a464adf42cdd476d1efb2a7e129ef83b9ee00f8 Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Tue, 24 Oct 2023 19:04:22 -0300 Subject: [PATCH 03/12] improve: dispatcher (#1732) This PR consists of: 1- removing redundant code 2- task expiration Log 3- priority_queue to btree_multiset 4- flat_hash_set to unordered_set on hasTraceableContext 5- scheduled tasks with batch erase and insert 6- some other adjustments. **Benchmark:** ![image](https://github.com/opentibiabr/canary/assets/2267386/0f3dd190-2d19-40f6-b61e-5f8bd34dd2d1) Note; Unfortunately priority_queue does not have batch insertion.
```c++ static constexpr auto max_stress = 999999; const auto time_now = OTSYS_TIME(); std::multiset, Task::Compare> multiset; phmap::btree_multiset, Task::Compare> btreemultiset; std::priority_queue, std::deque>, Task::Compare> priority; std::vector> tasks; for (int i = 0; ++i < max_stress;) { tasks.emplace_back(std::make_shared([] {}, "priority", time_now + (100 + (rand() % 2000)))); } const auto &benchmark = [&](std::string_view type, auto set, uint64_t stress) { stress = std::min(stress, max_stress); Benchmark bm; for (int i = 0; ++i <= 3;) { set.insert(tasks.begin(), tasks.begin() + stress); while (!set.empty()) { auto it = set.begin(); // top: auto s = *it; // pop: set.erase(it); } } g_logger().info("{}({}) - {}ms", type, stress, bm.duration()); }; const auto &benchmarkPriority = [&](auto priority, uint64_t stress) { stress = std::min(stress, max_stress); Benchmark bm; for (int i = 0; ++i <= 3;) { for (int d = 0; ++d <= stress;) { priority.emplace(tasks[d]); } while (!priority.empty()) { auto t = priority.top(); priority.pop(); } } g_logger().info("{}({}) - {}ms", "priority", stress, bm.duration()); }; benchmark("multiset", multiset, 999); benchmark("multiset", multiset, 99999); benchmark("multiset", multiset, 999999); benchmark("btreemultiset", btreemultiset, 999); benchmark("btreemultiset", btreemultiset, 99999); benchmark("btreemultiset", btreemultiset, 999999); benchmarkPriority(priority, 999); benchmarkPriority(priority, 99999); benchmarkPriority(priority, 999999); ```
![image](https://github.com/opentibiabr/canary/assets/2267386/39fd3f5e-629b-4151-8315-9e07e5cd41b1)
```c++ static constexpr auto max_stress = 999999; const std::vector < std::string_view> tasks ({ "Creature::checkCreatureWalk", "Decay::checkDecay", "Dispatcher::asyncEvent", "Game::checkCreatureAttack", "Game::checkCreatures", "Game::checkImbuements", "Game::checkLight", "Game::createFiendishMonsters", "Game::createInfluencedMonsters", "Game::updateCreatureWalk", "Game::updateForgeableMonsters", "GlobalEvents::think", "LuaEnvironment::executeTimerEvent", "Modules::executeOnRecvbyte", "OutputMessagePool::sendAll", "ProtocolGame::addGameTask", "ProtocolGame::parsePacketFromDispatcher", "Raids::checkRaids", "SpawnMonster::checkSpawnMonster", "SpawnMonster::scheduleSpawn", "SpawnNpc::checkSpawnNpc", "Webhook::run", "sendRecvMessageCallback", }); const std::set set(tasks.begin(), tasks.end()); const std::unordered_set unordered_set(tasks.begin(), tasks.end()); const phmap::btree_set btree_set(tasks.begin(), tasks.end()); const phmap::flat_hash_set flat_hash_set(tasks.begin(), tasks.end()); // stress for (int i = 0; ++i <= 9999999;) { } const auto &benchmark = [&](std::string_view type, auto set, uint64_t stress) { stress = std::min(stress, max_stress); Benchmark bm; for (int i = 0; ++i <= stress;) { set.contains(tasks[rand() % (tasks.size() - 1)]); } g_logger().info("{}({}) - {}ms", type, stress, bm.duration()); }; benchmark("set", set, 999); benchmark("set", set, 99999); benchmark("set", set, 999999); benchmark("unordered_set", unordered_set, 999); benchmark("unordered_set", unordered_set, 99999); benchmark("unordered_set", unordered_set, 999999); benchmark("btree_set", btree_set, 999); benchmark("btree_set", btree_set, 99999); benchmark("btree_set", btree_set, 999999); benchmark("flat_hash_set", flat_hash_set, 999); benchmark("flat_hash_set", flat_hash_set, 99999); benchmark("flat_hash_set", flat_hash_set, 999999); ```
--- src/game/scheduling/dispatcher.cpp | 40 +++++++++-------- src/game/scheduling/dispatcher.hpp | 20 +++------ src/game/scheduling/task.cpp | 8 +++- src/game/scheduling/task.hpp | 70 +++++++++++++----------------- src/lib/thread/thread_pool.cpp | 2 +- src/lib/thread/thread_pool.hpp | 18 ++++++++ 6 files changed, 86 insertions(+), 72 deletions(-) diff --git a/src/game/scheduling/dispatcher.cpp b/src/game/scheduling/dispatcher.cpp index 638e69aaeb8..1bb7512f11d 100644 --- a/src/game/scheduling/dispatcher.cpp +++ b/src/game/scheduling/dispatcher.cpp @@ -92,6 +92,7 @@ void Dispatcher::executeEvents(std::unique_lock &asyncLock) { if (groupId == static_cast(TaskGroup::Serial)) { executeSerialEvents(tasks); + mergeEvents(); // merge request, as there may be async event requests } else { executeParallelEvents(tasks, groupId, asyncLock); } @@ -99,8 +100,11 @@ void Dispatcher::executeEvents(std::unique_lock &asyncLock) { } void Dispatcher::executeScheduledEvents() { - for (uint_fast64_t i = 0, max = scheduledTasks.size(); i < max && !scheduledTasks.empty(); ++i) { - const auto &task = scheduledTasks.top(); + auto &threadScheduledTasks = getThreadTask()->scheduledTasks; + + auto it = scheduledTasks.begin(); + while (it != scheduledTasks.end()) { + const auto &task = *it; if (task->getTime() > Task::TIME_NOW) { break; } @@ -111,12 +115,16 @@ void Dispatcher::executeScheduledEvents() { if (task->execute() && task->isCycle()) { task->updateTime(); - scheduledTasks.emplace(task); + threadScheduledTasks.emplace_back(task); } else { - scheduledTasksRef.erase(task->getEventId()); + scheduledTasksRef.erase(task->getId()); } - scheduledTasks.pop(); + ++it; + } + + if (it != scheduledTasks.begin()) { + scheduledTasks.erase(scheduledTasks.begin(), it); } dispacherContext.reset(); @@ -126,18 +134,15 @@ void Dispatcher::executeScheduledEvents() { void Dispatcher::mergeEvents() { for (const auto &thread : threads) { std::scoped_lock lock(thread->mutex); - if (!thread->tasks.empty()) { - for (uint_fast8_t i = 0; i < static_cast(TaskGroup::Last); ++i) { + for (uint_fast8_t i = 0; i < static_cast(TaskGroup::Last); ++i) { + if (!thread->tasks[i].empty()) { m_tasks[i].insert(m_tasks[i].end(), make_move_iterator(thread->tasks[i].begin()), make_move_iterator(thread->tasks[i].end())); thread->tasks[i].clear(); } } if (!thread->scheduledTasks.empty()) { - for (auto &task : thread->scheduledTasks) { - scheduledTasks.emplace(task); - scheduledTasksRef.emplace(task->getEventId(), task); - } + scheduledTasks.insert(make_move_iterator(thread->scheduledTasks.begin()), make_move_iterator(thread->scheduledTasks.end())); thread->scheduledTasks.clear(); } } @@ -153,23 +158,24 @@ std::chrono::nanoseconds Dispatcher::timeUntilNextScheduledTask() const { return CHRONO_MILI_MAX; } - const auto &task = scheduledTasks.top(); + const auto &task = *scheduledTasks.begin(); const auto timeRemaining = task->getTime() - Task::TIME_NOW; return std::max(timeRemaining, CHRONO_NANO_0); } void Dispatcher::addEvent(std::function &&f, std::string_view context, uint32_t expiresAfterMs) { - const auto &thread = threads[getThreadId()]; + const auto &thread = getThreadTask(); std::scoped_lock lock(thread->mutex); thread->tasks[static_cast(TaskGroup::Serial)].emplace_back(expiresAfterMs, std::move(f), context); notify(); } uint64_t Dispatcher::scheduleEvent(const std::shared_ptr &task) { - const auto &thread = threads[getThreadId()]; + const auto &thread = getThreadTask(); std::scoped_lock lock(thread->mutex); + auto eventId = scheduledTasksRef - .emplace(task->generateId(), thread->scheduledTasks.emplace_back(task)) + .emplace(task->getId(), thread->scheduledTasks.emplace_back(task)) .first->first; notify(); @@ -177,14 +183,14 @@ uint64_t Dispatcher::scheduleEvent(const std::shared_ptr &task) { } void Dispatcher::asyncEvent(std::function &&f, TaskGroup group) { - const auto &thread = threads[getThreadId()]; + const auto &thread = getThreadTask(); std::scoped_lock lock(thread->mutex); thread->tasks[static_cast(group)].emplace_back(0, std::move(f), dispacherContext.taskName); notify(); } void Dispatcher::stopEvent(uint64_t eventId) { - auto it = scheduledTasksRef.find(eventId); + const auto &it = scheduledTasksRef.find(eventId); if (it != scheduledTasksRef.end()) { it->second->cancel(); scheduledTasksRef.erase(it); diff --git a/src/game/scheduling/dispatcher.hpp b/src/game/scheduling/dispatcher.hpp index f360566c275..73ba38ed224 100644 --- a/src/game/scheduling/dispatcher.hpp +++ b/src/game/scheduling/dispatcher.hpp @@ -78,8 +78,8 @@ class Dispatcher { public: explicit Dispatcher(ThreadPool &threadPool) : threadPool(threadPool) { - threads.reserve(std::thread::hardware_concurrency() + 1); - for (uint_fast16_t i = 0; i < std::thread::hardware_concurrency() + 1; ++i) { + threads.reserve(threadPool.getNumberOfThreads() + 1); + for (uint_fast16_t i = 0; i < threads.capacity(); ++i) { threads.emplace_back(std::make_unique()); } }; @@ -133,17 +133,9 @@ class Dispatcher { Task::TIME_NOW = std::chrono::system_clock::now(); } - static int16_t getThreadId() { - static std::atomic_int16_t lastId = -1; - thread_local static int16_t id = -1; - - if (id == -1) { - lastId.fetch_add(1); - id = lastId.load(); - } - - return id; - }; + const auto &getThreadTask() const { + return threads[ThreadPool::getThreadId()]; + } uint64_t scheduleEvent(uint32_t delay, std::function &&f, std::string_view context, bool cycle, bool log = true) { return scheduleEvent(std::make_shared(std::move(f), context, delay, cycle, log)); @@ -204,7 +196,7 @@ class Dispatcher { // Main Events std::array, static_cast(TaskGroup::Last)> m_tasks; - std::priority_queue, std::deque>, Task::Compare> scheduledTasks; + phmap::btree_multiset, Task::Compare> scheduledTasks; phmap::parallel_flat_hash_map_m> scheduledTasksRef; friend class CanaryServer; diff --git a/src/game/scheduling/task.cpp b/src/game/scheduling/task.cpp index 59e007935ef..c9e6157ab9e 100644 --- a/src/game/scheduling/task.cpp +++ b/src/game/scheduling/task.cpp @@ -15,9 +15,15 @@ std::chrono::system_clock::time_point Task::TIME_NOW = SYSTEM_TIME_ZERO; std::atomic_uint_fast64_t Task::LAST_EVENT_ID = 0; bool Task::execute() const { - if (!func || hasExpired()) { + if (isCanceled()) { return false; } + + if (hasExpired()) { + g_logger().info("The task '{}' has expired, it has not been executed in {} ms.", getContext(), expiration - utime); + return false; + } + if (log) { if (hasTraceableContext()) { g_logger().trace("Executing task {}.", getContext()); diff --git a/src/game/scheduling/task.hpp b/src/game/scheduling/task.hpp index 6a2d33b465b..f42602242c5 100644 --- a/src/game/scheduling/task.hpp +++ b/src/game/scheduling/task.hpp @@ -9,6 +9,7 @@ #pragma once #include "utils/tools.hpp" +#include static constexpr auto SYSTEM_TIME_ZERO = std::chrono::system_clock::time_point(std::chrono::milliseconds(0)); @@ -17,24 +18,27 @@ class Task { static std::chrono::system_clock::time_point TIME_NOW; Task(uint32_t expiresAfterMs, std::function &&f, std::string_view context) : - expiration(expiresAfterMs > 0 ? TIME_NOW + std::chrono::milliseconds(expiresAfterMs) : SYSTEM_TIME_ZERO), - context(context), func(std::move(f)) { + func(std::move(f)), context(context), utime(TIME_NOW), expiration(expiresAfterMs > 0 ? TIME_NOW + std::chrono::milliseconds(expiresAfterMs) : SYSTEM_TIME_ZERO) { assert(!this->context.empty() && "Context cannot be empty!"); } Task(std::function &&f, std::string_view context, uint32_t delay, bool cycle = false, bool log = true) : - cycle(cycle), log(log), delay(delay), utime(TIME_NOW + std::chrono::milliseconds(delay)), context(context), func(std::move(f)) { + func(std::move(f)), context(context), utime(TIME_NOW + std::chrono::milliseconds(delay)), delay(delay), cycle(cycle), log(log) { assert(!this->context.empty() && "Context cannot be empty!"); } ~Task() = default; - void setEventId(uint64_t id) { - eventId = id; - } + uint64_t getId() { + if (id == 0) { + if (++LAST_EVENT_ID == 0) { + LAST_EVENT_ID = 1; + } + + id = LAST_EVENT_ID; + } - uint64_t getEventId() const { - return eventId; + return id; } uint32_t getDelay() const { @@ -58,43 +62,24 @@ class Task { } bool isCanceled() const { - return canceled; + return func == nullptr; } void cancel() { - canceled = true; func = nullptr; } bool execute() const; +private: + static std::atomic_uint_fast64_t LAST_EVENT_ID; + void updateTime() { utime = TIME_NOW + std::chrono::milliseconds(delay); } - uint64_t generateId() { - if (eventId == 0) { - if (++LAST_EVENT_ID == 0) { - LAST_EVENT_ID = 1; - } - - eventId = LAST_EVENT_ID; - } - - return eventId; - } - - struct Compare { - bool operator()(const std::shared_ptr &a, const std::shared_ptr &b) const { - return b->utime < a->utime; - } - }; - -private: - static std::atomic_uint_fast64_t LAST_EVENT_ID; - bool hasTraceableContext() const { - const static auto tasksContext = phmap::flat_hash_set({ + const static auto tasksContext = std::unordered_set({ "Creature::checkCreatureWalk", "Decay::checkDecay", "Dispatcher::asyncEvent", @@ -123,16 +108,23 @@ class Task { return tasksContext.contains(context); } - bool canceled = false; - bool cycle = false; - bool log = true; + struct Compare { + bool operator()(const std::shared_ptr &a, const std::shared_ptr &b) const { + return a->utime < b->utime; + } + }; - uint32_t delay = 0; - uint64_t eventId = 0; + std::function func = nullptr; + std::string_view context; std::chrono::system_clock::time_point utime = SYSTEM_TIME_ZERO; std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; - std::string_view context; - std::function func = nullptr; + uint64_t id = 0; + uint32_t delay = 0; + + bool cycle = false; + bool log = true; + + friend class Dispatcher; }; diff --git a/src/lib/thread/thread_pool.cpp b/src/lib/thread/thread_pool.cpp index a88d73fc6c4..93b730b0b8f 100644 --- a/src/lib/thread/thread_pool.cpp +++ b/src/lib/thread/thread_pool.cpp @@ -29,7 +29,7 @@ void ThreadPool::start() { * will make processing non-blocking in some way and that would allow * single core computers to process things concurrently, but not in parallel. */ - int nThreads = std::max(static_cast(getNumberOfCores()), DEFAULT_NUMBER_OF_THREADS); + nThreads = std::max(static_cast(getNumberOfCores()), DEFAULT_NUMBER_OF_THREADS); for (std::size_t i = 0; i < nThreads; ++i) { threads.emplace_back([this] { ioService.run(); }); diff --git a/src/lib/thread/thread_pool.hpp b/src/lib/thread/thread_pool.hpp index 90a57bae16a..8ad60f14676 100644 --- a/src/lib/thread/thread_pool.hpp +++ b/src/lib/thread/thread_pool.hpp @@ -23,9 +23,27 @@ class ThreadPool { asio::io_context &getIoContext(); void addLoad(const std::function &load); + uint16_t getNumberOfThreads() const { + return nThreads; + } + + static int16_t getThreadId() { + static std::atomic_int16_t lastId = -1; + thread_local static int16_t id = -1; + + if (id == -1) { + lastId.fetch_add(1); + id = lastId.load(); + } + + return id; + }; + private: Logger &logger; asio::io_context ioService; std::vector threads; asio::io_context::work work { ioService }; + + uint16_t nThreads = 0; }; From 42f0d221415f0a465b0e7d8faec7f41be8109715 Mon Sep 17 00:00:00 2001 From: Roberto Carlos PMG <67324932+RCP91@users.noreply.github.com> Date: Tue, 24 Oct 2023 21:29:05 -0400 Subject: [PATCH 04/12] fix: add royal costume outfits quest (#1625) Co-authored-by: Eduardo Dantas --- data-otservbr-global/lib/core/storages.lua | 1 + data-otservbr-global/npc/percybald.lua | 95 ++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index a2a0131ce30..741532cda7b 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -908,6 +908,7 @@ Storage = { DeeplingAnchor = 51023, FirstOrientalAddon = 51024, SecondOrientalAddon = 51025, + RoyalCostumeOutfit = 51026, }, TheAncientTombs = { -- Reserved storage from 50940 - 51059 diff --git a/data-otservbr-global/npc/percybald.lua b/data-otservbr-global/npc/percybald.lua index 821e3e71ce5..957504acc49 100644 --- a/data-otservbr-global/npc/percybald.lua +++ b/data-otservbr-global/npc/percybald.lua @@ -134,6 +134,101 @@ local function creatureSayCallback(npc, creature, type, message) npcHandler:setTopic(playerId, 0) end end + + if MsgContains(message, "outfit") or MsgContains(message, "addon") or MsgContains(message, "royal") then + npcHandler:say("In exchange for a generous donation of gold and silver tokens, I can offer you a special outfit. Would you like to donate?", npc, creature) + npcHandler:setTopic(playerId, 12) + elseif MsgContains(message, "yes") then + -- Topic 12: Initial explanation about the outfit + if npcHandler:getTopic(playerId) == 12 then + npcHandler:say({ + "Great! To clarify, donating 30,000 silver tokens and 25,000 gold tokens will entitle you to a unique outfit. ...", + "For 15,000 silver tokens and 12,500 gold tokens, you will receive the {armor}. For an additional 7,500 silver tokens and 6,250 gold tokens each, you can also receive the {shield} and {crown}. ...", + "What will you choose?" + }, npc, creature) + npcHandler:setTopic(playerId, 13) + + -- Topic 13: User already accepted to learn about donations, further actions here + elseif npcHandler:getTopic(playerId) == 13 then + npcHandler:say("If you haven't made up your mind, please come back when you are ready.", npc, creature) + npcHandler:setTopic(playerId, 0) + elseif npcHandler:getTopic(playerId) == 14 then + if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) < 1 then + if (player:removeItem(22516, 15000) and player:removeItem(22721, 12500)) then + npcHandler:say("Take this armor as a token of great gratitude. Let us forever remember this day, my friend!", npc, creature) + player:addOutfit(1457) + player:addOutfit(1456) + player:getPosition():sendMagicEffect(171) + player:setStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit, 1) + else + npcHandler:say("You do not have enough tokens to donate that amount.", npc, creature) + end + else + npcHandler:say("You alread have that addon.", npc, creature) + end + npcHandler:setTopic(playerId, 13) + elseif npcHandler:getTopic(playerId) == 15 then + if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) == 1 then + if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) < 2 then + if (player:removeItem(22516, 7500) and player:removeItem(22721, 6250)) then + npcHandler:say("Take this sheild as a token of great gratitude. Let us forever remember this day, my friend. ", npc, creature) + player:addOutfitAddon(1457, 1) + player:addOutfitAddon(1456, 1) + player:getPosition():sendMagicEffect(171) + player:setStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit, 2) + npcHandler:setTopic(playerId, 13) + else + npcHandler:say("You do not have enough tokens to donate that amount.", npc, creature) + npcHandler:setTopic(playerId, 13) + end + else + npcHandler:say("You alread have that outfit.", npc, creature) + npcHandler:setTopic(playerId, 13) + end + else + npcHandler:say("You need to donate {armor} outfit first.", npc, creature) + npcHandler:setTopic(playerId, 13) + end + npcHandler:setTopic(playerId, 13) + elseif npcHandler:getTopic(playerId) == 16 then + if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) == 2 then + if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) < 3 then + if (player:removeItem(22516, 7500) and player:removeItem(22721, 6250)) then + npcHandler:say("Take this crown as a token of great gratitude. Let us forever remember this day, my friend. ", npc, creature) + player:addOutfitAddon(1457, 2) + player:addOutfitAddon(1456, 2) + player:getPosition():sendMagicEffect(171) + player:setStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit, 3) + npcHandler:setTopic(playerId, 13) + else + npcHandler:say("You do not have enough tokens to donate that amount.", npc, creature) + npcHandler:setTopic(playerId, 13) + end + else + npcHandler:say("You alread have that outfit.", npc, creature) + npcHandler:setTopic(playerId, 13) + end + else + npcHandler:say("You need to donate {shield} addon first.", npc, creature) + npcHandler:setTopic(playerId, 13) + end + npcHandler:setTopic(playerId, 13) + end + + -- Handle options for armor, shield, and crown + elseif (MsgContains(message, "armor")) and npcHandler:getTopic(playerId) == 13 then + npcHandler:say("Would you like to donate 15,000 silver tokens and 12,500 gold tokens for a unique red armor?", npc, creature) + npcHandler:setTopic(playerId, 14) + + elseif (MsgContains(message, "shield")) and npcHandler:getTopic(playerId) == 13 then + npcHandler:say("Would you like to donate 7,500 silver tokens and 6,250 gold tokens for a unique shield?", npc, creature) + npcHandler:setTopic(playerId, 15) + + elseif (MsgContains(message, "crown")) and npcHandler:getTopic(playerId) == 13 then + npcHandler:say("Would you like to donate 7,500 silver tokens and 6,250 gold tokens for a unique crown?", npc, creature) + npcHandler:setTopic(playerId, 16) + end + return true end From 28027abd3b8ef8d7bd2103207883a4f9678a01d0 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 24 Oct 2023 22:59:46 -0300 Subject: [PATCH 05/12] fix: royal outfit memorial (#1744) Complement pr: https://github.com/opentibiabr/canary/pull/1625 --- .../actions/other/golden_outfit_memorial.lua | 58 ----------------- .../scripts/actions/other/outfit_memorial.lua | 64 +++++++++++++++++++ 2 files changed, 64 insertions(+), 58 deletions(-) delete mode 100644 data-otservbr-global/scripts/actions/other/golden_outfit_memorial.lua create mode 100644 data-otservbr-global/scripts/actions/other/outfit_memorial.lua diff --git a/data-otservbr-global/scripts/actions/other/golden_outfit_memorial.lua b/data-otservbr-global/scripts/actions/other/golden_outfit_memorial.lua deleted file mode 100644 index aeeaab98469..00000000000 --- a/data-otservbr-global/scripts/actions/other/golden_outfit_memorial.lua +++ /dev/null @@ -1,58 +0,0 @@ -local goldenOutfitCache -local lastUpdated = 0 - -local function updateGoldenOutfitCache() - if os.time() < lastUpdated + (5 * 60) then -- Memorial cache update interval (5 minutes) - return - end - - goldenOutfitCache = { [1] = {}, [2] = {}, [3] = {} } - - local resultId = db.storeQuery("SELECT `name`, `value` FROM `player_storage` INNER JOIN `players` as `p` ON `p`.`id` = `player_id` WHERE `key` = " .. Storage.OutfitQuest.GoldenOutfit .. " AND `value` >= 1;") - if not resultId then - Result.free(resultId) - lastUpdated = os.time() - return - end - - repeat - table.insert(goldenOutfitCache[Result.getNumber(resultId, "value")], Result.getString(resultId, "name")) - until not Result.next(resultId) - Result.free(resultId) - - lastUpdated = os.time() -end - -local goldenOutfitMemorial = Action() - -function goldenOutfitMemorial.onUse(player, item, fromPosition, target, toPosition, isHotkey) - updateGoldenOutfitCache() - local response = NetworkMessage() - response:addByte(0xB0) - - response:addU32(500000000) -- Armor price - response:addU32(750000000) -- Armor + helmet price - response:addU32(1000000000) -- Armor + helmet + boots price - - for i = 1, 3 do - response:addU16(#goldenOutfitCache[i]) - for j = 1, #goldenOutfitCache[i] do - response:addString(goldenOutfitCache[i][j]) - end - end - - for i = 1, 3 do - response:addU16(0) -- price in silver tokens - response:addU16(0) -- price in golden tokens - end - - for i = 1, 3 do - response:addU16(0) -- list of spenders - end - - response:sendToPlayer(player) - return true -end - -goldenOutfitMemorial:id(31518, 31519, 31520, 31521, 31522, 31523) -goldenOutfitMemorial:register() diff --git a/data-otservbr-global/scripts/actions/other/outfit_memorial.lua b/data-otservbr-global/scripts/actions/other/outfit_memorial.lua new file mode 100644 index 00000000000..d722bb13274 --- /dev/null +++ b/data-otservbr-global/scripts/actions/other/outfit_memorial.lua @@ -0,0 +1,64 @@ +local CACHE_UPDATE_INTERVAL = 60 -- 1 minute for update cache + +local goldenOutfitCache = { [1] = {}, [2] = {}, [3] = {} } +local royalOutfitCache = { [1] = {}, [2] = {}, [3] = {} } +local lastUpdatedGolden = 0 +local lastUpdatedRoyal = 0 + +local function updateOutfitCache(storageKey, cache, lastUpdated) + if os.time() < lastUpdated + CACHE_UPDATE_INTERVAL then + return cache, lastUpdated + end + + local newCache = { [1] = {}, [2] = {}, [3] = {} } + + local resultId = db.storeQuery("SELECT `name`, `value` FROM `player_storage` INNER JOIN `players` as `p` ON `p`.`id` = `player_id` WHERE `key` = " .. storageKey .. " AND `value` >= 1;") + if resultId then + repeat + table.insert(newCache[Result.getNumber(resultId, "value")], Result.getString(resultId, "name")) + until not Result.next(resultId) + Result.free(resultId) + end + + return newCache, os.time() +end + +local outfitMemorial = Action() + +function outfitMemorial.onUse(player, item, fromPosition, target, toPosition, isHotkey) + goldenOutfitCache, lastUpdatedGolden = updateOutfitCache(Storage.OutfitQuest.GoldenOutfit, goldenOutfitCache, lastUpdatedGolden) + royalOutfitCache, lastUpdatedRoyal = updateOutfitCache(Storage.OutfitQuest.RoyalCostumeOutfit, royalOutfitCache, lastUpdatedRoyal) + local response = NetworkMessage() + response:addByte(0xB0) + + -- Golden outfit bytes + response:addU32(500000000) -- Armor price + response:addU32(750000000) -- Armor + helmet price + response:addU32(1000000000) -- Armor + helmet + boots price + + for i = 1, 3 do + response:addU16(#goldenOutfitCache[i]) + for j = 1, #goldenOutfitCache[i] do + response:addString(goldenOutfitCache[i][j]) + end + end + + -- Royal outfit bytes + for i = 1, 3 do + response:addU16(30000) -- price in silver tokens + response:addU16(25000) -- price in golden tokens + end + + for i = 1, 3 do + response:addU16(#royalOutfitCache[i]) + for j = 1, #royalOutfitCache[i] do + response:addString(royalOutfitCache[i][j]) + end + end + + response:sendToPlayer(player) + return true +end + +outfitMemorial:id(31518, 31519, 31520, 31521, 31522, 31523) +outfitMemorial:register() From c3dc39c7f47d0ee981642c7c3da076492b9d2e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Lu=C3=ADs=20Lucarelo=20Lamonato?= Date: Tue, 24 Oct 2023 23:06:16 -0300 Subject: [PATCH 06/12] fix: yonan npc now can exchange items (#1742) Exchange the items Blue, Golden Cordon, Golden Bijou, Sun Medal, Sunray Emblem for a Regalia of Suon (mount item). --- data-otservbr-global/npc/percybald.lua | 20 +++++++++----------- data-otservbr-global/npc/yonan.lua | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/data-otservbr-global/npc/percybald.lua b/data-otservbr-global/npc/percybald.lua index 957504acc49..5ecb521302e 100644 --- a/data-otservbr-global/npc/percybald.lua +++ b/data-otservbr-global/npc/percybald.lua @@ -144,17 +144,17 @@ local function creatureSayCallback(npc, creature, type, message) npcHandler:say({ "Great! To clarify, donating 30,000 silver tokens and 25,000 gold tokens will entitle you to a unique outfit. ...", "For 15,000 silver tokens and 12,500 gold tokens, you will receive the {armor}. For an additional 7,500 silver tokens and 6,250 gold tokens each, you can also receive the {shield} and {crown}. ...", - "What will you choose?" + "What will you choose?", }, npc, creature) npcHandler:setTopic(playerId, 13) - + -- Topic 13: User already accepted to learn about donations, further actions here elseif npcHandler:getTopic(playerId) == 13 then npcHandler:say("If you haven't made up your mind, please come back when you are ready.", npc, creature) npcHandler:setTopic(playerId, 0) elseif npcHandler:getTopic(playerId) == 14 then if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) < 1 then - if (player:removeItem(22516, 15000) and player:removeItem(22721, 12500)) then + if player:removeItem(22516, 15000) and player:removeItem(22721, 12500) then npcHandler:say("Take this armor as a token of great gratitude. Let us forever remember this day, my friend!", npc, creature) player:addOutfit(1457) player:addOutfit(1456) @@ -170,7 +170,7 @@ local function creatureSayCallback(npc, creature, type, message) elseif npcHandler:getTopic(playerId) == 15 then if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) == 1 then if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) < 2 then - if (player:removeItem(22516, 7500) and player:removeItem(22721, 6250)) then + if player:removeItem(22516, 7500) and player:removeItem(22721, 6250) then npcHandler:say("Take this sheild as a token of great gratitude. Let us forever remember this day, my friend. ", npc, creature) player:addOutfitAddon(1457, 1) player:addOutfitAddon(1456, 1) @@ -193,7 +193,7 @@ local function creatureSayCallback(npc, creature, type, message) elseif npcHandler:getTopic(playerId) == 16 then if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) == 2 then if player:getStorageValue(Storage.OutfitQuest.RoyalCostumeOutfit) < 3 then - if (player:removeItem(22516, 7500) and player:removeItem(22721, 6250)) then + if player:removeItem(22516, 7500) and player:removeItem(22721, 6250) then npcHandler:say("Take this crown as a token of great gratitude. Let us forever remember this day, my friend. ", npc, creature) player:addOutfitAddon(1457, 2) player:addOutfitAddon(1456, 2) @@ -214,17 +214,15 @@ local function creatureSayCallback(npc, creature, type, message) end npcHandler:setTopic(playerId, 13) end - + -- Handle options for armor, shield, and crown - elseif (MsgContains(message, "armor")) and npcHandler:getTopic(playerId) == 13 then + elseif MsgContains(message, "armor") and npcHandler:getTopic(playerId) == 13 then npcHandler:say("Would you like to donate 15,000 silver tokens and 12,500 gold tokens for a unique red armor?", npc, creature) npcHandler:setTopic(playerId, 14) - - elseif (MsgContains(message, "shield")) and npcHandler:getTopic(playerId) == 13 then + elseif MsgContains(message, "shield") and npcHandler:getTopic(playerId) == 13 then npcHandler:say("Would you like to donate 7,500 silver tokens and 6,250 gold tokens for a unique shield?", npc, creature) npcHandler:setTopic(playerId, 15) - - elseif (MsgContains(message, "crown")) and npcHandler:getTopic(playerId) == 13 then + elseif MsgContains(message, "crown") and npcHandler:getTopic(playerId) == 13 then npcHandler:say("Would you like to donate 7,500 silver tokens and 6,250 gold tokens for a unique crown?", npc, creature) npcHandler:setTopic(playerId, 16) end diff --git a/data-otservbr-global/npc/yonan.lua b/data-otservbr-global/npc/yonan.lua index 65aee681ac9..1c6101ab842 100644 --- a/data-otservbr-global/npc/yonan.lua +++ b/data-otservbr-global/npc/yonan.lua @@ -179,6 +179,24 @@ local function creatureSayCallback(npc, creature, type, message) npcHandler:say({ "Sorry." }, npc, creature) -- It needs to be revised, it's not the same as the global end end + + if MsgContains(message, "regalia of suon") then + npcHandler:say({ "You have all parts of the famous Regalia of Suon! Only a few of them were ever forged, back in the times of the old empire. You are holding a very rare and precious treasure, my friend. ... As you have all four pieces, I could combine them into the full insignia. Shall I do this for you?" }, npc, creature) + npcHandler:setTopic(playerId, 5) + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 5 then + if player:getItemById(31572, 1) and player:getItemById(31573, 1) and player:getItemById(31574, 1) and player:getItemById(31575, 1) then + if player:addItem(31576, 1) then -- regalia of suon + player:removeItem(31572, 1) -- blue and golden cordon + player:removeItem(31573, 1) -- sun medal + player:removeItem(31574, 1) -- sunray emblem + player:removeItem(31575, 1) -- golden bijou + npcHandler:say({ "Well then, let me have a look." }, npc, creature) + end + else + npcHandler:say({ "Sorry, you dont have the necessary items." }, npc, creature) -- It needs to be revised, it's not the same as the global + end + end + return true end From 8233a246cd338dc61942c681448b46dc1235d769 Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Thu, 26 Oct 2023 00:33:40 -0300 Subject: [PATCH 07/12] fix: some scripts (#1741) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added 'RatmiralBlackwhiskersDeath' event to win outfit. • Fixed some typos. • Adjusted some validations. • Fixed dream_courts_lever.lua that was broken index. • Added validation to do not permit rookers players with bless. --- data-otservbr-global/lib/core/storages.lua | 1 + .../monster/bosses/the_first_dragon.lua | 2 +- .../a_pirates_tail_quest/ratmiral_blackwhiskers.lua | 4 ++++ .../monster/quests/soul_war/goshnars_megalomania.lua | 1 + .../trainers/training_machine.lua} | 3 ++- .../actions/adventurers_guild/adventurers_stone.lua | 6 +++--- .../scripts/actions/other/potions.lua | 2 +- .../quests/dangerous_depth/gnomish_pesticide.lua | 2 +- .../quests/dangerous_depth/using_crystals.lua | 2 +- .../quests/feaster_of_souls/portal_minis_feaster.lua | 2 +- .../quests/ferumbras_ascendant/ferumbras_lever.lua | 2 +- .../quests/kilmaresh/portal_minis_kilmaresh.lua | 2 +- .../actions/quests/soul_war/reward_soul_war.lua | 12 ++++++++++++ .../quests/the_dream_courts/dream_courts_lever.lua | 9 +++++---- .../quests/a_pirates_tail/ratmiral_death.lua | 6 +++--- .../scripts/custom/movement_trainer_entrance.lua | 4 ++-- .../movements/quests/cults_of_tibia/boss_timer.lua | 2 +- .../quests/first_dragon/entrance_teleport.lua | 2 +- .../quests/forgotten_knowledge/servant_teleport.lua | 2 +- data/events/scripts/player.lua | 4 ++-- data/libs/functions/player.lua | 5 +---- data/scripts/reward_chest/boss_death.lua | 11 ++++++++--- src/creatures/players/player.cpp | 2 +- src/server/network/protocol/protocolgame.cpp | 4 ++-- src/server/network/webhook/webhook.cpp | 2 +- 25 files changed, 58 insertions(+), 36 deletions(-) rename data-otservbr-global/{scripts/custom/monster_training_machine.lua => monster/trainers/training_machine.lua} (97%) diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index 741532cda7b..202bb290a38 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -2768,6 +2768,7 @@ Storage = { GoshnarMegalomaniaAccess = 47220, GoshnarMegalomaniaKilled = 47222, QuestReward = 47223, + OutfitReward = 47224, }, }, U12_60 = { -- update 12.60 - Reserved Storages 47501 - 47600 diff --git a/data-otservbr-global/monster/bosses/the_first_dragon.lua b/data-otservbr-global/monster/bosses/the_first_dragon.lua index e70114de0a3..34294280dd1 100644 --- a/data-otservbr-global/monster/bosses/the_first_dragon.lua +++ b/data-otservbr-global/monster/bosses/the_first_dragon.lua @@ -58,7 +58,7 @@ monster.flags = { } monster.events = { - "First Dragon Death", + "FirstDragonDeath", } monster.light = { diff --git a/data-otservbr-global/monster/quests/a_pirates_tail_quest/ratmiral_blackwhiskers.lua b/data-otservbr-global/monster/quests/a_pirates_tail_quest/ratmiral_blackwhiskers.lua index a1d2b57ae54..d399413ed93 100644 --- a/data-otservbr-global/monster/quests/a_pirates_tail_quest/ratmiral_blackwhiskers.lua +++ b/data-otservbr-global/monster/quests/a_pirates_tail_quest/ratmiral_blackwhiskers.lua @@ -20,6 +20,10 @@ monster.corpse = 35846 monster.speed = 115 monster.manaCost = 0 +monster.events = { + "RatmiralBlackwhiskersDeath", +} + monster.changeTarget = { interval = 4000, chance = 10, diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua index 7487f4fda9a..1c871919b87 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua @@ -18,6 +18,7 @@ monster.maxHealth = 500000 monster.race = "undead" monster.corpse = 33889 monster.speed = 165 +monster.manaCost = 0 monster.changeTarget = { interval = 2000, diff --git a/data-otservbr-global/scripts/custom/monster_training_machine.lua b/data-otservbr-global/monster/trainers/training_machine.lua similarity index 97% rename from data-otservbr-global/scripts/custom/monster_training_machine.lua rename to data-otservbr-global/monster/trainers/training_machine.lua index 7ef2f78d707..c5a98a9ce28 100644 --- a/data-otservbr-global/scripts/custom/monster_training_machine.lua +++ b/data-otservbr-global/monster/trainers/training_machine.lua @@ -1,6 +1,7 @@ local mType = Game.createMonsterType("Training Machine") local monster = {} -monster.description = "Training Machine" + +monster.description = "a training machine" monster.experience = 0 monster.outfit = { lookType = 1142, diff --git a/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua b/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua index 9de94cbe1e3..4218f522bbb 100644 --- a/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua +++ b/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua @@ -1,5 +1,5 @@ local config = { - enableTemplaes = true, + enableTemples = true, enableDepots = false, Temples = { @@ -47,7 +47,7 @@ local adventurersStone = Action() function adventurersStone.onUse(player, item, fromPosition, target, toPosition, isHotkey) local playerPos, allowed, townId = player:getPosition(), false - if config.enableTemplaes then + if config.enableTemples then for _, temple in ipairs(config.Temples) do if isInRangeIgnoreZ(playerPos, temple.fromPos, temple.toPos) then allowed, townId = true, temple.townId @@ -67,7 +67,7 @@ function adventurersStone.onUse(player, item, fromPosition, target, toPosition, if not allowed then local enabledLocations = {} - if config.enableTemplaes then + if config.enableTemples then table.insert(enabledLocations, "temple") end if config.enableDepots then diff --git a/data-otservbr-global/scripts/actions/other/potions.lua b/data-otservbr-global/scripts/actions/other/potions.lua index ab5b97c8ff6..38c323e978e 100644 --- a/data-otservbr-global/scripts/actions/other/potions.lua +++ b/data-otservbr-global/scripts/actions/other/potions.lua @@ -262,7 +262,7 @@ function flaskPotion.onUse(player, item, fromPosition, target, toPosition, isHot potion.combat:execute(target, Variant(target:getId())) end - if not potion.effect then + if not potion.effect and target:getPosition() ~= nil then target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) end diff --git a/data-otservbr-global/scripts/actions/quests/dangerous_depth/gnomish_pesticide.lua b/data-otservbr-global/scripts/actions/quests/dangerous_depth/gnomish_pesticide.lua index 68fe03c67e9..bbc6fe66ede 100644 --- a/data-otservbr-global/scripts/actions/quests/dangerous_depth/gnomish_pesticide.lua +++ b/data-otservbr-global/scripts/actions/quests/dangerous_depth/gnomish_pesticide.lua @@ -4,7 +4,7 @@ function dangerousDepthPesticide.onUse(player, item, fromPosition, target, toPos return true end - if not target:isItem() then + if not target or not target:isItem() then return false end diff --git a/data-otservbr-global/scripts/actions/quests/dangerous_depth/using_crystals.lua b/data-otservbr-global/scripts/actions/quests/dangerous_depth/using_crystals.lua index e3cb7244b7b..ca95fa99bc9 100644 --- a/data-otservbr-global/scripts/actions/quests/dangerous_depth/using_crystals.lua +++ b/data-otservbr-global/scripts/actions/quests/dangerous_depth/using_crystals.lua @@ -408,7 +408,7 @@ function dangerousDepthCrystals.onUse(player, item, fromPosition, target, toPosi return true end - if not target:isItem() then + if not target or not target:isItem() then return false end diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_minis_feaster.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_minis_feaster.lua index 5e10645ee23..fc957813d3d 100644 --- a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_minis_feaster.lua +++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_minis_feaster.lua @@ -84,7 +84,7 @@ function teleportBoss.onStepIn(creature, item, position, fromPosition) creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "All the players need to be level " .. value.requiredLevel .. " or higher.") return true end - if creature:canFightBoss(value.bossName) then + if not creature:canFightBoss(value.bossName) then creature:teleportTo(fromPosition, true) creature:getPosition():sendMagicEffect(CONST_ME_TELEPORT) creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. value.timeToFightAgain .. " hours to face " .. value.bossName .. " again!") diff --git a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/ferumbras_lever.lua b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/ferumbras_lever.lua index 847a22b8440..674d6d77e4e 100644 --- a/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/ferumbras_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/ferumbras_ascendant/ferumbras_lever.lua @@ -50,7 +50,7 @@ function ferumbrasAscendantLever.onUse(player, item, fromPosition, target, toPos for y = 31477, 31481 do local playerTile = Tile(Position(x, y, 14)):getTopCreature() if playerTile and playerTile:isPlayer() then - if playerTile:canFightBoss("Ferumbras Mortal Shell") then + if not playerTile:canFightBoss("Ferumbras Mortal Shell") then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You or a member in your team have to wait 5 days to face Ferumbras again!") item:transform(8912) return true diff --git a/data-otservbr-global/scripts/actions/quests/kilmaresh/portal_minis_kilmaresh.lua b/data-otservbr-global/scripts/actions/quests/kilmaresh/portal_minis_kilmaresh.lua index bb334e678d1..bd54e70fa01 100644 --- a/data-otservbr-global/scripts/actions/quests/kilmaresh/portal_minis_kilmaresh.lua +++ b/data-otservbr-global/scripts/actions/quests/kilmaresh/portal_minis_kilmaresh.lua @@ -84,7 +84,7 @@ function teleportBoss.onStepIn(creature, item, position, fromPosition) creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "All the players need to be level " .. value.requiredLevel .. " or higher.") return true end - if creature:canFightBoss(value.bossName) then + if not creature:canFightBoss(value.bossName) then creature:teleportTo(fromPosition, true) creature:getPosition():sendMagicEffect(CONST_ME_TELEPORT) creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. value.timeToFightAgain .. " hours to face " .. value.bossName .. " again!") diff --git a/data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua b/data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua index b069e43fb69..2ab572b9376 100644 --- a/data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua +++ b/data-otservbr-global/scripts/actions/quests/soul_war/reward_soul_war.lua @@ -18,6 +18,16 @@ local rewards = { { id = 34098, name = "Pair of Soulstalkers" }, { id = 34099, name = "Soulbastion" }, } +local outfits = { 1322, 1323 } + +local function addOutfits(player) + if player:getStorageValue(Storage.Quest.U12_40.SoulWar.OutfitReward) < 0 then + player:addOutfit(outfits[1], 0) + player:addOutfit(outfits[2], 0) + player:setStorageValue(Storage.Quest.U12_40.SoulWar.OutfitReward, 1) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations you received the Revenant Outfit.") + end +end local rewardSoulWar = Action() function rewardSoulWar.onUse(creature, item, fromPosition, target, toPosition, isHotkey) @@ -31,8 +41,10 @@ function rewardSoulWar.onUse(creature, item, fromPosition, target, toPosition, i player:addItem(rewardItem.id, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found a " .. rewardItem.name .. ".") player:setStorageValue(Storage.Quest.U12_40.SoulWar.QuestReward, 1) + addOutfits(player) return true else + addOutfits(player) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have already collected your reward") return false end diff --git a/data-otservbr-global/scripts/actions/quests/the_dream_courts/dream_courts_lever.lua b/data-otservbr-global/scripts/actions/quests/the_dream_courts/dream_courts_lever.lua index b336b0a380c..eef26894389 100644 --- a/data-otservbr-global/scripts/actions/quests/the_dream_courts/dream_courts_lever.lua +++ b/data-otservbr-global/scripts/actions/quests/the_dream_courts/dream_courts_lever.lua @@ -25,6 +25,7 @@ local config = { }, exit = Position(32210, 32035, 13), } +local bossToday = config.bossName[os.date("%A")] local dreamCourtsLever = Action() function dreamCourtsLever.onUse(player, item, fromPosition, target, toPosition, isHotkey) @@ -37,7 +38,7 @@ function dreamCourtsLever.onUse(player, item, fromPosition, target, toPosition, spec:setCheckPosition(config.specPos) spec:check() if spec:getPlayers() > 0 then - player:say("There's someone fighting with " .. config.bossName[os.date("%A")] .. ".", TALKTYPE_MONSTER_SAY) + player:say("There's someone fighting with " .. bossToday .. ".", TALKTYPE_MONSTER_SAY) return true end local lever = Lever() @@ -55,7 +56,7 @@ function dreamCourtsLever.onUse(player, item, fromPosition, target, toPosition, for _, v in pairs(info) do local newPlayer = v.creature if newPlayer then - newPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You or a member in your team have to wait " .. config.timeToFightAgain .. " hours to face " .. config.bossName[os.date("%A")] .. " again!") + newPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You or a member in your team have to wait " .. config.timeToFightAgain .. " hours to face " .. bossToday .. " again!") if newPlayer:getStorageValue(config.storage) > os.time() then newPlayer:getPosition():sendMagicEffect(CONST_ME_POFF) end @@ -68,12 +69,12 @@ function dreamCourtsLever.onUse(player, item, fromPosition, target, toPosition, lever:checkPositions() if lever:checkConditions() then spec:removeMonsters() - local monster = Game.createMonster(config.bossName[os.date("%A")], config.bossPosition, true, true) + local monster = Game.createMonster(bossToday, config.bossPosition, true, true) if not monster then return true end lever:teleportPlayers() - lever:setCooldownAllPlayers(config.bossName, os.time() + config.timeToFightAgain * 3600) + lever:setCooldownAllPlayers(bossToday, os.time() + config.timeToFightAgain * 3600) addEvent(function() local old_players = lever:getInfoPositions() spec:clearCreaturesCache() 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 index ea00d6ead07..8d659edbf10 100644 --- 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 @@ -1,7 +1,7 @@ -local event = CreatureEvent("RatmiralBlackwhiskersDeath") +local ratmiralBlackwhiskersDeath = CreatureEvent("RatmiralBlackwhiskersDeath") local outfits = { 1371, 1372 } -function event.onDeath(creature, corpse, killer, mostDamage, unjustified, mostDamage_unjustified) +function ratmiralBlackwhiskersDeath.onDeath(creature, corpse, killer, mostDamage, unjustified, mostDamage_unjustified) if not creature or not creature:getMonster() then return end @@ -17,4 +17,4 @@ function event.onDeath(creature, corpse, killer, mostDamage, unjustified, mostDa end end -event:register() +ratmiralBlackwhiskersDeath:register() diff --git a/data-otservbr-global/scripts/custom/movement_trainer_entrance.lua b/data-otservbr-global/scripts/custom/movement_trainer_entrance.lua index 161c0ffb220..c7d805e8f42 100644 --- a/data-otservbr-global/scripts/custom/movement_trainer_entrance.lua +++ b/data-otservbr-global/scripts/custom/movement_trainer_entrance.lua @@ -67,8 +67,8 @@ function trainerEntrance.onStepIn(creature, item, position, fromPosition) end calculatingRoom(creature.uid, config.firstRoomPosition, 0, 0) - Game.createMonster("training machine", creature:getPosition(), true, false) - Game.createMonster("training machine", creature:getPosition(), true, false) + Game.createMonster("Training Machine", creature:getPosition(), true, false) + Game.createMonster("Training Machine", creature:getPosition(), true, false) return true end diff --git a/data-otservbr-global/scripts/movements/quests/cults_of_tibia/boss_timer.lua b/data-otservbr-global/scripts/movements/quests/cults_of_tibia/boss_timer.lua index 9b03b83d74e..1c2f1834270 100644 --- a/data-otservbr-global/scripts/movements/quests/cults_of_tibia/boss_timer.lua +++ b/data-otservbr-global/scripts/movements/quests/cults_of_tibia/boss_timer.lua @@ -40,7 +40,7 @@ function bossTimer.onStepIn(creature, item, position, fromPosition) end for b = 1, #setting do if player:getPosition() == Position(setting[b].tpPos) then - if not player:canFightBoss(setting[b].storage) > os.time() then + if not player:canFightBoss(setting[b].boss) then player:sendCancelMessage("You need to wait for 20 hours to face this boss again.") player:teleportTo(fromPosition) return false diff --git a/data-otservbr-global/scripts/movements/quests/first_dragon/entrance_teleport.lua b/data-otservbr-global/scripts/movements/quests/first_dragon/entrance_teleport.lua index 642c02f57e4..f0d30494c8c 100644 --- a/data-otservbr-global/scripts/movements/quests/first_dragon/entrance_teleport.lua +++ b/data-otservbr-global/scripts/movements/quests/first_dragon/entrance_teleport.lua @@ -57,7 +57,7 @@ function entranceTeleport.onStepIn(creature, item, position, fromPosition) return true end - if player:canFightBoss(setting.bossName) >= os.time() then + if not player:canFightBoss(setting.bossName) then player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) player:teleportTo(fromPosition) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) diff --git a/data-otservbr-global/scripts/movements/quests/forgotten_knowledge/servant_teleport.lua b/data-otservbr-global/scripts/movements/quests/forgotten_knowledge/servant_teleport.lua index 534373c47fd..42921e1712f 100644 --- a/data-otservbr-global/scripts/movements/quests/forgotten_knowledge/servant_teleport.lua +++ b/data-otservbr-global/scripts/movements/quests/forgotten_knowledge/servant_teleport.lua @@ -6,7 +6,7 @@ function servantTeleport.onStepIn(creature, item, position, fromPosition) return end - if player:canFightBoss("LLoyd") then + if not player:canFightBoss("LLoyd") then player:teleportTo(Position(32815, 32872, 13)) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) position:sendMagicEffect(CONST_ME_TELEPORT) diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua index 6ff45d87d65..0a78d96c40d 100644 --- a/data/events/scripts/player.lua +++ b/data/events/scripts/player.lua @@ -247,8 +247,8 @@ function Player:onMoveItem(item, count, fromPosition, toPosition, fromCylinder, -- SSA exhaust local exhaust = {} if toPosition.x == CONTAINER_POSITION and toPosition.y == CONST_SLOT_NECKLACE and item:getId() == ITEM_STONE_SKIN_AMULET then - local pid = self:getId() - if exhaust[pid] then + local playerId = self:getId() + if exhaust[playerId] then self:sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED) return false end diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index 3cda2a65b80..ac1706a364f 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -677,8 +677,5 @@ end function Player:canFightBoss(bossNameOrId) local cooldown = self:getBossCooldown(bossNameOrId) - if cooldown > os.time() then - return false - end - return true + return cooldown > os.time() and false or true end diff --git a/data/scripts/reward_chest/boss_death.lua b/data/scripts/reward_chest/boss_death.lua index 7b3f992b384..f3a80909c8f 100644 --- a/data/scripts/reward_chest/boss_death.lua +++ b/data/scripts/reward_chest/boss_death.lua @@ -1,7 +1,7 @@ local bossDeath = CreatureEvent("BossDeath") function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) - if not corpse then + if not corpse or corpse == 0 then return true end -- Deny summons and players @@ -14,9 +14,14 @@ function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUn -- Make sure it is a boss if monsterType and monsterType:isRewardBoss() 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()) + if corpse.getId() then + logger.warn("[bossDeath.onDeath] Corpse (id: {}, name: {}) for reward boss {} is not a container.", corpse:getId(), corpse:getName(), creature:getName()) + else + logger.warn("[bossDeath.onDeath] Error to get corpseId from boss: {}", creature:getName()) + end + else + corpse:registerReward() end - corpse:registerReward() local bossId = creature:getId() local rewardId = corpse:getAttribute(ITEM_ATTRIBUTE_DATE) diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index bd11cc3394e..240993afaca 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1658,7 +1658,7 @@ void Player::onCreatureAppear(std::shared_ptr creature, bool isLogin) g_game().checkPlayersRecord(); IOLoginData::updateOnlineStatus(guid, true); - if (getLevel() < g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL)) { + if (getLevel() < g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL) && getVocationId() > VOCATION_NONE) { for (uint8_t i = 2; i <= 6; i++) { if (!hasBlessing(i)) { addBlessing(i, 1); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index a2466038c99..6da9e94180e 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -919,7 +919,7 @@ void ProtocolGame::addBless() { std::ostringstream lostBlesses; (bless.length() == 0) ? lostBlesses << "You lost all your blessings." : lostBlesses << "You are still blessed with " << bless; player->sendTextMessage(MESSAGE_EVENT_ADVANCE, lostBlesses.str()); - if (player->getLevel() < g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL)) { + if (player->getLevel() < g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL) && player->getVocationId() > VOCATION_NONE) { for (uint8_t i = 2; i <= 6; i++) { if (!player->hasBlessing(i)) { player->addBlessing(i, 1); @@ -3972,7 +3972,7 @@ void ProtocolGame::sendBlessStatus() { if (oldProtocol) { msg.add(blessCount >= 5 ? 0x01 : 0x00); } else { - bool glow = (g_configManager().getBoolean(INVENTORY_GLOW) && blessCount >= 5) || player->getLevel() < g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL); + bool glow = player->getVocationId() > VOCATION_NONE && ((g_configManager().getBoolean(INVENTORY_GLOW) && blessCount >= 5) || player->getLevel() < g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL)); msg.add(glow ? 1 : 0); // Show up the glowing effect in items if you have all blesses or adventurer's blessing msg.addByte((blessCount >= 7) ? 3 : ((blessCount >= 5) ? 2 : 1)); // 1 = Disabled | 2 = normal | 3 = green } diff --git a/src/server/network/webhook/webhook.cpp b/src/server/network/webhook/webhook.cpp index 386e804a7a9..69d05070aab 100644 --- a/src/server/network/webhook/webhook.cpp +++ b/src/server/network/webhook/webhook.cpp @@ -74,7 +74,7 @@ int Webhook::sendRequest(const char* url, const char* payload, std::string* resp curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &Webhook::writeCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, reinterpret_cast(response_body)); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "canary (https://github.com/Hydractify/canary)"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "canary (https://github.com/opentibiabr/canary)"); CURLcode res = curl_easy_perform(curl); From f017e1fd508ae6f341622a48b4fc7b0348eff9e1 Mon Sep 17 00:00:00 2001 From: Daanyx <62149991+daanyx@users.noreply.github.com> Date: Thu, 26 Oct 2023 00:51:42 -0300 Subject: [PATCH 08/12] feat: improve imbuement assistant npc (#1590) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feature and Fixes: Enhanced Imbuement Assistant and New NPCs • The "Imbuement Assistant" NPC now offers complete imbuement packages contained within a gift box, including an exchange option. • Fixed an issue in imbuements.xml regarding the incorrect number of items required for specific imbuements. • Added new NPCs Battlemart and The Lootmonger to the custom map. These NPCs will be gradually updated with more buy/sell options. Co-authored-by: Eduardo Dantas --- .../npc/imbuement_assistant.lua | 307 +++++++++++++++++- data-otservbr-global/npc/the_lootmonger.lua | 2 +- .../world/custom/otservbr-custom-npc.xml | 6 + data/XML/imbuements.xml | 8 +- 4 files changed, 313 insertions(+), 10 deletions(-) diff --git a/data-otservbr-global/npc/imbuement_assistant.lua b/data-otservbr-global/npc/imbuement_assistant.lua index 35ab1a282ea..85ff7c267a3 100644 --- a/data-otservbr-global/npc/imbuement_assistant.lua +++ b/data-otservbr-global/npc/imbuement_assistant.lua @@ -30,6 +30,8 @@ npcConfig.voices = { { text = "Hello adventurer, looking for Imbuement items? Just ask me!" }, } +local playerImbuementData = {} + local keywordHandler = KeywordHandler:new() local npcHandler = NpcHandler:new(keywordHandler) @@ -55,15 +57,310 @@ end npcType.onCloseChannel = function(npc, creature) npcHandler:onCloseChannel(npc, creature) + playerImbuementData[creature:getId()] = nil +end + +function addItemsToShoppingBag(npc, player) + local playerId = player:getId() + local playerData = playerImbuementData[playerId] + + if playerData then + local moneyRequired = playerData.moneyRequired + local itemList = playerData.itemList + if player:getMoney() + player:getBankBalance() < moneyRequired then + npcHandler:say("Sorry, you don't have enough money", npc, player) + npcHandler:setTopic(player:getId(), 0) + return false, "You don't have enough money." + end + + local totalWeight = 0 + for _, item in pairs(itemList) do + local itemType = ItemType(item.itemId) + totalWeight = totalWeight + (itemType:getWeight() * item.count) + end + + if player:getFreeCapacity() < totalWeight then + return false, "You don't have enough weight." + end + + if player:getFreeBackpackSlots() == 0 then + return false, "You don't have enough room." + end + + local shoppingBag = player:addItem(2856, 1) -- present box + for _, item in pairs(itemList) do + shoppingBag:addItem(item.itemId, item.count) + end + + player:removeMoneyBank(moneyRequired) + + return true + end + + return false end --- Basic +local imbuementPackagesData = { + -- Skill increase packages + ["bash"] = { + text = "skill club", + moneyRequired = 6250, + itemList = { + {itemId = 9657, count = 20}, + {itemId = 22189, count = 15}, + {itemId = 10405, count = 10} + } + }, + ["blockade"] = { + text = "skill shield", + moneyRequired = 16150, + itemList = { + {itemId = 9641, count = 20}, + {itemId = 11703, count = 25}, + {itemId = 20199, count = 25} + } + }, + ["chop"] = { + text = "skill axe", + moneyRequired = 13050, + itemList = { + {itemId = 10196, count = 20}, -- orc tooth + {itemId = 11447, count = 25}, -- battle stone + {itemId = 21200, count = 20} -- moohtant horn + } + }, + ["epiphany"] = { + text = "magic level", + moneyRequired = 10650, + itemList = { + {itemId = 9635, count = 25}, -- elvish talisman + {itemId = 11452, count = 15}, -- broken shamanic staff + {itemId = 10309, count = 15} -- strand of medusa hair + } + }, + ["precision"] = { + text = "skill distance", + moneyRequired = 6750, + itemList = { + {itemId = 11464, count = 25}, -- elven scouting glass + {itemId = 18994, count = 20}, -- elven hoof + {itemId = 10298, count = 10} -- metal spike + } + }, + ["slash"] = { + text = "skill sword", + moneyRequired = 6550, + itemList = { + {itemId = 9691, count = 25}, -- lion's mane + {itemId = 21202, count = 25}, -- mooh'tah shell + {itemId = 9654, count = 5} -- war crystal + } + }, + -- Additional attributes packages + ["featherweight"] = { + text = "capacity increase", + moneyRequired = 12250, + itemList = { + {itemId = 25694, count = 20}, -- fairy wings + {itemId = 25702, count = 10}, -- little bowl of myrrh + {itemId = 20205, count = 5} -- goosebump leather + } + }, + ["strike"] = { + text = "critical", + moneyRequired = 16700, + itemList = { + {itemId = 11444, count = 20}, -- protective charm + {itemId = 10311, count = 25}, -- sabretooth + {itemId = 22728, count = 5} -- vexclaw talon + } + }, + ["swiftness"] = { + text = "speed", + moneyRequired = 5225, + itemList = { + {itemId = 17458, count = 15}, -- damselfly wing + {itemId = 10302, count = 25}, -- compass + {itemId = 14081, count = 20} -- waspoid wing + } + }, + ["vampirism"] = { + text = "life leech", + moneyRequired = 10475, + itemList = { + {itemId = 9685, count = 25}, -- vampire teeth + {itemId = 9633, count = 15}, -- bloody pincers + {itemId = 9663, count = 5} -- piece of dead brain + } + }, + ["vibrancy"] = { + text = "paralysis removal", + moneyRequired = 15000, + itemList = { + {itemId = 22053, count = 20}, -- wereboar hooves + {itemId = 23507, count = 15}, -- crystallized anger + {itemId = 28567, count = 5} -- quill + } + }, + ["void"] = { + text = "mana leech", + moneyRequired = 17400, + itemList = { + {itemId = 11492, count = 25}, -- rope belt + {itemId = 20200, count = 25}, -- silencer claws + {itemId = 22730, count = 5} -- some grimeleech wings + } + }, + -- Elemental damage packages + ["electrify"] = { + text = "energy damage", + moneyRequired = 3770, + itemList = { + {itemId = 18993, count = 25}, -- rorc feather + {itemId = 21975, count = 5}, -- peacock feather fan + {itemId = 23508, count = 1} -- energy vein + } + }, + ["frost"] = { + text = "ice damage", + moneyRequired = 9750, + itemList = { + {itemId = 9661, count = 25}, -- frosty heart + {itemId = 21801, count = 10}, -- seacrest hair + {itemId = 9650, count = 5} -- polar bear paw + } + }, + ["reap"] = { + text = "death damage", + moneyRequired = 3475, + itemList = { + {itemId = 11484, count = 25}, -- pile of grave earth + {itemId = 9647, count = 20}, -- demonic skeletal hand + {itemId = 10420, count = 5} -- petrified scream + } + }, + ["scorch"] = { + text = "fire damage", + moneyRequired = 15875, + itemList = { + {itemId = 9636, count = 25}, -- fiery heart + {itemId = 5920, count = 5}, -- green dragon scale + {itemId = 5954, count = 5} -- demon horn + } + }, + ["venom"] = { + text = "earth damage", + moneyRequired = 1820, + itemList = { + {itemId = 9686, count = 25}, -- swamp grass + {itemId = 9640, count = 20}, -- poisonous slime + {itemId = 21194, count = 2} -- slime heart + } + }, + -- Elemental protection packages + ["cloud fabric"] = { + text = "energy protection", + moneyRequired = 13775, + itemList = { + {itemId = 9644, count = 20}, -- wyvern talisman + {itemId = 14079, count = 15}, -- crawler head plating + {itemId = 9665, count = 10} -- wyrm scale + } + }, + ["demon presence"] = { + text = "holy protection", + moneyRequired = 20250, + itemList = { + {itemId = 9639, count = 25}, -- cultish robe + {itemId = 9638, count = 25}, -- cultish mask + {itemId = 10304, count = 20} -- hellspawn tail + } + }, + ["dragon hide"] = { + text = "fire protection", + moneyRequired = 10850, + itemList = { + {itemId = 5877, count = 20}, -- green dragon leather + {itemId = 16131, count = 10}, -- blazing bone + {itemId = 11658, count = 5} -- draken sulphur + } + }, + ["lich shroud"] = { + text = "death protection", + moneyRequired = 5650, + itemList = { + {itemId = 11466, count = 25}, -- flask of embalming fluid + {itemId = 22007, count = 20}, -- gloom wolf fur + {itemId = 9660, count = 5} -- mystical hourglass + } + }, + ["quara scale"] = { + text = "ice protection", + moneyRequired = 3650, + itemList = { + {itemId = 10295, count = 25}, -- winter wolf fur + {itemId = 10307, count = 15}, -- thick fur + {itemId = 14012, count = 10} -- deepling warts + } + }, + ["snake skin"] = { + text = "earth protection", + moneyRequired = 12550, + itemList = { + {itemId = 17823, count = 25}, -- piece of swampling wood + {itemId = 9694, count = 20}, -- snake skin + {itemId = 11702, count = 10} -- brimstone fangs + } + } +} + +local function purchaseItems(npc, player, message) + local packageData = imbuementPackagesData[message] + if packageData and npcHandler:getTopic(player:getId()) == 1 then + npcHandler:say("Do you want to buy items for " .. packageData.text .. " imbuement for " .. packageData.moneyRequired .. " gold?", npc, player) + npcHandler:setTopic(player:getId(), 2) + playerImbuementData[player:getId()] = { + moneyRequired = packageData.moneyRequired, + itemList = packageData.itemList + } + end +end + +local function creatureSayCallback(npc, creature, type, message) + local player = Player(creature) + local playerId = player:getId() + if not npcHandler:checkInteraction(npc, creature) then + return false + end + + local imbuementPackages = "Available imbuement packages: {bash}, {blockade}, {chop}, {epiphany}, {precision}, {slash}. additional attributes: {featherweight}, {strike}, {swiftness}, {vampirism}, {vibrancy}, {void}. elemental damage: {electrify}, {frost}, {reap}, {scorch}, {venom}. elemental protection: {cloud fabric}, {demon presence}, {dragon hide}, {lich shroud}, {quara scale}, {snake skin}." + if MsgContains(message, "imbuement packages") then + npcHandler:setTopic(playerId, 1) + npcHandler:say(imbuementPackages, npc, creature) + -- Skill increase packages + elseif imbuementPackagesData[message] then + purchaseItems(npc, player, message) + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 2 then + local success, message = addItemsToShoppingBag(npc, player) + if not success then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) + npcHandler:setTopic(playerId, 1) + npcHandler:say(imbuementPackages, npc, player) + return + end + + playerImbuementData[playerId] = nil + npcHandler:say("You have successfully completed your purchase of the items.", npc, player) + npcHandler:setTopic(playerId, 1) + npcHandler:say(imbuementPackages, npc, creature) + end +end -keywordHandler:addKeyword({ "job" }, StdModule.say, { npcHandler = npcHandler, text = "Currently I have been working selling items for imbuement." }) +npcHandler:setMessage(MESSAGE_GREET, "Hello |PLAYERNAME|, say {imbuement packages} or {trade} for buy imbuement items.") +npcHandler:setMessage(MESSAGE_WALKAWAY, "See you later |PLAYERNAME| come back soon.") +npcHandler:setMessage(MESSAGE_FAREWELL, "See you later |PLAYERNAME| come back soon.") -npcHandler:setMessage(MESSAGE_GREET, "Welcome to Imbuement's shop!") -npcHandler:setMessage(MESSAGE_FAREWELL, "Good bye and come again.") -npcHandler:setMessage(MESSAGE_WALKAWAY, "Good bye and come again.") +npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) diff --git a/data-otservbr-global/npc/the_lootmonger.lua b/data-otservbr-global/npc/the_lootmonger.lua index bfeea87da97..7374b7645c2 100644 --- a/data-otservbr-global/npc/the_lootmonger.lua +++ b/data-otservbr-global/npc/the_lootmonger.lua @@ -7,7 +7,7 @@ npcConfig.description = internalNpcName npcConfig.health = 100 npcConfig.maxHealth = npcConfig.health -npcConfig.walkInterval = 0 +npcConfig.walkInterval = 2000 npcConfig.walkRadius = 2 npcConfig.outfit = { diff --git a/data-otservbr-global/world/custom/otservbr-custom-npc.xml b/data-otservbr-global/world/custom/otservbr-custom-npc.xml index 6978f4a6bc1..3597ef592c3 100644 --- a/data-otservbr-global/world/custom/otservbr-custom-npc.xml +++ b/data-otservbr-global/world/custom/otservbr-custom-npc.xml @@ -48,4 +48,10 @@ + + + + + + diff --git a/data/XML/imbuements.xml b/data/XML/imbuements.xml index 4fca37e46b9..867bab66f52 100644 --- a/data/XML/imbuements.xml +++ b/data/XML/imbuements.xml @@ -242,23 +242,23 @@ - + - + - + - + From f9e39243433a5e8c2434c76f323cf8f7417ed64a Mon Sep 17 00:00:00 2001 From: Luan Santos Date: Thu, 26 Oct 2023 04:10:53 -0700 Subject: [PATCH 09/12] improve: add utility functions to encounter/bosslever (#1736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Ability to set an exit teleporter from the lever config • Ability to get monsters by name in a zone • Reset encounter on lever pull • Multiple events per monster in encounter • Silence some noisy debug logs --- .../monster/bosses/doctor_marrow.lua | 4 - .../npc/imbuement_assistant.lua | 189 +++++++++--------- data/items/items.xml | 18 ++ data/libs/encounters_lib.lua | 7 +- data/libs/functions/bosslever.lua | 13 +- data/libs/zones_lib.lua | 10 + src/creatures/combat/combat.cpp | 2 +- src/creatures/creature.cpp | 2 +- src/creatures/players/player.cpp | 2 +- 9 files changed, 142 insertions(+), 105 deletions(-) diff --git a/data-otservbr-global/monster/bosses/doctor_marrow.lua b/data-otservbr-global/monster/bosses/doctor_marrow.lua index 61f19f0b70b..308ab14b27b 100644 --- a/data-otservbr-global/monster/bosses/doctor_marrow.lua +++ b/data-otservbr-global/monster/bosses/doctor_marrow.lua @@ -20,10 +20,6 @@ monster.corpse = 18074 monster.speed = 180 monster.manaCost = 0 -monster.events = { - "DoctorMarrowHealth", -} - monster.changeTarget = { interval = 4000, chance = 15, diff --git a/data-otservbr-global/npc/imbuement_assistant.lua b/data-otservbr-global/npc/imbuement_assistant.lua index 85ff7c267a3..a4f1228ecd6 100644 --- a/data-otservbr-global/npc/imbuement_assistant.lua +++ b/data-otservbr-global/npc/imbuement_assistant.lua @@ -106,212 +106,212 @@ local imbuementPackagesData = { text = "skill club", moneyRequired = 6250, itemList = { - {itemId = 9657, count = 20}, - {itemId = 22189, count = 15}, - {itemId = 10405, count = 10} - } + { itemId = 9657, count = 20 }, + { itemId = 22189, count = 15 }, + { itemId = 10405, count = 10 }, + }, }, ["blockade"] = { text = "skill shield", moneyRequired = 16150, itemList = { - {itemId = 9641, count = 20}, - {itemId = 11703, count = 25}, - {itemId = 20199, count = 25} - } + { itemId = 9641, count = 20 }, + { itemId = 11703, count = 25 }, + { itemId = 20199, count = 25 }, + }, }, ["chop"] = { text = "skill axe", moneyRequired = 13050, itemList = { - {itemId = 10196, count = 20}, -- orc tooth - {itemId = 11447, count = 25}, -- battle stone - {itemId = 21200, count = 20} -- moohtant horn - } + { itemId = 10196, count = 20 }, -- orc tooth + { itemId = 11447, count = 25 }, -- battle stone + { itemId = 21200, count = 20 }, -- moohtant horn + }, }, ["epiphany"] = { text = "magic level", moneyRequired = 10650, itemList = { - {itemId = 9635, count = 25}, -- elvish talisman - {itemId = 11452, count = 15}, -- broken shamanic staff - {itemId = 10309, count = 15} -- strand of medusa hair - } + { itemId = 9635, count = 25 }, -- elvish talisman + { itemId = 11452, count = 15 }, -- broken shamanic staff + { itemId = 10309, count = 15 }, -- strand of medusa hair + }, }, ["precision"] = { text = "skill distance", moneyRequired = 6750, itemList = { - {itemId = 11464, count = 25}, -- elven scouting glass - {itemId = 18994, count = 20}, -- elven hoof - {itemId = 10298, count = 10} -- metal spike - } + { itemId = 11464, count = 25 }, -- elven scouting glass + { itemId = 18994, count = 20 }, -- elven hoof + { itemId = 10298, count = 10 }, -- metal spike + }, }, ["slash"] = { text = "skill sword", moneyRequired = 6550, itemList = { - {itemId = 9691, count = 25}, -- lion's mane - {itemId = 21202, count = 25}, -- mooh'tah shell - {itemId = 9654, count = 5} -- war crystal - } + { itemId = 9691, count = 25 }, -- lion's mane + { itemId = 21202, count = 25 }, -- mooh'tah shell + { itemId = 9654, count = 5 }, -- war crystal + }, }, -- Additional attributes packages ["featherweight"] = { text = "capacity increase", moneyRequired = 12250, itemList = { - {itemId = 25694, count = 20}, -- fairy wings - {itemId = 25702, count = 10}, -- little bowl of myrrh - {itemId = 20205, count = 5} -- goosebump leather - } + { itemId = 25694, count = 20 }, -- fairy wings + { itemId = 25702, count = 10 }, -- little bowl of myrrh + { itemId = 20205, count = 5 }, -- goosebump leather + }, }, ["strike"] = { text = "critical", moneyRequired = 16700, itemList = { - {itemId = 11444, count = 20}, -- protective charm - {itemId = 10311, count = 25}, -- sabretooth - {itemId = 22728, count = 5} -- vexclaw talon - } + { itemId = 11444, count = 20 }, -- protective charm + { itemId = 10311, count = 25 }, -- sabretooth + { itemId = 22728, count = 5 }, -- vexclaw talon + }, }, ["swiftness"] = { text = "speed", moneyRequired = 5225, itemList = { - {itemId = 17458, count = 15}, -- damselfly wing - {itemId = 10302, count = 25}, -- compass - {itemId = 14081, count = 20} -- waspoid wing - } + { itemId = 17458, count = 15 }, -- damselfly wing + { itemId = 10302, count = 25 }, -- compass + { itemId = 14081, count = 20 }, -- waspoid wing + }, }, ["vampirism"] = { text = "life leech", moneyRequired = 10475, itemList = { - {itemId = 9685, count = 25}, -- vampire teeth - {itemId = 9633, count = 15}, -- bloody pincers - {itemId = 9663, count = 5} -- piece of dead brain - } + { itemId = 9685, count = 25 }, -- vampire teeth + { itemId = 9633, count = 15 }, -- bloody pincers + { itemId = 9663, count = 5 }, -- piece of dead brain + }, }, ["vibrancy"] = { text = "paralysis removal", moneyRequired = 15000, itemList = { - {itemId = 22053, count = 20}, -- wereboar hooves - {itemId = 23507, count = 15}, -- crystallized anger - {itemId = 28567, count = 5} -- quill - } + { itemId = 22053, count = 20 }, -- wereboar hooves + { itemId = 23507, count = 15 }, -- crystallized anger + { itemId = 28567, count = 5 }, -- quill + }, }, ["void"] = { text = "mana leech", moneyRequired = 17400, itemList = { - {itemId = 11492, count = 25}, -- rope belt - {itemId = 20200, count = 25}, -- silencer claws - {itemId = 22730, count = 5} -- some grimeleech wings - } + { itemId = 11492, count = 25 }, -- rope belt + { itemId = 20200, count = 25 }, -- silencer claws + { itemId = 22730, count = 5 }, -- some grimeleech wings + }, }, -- Elemental damage packages ["electrify"] = { text = "energy damage", moneyRequired = 3770, itemList = { - {itemId = 18993, count = 25}, -- rorc feather - {itemId = 21975, count = 5}, -- peacock feather fan - {itemId = 23508, count = 1} -- energy vein - } + { itemId = 18993, count = 25 }, -- rorc feather + { itemId = 21975, count = 5 }, -- peacock feather fan + { itemId = 23508, count = 1 }, -- energy vein + }, }, ["frost"] = { text = "ice damage", moneyRequired = 9750, itemList = { - {itemId = 9661, count = 25}, -- frosty heart - {itemId = 21801, count = 10}, -- seacrest hair - {itemId = 9650, count = 5} -- polar bear paw - } + { itemId = 9661, count = 25 }, -- frosty heart + { itemId = 21801, count = 10 }, -- seacrest hair + { itemId = 9650, count = 5 }, -- polar bear paw + }, }, ["reap"] = { text = "death damage", moneyRequired = 3475, itemList = { - {itemId = 11484, count = 25}, -- pile of grave earth - {itemId = 9647, count = 20}, -- demonic skeletal hand - {itemId = 10420, count = 5} -- petrified scream - } + { itemId = 11484, count = 25 }, -- pile of grave earth + { itemId = 9647, count = 20 }, -- demonic skeletal hand + { itemId = 10420, count = 5 }, -- petrified scream + }, }, ["scorch"] = { text = "fire damage", moneyRequired = 15875, itemList = { - {itemId = 9636, count = 25}, -- fiery heart - {itemId = 5920, count = 5}, -- green dragon scale - {itemId = 5954, count = 5} -- demon horn - } + { itemId = 9636, count = 25 }, -- fiery heart + { itemId = 5920, count = 5 }, -- green dragon scale + { itemId = 5954, count = 5 }, -- demon horn + }, }, ["venom"] = { text = "earth damage", moneyRequired = 1820, itemList = { - {itemId = 9686, count = 25}, -- swamp grass - {itemId = 9640, count = 20}, -- poisonous slime - {itemId = 21194, count = 2} -- slime heart - } + { itemId = 9686, count = 25 }, -- swamp grass + { itemId = 9640, count = 20 }, -- poisonous slime + { itemId = 21194, count = 2 }, -- slime heart + }, }, -- Elemental protection packages ["cloud fabric"] = { text = "energy protection", moneyRequired = 13775, itemList = { - {itemId = 9644, count = 20}, -- wyvern talisman - {itemId = 14079, count = 15}, -- crawler head plating - {itemId = 9665, count = 10} -- wyrm scale - } + { itemId = 9644, count = 20 }, -- wyvern talisman + { itemId = 14079, count = 15 }, -- crawler head plating + { itemId = 9665, count = 10 }, -- wyrm scale + }, }, ["demon presence"] = { text = "holy protection", moneyRequired = 20250, itemList = { - {itemId = 9639, count = 25}, -- cultish robe - {itemId = 9638, count = 25}, -- cultish mask - {itemId = 10304, count = 20} -- hellspawn tail - } + { itemId = 9639, count = 25 }, -- cultish robe + { itemId = 9638, count = 25 }, -- cultish mask + { itemId = 10304, count = 20 }, -- hellspawn tail + }, }, ["dragon hide"] = { text = "fire protection", moneyRequired = 10850, itemList = { - {itemId = 5877, count = 20}, -- green dragon leather - {itemId = 16131, count = 10}, -- blazing bone - {itemId = 11658, count = 5} -- draken sulphur - } + { itemId = 5877, count = 20 }, -- green dragon leather + { itemId = 16131, count = 10 }, -- blazing bone + { itemId = 11658, count = 5 }, -- draken sulphur + }, }, ["lich shroud"] = { text = "death protection", moneyRequired = 5650, itemList = { - {itemId = 11466, count = 25}, -- flask of embalming fluid - {itemId = 22007, count = 20}, -- gloom wolf fur - {itemId = 9660, count = 5} -- mystical hourglass - } + { itemId = 11466, count = 25 }, -- flask of embalming fluid + { itemId = 22007, count = 20 }, -- gloom wolf fur + { itemId = 9660, count = 5 }, -- mystical hourglass + }, }, ["quara scale"] = { text = "ice protection", moneyRequired = 3650, itemList = { - {itemId = 10295, count = 25}, -- winter wolf fur - {itemId = 10307, count = 15}, -- thick fur - {itemId = 14012, count = 10} -- deepling warts - } + { itemId = 10295, count = 25 }, -- winter wolf fur + { itemId = 10307, count = 15 }, -- thick fur + { itemId = 14012, count = 10 }, -- deepling warts + }, }, ["snake skin"] = { text = "earth protection", moneyRequired = 12550, itemList = { - {itemId = 17823, count = 25}, -- piece of swampling wood - {itemId = 9694, count = 20}, -- snake skin - {itemId = 11702, count = 10} -- brimstone fangs - } - } + { itemId = 17823, count = 25 }, -- piece of swampling wood + { itemId = 9694, count = 20 }, -- snake skin + { itemId = 11702, count = 10 }, -- brimstone fangs + }, + }, } local function purchaseItems(npc, player, message) @@ -321,7 +321,7 @@ local function purchaseItems(npc, player, message) npcHandler:setTopic(player:getId(), 2) playerImbuementData[player:getId()] = { moneyRequired = packageData.moneyRequired, - itemList = packageData.itemList + itemList = packageData.itemList, } end end @@ -337,7 +337,6 @@ local function creatureSayCallback(npc, creature, type, message) if MsgContains(message, "imbuement packages") then npcHandler:setTopic(playerId, 1) npcHandler:say(imbuementPackages, npc, creature) - -- Skill increase packages elseif imbuementPackagesData[message] then purchaseItems(npc, player, message) elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 2 then diff --git a/data/items/items.xml b/data/items/items.xml index d23468cabf3..ec4ab6f9bbb 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -67826,6 +67826,10 @@ + + + + @@ -67964,6 +67968,20 @@ + + + + + + + + + + + + + + diff --git a/data/libs/encounters_lib.lua b/data/libs/encounters_lib.lua index 1707799b8ac..dee7a38c0c1 100644 --- a/data/libs/encounters_lib.lua +++ b/data/libs/encounters_lib.lua @@ -184,7 +184,12 @@ function Encounter:spawnMonsters(config) spawn(monster) end if event then - monster:registerEvent(event) + if type(event) == "string" then + event = { event } + end + for _, event in ipairs(event) do + monster:registerEvent(event) + end end if timeLimit then self:addEvent(function(monsterId) diff --git a/data/libs/functions/bosslever.lua b/data/libs/functions/bosslever.lua index a1441d6cb86..7e099ec9183 100644 --- a/data/libs/functions/bosslever.lua +++ b/data/libs/functions/bosslever.lua @@ -14,9 +14,11 @@ ---@field private playerPositions {pos: Position, teleport: Position}[] ---@field private area {from: Position, to: Position} ---@field private monsters {name: string, pos: Position}[] +---@field private exitTeleporter Position ---@field private exit Position ---@field private encounter Encounter ---@field private timeoutEvent Event +---@field private testMode boolean BossLever = {} --[[ @@ -65,9 +67,11 @@ setmetatable(BossLever, { disabled = config.disabled, playerPositions = config.playerPositions, onUseExtra = config.onUseExtra or function() end, + exitTeleporter = config.exitTeleporter, exit = config.exit, area = config.specPos, monsters = config.monsters or {}, + testMode = config.testMode, _position = nil, _uid = nil, _aid = nil, @@ -111,7 +115,7 @@ end ---@param player Player ---@return number function BossLever:lastEncounterTime(player) - if not player then + if not player or self.testMode then return 0 end return player:getBossCooldown(self.name) @@ -142,7 +146,7 @@ end function BossLever:onUse(player) local isParticipant = false for _, v in ipairs(self.playerPositions) do - if v.pos == player:getPosition() then + if Position(v.pos) == player:getPosition() then isParticipant = true end end @@ -212,6 +216,7 @@ function BossLever:onUse(player) lever:teleportPlayers() if self.encounter then local encounter = Encounter(self.encounter) + encounter:reset() encounter:start() end self:setLastEncounterTime(os.time() + self.timeToFightAgain) @@ -278,5 +283,9 @@ function BossLever:register() end action:register() BossLever[self.name] = self + + if self.exitTeleporter then + SimpleTeleport(self.exitTeleporter, self.exit) + end return true end diff --git a/data/libs/zones_lib.lua b/data/libs/zones_lib.lua index 833c4d2cd4b..ff37af5b16a 100644 --- a/data/libs/zones_lib.lua +++ b/data/libs/zones_lib.lua @@ -41,6 +41,16 @@ function Zone:countMonsters(name) return count end +function Zone:getMonstersByName(name) + local monsters = {} + for _, monster in ipairs(self:getMonsters()) do + if monster:getName():lower() == name:lower() then + table.insert(monsters, monster) + end + end + return monsters +end + function Zone:countPlayers(notFlag) local players = self:getPlayers() local count = 0 diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 75cec14c30f..c398f07a2b2 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -2016,7 +2016,7 @@ void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptrgetName(), target ? target->getName() : "null", damage.primary.value); + g_logger().trace("[Combat::applyExtensions] - Applying extensions for {} on {}. Initial damage: {}", caster->getName(), target ? target->getName() : "null", damage.primary.value); // Critical hit uint16_t chance = 0; diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index e0df6f8b80f..2f855c6ac15 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -841,7 +841,7 @@ void Creature::mitigateDamage(const CombatType_t &combatType, BlockType_t &block if (combatType != COMBAT_MANADRAIN && combatType != COMBAT_LIFEDRAIN && combatType != COMBAT_AGONYDAMAGE) { // Increase mitigate damage auto originalDamage = damage; damage -= (damage * getMitigation()) / 100.; - g_logger().debug("[mitigation] creature: {}, original damage: {}, mitigation damage: {}", getName(), originalDamage, damage); + g_logger().trace("[mitigation] creature: {}, original damage: {}, mitigation damage: {}", getName(), originalDamage, damage); if (damage <= 0) { damage = 0; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 240993afaca..1f7f9f088b0 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -592,7 +592,7 @@ void Player::updateInventoryImbuement() { continue; } - g_logger().debug("Decaying imbuement {} from item {} of player {}", imbuement->getName(), item->getName(), getName()); + g_logger().trace("Decaying imbuement {} from item {} of player {}", imbuement->getName(), item->getName(), getName()); // Calculate the new duration of the imbuement, making sure it doesn't go below 0 uint32_t duration = std::max(0, imbuementInfo.duration - EVENT_IMBUEMENT_INTERVAL / 1000); // Update the imbuement's duration in the item From 49de5d61805ccf3edda6826a29e1b1bd67487a1b Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 26 Oct 2023 08:20:29 -0300 Subject: [PATCH 10/12] fix: custom attribute value string/bool/double (#1748) Fixes #1731 --- src/items/functions/item/custom_attribute.cpp | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/items/functions/item/custom_attribute.cpp b/src/items/functions/item/custom_attribute.cpp index 596ac55d127..f7cfa925eac 100644 --- a/src/items/functions/item/custom_attribute.cpp +++ b/src/items/functions/item/custom_attribute.cpp @@ -18,23 +18,19 @@ CustomAttribute::~CustomAttribute() = default; // Constructor for int64_t CustomAttribute::CustomAttribute(const std::string &initStringKey, const int64_t initInt64) : - stringKey(initStringKey) { - setValue(initInt64); + stringKey(initStringKey), value(initInt64) { } // Constructor for string CustomAttribute::CustomAttribute(const std::string &initStringKey, const std::string &initStringValue) : - stringKey(initStringKey) { - setValue(initStringValue); + stringKey(initStringKey), value(initStringValue) { } // Constructor for double CustomAttribute::CustomAttribute(const std::string &initStringKey, const double initDoubleValue) : - stringKey(initStringKey) { - setValue(initDoubleValue); + stringKey(initStringKey), value(initDoubleValue) { } // Constructor for boolean CustomAttribute::CustomAttribute(const std::string &initStringKey, const bool initBoolValue) : - stringKey(initStringKey) { - setValue(initBoolValue); + stringKey(initStringKey), value(initBoolValue) { } const std::string &CustomAttribute::getStringKey() const { @@ -122,7 +118,7 @@ bool CustomAttribute::unserialize(PropStream &propStream, const std::string &fun g_logger().error("[{}] failed to read string, call function: {}", __FUNCTION__, function); return false; } - setValue(readString); + value = readString; break; } case 2: { @@ -131,7 +127,7 @@ bool CustomAttribute::unserialize(PropStream &propStream, const std::string &fun g_logger().error("[{}] failed to read int64, call function: {}", __FUNCTION__, function); return false; } - setValue(readInt); + value = readInt; break; } case 3: { @@ -140,7 +136,7 @@ bool CustomAttribute::unserialize(PropStream &propStream, const std::string &fun g_logger().error("[{}] failed to read double, call function: {}", __FUNCTION__, function); return false; } - setValue(readDouble); + value = readDouble; break; } case 4: { @@ -149,7 +145,7 @@ bool CustomAttribute::unserialize(PropStream &propStream, const std::string &fun g_logger().error("[{}] failed to read boolean, call function: {}", __FUNCTION__, function); return false; } - setValue(readBoolean); + value = readBoolean; break; } default: From 178372ba8b239d523860678dd538265c1fce17a7 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 27 Oct 2023 07:48:02 -0300 Subject: [PATCH 11/12] fix: login with account name on old protocol (11.00) (#1749) Resolves #1683 --- config.lua.dist | 2 +- src/account/account.cpp | 2 +- src/account/account_repository.hpp | 2 +- src/account/account_repository_db.cpp | 7 ++++--- src/account/account_repository_db.hpp | 2 +- src/canary_server.cpp | 11 ++++++----- src/core.hpp | 7 ------- src/lib/logging/log_with_spd_log.cpp | 4 ++-- src/server/network/protocol/protocolstatus.cpp | 16 ++++++++++------ src/server/network/protocol/protocolstatus.hpp | 4 ++++ .../account/in_memory_account_repository.hpp | 2 +- tests/integration/main.cpp | 4 ++-- 12 files changed, 33 insertions(+), 30 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index a78e73e63ca..5b022b17e58 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -10,7 +10,7 @@ coreDirectory = "data" -- Set log level -- It can be trace, debug, info, warning, error, critical, off (default: info). -- NOTE: Will only display logs with level higher or equal the one set. -logLevel = "debug" +logLevel = "info" -- Combat settings -- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" diff --git a/src/account/account.cpp b/src/account/account.cpp index 5c093f917bd..881b9491406 100644 --- a/src/account/account.cpp +++ b/src/account/account.cpp @@ -36,7 +36,7 @@ namespace account { return ERROR_NO; } - if (!m_descriptor.empty() && accountRepository.loadByEmail(m_descriptor, m_account)) { + if (!m_descriptor.empty() && accountRepository.loadByEmailOrName(getProtocolCompat(), m_descriptor, m_account)) { m_accLoaded = true; return ERROR_NO; } diff --git a/src/account/account_repository.hpp b/src/account/account_repository.hpp index daf869e249d..b883050993b 100644 --- a/src/account/account_repository.hpp +++ b/src/account/account_repository.hpp @@ -17,7 +17,7 @@ namespace account { virtual ~AccountRepository() = default; virtual bool loadByID(const uint32_t &id, AccountInfo &acc) = 0; - virtual bool loadByEmail(const std::string &email, AccountInfo &acc) = 0; + virtual bool loadByEmailOrName(bool oldProtocol, const std::string &emailOrName, AccountInfo &acc) = 0; virtual bool loadBySession(const std::string &email, AccountInfo &acc) = 0; virtual bool save(const AccountInfo &accInfo) = 0; diff --git a/src/account/account_repository_db.cpp b/src/account/account_repository_db.cpp index 601aa1ec8b1..c4a960e6244 100644 --- a/src/account/account_repository_db.cpp +++ b/src/account/account_repository_db.cpp @@ -19,8 +19,9 @@ namespace account { return load(query, acc); }; - bool AccountRepositoryDB::loadByEmail(const std::string &email, AccountInfo &acc) { - auto query = fmt::format("SELECT `id`, `type`, `premdays`, `lastday`, `creation`, `premdays_purchased`, 0 AS `expires` FROM `accounts` WHERE `email` = {}", db.escapeString(email)); + bool AccountRepositoryDB::loadByEmailOrName(bool oldProtocol, const std::string &emailOrName, AccountInfo &acc) { + auto identifier = oldProtocol ? "name" : "email"; + auto query = fmt::format("SELECT `id`, `type`, `premdays`, `lastday`, `creation`, `premdays_purchased`, 0 AS `expires` FROM `accounts` WHERE `{}` = {}", identifier, db.escapeString(emailOrName)); return load(query, acc); }; @@ -163,7 +164,7 @@ namespace account { acc.premiumLastDay = result->getNumber("lastday"); acc.sessionExpires = result->getNumber("expires"); acc.premiumDaysPurchased = result->getNumber("premdays_purchased"); - acc.creationTime = result->getNumber("creation"); + acc.creationTime = result->getNumber("creation"); setupLoyaltyInfo(acc); diff --git a/src/account/account_repository_db.hpp b/src/account/account_repository_db.hpp index 9da0486ee2b..d09f6cf7be5 100644 --- a/src/account/account_repository_db.hpp +++ b/src/account/account_repository_db.hpp @@ -21,7 +21,7 @@ namespace account { db(db), logger(logger) { } bool loadByID(const uint32_t &id, AccountInfo &acc) override; - bool loadByEmail(const std::string &email, AccountInfo &acc) override; + bool loadByEmailOrName(bool oldProtocol, const std::string &emailOrName, AccountInfo &acc) override; bool loadBySession(const std::string &esseionKey, AccountInfo &acc) override; bool save(const AccountInfo &accInfo) override; diff --git a/src/canary_server.cpp b/src/canary_server.cpp index bdb3ad2c5b1..6d833e0ce0b 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -26,6 +26,7 @@ #include "lua/scripts/lua_environment.hpp" #include "lua/scripts/scripts.hpp" #include "server/network/protocol/protocollogin.hpp" +#include "server/network/protocol/protocolstatus.hpp" #include "server/network/webhook/webhook.hpp" #include "io/ioprey.hpp" #include "io/io_bosstiary.hpp" @@ -50,7 +51,7 @@ CanaryServer::CanaryServer( g_dispatcher().init(); #ifdef _WIN32 - SetConsoleTitleA(STATUS_SERVER_NAME); + SetConsoleTitleA(ProtocolStatus::SERVER_NAME.c_str()); #endif } @@ -83,7 +84,7 @@ int CanaryServer::run() { if (getuid() == 0 || geteuid() == 0) { logger.warn("{} has been executed as root user, " "please consider running it as a normal user", - STATUS_SERVER_NAME); + ProtocolStatus::SERVER_NAME); } #endif @@ -181,12 +182,12 @@ void CanaryServer::setupHousesRent() { void CanaryServer::logInfos() { #if defined(GIT_RETRIEVED_STATE) && GIT_RETRIEVED_STATE - logger.debug("{} - Version [{}] dated [{}]", STATUS_SERVER_NAME, SERVER_RELEASE_VERSION, GIT_COMMIT_DATE_ISO8601); + logger.debug("{} - Version [{}] dated [{}]", ProtocolStatus::SERVER_NAME, SERVER_RELEASE_VERSION, GIT_COMMIT_DATE_ISO8601); #if GIT_IS_DIRTY logger.debug("DIRTY - NOT OFFICIAL RELEASE"); #endif #else - logger.info("{} - Version {}", STATUS_SERVER_NAME, SERVER_RELEASE_VERSION); + logger.info("{} - Version {}", ProtocolStatus::SERVER_NAME, SERVER_RELEASE_VERSION); #endif logger.debug("Compiled with {}, on {} {}, for platform {}\n", getCompiler(), __DATE__, __TIME__, getPlatform()); @@ -195,7 +196,7 @@ void CanaryServer::logInfos() { logger.debug("Linked with {} for Lua support", LUAJIT_VERSION); #endif - logger.info("A server developed by: {}", STATUS_SERVER_DEVELOPERS); + logger.info("A server developed by: {}", ProtocolStatus::SERVER_DEVELOPERS); logger.info("Visit our website for updates, support, and resources: " "https://docs.opentibiabr.com/"); } diff --git a/src/core.hpp b/src/core.hpp index 25f3f5cb1f5..f89e3adedfc 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -9,13 +9,6 @@ #pragma once -static constexpr auto STATUS_SERVER_NAME = "Canary"; -// STATUS_SERVER_VERSION is used for external display purposes, such as listings on otlist. -// This version should generally only show the major version to avoid frequent changes in otlist categories. -static constexpr auto STATUS_SERVER_VERSION = "3.0"; - -static constexpr auto STATUS_SERVER_DEVELOPERS = "OpenTibiaBR Organization"; - static constexpr auto AUTHENTICATOR_DIGITS = 6U; static constexpr auto AUTHENTICATOR_PERIOD = 30U; diff --git a/src/lib/logging/log_with_spd_log.cpp b/src/lib/logging/log_with_spd_log.cpp index d06e5e195dd..9b58317b819 100644 --- a/src/lib/logging/log_with_spd_log.cpp +++ b/src/lib/logging/log_with_spd_log.cpp @@ -12,7 +12,7 @@ #include "lib/di/container.hpp" LogWithSpdLog::LogWithSpdLog() { - setLevel("debug"); + setLevel("info"); spdlog::set_pattern("[%Y-%d-%m %H:%M:%S.%e] [%^%l%$] %v "); #ifdef DEBUG_LOG @@ -25,7 +25,7 @@ Logger &LogWithSpdLog::getInstance() { } void LogWithSpdLog::setLevel(const std::string &name) { - info("Setting log level to {}.", name); + debug("Setting log level to: {}.", name); auto level = spdlog::level::from_str(name); spdlog::set_level(level); } diff --git a/src/server/network/protocol/protocolstatus.cpp b/src/server/network/protocol/protocolstatus.cpp index 7a2a4d5bf1a..f07e5e85d18 100644 --- a/src/server/network/protocol/protocolstatus.cpp +++ b/src/server/network/protocol/protocolstatus.cpp @@ -16,6 +16,10 @@ #include "game/scheduling/dispatcher.hpp" #include "server/network/message/outputmessage.hpp" +std::string ProtocolStatus::SERVER_NAME = "Canary"; +std::string ProtocolStatus::SERVER_VERSION = "3.0"; +std::string ProtocolStatus::SERVER_DEVELOPERS = "OpenTibiaBR Organization"; + std::map ProtocolStatus::ipConnectMap; const uint64_t ProtocolStatus::start = OTSYS_TIME(); @@ -78,12 +82,12 @@ void ProtocolStatus::sendStatusString() { uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; serverinfo.append_attribute("uptime") = std::to_string(uptime).c_str(); serverinfo.append_attribute("ip") = g_configManager().getString(IP).c_str(); - serverinfo.append_attribute("servername") = g_configManager().getString(SERVER_NAME).c_str(); + serverinfo.append_attribute("servername") = g_configManager().getString(stringConfig_t::SERVER_NAME).c_str(); serverinfo.append_attribute("port") = std::to_string(g_configManager().getNumber(LOGIN_PORT)).c_str(); serverinfo.append_attribute("location") = g_configManager().getString(LOCATION).c_str(); serverinfo.append_attribute("url") = g_configManager().getString(URL).c_str(); - serverinfo.append_attribute("server") = STATUS_SERVER_NAME; - serverinfo.append_attribute("version") = STATUS_SERVER_VERSION; + serverinfo.append_attribute("server") = ProtocolStatus::SERVER_NAME.c_str(); + serverinfo.append_attribute("version") = ProtocolStatus::SERVER_VERSION.c_str(); serverinfo.append_attribute("client") = fmt::format("{}.{}", CLIENT_VERSION_UPPER, CLIENT_VERSION_LOWER).c_str(); pugi::xml_node owner = tsqp.append_child("owner"); @@ -150,7 +154,7 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &charact if (requestedInfo & REQUEST_BASIC_SERVER_INFO) { output->addByte(0x10); - output->addString(g_configManager().getString(SERVER_NAME)); + output->addString(g_configManager().getString(stringConfig_t::SERVER_NAME)); output->addString(g_configManager().getString(IP)); output->addString(std::to_string(g_configManager().getNumber(LOGIN_PORT))); } @@ -208,8 +212,8 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &charact if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) { output->addByte(0x23); // server software info - output->addString(STATUS_SERVER_NAME); - output->addString(STATUS_SERVER_VERSION); + output->addString(ProtocolStatus::SERVER_NAME); + output->addString(ProtocolStatus::SERVER_VERSION); output->addString(fmt::format("{}.{}", CLIENT_VERSION_UPPER, CLIENT_VERSION_LOWER)); } send(output); diff --git a/src/server/network/protocol/protocolstatus.hpp b/src/server/network/protocol/protocolstatus.hpp index be8b35462c3..36d83f77d65 100644 --- a/src/server/network/protocol/protocolstatus.hpp +++ b/src/server/network/protocol/protocolstatus.hpp @@ -32,6 +32,10 @@ class ProtocolStatus final : public Protocol { static const uint64_t start; + static std::string SERVER_NAME; + static std::string SERVER_VERSION; + static std::string SERVER_DEVELOPERS; + private: static std::map ipConnectMap; }; diff --git a/tests/fixture/account/in_memory_account_repository.hpp b/tests/fixture/account/in_memory_account_repository.hpp index dd4e77881b1..3996b7d11a2 100644 --- a/tests/fixture/account/in_memory_account_repository.hpp +++ b/tests/fixture/account/in_memory_account_repository.hpp @@ -41,7 +41,7 @@ namespace account::tests { return false; } - bool loadByEmail(const std::string &email, AccountInfo &acc) final { + bool loadByEmailOrName(bool oldProtocol, const std::string &email, AccountInfo &acc) final { auto account = accounts.find(email); if (account == accounts.end()) { diff --git a/tests/integration/main.cpp b/tests/integration/main.cpp index 91fd28a30cd..5b4055af7b3 100644 --- a/tests/integration/main.cpp +++ b/tests/integration/main.cpp @@ -78,13 +78,13 @@ int main() { expect(eq(acc.sessionExpires, 0)); }); - test("AccountRepositoryDB::loadByEmail") = databaseTest(db, [&db] { + test("AccountRepositoryDB::loadByEmailOrName") = databaseTest(db, [&db] { InMemoryLogger logger {}; AccountRepositoryDB accRepo { db, logger }; createAccount(db); AccountInfo acc {}; - accRepo.loadByEmail("@test", acc); + accRepo.loadByEmailOrName(false, "@test", acc); assertAccountLoad(acc); expect(eq(acc.sessionExpires, 0)); }); From e54b9c7ef75ba7bcf982a1227684a8e20b2db567 Mon Sep 17 00:00:00 2001 From: Luan Colombo <94877887+luancolombo@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:12:41 +0000 Subject: [PATCH 12/12] feat: The Paleworm loot and mount (#1758) Fix paleworm loot items, and add the haze mount script. --- .../quests/feaster_of_souls/the_pale_worm.lua | 2 ++ .../scripts/actions/mounts/haze_mount.lua | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 data-otservbr-global/scripts/actions/mounts/haze_mount.lua diff --git a/data-otservbr-global/monster/quests/feaster_of_souls/the_pale_worm.lua b/data-otservbr-global/monster/quests/feaster_of_souls/the_pale_worm.lua index 8f1258b6e70..41bf63ad767 100644 --- a/data-otservbr-global/monster/quests/feaster_of_souls/the_pale_worm.lua +++ b/data-otservbr-global/monster/quests/feaster_of_souls/the_pale_worm.lua @@ -95,6 +95,8 @@ monster.loot = { { name = "bloody tears", chance = 1500 }, { name = "ghost chestplate", chance = 150 }, { name = "spooky hood", chance = 150 }, + { name = "pale worm's scalp", chance = 1200 }, + { name = "spectral scrap of cloth", chance = 250 }, { name = "fabulous legs", chance = 150 }, { name = "phantasmal axe", chance = 150 }, { name = "ghost backpack", chance = 150 }, diff --git a/data-otservbr-global/scripts/actions/mounts/haze_mount.lua b/data-otservbr-global/scripts/actions/mounts/haze_mount.lua new file mode 100644 index 00000000000..bd289b57df9 --- /dev/null +++ b/data-otservbr-global/scripts/actions/mounts/haze_mount.lua @@ -0,0 +1,25 @@ +local config = { + [32629] = { mountId = 162, message = "You are now versed to ride the haze!" }, +} + +local hazemount = Action() + +function hazemount.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local mount = config[item.itemid] + + if not mount then + return true + end + + if not player:hasMount(mount.mountId) then + player:addMount(mount.mountId) + player:say(mount.message, TALKTYPE_MONSTER_SAY) + item:remove(1) + else + player:sendTextMessage(19, "You already have this mount") + end + return true +end + +hazemount:id(32629) +hazemount:register()