From 4c7786e95ed1b5d5cc7f51f380c398224b252ce9 Mon Sep 17 00:00:00 2001 From: Matt Gomez Date: Tue, 31 Oct 2023 20:11:36 -0600 Subject: [PATCH] update to : https://github.com/opentibiabr/canary/commits/main --- .github/workflows/build-ubuntu.yml | 16 +- .github/workflows/build-windows-cmake.yml | 16 +- schema.sql | 4 +- 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 | 18 +- src/core.hpp | 7 +- src/creatures/combat/combat.cpp | 139 +++--- src/creatures/combat/combat.hpp | 2 + src/creatures/combat/condition.cpp | 2 +- src/creatures/creature.cpp | 29 +- src/creatures/creature.hpp | 3 +- src/creatures/interactions/chat.cpp | 4 +- src/creatures/monsters/monster.cpp | 59 +-- src/creatures/monsters/monster.hpp | 30 +- src/creatures/monsters/monsters.cpp | 6 +- src/creatures/monsters/monsters.hpp | 7 +- .../monsters/spawns/spawn_monster.cpp | 30 +- .../monsters/spawns/spawn_monster.hpp | 6 + src/creatures/npcs/npc.cpp | 9 +- src/creatures/npcs/spawns/spawn_npc.cpp | 13 +- src/creatures/players/grouping/party.cpp | 14 +- src/creatures/players/player.cpp | 90 ++-- src/creatures/players/player.hpp | 30 +- src/creatures/players/wheel/player_wheel.cpp | 64 ++- .../players/wheel/wheel_definitions.hpp | 4 +- src/database/databasetasks.cpp | 4 +- src/game/CMakeLists.txt | 3 +- src/game/bank/bank.cpp | 5 +- src/game/game.cpp | 404 ++++++++++++------ src/game/game.hpp | 33 +- src/game/scheduling/dispatcher.cpp | 198 +++++++-- src/game/scheduling/dispatcher.hpp | 174 +++++++- src/game/scheduling/save_manager.cpp | 144 +++++++ src/game/scheduling/save_manager.hpp | 46 ++ src/game/scheduling/scheduler.cpp | 71 --- src/game/scheduling/scheduler.hpp | 43 -- src/game/scheduling/task.cpp | 38 ++ src/game/scheduling/task.hpp | 97 ++++- src/game/zones/zone.cpp | 272 ++++++------ src/game/zones/zone.hpp | 135 +++++- src/io/functions/iologindata_load_player.cpp | 2 - src/io/io_bosstiary.cpp | 33 +- src/io/io_bosstiary.hpp | 1 - src/io/io_definitions.hpp | 4 +- src/io/iobestiary.cpp | 2 +- src/io/iologindata.cpp | 8 +- src/io/iomap.cpp | 81 ++-- src/io/iomap.hpp | 32 ++ src/io/iomapserialize.cpp | 14 +- src/io/iomarket.cpp | 9 +- src/items/bed.cpp | 7 +- src/items/containers/container.cpp | 25 +- src/items/containers/container.hpp | 5 + src/items/containers/mailbox/mailbox.cpp | 3 +- src/items/decay/decay.cpp | 22 +- src/items/functions/item/custom_attribute.cpp | 20 +- src/items/functions/item/item_parse.cpp | 11 +- src/items/item.cpp | 4 +- src/items/thing.hpp | 4 + src/items/tile.cpp | 47 +- src/items/tile.hpp | 6 +- src/kv/value_wrapper.hpp | 14 +- src/lib/logging/log_with_spd_log.cpp | 4 +- src/lib/thread/thread_pool.cpp | 2 +- src/lib/thread/thread_pool.hpp | 18 + src/lua/creature/actions.cpp | 4 + src/lua/creature/raids.cpp | 15 +- .../functions/core/game/game_functions.cpp | 50 ++- .../functions/core/game/global_functions.cpp | 9 +- .../functions/core/game/zone_functions.cpp | 43 +- .../functions/core/game/zone_functions.hpp | 6 +- .../monster/monster_type_functions.cpp | 54 ++- .../monster/monster_type_functions.hpp | 13 +- .../functions/creatures/npc/npc_functions.cpp | 82 +--- .../creatures/player/player_functions.cpp | 22 +- .../creatures/player/player_functions.hpp | 2 + src/lua/functions/items/item_functions.cpp | 1 + src/lua/functions/lua_functions_loader.cpp | 70 +++ src/lua/functions/lua_functions_loader.hpp | 8 + src/lua/global/globalevent.cpp | 14 +- src/map/house/house.cpp | 20 +- src/map/map.cpp | 44 +- src/map/map.hpp | 11 +- src/map/mapcache.cpp | 4 + src/pch.hpp | 2 + src/server/network/connection/connection.cpp | 30 +- src/server/network/connection/connection.hpp | 4 +- src/server/network/message/outputmessage.cpp | 4 +- src/server/network/protocol/protocol.cpp | 8 +- src/server/network/protocol/protocolgame.cpp | 66 +-- src/server/network/protocol/protocolgame.hpp | 4 +- src/server/network/protocol/protocollogin.cpp | 2 +- .../network/protocol/protocolstatus.cpp | 20 +- .../network/protocol/protocolstatus.hpp | 4 + src/server/network/webhook/webhook.cpp | 6 +- src/server/server.cpp | 6 +- src/server/signals.cpp | 13 +- src/utils/arraylist.hpp | 158 +++++++ src/utils/benchmark.hpp | 2 +- src/utils/const.hpp | 1 - src/utils/tools.cpp | 28 +- src/utils/tools.hpp | 4 +- src/utils/utils_definitions.hpp | 3 + src/utils/vectorsort.hpp | 178 ++++++++ .../account/in_memory_account_repository.hpp | 2 +- tests/integration/main.cpp | 4 +- vcproj/otxserver.vcxproj | 11 +- vcproj/settings.props | 2 +- 111 files changed, 2548 insertions(+), 1146 deletions(-) create mode 100644 src/game/scheduling/save_manager.cpp create mode 100644 src/game/scheduling/save_manager.hpp delete mode 100644 src/game/scheduling/scheduler.cpp delete mode 100644 src/game/scheduling/scheduler.hpp create mode 100644 src/game/scheduling/task.cpp create mode 100644 src/utils/arraylist.hpp create mode 100644 src/utils/vectorsort.hpp diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 55efc8f1a..75ce539ff 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -18,6 +18,15 @@ env: MAKEFLAGS: '-j 2' jobs: + cancel-runs: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + job: if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} name: ${{ matrix.os }}-${{ matrix.buildtype }} @@ -35,13 +44,6 @@ jobs: triplet: x64-linux steps: - - name: Cancel Previous Runs - if: github.ref != 'refs/heads/main' - uses: fkirc/skip-duplicate-actions@master - with: - concurrent_skipping: 'same_content' - cancel_others: true - - name: Checkout repository uses: actions/checkout@main diff --git a/.github/workflows/build-windows-cmake.yml b/.github/workflows/build-windows-cmake.yml index 174ae588a..cf2514d7c 100644 --- a/.github/workflows/build-windows-cmake.yml +++ b/.github/workflows/build-windows-cmake.yml @@ -15,6 +15,15 @@ env: CMAKE_BUILD_PARALLEL_LEVEL: 2 MAKEFLAGS: '-j 2' jobs: + cancel-runs: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + job: if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} name: ${{ matrix.os }}-${{ matrix.buildtype }} @@ -29,13 +38,6 @@ jobs: packages: > sccache steps: - - name: Cancel Previous Runs - if: github.ref != 'refs/heads/main' - uses: fkirc/skip-duplicate-actions@master - with: - concurrent_skipping: 'same_content' - cancel_others: true - - name: Checkout repository uses: actions/checkout@main diff --git a/schema.sql b/schema.sql index b54d9e299..dc58d3a71 100644 --- a/schema.sql +++ b/schema.sql @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS `server_config` ( CONSTRAINT `server_config_pk` PRIMARY KEY (`config`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '38'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '41'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); -- Table structure `accounts` CREATE TABLE IF NOT EXISTS `accounts` ( @@ -438,7 +438,7 @@ DELIMITER ; CREATE TABLE IF NOT EXISTS `house_lists` ( `house_id` int NOT NULL, `listid` int NOT NULL, - `version` int NOT NULL DEFAULT '0', + `version` bigint NOT NULL DEFAULT '0', `list` text NOT NULL, PRIMARY KEY (`house_id`, `listid`), KEY `house_id_index` (`house_id`), diff --git a/src/account/account.cpp b/src/account/account.cpp index 5c093f917..881b94914 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 daf869e24..b88305099 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 601aa1ec8..c4a960e62 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 9da0486ee..d09f6cf7b 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 d9c9d7011..286772d03 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -16,6 +16,7 @@ #include "creatures/players/storages/storages.hpp" #include "database/databasemanager.hpp" #include "game/game.hpp" +#include "game/zones/zone.hpp" #include "game/scheduling/dispatcher.hpp" #include "game/scheduling/events_scheduler.hpp" #include "io/iomarket.hpp" @@ -25,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" @@ -46,13 +48,15 @@ CanaryServer::CanaryServer( std::set_new_handler(badAllocationHandler); srand(static_cast(OTSYS_TIME())); + g_dispatcher().init(); + #ifdef _WIN32 - SetConsoleTitleA(STATUS_SERVER_NAME); + SetConsoleTitleA(ProtocolStatus::SERVER_NAME.c_str()); #endif } int CanaryServer::run() { - g_dispatcher().addTask( + g_dispatcher().addEvent( [this] { try { loadConfigLua(); @@ -80,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 @@ -151,6 +155,7 @@ void CanaryServer::loadMaps() const { if (g_configManager().getBoolean(TOGGLE_MAP_CUSTOM)) { g_game().loadCustomMaps(g_configManager().getString(DATA_DIRECTORY) + "/world/custom/"); } + Zone::refreshAll(); } catch (const std::exception &err) { throw FailedToInitializeCanary(err.what()); } @@ -177,12 +182,12 @@ void CanaryServer::setupHousesRent() { void CanaryServer::logInfos() { #if defined(GIT_RETRIEVED_STATE) && GIT_RETRIEVED_STATE - logger.debug("{} - Version [{}] dated [{}]", STATUS_SERVER_NAME, STATUS_SERVER_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("The {} - Version {}", STATUS_SERVER_NAME, STATUS_SERVER_VERSION); + logger.info("{} - Version {}", ProtocolStatus::SERVER_NAME, SERVER_RELEASE_VERSION); #endif logger.debug("Compiled with {}, on {} {}, for platform {}\n", getCompiler(), __DATE__, __TIME__, getPlatform()); @@ -191,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:\n" "https://docs.opentibiabr.com/home/welcome/ \n" "https://github.com/mattyx14/otxserver/\n"); @@ -366,4 +371,5 @@ void CanaryServer::modulesLoadHelper(bool loaded, std::string moduleName) { void CanaryServer::shutdown() { inject().shutdown(); + g_dispatcher().shutdown(); } diff --git a/src/core.hpp b/src/core.hpp index 4670a43f9..de44be546 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -9,13 +9,12 @@ #pragma once -static constexpr auto STATUS_SERVER_NAME = "OTX Server"; -static constexpr auto STATUS_SERVER_VERSION = "6.2"; -static constexpr auto STATUS_SERVER_DEVELOPERS = "Canary Base - OpenTibiaBR Organization and data editor: Mattyx14"; - static constexpr auto AUTHENTICATOR_DIGITS = 6U; static constexpr auto AUTHENTICATOR_PERIOD = 30U; +// SERVER_MAJOR_VERSION is the actual full version of the server, including minor and patch numbers. +// This is intended for internal use to identify the exact state of the server (release) software. +static constexpr auto SERVER_RELEASE_VERSION = "6.2.0"; static constexpr auto CLIENT_VERSION = 1321; #define CLIENT_VERSION_UPPER (CLIENT_VERSION / 100) diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 8b6502535..c398f07a2 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -1139,41 +1139,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptrgetPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - // Charm low blow rune) - if (target && target->getMonster() && damage.primary.type != COMBAT_HEALING) { - uint16_t playerCharmRaceid = caster->getPlayer()->parseRacebyCharm(CHARM_LOW, false, 0); - if (playerCharmRaceid != 0) { - const auto mType = g_monsters().getMonsterType(target->getName()); - if (mType && playerCharmRaceid == mType->info.raceid) { - const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW); - if (charm) { - chance += charm->percent; - g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster); - } - } - } - } - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - - // Fatal hit (onslaught) - if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT); - playerWeapon != nullptr && playerWeapon->getTier()) { - double_t fatalChance = playerWeapon->getFatalChance(); - double_t randomChance = uniform_random(0, 10000) / 100; - if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) { - damage.fatal = true; - damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); - damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); - } - } - } + applyExtensions(caster, target, damage, params); if (canCombat) { if (target && caster && params.distanceEffect != CONST_ANI_NONE) { @@ -1194,27 +1160,7 @@ void Combat::doCombatHealth(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) { - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (damage.primary.type != COMBAT_HEALING && chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - - // Fatal hit (onslaught) - if (auto playerWeapon = caster->getPlayer()->getInventoryItem(CONST_SLOT_LEFT); - playerWeapon != nullptr && playerWeapon->getTier() > 0) { - double_t fatalChance = playerWeapon->getFatalChance(); - double_t randomChance = uniform_random(0, 10000) / 100; - if (damage.primary.type != COMBAT_HEALING && fatalChance > 0 && randomChance < fatalChance) { - damage.fatal = true; - damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); - damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); - } - } - } + applyExtensions(caster, nullptr, damage, params); const auto origin = caster ? caster->getPosition() : Position(); CombatFunc(caster, origin, position, area, params, CombatHealthFunc, &damage); } @@ -1231,15 +1177,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptrgetPosition(), params.impactEffect); } - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - } + applyExtensions(caster, target, damage, params); if (canCombat) { if (caster && target && params.distanceEffect != CONST_ANI_NONE) { @@ -1260,15 +1198,7 @@ void Combat::doCombatMana(std::shared_ptr caster, std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms) { - if (caster && caster->getPlayer()) { - // Critical damage - uint16_t chance = caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE) + (uint16_t)damage.criticalChance; - if (chance != 0 && uniform_random(1, 100) <= chance) { - damage.critical = true; - damage.primary.value += (damage.primary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - damage.secondary.value += (damage.secondary.value * (caster->getPlayer()->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE) + damage.criticalDamage)) / 100; - } - } + applyExtensions(caster, nullptr, damage, params); const auto origin = caster ? caster->getPosition() : Position(); CombatFunc(caster, origin, position, area, params, CombatManaFunc, &damage); } @@ -2080,3 +2010,64 @@ void MagicField::onStepInField(const std::shared_ptr &creature) { creature->addCondition(conditionCopy); } } + +void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms) { + if (damage.extension || !caster || damage.primary.type == COMBAT_HEALING) { + return; + } + + 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; + int32_t multiplier = 50; + auto player = caster->getPlayer(); + auto monster = caster->getMonster(); + if (player) { + chance = player->getSkillLevel(SKILL_CRITICAL_HIT_CHANCE); + multiplier = player->getSkillLevel(SKILL_CRITICAL_HIT_DAMAGE); + + if (target) { + uint16_t playerCharmRaceid = player->parseRacebyCharm(CHARM_LOW, false, 0); + if (playerCharmRaceid != 0) { + const auto mType = g_monsters().getMonsterType(target->getName()); + if (mType && playerCharmRaceid == mType->info.raceid) { + const auto charm = g_iobestiary().getBestiaryCharm(CHARM_LOW); + if (charm) { + chance += charm->percent; + g_game().sendDoubleSoundEffect(target->getPosition(), charm->soundCastEffect, charm->soundImpactEffect, caster); + } + } + } + } + } else if (monster) { + chance = monster->critChance(); + } + + multiplier += damage.criticalDamage; + multiplier = 1 + multiplier / 100; + chance += (uint16_t)damage.criticalChance; + + if (chance != 0 && uniform_random(1, 100) <= chance) { + damage.critical = true; + damage.primary.value *= multiplier; + damage.secondary.value *= multiplier; + } + + if (player) { + // Fatal hit (onslaught) + if (auto playerWeapon = player->getInventoryItem(CONST_SLOT_LEFT); + playerWeapon != nullptr && playerWeapon->getTier() > 0) { + double_t fatalChance = playerWeapon->getFatalChance(); + double_t randomChance = uniform_random(0, 10000) / 100; + if (fatalChance > 0 && randomChance < fatalChance) { + damage.fatal = true; + damage.primary.value += static_cast(std::round(damage.primary.value * 0.6)); + damage.secondary.value += static_cast(std::round(damage.secondary.value * 0.6)); + } + } + } else if (monster) { + damage.primary.value *= monster->getAttackMultiplier(); + damage.secondary.value *= monster->getAttackMultiplier(); + } +} diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp index 16062cb68..2bb2d0a35 100644 --- a/src/creatures/combat/combat.hpp +++ b/src/creatures/combat/combat.hpp @@ -260,6 +260,8 @@ class Combat { Combat(const Combat &) = delete; Combat &operator=(const Combat &) = delete; + static void applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms); + static void doCombatHealth(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms); static void doCombatHealth(std::shared_ptr caster, const Position &position, const std::unique_ptr &area, CombatDamage &damage, const CombatParams ¶ms); diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index fc6ee1206..f69488175 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -2040,7 +2040,7 @@ bool ConditionFeared::executeCondition(std::shared_ptr creature, int32 } if (getFleePath(creature, currentPos, listDir)) { - g_dispatcher().addTask(std::bind(&Game::forcePlayerAutoWalk, &g_game(), creature->getID(), listDir), "ConditionFeared::executeCondition"); + g_dispatcher().addEvent(std::bind(&Game::forcePlayerAutoWalk, &g_game(), creature->getID(), listDir), "ConditionFeared::executeCondition"); g_logger().debug("[ConditionFeared::executeCondition] Walking Scheduled"); } } diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 8d6eb1972..2f855c6ac 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -14,7 +14,6 @@ #include "game/scheduling/dispatcher.hpp" #include "game/game.hpp" #include "creatures/monsters/monster.hpp" -#include "game/scheduling/scheduler.hpp" #include "game/zones/zone.hpp" #include "map/spectators.hpp" @@ -260,7 +259,7 @@ void Creature::addEventWalk(bool firstStep) { g_game().checkCreatureWalk(getID()); } - eventWalk = g_scheduler().addEvent( + eventWalk = g_dispatcher().scheduleEvent( static_cast(ticks), std::bind(&Game::checkCreatureWalk, &g_game(), getID()), "Creature::checkCreatureWalk" ); @@ -268,7 +267,7 @@ void Creature::addEventWalk(bool firstStep) { void Creature::stopEventWalk() { if (eventWalk != 0) { - g_scheduler().stopEvent(eventWalk); + g_dispatcher().stopEvent(eventWalk); eventWalk = 0; } } @@ -598,7 +597,7 @@ void Creature::onCreatureMove(std::shared_ptr creature, std::shared_pt if (followCreature && (creature == getCreature() || creature == followCreature)) { if (hasFollowPath) { isUpdatingPath = true; - g_dispatcher().addTask(std::bind(&Game::updateCreatureWalk, &g_game(), getID()), "Game::updateCreatureWalk"); + g_dispatcher().addEvent(std::bind(&Game::updateCreatureWalk, &g_game(), getID()), "Game::updateCreatureWalk"); } if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { @@ -613,7 +612,7 @@ void Creature::onCreatureMove(std::shared_ptr creature, std::shared_pt } else { if (hasExtraSwing()) { // our target is moving lets see if we can get in hit - g_dispatcher().addTask(std::bind(&Game::checkCreatureAttack, &g_game(), getID()), "Game::checkCreatureAttack"); + g_dispatcher().addEvent(std::bind(&Game::checkCreatureAttack, &g_game(), getID()), "Game::checkCreatureAttack"); } if (newTile->getZoneType() != oldTile->getZoneType()) { @@ -739,8 +738,8 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared dropLoot(corpse->getContainer(), lastHitCreature); corpse->startDecaying(); bool corpses = corpse->isRewardCorpse() || (corpse->getID() == ITEM_MALE_CORPSE || corpse->getID() == ITEM_FEMALE_CORPSE); - if (corpse->getContainer() && mostDamageCreature && mostDamageCreature->getPlayer() && !corpses) { - const auto player = mostDamageCreature->getPlayer(); + const auto player = mostDamageCreature ? mostDamageCreature->getPlayer() : nullptr; + if (corpse->getContainer() && player && !corpses) { auto monster = getMonster(); if (monster && !monster->isRewardBoss()) { std::ostringstream lootMessage; @@ -754,7 +753,7 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared if (player->checkAutoLoot()) { int32_t pos = tile->getStackposOfItem(player, corpse); - g_dispatcher().addTask( + g_dispatcher().addEvent( std::bind(&Game::playerQuickLoot, &g_game(), mostDamageCreature->getID(), this->getPosition(), corpse->getID(), pos - 1, nullptr, false, true), "Game::playerQuickLoot" ); @@ -801,7 +800,7 @@ void Creature::changeHealth(int32_t healthChange, bool sendHealthChange /* = tru g_game().addCreatureHealth(static_self_cast()); } if (health <= 0) { - g_dispatcher().addTask(std::bind(&Game::executeDeath, &g_game(), getID()), "Game::executeDeath"); + g_dispatcher().addEvent(std::bind(&Game::executeDeath, &g_game(), getID()), "Game::executeDeath"); } } @@ -842,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; @@ -1309,7 +1308,7 @@ void Creature::removeCondition(ConditionType_t conditionType, ConditionId_t cond if (!force && conditionType == CONDITION_PARALYZE) { int32_t walkDelay = getWalkDelay(); if (walkDelay > 0) { - g_scheduler().addEvent( + g_dispatcher().scheduleEvent( walkDelay, std::bind(&Game::forceRemoveCondition, &g_game(), getID(), conditionType, conditionId), "Game::forceRemoveCondition" @@ -1790,8 +1789,12 @@ void Creature::setIncreasePercent(CombatType_t combat, int32_t value) { } } -const phmap::parallel_flat_hash_set> Creature::getZones() { - return Zone::getZones(getPosition()); +phmap::flat_hash_set> Creature::getZones() { + auto tile = getTile(); + if (tile) { + return tile->getZones(); + } + return {}; } void Creature::iconChanged() { diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index b6a8df61a..0195ec17c 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -263,7 +263,7 @@ class Creature : virtual public Thing, public SharedObject { return ZONE_NORMAL; } - const phmap::parallel_flat_hash_set> getZones(); + phmap::flat_hash_set> getZones(); // walk functions void startAutoWalk(const std::forward_list &listDir, bool ignoreConditions = false); @@ -493,6 +493,7 @@ class Creature : virtual public Thing, public SharedObject { std::shared_ptr getParent() override final { return getTile(); } + void setParent(std::weak_ptr cylinder) override final { auto lockedCylinder = cylinder.lock(); if (lockedCylinder) { diff --git a/src/creatures/interactions/chat.cpp b/src/creatures/interactions/chat.cpp index 3dba27371..ddb6d0433 100644 --- a/src/creatures/interactions/chat.cpp +++ b/src/creatures/interactions/chat.cpp @@ -12,7 +12,7 @@ #include "creatures/interactions/chat.hpp" #include "game/game.hpp" #include "utils/pugicast.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" bool PrivateChatChannel::isInvited(uint32_t guid) const { if (guid == getOwner()) { @@ -81,7 +81,7 @@ bool ChatChannel::addUser(const std::shared_ptr &player) { if (id == CHANNEL_GUILD) { const auto guild = player->getGuild(); if (guild && !guild->getMotd().empty()) { - g_scheduler().addEvent(150, std::bind(&Game::sendGuildMotd, &g_game(), player->getID()), "Game::sendGuildMotd"); + g_dispatcher().scheduleEvent(150, std::bind(&Game::sendGuildMotd, &g_game(), player->getID()), "Game::sendGuildMotd"); } } diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index eef9aaa38..9bef75a5e 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -225,6 +225,9 @@ void Monster::onCreatureMove(std::shared_ptr creature, std::shared_ptr } updateIdleStatus(); + if (!m_attackedCreature.expired()) { + return; + } if (!isSummon()) { auto followCreature = getFollowCreature(); @@ -340,12 +343,21 @@ void Monster::updateTargetList() { auto targetIterator = targetIDList.begin(); while (targetIterator != targetIDList.end()) { - auto creature = targetListMap[*targetIterator].lock(); - if (!creature || creature->getHealth() <= 0 || !canSee(creature->getPosition())) { - targetIterator = targetIDList.erase(targetIterator); - targetListMap.erase(*targetIterator); + const uint32_t targetId = *targetIterator; + + auto itTLM = targetListMap.find(targetId); + const bool existTarget = itTLM != targetListMap.end(); + + if (existTarget) { + const auto &creature = itTLM->second.lock(); + if (!creature || creature->getHealth() <= 0 || !canSee(creature->getPosition())) { + targetIterator = targetIDList.erase(targetIterator); + targetListMap.erase(itTLM); + } else { + ++targetIterator; + } } else { - ++targetIterator; + targetIterator = targetIDList.erase(targetIterator); } } @@ -662,7 +674,7 @@ bool Monster::selectTarget(std::shared_ptr creature) { if (isHostile() || isSummon()) { if (setAttackedCreature(creature)) { - g_dispatcher().addTask(std::bind(&Game::checkCreatureAttack, &g_game(), getID()), "Game::checkCreatureAttack"); + g_dispatcher().addEvent(std::bind(&Game::checkCreatureAttack, &g_game(), getID()), "Game::checkCreatureAttack"); } } return setFollowCreature(creature); @@ -783,7 +795,7 @@ void Monster::onThink(uint32_t interval) { // This happens just after a master orders an attack, so lets follow it aswell. setFollowCreature(attackedCreature); } - } else if (!targetIDList.empty()) { + } else if (!attackedCreature && !targetIDList.empty()) { if (!followCreature || !hasFollowPath) { searchTarget(TARGETSEARCH_NEAREST); } else if (isFleeing()) { @@ -811,12 +823,6 @@ void Monster::doAttacking(uint32_t interval) { bool resetTicks = interval != 0; attackTicks += interval; - float forgeAttackBonus = 0; - if (monsterForgeClassification > ForgeClassifications_t::FORGE_NORMAL_MONSTER) { - uint16_t damageBase = 3; - forgeAttackBonus = static_cast(damageBase + 100) / 100.f; - } - const Position &myPos = getPosition(); const Position &targetPos = attackedCreature->getPosition(); @@ -834,20 +840,8 @@ void Monster::doAttacking(uint32_t interval) { updateLook = false; } - float multiplier; - if (maxCombatValue > 0) { // Defense - multiplier = getDefenseMultiplier(); - } else { // Attack - multiplier = getAttackMultiplier(); - } - - minCombatValue = spellBlock.minCombatValue * multiplier; - maxCombatValue = spellBlock.maxCombatValue * multiplier; - - if (maxCombatValue <= 0 && forgeAttackBonus > 0) { - minCombatValue *= static_cast(forgeAttackBonus); - maxCombatValue *= static_cast(forgeAttackBonus); - } + minCombatValue = spellBlock.minCombatValue; + maxCombatValue = spellBlock.maxCombatValue; if (spellBlock.spell == nullptr) { continue; @@ -1912,15 +1906,8 @@ bool Monster::getCombatValues(int32_t &min, int32_t &max) { return false; } - float multiplier; - if (maxCombatValue > 0) { // Defense - multiplier = getDefenseMultiplier(); - } else { // Attack - multiplier = getAttackMultiplier(); - } - - min = minCombatValue * multiplier; - max = maxCombatValue * multiplier; + min = minCombatValue; + max = maxCombatValue; return true; } diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 5f589af0e..adcb02585 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -77,13 +77,13 @@ class Monster final : public Creature { return mType->info.race; } float getMitigation() const override { - return mType->info.mitigation; + return mType->info.mitigation * getDefenseMultiplier(); } int32_t getArmor() const override { - return mType->info.armor; + return mType->info.armor * getDefenseMultiplier(); } int32_t getDefense() const override { - return mType->info.defense; + return mType->info.defense * getDefenseMultiplier(); } Faction_t getFaction() const override { @@ -126,6 +126,9 @@ class Monster final : public Creature { bool canSeeInvisibility() const override { return isImmune(CONDITION_INVISIBLE); } + uint16_t critChance() const { + return mType->info.critChance; + } uint32_t getManaCost() const { return mType->info.manaCost; } @@ -325,6 +328,16 @@ class Monster final : public Creature { bool isImmune(ConditionType_t conditionType) const override; bool isImmune(CombatType_t combatType) const override; + float getAttackMultiplier() const { + float multiplier = mType->getAttackMultiplier(); + return multiplier * std::pow(1.03f, getForgeStack()); + } + + float getDefenseMultiplier() const { + float multiplier = mType->getDefenseMultiplier(); + return multiplier * std::pow(1.01f, getForgeStack()); + } + private: CreatureWeakHashMap friendList; CreatureIDList targetIDList; @@ -344,7 +357,6 @@ class Monster final : public Creature { int64_t lastMeleeAttack = 0; uint32_t attackTicks = 0; - uint32_t targetTicks = 0; uint32_t targetChangeTicks = 0; uint32_t defenseTicks = 0; uint32_t yellTicks = 0; @@ -436,14 +448,4 @@ class Monster final : public Creature { void doRandomStep(Direction &nextDirection, bool &result); void onConditionStatusChange(const ConditionType_t &type); - - float getAttackMultiplier() const { - float multiplier = mType->getAttackMultiplier(); - return multiplier * std::pow(1.03f, getForgeStack()); - } - - float getDefenseMultiplier() const { - float multiplier = mType->getAttackMultiplier(); - return multiplier * std::pow(1.01f, getForgeStack()); - } }; diff --git a/src/creatures/monsters/monsters.cpp b/src/creatures/monsters/monsters.cpp index eb74178c0..ba05f5293 100644 --- a/src/creatures/monsters/monsters.cpp +++ b/src/creatures/monsters/monsters.cpp @@ -291,7 +291,7 @@ bool MonsterType::loadCallback(LuaScriptInterface* scriptInterface) { return true; } -std::shared_ptr Monsters::getMonsterType(const std::string &name) { +std::shared_ptr Monsters::getMonsterType(const std::string &name, bool silent /* = false*/) const { std::string lowerCaseName = asLowerCaseString(name); if (auto it = monsters.find(lowerCaseName); it != monsters.end() @@ -299,7 +299,9 @@ std::shared_ptr Monsters::getMonsterType(const std::string &name) { && it->first.find(lowerCaseName) != it->first.npos) { return it->second; } - g_logger().error("[Monsters::getMonsterType] - Monster with name {} not exist", lowerCaseName); + if (!silent) { + g_logger().error("[Monsters::getMonsterType] - Monster with name {} not exist", lowerCaseName); + } return nullptr; } diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp index a13792fd2..fd911c661 100644 --- a/src/creatures/monsters/monsters.hpp +++ b/src/creatures/monsters/monsters.hpp @@ -105,7 +105,6 @@ class MonsterType { BestiaryType_t bestiaryRace = BESTY_RACE_NONE; // Number (addByte) // Bosstiary - uint32_t bossStorageCooldown = 0; BosstiaryRarity_t bosstiaryRace = BosstiaryRarity_t::BOSS_INVALID; std::string bosstiaryClass; @@ -128,6 +127,7 @@ class MonsterType { int32_t changeTargetChance = 0; int32_t defense = 0; int32_t armor = 0; + uint16_t critChance = 0; int32_t strategiesTargetNearest = 0; int32_t strategiesTargetHealth = 0; int32_t strategiesTargetDamage = 0; @@ -161,7 +161,7 @@ class MonsterType { public: MonsterType() = default; explicit MonsterType(const std::string &initName) : - name(initName), typeName(initName), nameDescription(initName) {}; + name(initName), typeName(initName), nameDescription(initName), variantName("") {}; // non-copyable MonsterType(const MonsterType &) = delete; @@ -172,6 +172,7 @@ class MonsterType { std::string name; std::string typeName; std::string nameDescription; + std::string variantName; MonsterInfo info; @@ -263,7 +264,7 @@ class Monsters { monsters.clear(); } - std::shared_ptr getMonsterType(const std::string &name); + std::shared_ptr getMonsterType(const std::string &name, bool silent = false) const; std::shared_ptr getMonsterTypeByRaceId(uint16_t raceId, bool isBoss = false) const; bool tryAddMonsterType(const std::string &name, const std::shared_ptr mType); bool deserializeSpell(const std::shared_ptr spell, spellBlock_t &sb, const std::string &description = ""); diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 414e3df10..aa9bcb14c 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -12,12 +12,13 @@ #include "creatures/monsters/spawns/spawn_monster.hpp" #include "game/game.hpp" #include "creatures/monsters/monster.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" #include "game/scheduling/events_scheduler.hpp" #include "lua/creature/events.hpp" #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" #include "utils/pugicast.hpp" +#include "game/zones/zone.hpp" #include "map/spectators.hpp" static constexpr int32_t MONSTER_MINSPAWN_INTERVAL = 1000; // 1 second @@ -144,7 +145,7 @@ bool SpawnsMonster::isInZone(const Position ¢erPos, int32_t radius, const Po void SpawnMonster::startSpawnMonsterCheck() { if (checkSpawnMonsterEvent == 0) { - checkSpawnMonsterEvent = g_scheduler().addEvent(getInterval(), std::bind(&SpawnMonster::checkSpawnMonster, this), "SpawnMonster::checkSpawnMonster"); + checkSpawnMonsterEvent = g_dispatcher().scheduleEvent(getInterval(), std::bind(&SpawnMonster::checkSpawnMonster, this), "SpawnMonster::checkSpawnMonster"); } } @@ -239,7 +240,7 @@ void SpawnMonster::checkSpawnMonster() { } if (spawnedMonsterMap.size() < spawnMonsterMap.size()) { - checkSpawnMonsterEvent = g_scheduler().addEvent(getInterval(), std::bind(&SpawnMonster::checkSpawnMonster, this), "SpawnMonster::checkSpawnMonster"); + checkSpawnMonsterEvent = g_dispatcher().scheduleEvent(getInterval(), std::bind(&SpawnMonster::checkSpawnMonster, this), "SpawnMonster::checkSpawnMonster"); } } @@ -248,7 +249,7 @@ void SpawnMonster::scheduleSpawn(uint32_t spawnMonsterId, spawnBlock_t &sb, uint spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction); } else { g_game().addMagicEffect(sb.pos, CONST_ME_TELEPORT); - g_scheduler().addEvent(1400, std::bind(&SpawnMonster::scheduleSpawn, this, spawnMonsterId, sb, interval - NONBLOCKABLE_SPAWN_MONSTER_INTERVAL), "SpawnMonster::scheduleSpawn"); + g_dispatcher().scheduleEvent(1400, std::bind(&SpawnMonster::scheduleSpawn, this, spawnMonsterId, sb, interval - NONBLOCKABLE_SPAWN_MONSTER_INTERVAL), "SpawnMonster::scheduleSpawn"); } } @@ -257,7 +258,7 @@ void SpawnMonster::cleanup() { while (it != spawnedMonsterMap.end()) { uint32_t spawnMonsterId = it->first; std::shared_ptr monster = it->second; - if (monster->isRemoved()) { + if (!monster || monster->isRemoved()) { spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME(); it = spawnedMonsterMap.erase(it); } else { @@ -267,7 +268,14 @@ void SpawnMonster::cleanup() { } bool SpawnMonster::addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t scheduleInterval) { - const auto monsterType = g_monsters().getMonsterType(name); + std::string variant = ""; + for (const auto &zone : Zone::getZones(pos)) { + if (!zone->getMonsterVariant().empty()) { + variant = zone->getMonsterVariant() + "|"; + break; + } + } + const auto monsterType = g_monsters().getMonsterType(variant + name); if (!monsterType) { g_logger().error("Can not find {}", name); return false; @@ -296,9 +304,17 @@ void SpawnMonster::removeMonster(std::shared_ptr monster) { } } +void SpawnMonster::setMonsterVariant(const std::string &variant) { + for (auto &it : spawnMonsterMap) { + auto variantName = variant + it.second.monsterType->typeName; + auto variantType = g_monsters().getMonsterType(variantName, false); + it.second.monsterType = variantType ? variantType : it.second.monsterType; + } +} + void SpawnMonster::stopEvent() { if (checkSpawnMonsterEvent != 0) { - g_scheduler().stopEvent(checkSpawnMonsterEvent); + g_dispatcher().stopEvent(checkSpawnMonsterEvent); checkSpawnMonsterEvent = 0; } } diff --git a/src/creatures/monsters/spawns/spawn_monster.hpp b/src/creatures/monsters/spawns/spawn_monster.hpp index 3c361f956..749856525 100644 --- a/src/creatures/monsters/spawns/spawn_monster.hpp +++ b/src/creatures/monsters/spawns/spawn_monster.hpp @@ -47,6 +47,12 @@ class SpawnMonster { bool isInSpawnMonsterZone(const Position &pos); void cleanup(); + const Position &getCenterPos() const { + return centerPos; + } + + void setMonsterVariant(const std::string &variant); + private: // map of the spawned creatures using SpawnedMap = std::multimap>; diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index e37237626..89eabdae2 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -15,7 +15,6 @@ #include "game/game.hpp" #include "lua/callbacks/creaturecallback.hpp" #include "game/scheduling/dispatcher.hpp" -#include "game/scheduling/scheduler.hpp" #include "map/spectators.hpp" int32_t Npc::despawnRange; @@ -250,7 +249,7 @@ void Npc::onPlayerBuyItem(std::shared_ptr player, uint16_t itemId, uint8 if (std::shared_ptr tile = ignore ? player->getTile() : nullptr; tile) { double slotsNedeed = 0; if (itemType.stackable) { - slotsNedeed = inBackpacks ? std::ceil(std::ceil(static_cast(amount) / 100) / shoppingBagSlots) : std::ceil(static_cast(amount) / 100); + slotsNedeed = inBackpacks ? std::ceil(std::ceil(static_cast(amount) / itemType.stackSize) / shoppingBagSlots) : std::ceil(static_cast(amount) / itemType.stackSize); } else { slotsNedeed = inBackpacks ? std::ceil(static_cast(amount) / shoppingBagSlots) : static_cast(amount); } @@ -272,7 +271,7 @@ void Npc::onPlayerBuyItem(std::shared_ptr player, uint16_t itemId, uint8 uint32_t totalCost = buyPrice * amount; uint32_t bagsCost = 0; if (inBackpacks && itemType.stackable) { - bagsCost = shoppingBagPrice * static_cast(std::ceil(std::ceil(static_cast(amount) / 100) / shoppingBagSlots)); + bagsCost = shoppingBagPrice * static_cast(std::ceil(std::ceil(static_cast(amount) / itemType.stackSize) / shoppingBagSlots)); } else if (inBackpacks && !itemType.stackable) { bagsCost = shoppingBagPrice * static_cast(std::ceil(static_cast(amount) / shoppingBagSlots)); } @@ -350,7 +349,7 @@ void Npc::onPlayerSellAllLoot(uint32_t playerId, uint16_t itemId, bool ignore, u return; } if (hasMore) { - g_scheduler().addEvent(SCHEDULER_MINTICKS, std::bind(&Npc::onPlayerSellAllLoot, this, player->getID(), itemId, ignore, totalPrice), __FUNCTION__); + g_dispatcher().scheduleEvent(SCHEDULER_MINTICKS, std::bind(&Npc::onPlayerSellAllLoot, this, player->getID(), itemId, ignore, totalPrice), __FUNCTION__); return; } ss << "You sold all of the items from your loot pouch for "; @@ -365,7 +364,7 @@ void Npc::onPlayerSellItem(std::shared_ptr player, uint16_t itemId, uint return; } if (itemId == ITEM_GOLD_POUCH) { - g_scheduler().addEvent(SCHEDULER_MINTICKS, std::bind(&Npc::onPlayerSellAllLoot, this, player->getID(), itemId, ignore, 0), __FUNCTION__); + g_dispatcher().scheduleEvent(SCHEDULER_MINTICKS, std::bind(&Npc::onPlayerSellAllLoot, this, player->getID(), itemId, ignore, 0), __FUNCTION__); return; } diff --git a/src/creatures/npcs/spawns/spawn_npc.cpp b/src/creatures/npcs/spawns/spawn_npc.cpp index 878c1830b..68fa0c240 100644 --- a/src/creatures/npcs/spawns/spawn_npc.cpp +++ b/src/creatures/npcs/spawns/spawn_npc.cpp @@ -12,7 +12,7 @@ #include "creatures/npcs/spawns/spawn_npc.hpp" #include "creatures/npcs/npc.hpp" #include "game/game.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" #include "lua/creature/events.hpp" #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" @@ -131,13 +131,14 @@ bool SpawnsNpc::isInZone(const Position ¢erPos, int32_t radius, const Positi void SpawnNpc::startSpawnNpcCheck() { if (checkSpawnNpcEvent == 0) { - checkSpawnNpcEvent = g_scheduler().addEvent(getInterval(), std::bind(&SpawnNpc::checkSpawnNpc, this), "SpawnNpc::checkSpawnNpc"); + checkSpawnNpcEvent = g_dispatcher().scheduleEvent(getInterval(), std::bind(&SpawnNpc::checkSpawnNpc, this), "SpawnNpc::checkSpawnNpc"); } } SpawnNpc::~SpawnNpc() { for (const auto &it : spawnedNpcMap) { - it.second->setSpawnNpc(nullptr); + auto npc = it.second; + npc->setSpawnNpc(nullptr); } } @@ -216,7 +217,7 @@ void SpawnNpc::checkSpawnNpc() { } if (spawnedNpcMap.size() < spawnNpcMap.size()) { - checkSpawnNpcEvent = g_scheduler().addEvent(getInterval(), std::bind(&SpawnNpc::checkSpawnNpc, this), __FUNCTION__); + checkSpawnNpcEvent = g_dispatcher().scheduleEvent(getInterval(), std::bind(&SpawnNpc::checkSpawnNpc, this), __FUNCTION__); } } @@ -225,7 +226,7 @@ void SpawnNpc::scheduleSpawnNpc(uint32_t spawnId, spawnBlockNpc_t &sb, uint16_t spawnNpc(spawnId, sb.npcType, sb.pos, sb.direction); } else { g_game().addMagicEffect(sb.pos, CONST_ME_TELEPORT); - g_scheduler().addEvent(1400, std::bind(&SpawnNpc::scheduleSpawnNpc, this, spawnId, sb, interval - NONBLOCKABLE_SPAWN_NPC_INTERVAL), __FUNCTION__); + g_dispatcher().scheduleEvent(1400, std::bind(&SpawnNpc::scheduleSpawnNpc, this, spawnId, sb, interval - NONBLOCKABLE_SPAWN_NPC_INTERVAL), __FUNCTION__); } } @@ -275,7 +276,7 @@ void SpawnNpc::removeNpc(std::shared_ptr npc) { void SpawnNpc::stopEvent() { if (checkSpawnNpcEvent != 0) { - g_scheduler().stopEvent(checkSpawnNpcEvent); + g_dispatcher().stopEvent(checkSpawnNpcEvent); checkSpawnNpcEvent = 0; } } diff --git a/src/creatures/players/grouping/party.cpp b/src/creatures/players/grouping/party.cpp index 57cfab4da..b0a67a8ea 100644 --- a/src/creatures/players/grouping/party.cpp +++ b/src/creatures/players/grouping/party.cpp @@ -139,11 +139,6 @@ bool Party::leaveParty(std::shared_ptr player) { g_game().updatePlayerHelpers(member); } - leader->sendCreatureSkull(player); - player->sendCreatureSkull(player); - player->sendPlayerPartyIcons(leader); - leader->sendPartyCreatureUpdate(player); - player->sendTextMessage(MESSAGE_PARTY_MANAGEMENT, "You have left the party."); updateSharedExperience(); @@ -158,6 +153,11 @@ bool Party::leaveParty(std::shared_ptr player) { disband(); } + player->sendCreatureSkull(player); + leader->sendCreatureSkull(player); + player->sendPlayerPartyIcons(leader); + leader->sendPartyCreatureUpdate(player); + return true; } @@ -233,11 +233,13 @@ bool Party::joinParty(const std::shared_ptr &player) { for (auto member : getMembers()) { member->sendCreatureSkull(player); + member->sendPlayerPartyIcons(player); player->sendPlayerPartyIcons(member); } - player->sendCreatureSkull(player); leader->sendCreatureSkull(player); + player->sendCreatureSkull(player); + leader->sendPlayerPartyIcons(player); player->sendPlayerPartyIcons(leader); memberList.push_back(player); diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 0ec4bedf8..1f7f9f088 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -18,8 +18,8 @@ #include "creatures/players/storages/storages.hpp" #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" -#include "game/scheduling/scheduler.hpp" #include "game/scheduling/task.hpp" +#include "game/scheduling/save_manager.hpp" #include "grouping/familiars.hpp" #include "lua/creature/creatureevent.hpp" #include "lua/creature/events.hpp" @@ -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 @@ -790,11 +790,31 @@ void Player::closeContainer(uint8_t cid) { OpenContainer openContainer = it->second; std::shared_ptr container = openContainer.container; + if (container && container->isAnyKindOfRewardChest() && !hasOtherRewardContainerOpen(container)) { + removeEmptyRewards(); + } openContainers.erase(it); if (container && container->getID() == ITEM_BROWSEFIELD) { } } +void Player::removeEmptyRewards() { + std::erase_if(rewardMap, [this](const auto &rewardBag) { + auto [id, reward] = rewardBag; + if (reward->empty()) { + getRewardChest()->removeItem(reward); + return true; + } + return false; + }); +} + +bool Player::hasOtherRewardContainerOpen(const std::shared_ptr container) const { + return std::ranges::any_of(openContainers.begin(), openContainers.end(), [container](const auto &containerPair) { + return containerPair.second.container != container && containerPair.second.container->isAnyKindOfRewardContainer(); + }); +} + void Player::setContainerIndex(uint8_t cid, uint16_t index) { auto it = openContainers.find(cid); if (it == openContainers.end()) { @@ -1277,6 +1297,7 @@ void Player::sendPing() { } if (noPongTime >= 60000 && canLogout() && g_creatureEvents().playerLogout(static_self_cast())) { + g_logger().info("Player {} has been kicked due to ping timeout. (has client: {})", getName(), client != nullptr); if (client) { client->logout(true, true); } else { @@ -1596,6 +1617,11 @@ void Player::onCreatureAppear(std::shared_ptr creature, bool isLogin) } } + // Refresh bosstiary tracker onLogin + refreshCyclopediaMonsterTracker(true); + // Refresh bestiary tracker onLogin + refreshCyclopediaMonsterTracker(false); + for (const auto &condition : storedConditionList) { addCondition(condition); } @@ -1632,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); @@ -1717,20 +1743,20 @@ void Player::onAttackedCreatureChangeZone(ZoneType_t zone) { void Player::onRemoveCreature(std::shared_ptr creature, bool isLogout) { Creature::onRemoveCreature(creature, isLogout); - if (creature == getPlayer()) { + if (auto player = getPlayer(); player == creature) { if (isLogout) { if (party) { - party->leaveParty(static_self_cast()); + party->leaveParty(player); } if (guild) { - guild->removeMember(static_self_cast()); + guild->removeMember(player); } - g_game().removePlayerUniqueLogin(static_self_cast()); + g_game().removePlayerUniqueLogin(player); loginPosition = getPosition(); lastLogout = time(nullptr); g_logger().info("{} has logged out", getName()); - g_chat().removeUserFromAllChannels(getPlayer()); + g_chat().removeUserFromAllChannels(player); clearPartyInvitations(); IOLoginData::updateOnlineStatus(guid, false); } @@ -1740,16 +1766,12 @@ void Player::onRemoveCreature(std::shared_ptr creature, bool isLogout) } if (tradePartner) { - g_game().internalCloseTrade(static_self_cast()); + g_game().internalCloseTrade(player); } closeShopWindow(); - for (uint32_t tries = 0; tries < 3; ++tries) { - if (IOLoginData::savePlayer(static_self_cast())) { - break; - } - } + g_saveManager().savePlayer(player); } if (creature == shopOwner) { @@ -1814,7 +1836,7 @@ void Player::onCreatureMove(std::shared_ptr creature, std::shared_ptr< auto followCreature = getFollowCreature(); if (hasFollowPath && (creature == followCreature || (creature.get() == this && followCreature))) { isUpdatingPath = false; - g_dispatcher().addTask(std::bind(&Game::updateCreatureWalk, &g_game(), getID()), "Game::updateCreatureWalk"); + g_dispatcher().addEvent(std::bind(&Game::updateCreatureWalk, &g_game(), getID()), "Game::updateCreatureWalk"); } if (creature != getPlayer()) { @@ -1967,7 +1989,7 @@ void Player::checkTradeState(std::shared_ptr item) { void Player::setNextWalkActionTask(std::shared_ptr task) { if (walkTaskEvent != 0) { - g_scheduler().stopEvent(walkTaskEvent); + g_dispatcher().stopEvent(walkTaskEvent); walkTaskEvent = 0; } @@ -1976,19 +1998,19 @@ void Player::setNextWalkActionTask(std::shared_ptr task) { void Player::setNextWalkTask(std::shared_ptr task) { if (nextStepEvent != 0) { - g_scheduler().stopEvent(nextStepEvent); + g_dispatcher().stopEvent(nextStepEvent); nextStepEvent = 0; } if (task) { - nextStepEvent = g_scheduler().addEvent(task); + nextStepEvent = g_dispatcher().scheduleEvent(task); resetIdleTime(); } } void Player::setNextActionTask(std::shared_ptr task, bool resetIdleTime /*= true */) { if (actionTaskEvent != 0) { - g_scheduler().stopEvent(actionTaskEvent); + g_dispatcher().stopEvent(actionTaskEvent); actionTaskEvent = 0; } @@ -1997,7 +2019,7 @@ void Player::setNextActionTask(std::shared_ptr task, bool resetIdleTime /* } if (task) { - actionTaskEvent = g_scheduler().addEvent(task); + actionTaskEvent = g_dispatcher().scheduleEvent(task); if (resetIdleTime) { this->resetIdleTime(); } @@ -2006,25 +2028,25 @@ void Player::setNextActionTask(std::shared_ptr task, bool resetIdleTime /* void Player::setNextActionPushTask(std::shared_ptr task) { if (actionTaskEventPush != 0) { - g_scheduler().stopEvent(actionTaskEventPush); + g_dispatcher().stopEvent(actionTaskEventPush); actionTaskEventPush = 0; } if (task) { - actionTaskEventPush = g_scheduler().addEvent(task); + actionTaskEventPush = g_dispatcher().scheduleEvent(task); } } void Player::setNextPotionActionTask(std::shared_ptr task) { if (actionPotionTaskEvent != 0) { - g_scheduler().stopEvent(actionPotionTaskEvent); + g_dispatcher().stopEvent(actionPotionTaskEvent); actionPotionTaskEvent = 0; } cancelPush(); if (task) { - actionPotionTaskEvent = g_scheduler().addEvent(task); + actionPotionTaskEvent = g_dispatcher().scheduleEvent(task); // resetIdleTime(); } } @@ -2039,7 +2061,7 @@ uint32_t Player::getNextPotionActionTime() const { void Player::cancelPush() { if (actionTaskEventPush != 0) { - g_scheduler().stopEvent(actionTaskEventPush); + g_dispatcher().stopEvent(actionTaskEventPush); actionTaskEventPush = 0; inEventMovePush = false; } @@ -4049,7 +4071,9 @@ void Player::postRemoveNotification(std::shared_ptr thing, std::shared_pt assert(i ? i->getContainer() != nullptr : true); if (i) { - requireListUpdate = i->getContainer()->getHoldingPlayer() != getPlayer(); + if (auto container = i->getContainer()) { + requireListUpdate = container->getHoldingPlayer() != getPlayer(); + } } else { requireListUpdate = newParent != getPlayer(); } @@ -4109,7 +4133,7 @@ bool Player::updateSaleShopList(std::shared_ptr item) { return true; } - g_dispatcher().addTask(std::bind(&Game::updatePlayerSaleItems, &g_game(), getID()), "updatePlayerSaleItems"); + g_dispatcher().addEvent(std::bind(&Game::updatePlayerSaleItems, &g_game(), getID()), "updatePlayerSaleItems"); scheduledSaleUpdate = true; return true; } @@ -4180,7 +4204,7 @@ bool Player::setAttackedCreature(std::shared_ptr creature) { } if (creature) { - g_dispatcher().addTask(std::bind(&Game::checkCreatureAttack, &g_game(), getID()), "Game::checkCreatureAttack"); + g_dispatcher().addEvent(std::bind(&Game::checkCreatureAttack, &g_game(), getID()), "Game::checkCreatureAttack"); } return true; } @@ -4244,7 +4268,7 @@ void Player::doAttacking(uint32_t) { if (!classicSpeed) { setNextActionTask(task, false); } else { - g_scheduler().addEvent(task); + g_dispatcher().scheduleEvent(task); } if (result) { @@ -4307,7 +4331,7 @@ void Player::onWalkComplete() { } if (walkTask) { - walkTaskEvent = g_scheduler().addEvent(walkTask); + walkTaskEvent = g_dispatcher().scheduleEvent(walkTask); walkTask = nullptr; } } @@ -4855,8 +4879,8 @@ bool Player::canFamiliar(uint16_t lookType) const { } for (const FamiliarEntry &familiarEntry : familiars) { - if (familiarEntry.lookType != lookType) { - continue; + if (familiarEntry.lookType == lookType) { + return true; } } return false; @@ -7507,7 +7531,7 @@ bool Player::canAutoWalk(const Position &toPosition, const std::function // Check if can walk to the toPosition and send event to use function std::forward_list listDir; if (getPathTo(toPosition, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, &g_game(), getID(), listDir), __FUNCTION__); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, &g_game(), getID(), listDir), __FUNCTION__); std::shared_ptr task = createPlayerTask(delay, function, __FUNCTION__); setNextWalkActionTask(task); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 19614aa3d..791f69614 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -94,6 +94,23 @@ static constexpr int32_t PLAYER_SOUND_HEALTH_CHANGE = 10; class Player final : public Creature, public Cylinder, public Bankable { public: + class PlayerLock { + public: + explicit PlayerLock(const std::shared_ptr &p) : + player(p) { + player->mutex.lock(); + } + + PlayerLock(const PlayerLock &) = delete; + + ~PlayerLock() { + player->mutex.unlock(); + } + + private: + const std::shared_ptr &player; + }; + explicit Player(ProtocolGame_ptr p); ~Player(); @@ -473,7 +490,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void addStorageValueByName(const std::string &storageName, const int32_t value, const bool isLogin = false); std::shared_ptr kv() const { - return g_kv().scoped("player")->scoped(fmt::format("{}", getID())); + return g_kv().scoped("player")->scoped(fmt::format("{}", getGUID())); } void genReservedStorageRange(); @@ -1588,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) { @@ -2504,6 +2521,9 @@ class Player final : public Creature, public Cylinder, public Bankable { std::shared_ptr getLootPouch(); private: + friend class PlayerLock; + std::mutex mutex; + static uint32_t playerFirstID; static uint32_t playerLastID; @@ -2862,6 +2882,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void clearCooldowns(); friend class Game; + friend class SaveManager; friend class Npc; friend class PlayerFunctions; friend class NetworkMessageFunctions; @@ -2894,4 +2915,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void updateDamageReductionFromItemImbuement(std::array &combatReductionMap, std::shared_ptr item, uint16_t combatTypeIndex) const; void updateDamageReductionFromItemAbility(std::array &combatReductionMap, std::shared_ptr item, uint16_t combatTypeIndex) const; double_t calculateDamageReduction(double_t currentTotal, int16_t resistance) const; + + void removeEmptyRewards(); + bool hasOtherRewardContainerOpen(const std::shared_ptr container) const; }; diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index dc7d0ee77..ad65a95ec 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -849,7 +849,7 @@ void PlayerWheel::saveSlotPointsOnPressSaveButton(NetworkMessage &msg) { initializePlayerData(); registerPlayerBonusData(); - g_logger().debug("Player: {} is saved the all slots info in: {} seconds", m_player.getName(), bm_saveSlot.duration()); + g_logger().debug("Player: {} is saved the all slots info in: {} milliseconds", m_player.getName(), bm_saveSlot.duration()); } /* @@ -957,7 +957,7 @@ bool PlayerWheel::canOpenWheel() const { return false; } - if (m_player.getVocation()->getId() <= 4 && m_player.getStorageValue(STORAGEVALUE_PROMOTION) == -1) { + if (!m_player.isPromoted()) { return false; } @@ -1146,6 +1146,14 @@ void PlayerWheel::registerPlayerBonusData() { setSpellInstant("Divine Empowerment", false); } + if (m_playerBonusData.stages.divineGrenade > 0) { + for (int i = 0; i < m_playerBonusData.stages.divineGrenade; ++i) { + setSpellInstant("Divine Grenade", true); + } + } else { + setSpellInstant("Divine Grenade", false); + } + if (m_playerBonusData.stages.drainBody > 0) { for (int i = 0; i < m_playerBonusData.stages.drainBody; ++i) { setSpellInstant("Drain Body", true); @@ -1328,6 +1336,9 @@ void PlayerWheel::printPlayerWheelMethodsBonusData(const PlayerWheelMethodsBonus if (bonusData.stages.divineEmpowerment > 0) { g_logger().debug(" divineEmpowerment: {}", bonusData.stages.divineEmpowerment); } + if (bonusData.stages.divineGrenade > 0) { + g_logger().debug(" divineGrenade: {}", bonusData.stages.divineGrenade); + } if (bonusData.stages.blessingOfTheGrove > 0) { g_logger().debug(" blessingOfTheGrove: {}", bonusData.stages.blessingOfTheGrove); } @@ -1399,9 +1410,7 @@ void PlayerWheel::loadDedicationAndConvictionPerks() { } void PlayerWheel::addSpellToVector(const std::string &spellName) { - if (std::ranges::find(m_playerBonusData.spells.begin(), m_playerBonusData.spells.end(), spellName) == m_playerBonusData.spells.end()) { - m_playerBonusData.spells.emplace_back(spellName); - } + m_playerBonusData.spells.emplace_back(spellName); } void PlayerWheel::loadRevelationPerks() { @@ -1435,6 +1444,7 @@ void PlayerWheel::loadRevelationPerks() { addSpellToVector("Great Death Beam"); } } else if (vocationEnum == Vocation_t::VOCATION_PALADIN_CIP) { + m_playerBonusData.stages.divineGrenade = redStageValue; for (uint8_t i = 0; i < redStageValue; ++i) { addSpellToVector("Divine Grenade"); } @@ -1791,7 +1801,7 @@ bool PlayerWheel::checkCombatMastery() { bool PlayerWheel::checkDivineEmpowerment() { bool updateClient = false; - setOnThinkTimer(WheelOnThink_t::DIVINE_EMPOWERMENT, OTSYS_TIME() + 2000); + setOnThinkTimer(WheelOnThink_t::DIVINE_EMPOWERMENT, OTSYS_TIME() + 1000); const auto tile = m_player.getTile(); if (!tile) { @@ -1821,16 +1831,34 @@ bool PlayerWheel::checkDivineEmpowerment() { } else if (stage >= 1) { damageBonus = 8; } - - if (damageBonus != getMajorStat(WheelMajor_t::DAMAGE)) { - setMajorStat(WheelMajor_t::DAMAGE, damageBonus); - updateClient = true; - } + } + if (damageBonus != getMajorStat(WheelMajor_t::DAMAGE)) { + setMajorStat(WheelMajor_t::DAMAGE, damageBonus); + updateClient = true; } return updateClient; } +int32_t PlayerWheel::checkDivineGrenade(std::shared_ptr target) const { + if (!target || target == m_player.getPlayer()) { + return 0; + } + + int32_t damageBonus = 0; + uint8_t stage = getStage(WheelStage_t::DIVINE_GRENADE); + + if (stage >= 3) { + damageBonus = 100; + } else if (stage >= 2) { + damageBonus = 60; + } else if (stage >= 1) { + damageBonus = 30; + } + + return damageBonus; +} + void PlayerWheel::checkGiftOfLife() { // Healing CombatDamage giftDamage; @@ -2055,7 +2083,9 @@ void PlayerWheel::onThink(bool force /* = false*/) { m_player.sendStats(); g_game().reloadCreature(m_player.getPlayer()); } - return; + if (!force) { + return; + } } // Battle Instinct if (getInstant("Battle Instinct") && (force || getOnThinkTimer(WheelOnThink_t::BATTLE_INSTINCT) < OTSYS_TIME()) && checkBattleInstinct()) { @@ -2297,6 +2327,12 @@ void PlayerWheel::setSpellInstant(const std::string &name, bool value) { } else { setStage(WheelStage_t::DIVINE_EMPOWERMENT, 0); } + } else if (name == "Divine Grenade") { + if (value) { + setStage(WheelStage_t::DIVINE_GRENADE, getStage(WheelStage_t::DIVINE_GRENADE) + 1); + } else { + setStage(WheelStage_t::DIVINE_GRENADE, 0); + } } else if (name == "Twin Burst") { if (value) { setStage(WheelStage_t::TWIN_BURST, getStage(WheelStage_t::TWIN_BURST) + 1); @@ -2380,6 +2416,8 @@ uint8_t PlayerWheel::getStage(const std::string name) const { return PlayerWheel::getStage(WheelStage_t::DRAIN_BODY); } else if (name == "Divine Empowerment") { return PlayerWheel::getStage(WheelStage_t::DIVINE_EMPOWERMENT); + } else if (name == "Divine Grenade") { + return PlayerWheel::getStage(WheelStage_t::DIVINE_GRENADE); } else if (name == "Twin Burst") { return PlayerWheel::getStage(WheelStage_t::TWIN_BURST); } else if (name == "Executioner's Throw") { @@ -2502,6 +2540,8 @@ bool PlayerWheel::getInstant(const std::string name) const { return PlayerWheel::getStage(WheelStage_t::DRAIN_BODY); } else if (name == "Divine Empowerment") { return PlayerWheel::getStage(WheelStage_t::DIVINE_EMPOWERMENT); + } else if (name == "Divine Grenade") { + return PlayerWheel::getStage(WheelStage_t::DIVINE_GRENADE); } else if (name == "Twin Burst") { return PlayerWheel::getStage(WheelStage_t::TWIN_BURST); } else if (name == "Executioner's Throw") { diff --git a/src/creatures/players/wheel/wheel_definitions.hpp b/src/creatures/players/wheel/wheel_definitions.hpp index 6bcf9d508..91666a8f1 100644 --- a/src/creatures/players/wheel/wheel_definitions.hpp +++ b/src/creatures/players/wheel/wheel_definitions.hpp @@ -92,8 +92,9 @@ enum class WheelStage_t : uint8_t { AVATAR_OF_NATURE = 9, AVATAR_OF_STEEL = 10, AVATAR_OF_STORM = 11, + DIVINE_GRENADE = 12, - TOTAL_COUNT = 12 + TOTAL_COUNT = 13 }; enum class WheelOnThink_t : uint8_t { @@ -226,6 +227,7 @@ struct PlayerWheelMethodsBonusData { int combatMastery = 0; // Knight int giftOfLife = 0; // Knight/Paladin/Druid/Sorcerer int divineEmpowerment = 0; // Paladin + int divineGrenade = 0; // Paladin int blessingOfTheGrove = 0; // Druid int drainBody = 0; // Sorcerer int beamMastery = 0; // Sorcerer diff --git a/src/database/databasetasks.cpp b/src/database/databasetasks.cpp index b69be96fd..43370593b 100644 --- a/src/database/databasetasks.cpp +++ b/src/database/databasetasks.cpp @@ -26,7 +26,7 @@ void DatabaseTasks::execute(const std::string &query, std::function bankable) : m_bankable(bankable) { @@ -25,14 +26,14 @@ Bank::~Bank() { } std::shared_ptr player = bankable->getPlayer(); if (player && !player->isOnline()) { - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); return; } if (bankable->isGuild()) { const auto guild = static_self_cast(bankable); if (guild && !guild->isOnline()) { - IOGuild::saveGuild(guild); + g_saveManager().saveGuild(guild); } } } diff --git a/src/game/game.cpp b/src/game/game.cpp index 9f853ab6b..b3706b7bf 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -27,7 +27,7 @@ #include "creatures/monsters/monster.hpp" #include "lua/creature/movement.hpp" #include "game/scheduling/dispatcher.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/save_manager.hpp" #include "server/server.hpp" #include "creatures/combat/spells.hpp" #include "lua/creature/talkaction.hpp" @@ -286,18 +286,16 @@ void Game::start(ServiceManager* manager) { int minutes = tms->tm_min; lightHour = (minutes * LIGHT_DAY_LENGTH) / 60; - g_scheduler().addEvent(EVENT_LIGHTINTERVAL_MS, std::bind(&Game::checkLight, this), "Game::checkLight"); - g_scheduler().addEvent(EVENT_CREATURE_THINK_INTERVAL, std::bind(&Game::checkCreatures, this, 0), "Game::checkCreatures"); - g_scheduler().addEvent(EVENT_IMBUEMENT_INTERVAL, std::bind(&Game::checkImbuements, this), "Game::checkImbuements"); - g_scheduler().addEvent(EVENT_MS, std::bind_front(&Game::updateForgeableMonsters, this), "Game::updateForgeableMonsters"); - g_scheduler().addEvent(EVENT_MS + 1000, std::bind_front(&Game::createFiendishMonsters, this), "Game::createFiendishMonsters"); - g_scheduler().addEvent(EVENT_MS + 1000, std::bind_front(&Game::createInfluencedMonsters, this), "Game::createInfluencedMonsters"); + g_dispatcher().scheduleEvent(EVENT_MS + 1000, std::bind_front(&Game::createFiendishMonsters, this), "Game::createFiendishMonsters"); + g_dispatcher().scheduleEvent(EVENT_MS + 1000, std::bind_front(&Game::createInfluencedMonsters, this), "Game::createInfluencedMonsters"); - static const std::function &LUA_GC = [] { - g_scheduler().addEvent(EVENT_LUA_GARBAGE_COLLECTION, LUA_GC, "Calling GC"); - g_luaEnvironment().collectGarbage(); - }; - LUA_GC(); + g_dispatcher().cycleEvent(EVENT_MS, std::bind_front(&Game::updateForgeableMonsters, this), "Game::updateForgeableMonsters"); + g_dispatcher().cycleEvent(EVENT_LIGHTINTERVAL_MS, std::bind(&Game::checkLight, this), "Game::checkLight"); + g_dispatcher().cycleEvent(EVENT_CHECK_CREATURE_INTERVAL, std::bind(&Game::checkCreatures, this), "Game::checkCreatures"); + g_dispatcher().cycleEvent(EVENT_IMBUEMENT_INTERVAL, std::bind(&Game::checkImbuements, this), "Game::checkImbuements"); + g_dispatcher().cycleEvent( + EVENT_LUA_GARBAGE_COLLECTION, [this] { g_luaEnvironment().collectGarbage(); }, "Calling GC" + ); } GameState_t Game::getGameState() const { @@ -361,9 +359,9 @@ void Game::setGameState(GameState_t newState) { } saveMotdNum(); - saveGameState(); + g_saveManager().saveAll(); - g_dispatcher().addTask(std::bind(&Game::shutdown, this), "Game::shutdown"); + g_dispatcher().addEvent(std::bind(&Game::shutdown, this), "Game::shutdown"); break; } @@ -380,7 +378,7 @@ void Game::setGameState(GameState_t newState) { } } - saveGameState(); + g_saveManager().saveAll(); break; } @@ -389,31 +387,6 @@ void Game::setGameState(GameState_t newState) { } } -void Game::saveGameState() { - if (gameState == GAME_STATE_NORMAL) { - setGameState(GAME_STATE_MAINTAIN); - } - - g_logger().info("Saving server..."); - - for (const auto &it : players) { - it.second->loginPosition = it.second->getPosition(); - IOLoginData::savePlayer(it.second); - } - - for (const auto &it : guilds) { - IOGuild::saveGuild(it.second); - } - - Map::save(); - - g_kv().saveAll(); - - if (gameState == GAME_STATE_MAINTAIN) { - setGameState(GAME_STATE_NORMAL); - } -} - bool Game::loadItemsPrice() { itemsSaleCount = 0; std::ostringstream query, marketQuery; @@ -446,7 +419,7 @@ bool Game::loadItemsPrice() { void Game::loadMainMap(const std::string &filename) { Monster::despawnRange = g_configManager().getNumber(DEFAULT_DESPAWNRANGE); Monster::despawnRadius = g_configManager().getNumber(DEFAULT_DESPAWNRADIUS); - map.loadMap(g_configManager().getString(DATA_DIRECTORY) + "/world/" + filename + ".otbm", true, true, true, true); + map.loadMap(g_configManager().getString(DATA_DIRECTORY) + "/world/" + filename + ".otbm", true, true, true, true, true); } void Game::loadCustomMaps(const std::filesystem::path &customMapPath) { @@ -487,7 +460,7 @@ void Game::loadCustomMaps(const std::filesystem::path &customMapPath) { continue; } - map.loadMapCustom(filename, true, true, true, customMapIndex); + map.loadMapCustom(filename, true, true, true, true, customMapIndex); customMapIndex++; } @@ -497,7 +470,7 @@ void Game::loadCustomMaps(const std::filesystem::path &customMapPath) { } void Game::loadMap(const std::string &path, const Position &pos) { - map.loadMap(path, false, false, false, false, pos); + map.loadMap(path, false, false, false, false, false, pos); } std::shared_ptr Game::internalGetCylinder(std::shared_ptr player, const Position &pos) { @@ -727,7 +700,7 @@ std::shared_ptr Game::getPlayerByID(uint32_t id, bool loadTmp /* = false if (!loadTmp) { return nullptr; } - std::shared_ptr tmpPlayer(nullptr); + std::shared_ptr tmpPlayer = std::make_shared(nullptr); if (!IOLoginData::loadPlayerById(tmpPlayer, id)) { return nullptr; } @@ -795,7 +768,7 @@ std::shared_ptr Game::getPlayerByName(const std::string &s, bool loadTmp return it->second.lock(); } -std::shared_ptr Game::getPlayerByGUID(const uint32_t &guid) { +std::shared_ptr Game::getPlayerByGUID(const uint32_t &guid, bool loadTmp /* = false */) { if (guid == 0) { return nullptr; } @@ -804,7 +777,29 @@ std::shared_ptr Game::getPlayerByGUID(const uint32_t &guid) { return it.second; } } - return nullptr; + if (!loadTmp) { + return nullptr; + } + std::shared_ptr tmpPlayer = std::make_shared(nullptr); + if (!IOLoginData::loadPlayerById(tmpPlayer, guid)) { + return nullptr; + } + return tmpPlayer; +} + +std::string Game::getPlayerNameByGUID(const uint32_t &guid) { + if (guid == 0) { + return ""; + } + if (m_playerNameCache.contains(guid)) { + return m_playerNameCache.at(guid); + } + auto player = getPlayerByGUID(guid, true); + auto name = player ? player->getName() : ""; + if (!name.empty()) { + m_playerNameCache[guid] = name; + } + return name; } ReturnValue Game::getPlayerByNameWildcard(const std::string &s, std::shared_ptr &player) { @@ -846,7 +841,11 @@ bool Game::internalPlaceCreature(std::shared_ptr creature, const Posit if (creature->getParent() != nullptr) { return false; } - auto toZones = Zone::getZones(pos); + const auto &tile = map.getTile(pos); + if (!tile) { + return false; + } + auto toZones = tile->getZones(); if (auto ret = beforeCreatureZoneChange(creature, {}, toZones); ret != RETURNVALUE_NOERROR) { return false; } @@ -884,44 +883,52 @@ bool Game::placeCreature(std::shared_ptr creature, const Position &pos addCreatureCheck(creature); } - creature->getParent()->postAddNotification(creature, nullptr, 0); + auto parent = creature->getParent(); + if (parent) { + parent->postAddNotification(creature, nullptr, 0); + } creature->onPlacedCreature(); return true; } bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = true*/) { - if (creature->isRemoved()) { + if (!creature || creature->isRemoved()) { return false; } std::shared_ptr tile = creature->getTile(); + if (!tile) { + g_logger().error("[{}] tile on position '{}' for creature '{}' not exist", __FUNCTION__, creature->getPosition().toString(), creature->getName()); + } + auto fromZones = creature->getZones(); - std::vector oldStackPosVector; - - auto spectators = Spectators().find(tile->getPosition(), true); - auto playersSpectators = spectators.filter(); + if (tile) { + std::vector oldStackPosVector; + auto spectators = Spectators().find(tile->getPosition(), true); + auto playersSpectators = spectators.filter(); - for (const auto &spectator : playersSpectators) { - if (const auto &player = spectator->getPlayer()) { - oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); + for (const auto &spectator : playersSpectators) { + if (const auto &player = spectator->getPlayer()) { + oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); + } } - } - tile->removeCreature(creature); + tile->removeCreature(creature); - const Position &tilePosition = tile->getPosition(); + const Position &tilePosition = tile->getPosition(); - // Send to client - size_t i = 0; - for (const auto &spectator : playersSpectators) { - if (const auto &player = spectator->getPlayer()) { - player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); + // Send to client + size_t i = 0; + for (const auto &spectator : playersSpectators) { + if (const auto &player = spectator->getPlayer()) { + player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); + } } - } - // event method - for (auto spectator : spectators) { - spectator->onRemoveCreature(creature, isLogout); + // event method + for (auto spectator : spectators) { + spectator->onRemoveCreature(creature, isLogout); + } } if (creature->getMaster() && !creature->getMaster()->isRemoved()) { @@ -929,14 +936,14 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = } creature->getParent()->postRemoveNotification(creature, nullptr, 0); - afterCreatureZoneChange(creature, creature->getZones(), {}); + afterCreatureZoneChange(creature, fromZones, {}); creature->removeList(); creature->setRemoved(); removeCreatureCheck(creature); - for (const auto &summon : creature->m_summons) { + for (auto summon : creature->getSummons()) { summon->setSkillLoss(false); removeCreature(summon); } @@ -1137,7 +1144,7 @@ void Game::playerMoveCreature(std::shared_ptr player, std::shared_ptr listDir; if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(600, std::bind(&Game::playerMoveCreatureByID, this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()), "Game::playerMoveCreatureByID"); @@ -1434,7 +1441,7 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo // need to walk to the item first before using it std::forward_list listDir; if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(), fromPos, itemId, fromStackPos, toPos, count), "Game::playerMoveItemByPlayerID"); player->setNextWalkActionTask(task); @@ -1491,7 +1498,7 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo std::forward_list listDir; if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, player->getID(), itemPos, itemId, itemStackPos, toPos, count), "Game::playerMoveItemByPlayerID"); player->setNextWalkActionTask(task); @@ -1502,7 +1509,8 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo } } - if ((Position::getDistanceX(playerPos, mapToPos) > item->getThrowRange()) || (Position::getDistanceY(playerPos, mapToPos) > item->getThrowRange()) || (Position::getDistanceZ(mapFromPos, mapToPos) * 4 > item->getThrowRange())) { + auto throwRange = item->getThrowRange(); + if ((Position::getDistanceX(playerPos, mapToPos) > throwRange) || (Position::getDistanceY(playerPos, mapToPos) > throwRange) || (Position::getDistanceZ(mapFromPos, mapToPos) * 4 > throwRange)) { player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); return; } @@ -2049,6 +2057,106 @@ ReturnValue Game::internalRemoveItem(std::shared_ptr item, int32_t count / return RETURNVALUE_NOERROR; } +std::tuple Game::addItemBatch(const std::shared_ptr &toCylinder, const std::vector> &items, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + const auto player = toCylinder->getPlayer(); + bool dropping = false; + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; + uint32_t totalAdded = 0; + uint32_t containersCreated = 0; + + auto setupDestination = [&]() -> std::shared_ptr { + if (autoContainerId == 0) { + return toCylinder; + } + auto autoContainer = Item::CreateItem(autoContainerId); + if (!autoContainer) { + g_logger().error("[{}] Failed to create auto container", __FUNCTION__); + return toCylinder; + } + if (internalAddItem(toCylinder, autoContainer, CONST_SLOT_WHEREEVER, flags) != RETURNVALUE_NOERROR) { + if (internalAddItem(toCylinder->getTile(), autoContainer, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + g_logger().error("[{}] Failed to add auto container", __FUNCTION__); + return toCylinder; + } + } + auto container = autoContainer->getContainer(); + if (!container) { + g_logger().error("[{}] Failed to get auto container", __FUNCTION__); + return toCylinder; + } + containersCreated++; + return container; + }; + auto destination = setupDestination(); + + for (const auto &item : items) { + auto container = destination->getContainer(); + if (container && container->getFreeSlots() == 0) { + destination = setupDestination(); + } + if (!dropping) { + uint32_t remainderCount = 0; + ret = internalAddItem(destination, item, CONST_SLOT_WHEREEVER, flags, false, remainderCount); + if (remainderCount != 0) { + std::shared_ptr remainderItem = Item::CreateItem(item->getID(), remainderCount); + ReturnValue remaindRet = internalAddItem(destination->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (player && remaindRet != RETURNVALUE_NOERROR) { + player->sendLootStats(item, static_cast(item->getItemCount())); + } + } + } + + if (dropping || ret != RETURNVALUE_NOERROR && dropOnMap) { + dropping = true; + ret = internalAddItem(destination->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + if (player && ret == RETURNVALUE_NOERROR) { + player->sendForgingData(); + } + if (ret != RETURNVALUE_NOERROR) { + break; + } else { + totalAdded += item->getItemCount(); + } + } + + return std::make_tuple(ret, totalAdded, containersCreated); +} + +std::tuple Game::createItemBatch(const std::shared_ptr &toCylinder, const std::vector> &itemCounts, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + std::vector> items; + for (const auto &[itemId, count, subType] : itemCounts) { + const auto &itemType = Item::items[itemId]; + if (itemType.id <= 0) { + continue; + } + if (count == 0) { + continue; + } + uint32_t countPerItem = itemType.stackable ? itemType.stackSize : 1; + for (uint32_t i = 0; i < count; ++i) { + std::shared_ptr item; + if (itemType.isWrappable()) { + countPerItem = 1; + item = Item::CreateItem(ITEM_DECORATION_KIT, subType); + item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + itemType.name + ">."); + item->setCustomAttribute("unWrapId", static_cast(itemId)); + } else { + item = Item::CreateItem(itemId, itemType.stackable ? std::min(countPerItem, count - i) : subType); + } + items.push_back(item); + i += countPerItem - 1; + } + } + + return addItemBatch(toCylinder, items, flags, dropOnMap, autoContainerId); +} + +std::tuple Game::createItem(const std::shared_ptr &toCylinder, uint16_t itemId, uint32_t count, uint16_t subType, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + return createItemBatch(toCylinder, { std::make_tuple(itemId, count, subType) }, flags, dropOnMap, autoContainerId); +} + ReturnValue Game::internalPlayerAddItem(std::shared_ptr player, std::shared_ptr item, bool dropOnMap /*= true*/, Slots_t slot /*= CONST_SLOT_WHEREEVER*/) { uint32_t remainderCount = 0; ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); @@ -3190,7 +3298,7 @@ void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t f std::forward_list listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerUseItemEx, this, playerId, itemPos, itemStackPos, fromItemId, toPos, toStackPos, toItemId), "Game::playerUseItemEx"); if (it.isRune() || it.type == ITEM_TYPE_POTION) { @@ -3295,7 +3403,7 @@ void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPo if (ret == RETURNVALUE_TOOFARAWAY) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerUseItem, this, playerId, pos, stackPos, index, itemId), "Game::playerUseItem"); if (it.isRune() || it.type == ITEM_TYPE_POTION) { @@ -3429,7 +3537,7 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uin std::forward_list listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerUseWithCreature, this, playerId, itemPos, itemStackPos, creatureId, itemId), "Game::playerUseWithCreature"); if (it.isRune() || it.type == ITEM_TYPE_POTION) { @@ -3568,7 +3676,7 @@ void Game::playerRotateItem(uint32_t playerId, const Position &pos, uint8_t stac if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerRotateItem, this, playerId, pos, stackPos, itemId), "Game::playerRotateItem"); player->setNextWalkActionTask(task); @@ -3609,7 +3717,7 @@ void Game::playerConfigureShowOffSocket(uint32_t playerId, const Position &pos, if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, false)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task; if (isPodiumOfRenown) { task = createPlayerTask(400, std::bind_front(&Player::sendPodiumWindow, player, item, pos, itemId, stackPos), "Game::playerConfigureShowOffSocket"); @@ -3656,7 +3764,7 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, false)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerBrowseField, this, playerId, pos), "Game::playerBrowseField"); player->setNextWalkActionTask(task); } else { @@ -3785,7 +3893,7 @@ void Game::playerWrapableItem(uint32_t playerId, const Position &pos, uint8_t st if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerWrapableItem, this, playerId, pos, stackPos, itemId), "Game::playerWrapableItem"); player->setNextWalkActionTask(task); @@ -3842,9 +3950,10 @@ std::shared_ptr Game::wrapItem(std::shared_ptr item, std::shared_ptr house->removeBed(item->getBed()); } uint16_t oldItemID = item->getID(); + auto itemName = item->getName(); std::shared_ptr newItem = transformItem(item, ITEM_DECORATION_KIT); newItem->setCustomAttribute("unWrapId", static_cast(oldItemID)); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap it in your own house to create a <" + item->getName() + ">."); + item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap it in your own house to create a <" + itemName + ">."); if (hiddenCharges > 0) { newItem->setAttribute(DATE, hiddenCharges); } @@ -3954,7 +4063,7 @@ void Game::playerBrowseField(uint32_t playerId, const Position &pos) { if (!Position::areInRange<1, 1>(playerPos, pos)) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerBrowseField, this, playerId, pos), "Game::playerBrowseField"); player->setNextWalkActionTask(task); } else { @@ -4204,7 +4313,7 @@ void Game::playerRequestTrade(uint32_t playerId, const Position &pos, uint8_t st if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) { std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind(&Game::playerRequestTrade, this, playerId, pos, stackPos, tradePlayerId, itemId), "Game::playerRequestTrade"); player->setNextWalkActionTask(task); @@ -4753,7 +4862,7 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item // need to walk to the corpse first before looting it std::forward_list listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(0, std::bind(&Game::playerQuickLoot, this, player->getID(), pos, itemId, stackPos, defaultItem, lootAllCorpses, autoLoot), "Game::playerQuickLoot"); player->setNextWalkActionTask(task); } else { @@ -4767,7 +4876,7 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item return; } - std::lock_guard lock(player->quickLootMutex); + Player::PlayerLock lock(player); if (!autoLoot) { player->setNextActionTask(nullptr); } @@ -5116,7 +5225,7 @@ void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) { } player->setAttackedCreature(attackCreature); - g_dispatcher().addTask(std::bind(&Game::updateCreatureWalk, this, player->getID()), "Game::updateCreatureWalk"); + g_dispatcher().addEvent(std::bind(&Game::updateCreatureWalk, this, player->getID()), "Game::updateCreatureWalk"); } void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { @@ -5126,7 +5235,7 @@ void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { } player->setAttackedCreature(nullptr); - g_dispatcher().addTask(std::bind(&Game::updateCreatureWalk, this, player->getID()), "Game::updateCreatureWalk"); + g_dispatcher().addEvent(std::bind(&Game::updateCreatureWalk, this, player->getID()), "Game::updateCreatureWalk"); player->setFollowCreature(getCreatureByID(creatureId)); } @@ -5675,8 +5784,8 @@ void Game::removeCreatureCheck(std::shared_ptr creature) { } } -void Game::checkCreatures(size_t index) { - g_scheduler().addEvent(EVENT_CHECK_CREATURE_INTERVAL, std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT), "Game::checkCreatures"); +void Game::checkCreatures() { + static size_t index = 0; auto &checkCreatureList = checkCreatureLists[index]; size_t it = 0, end = checkCreatureList.size(); @@ -5701,6 +5810,8 @@ void Game::checkCreatures(size_t index) { } } cleanup(); + + index = (index + 1) % EVENT_CREATURECOUNT; } void Game::changeSpeed(std::shared_ptr creature, int32_t varSpeedDelta) { @@ -6218,6 +6329,13 @@ void Game::applyWheelOfDestinyEffectsToDamage(CombatDamage &damage, std::shared_ damage.secondary.value += (damage.secondary.value * damageBonus) / 100.; } } + if (damage.instantSpellName == "Divine Grenade") { + int32_t damageBonus = attackerPlayer->wheel()->checkDivineGrenade(target); + if (damageBonus != 0) { + damage.primary.value += (damage.primary.value * damageBonus) / 100.; + damage.secondary.value += (damage.secondary.value * damageBonus) / 100.; + } + } } } @@ -6569,6 +6687,20 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt damage.secondary.value = std::max(0, damage.secondary.value + damage.primary.value); damage.primary.value = 0; } + + if (attackerPlayer) { + attackerPlayer->updateImpactTracker(damage.primary.type, damage.primary.value); + if (damage.secondary.type != COMBAT_NONE) { + attackerPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value); + } + } + + if (targetPlayer) { + targetPlayer->updateImpactTracker(damage.primary.type, manaDamage); + if (damage.secondary.type != COMBAT_NONE) { + targetPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value); + } + } } } @@ -7187,8 +7319,6 @@ void Game::addDistanceEffect(const CreatureVector &spectators, const Position &f } void Game::checkImbuements() { - g_scheduler().addEvent(EVENT_IMBUEMENT_INTERVAL, std::bind(&Game::checkImbuements, this), "Game::checkImbuements"); - for (const auto &[mapPlayerId, mapPlayer] : getPlayers()) { if (!mapPlayer) { continue; @@ -7199,8 +7329,6 @@ void Game::checkImbuements() { } void Game::checkLight() { - g_scheduler().addEvent(EVENT_LIGHTINTERVAL_MS, std::bind(&Game::checkLight, this), "Game::checkLight"); - lightHour += lightHourDelta; if (lightHour > LIGHT_DAY_LENGTH) { @@ -7840,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()); @@ -7864,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) { @@ -7890,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; } @@ -7906,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; } @@ -7964,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); @@ -8338,7 +8463,7 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t ite // Exhausted for create offert in the market player->updateUIExhausted(); - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter) { @@ -8419,7 +8544,7 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 player->sendMarketEnter(player->getLastDepotId()); // Exhausted for cancel offer in the market player->updateUIExhausted(); - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) { @@ -8569,7 +8694,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 } if (buyerPlayer->isOffline()) { - IOLoginData::savePlayer(buyerPlayer); + g_saveManager().savePlayer(buyerPlayer); } } else if (offer.type == MARKETACTION_SELL) { std::shared_ptr sellerPlayer = getPlayerByGUID(offer.playerId); @@ -8663,7 +8788,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 } if (sellerPlayer->isOffline()) { - IOLoginData::savePlayer(sellerPlayer); + g_saveManager().savePlayer(sellerPlayer); } } @@ -8694,7 +8819,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 player->sendMarketAcceptOffer(offer); // Exhausted for accept offer in the market player->updateUIExhausted(); - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string &buffer) { @@ -8877,7 +9002,7 @@ void Game::playerSetMonsterPodium(uint32_t playerId, uint32_t monsterRaceId, con if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { if (std::forward_list listDir; player->getPathTo(pos, listDir, 0, 1, true, false)) { - g_dispatcher().addTask(std::bind_front(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind_front(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind_front(&Game::playerBrowseField, this, playerId, pos), "Game::playerBrowseField"); player->setNextWalkActionTask(task); } else { @@ -8968,7 +9093,7 @@ void Game::playerRotatePodium(uint32_t playerId, const Position &pos, uint8_t st if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { if (std::forward_list listDir; player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addTask(std::bind_front(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); + g_dispatcher().addEvent(std::bind_front(&Game::playerAutoWalk, this, player->getID(), listDir), "Game::playerAutoWalk"); std::shared_ptr task = createPlayerTask(400, std::bind_front(&Game::playerRotatePodium, this, playerId, pos, stackPos, itemId), "Game::playerRotatePodium"); player->setNextWalkActionTask(task); @@ -9177,7 +9302,7 @@ void Game::addGuild(const std::shared_ptr guild) { void Game::removeGuild(uint32_t guildId) { auto it = guilds.find(guildId); if (it != guilds.end()) { - IOGuild::saveGuild(it->second); + g_saveManager().saveGuild(it->second); } guilds.erase(guildId); } @@ -9456,7 +9581,7 @@ uint32_t Game::makeFiendishMonster(uint32_t forgeableMonsterId /* = 0*/, bool cr std::bind_front(&Game::updateFiendishMonsterStatus, this, monster->getID(), monster->getName()), "Game::updateFiendishMonsterStatus" ); - forgeMonsterEventIds[monster->getID()] = g_scheduler().addEvent(schedulerTask); + forgeMonsterEventIds[monster->getID()] = g_dispatcher().scheduleEvent(schedulerTask); return monster->getID(); } @@ -9492,7 +9617,7 @@ bool Game::removeInfluencedMonster(uint32_t id, bool create /* = false*/) { influencedMonsters.erase(find); if (create) { - g_scheduler().addEvent(200 * 1000, std::bind_front(&Game::makeInfluencedMonster, this), "Game::makeInfluencedMonster"); + g_dispatcher().scheduleEvent(200 * 1000, std::bind_front(&Game::makeInfluencedMonster, this), "Game::makeInfluencedMonster"); } } else { g_logger().warn("[Game::removeInfluencedMonster] - Failed to remove a Influenced Monster, error code: monster id not exist in the influenced monsters map"); @@ -9508,7 +9633,7 @@ bool Game::removeFiendishMonster(uint32_t id, bool create /* = true*/) { checkForgeEventId(id); if (create) { - g_scheduler().addEvent(300 * 1000, std::bind_front(&Game::makeFiendishMonster, this, 0, false), "Game::makeFiendishMonster"); + g_dispatcher().scheduleEvent(300 * 1000, std::bind_front(&Game::makeFiendishMonster, this, 0, false), "Game::makeFiendishMonster"); } } else { g_logger().warn("[Game::removeFiendishMonster] - Failed to remove a Fiendish Monster, error code: monster id not exist in the fiendish monsters map"); @@ -9518,7 +9643,6 @@ bool Game::removeFiendishMonster(uint32_t id, bool create /* = true*/) { } void Game::updateForgeableMonsters() { - g_scheduler().addEvent(EVENT_FORGEABLEMONSTERCHECKINTERVAL, std::bind_front(&Game::updateForgeableMonsters, this), "Game::updateForgeableMonsters"); forgeableMonsters.clear(); for (auto [monsterId, monster] : monsters) { auto monsterTile = monster->getTile(); @@ -9584,7 +9708,7 @@ void Game::createInfluencedMonsters() { void Game::checkForgeEventId(uint32_t monsterId) { auto find = forgeMonsterEventIds.find(monsterId); if (find != forgeMonsterEventIds.end()) { - g_scheduler().stopEvent(find->second); + g_dispatcher().stopEvent(find->second); forgeMonsterEventIds.erase(find); } } @@ -9707,7 +9831,7 @@ void Game::playerCheckActivity(const std::string &playerName, int interval) { } } - g_scheduler().addEvent(1000, std::bind(&Game::playerCheckActivity, this, playerName, interval), "Game::playerCheckActivity"); + g_dispatcher().scheduleEvent(1000, std::bind(&Game::playerCheckActivity, this, playerName, interval), "Game::playerCheckActivity"); } void Game::playerRewardChestCollect(uint32_t playerId, const Position &pos, uint16_t itemId, uint8_t stackPos, uint32_t maxMoveItems /* = 0*/) { @@ -9735,7 +9859,7 @@ void Game::playerRewardChestCollect(uint32_t playerId, const Position &pos, uint // Updates the parent of the reward chest and reward containers to avoid memory usage after cleaning auto playerRewardChest = player->getRewardChest(); - if (playerRewardChest->empty()) { + if (playerRewardChest && playerRewardChest->empty()) { player->sendCancelMessage(RETURNVALUE_REWARDCHESTISEMPTY); return; } @@ -9806,7 +9930,7 @@ void Game::setTransferPlayerHouseItems(uint32_t houseId, uint32_t playerId) { } template -phmap::parallel_flat_hash_set setDifference(const phmap::parallel_flat_hash_set &setA, const phmap::parallel_flat_hash_set &setB) { +phmap::parallel_flat_hash_set setDifference(const phmap::flat_hash_set &setA, const phmap::flat_hash_set &setB) { phmap::parallel_flat_hash_set setResult; for (const auto &elem : setA) { if (setB.find(elem) == setB.end()) { @@ -9816,7 +9940,7 @@ phmap::parallel_flat_hash_set setDifference(const phmap::parallel_flat_hash_s return setResult; } -ReturnValue Game::beforeCreatureZoneChange(std::shared_ptr creature, const phmap::parallel_flat_hash_set> &fromZones, const phmap::parallel_flat_hash_set> &toZones, bool force /* = false*/) const { +ReturnValue Game::beforeCreatureZoneChange(std::shared_ptr creature, const phmap::flat_hash_set> &fromZones, const phmap::flat_hash_set> &toZones, bool force /* = false*/) const { if (!creature) { return RETURNVALUE_NOTPOSSIBLE; } @@ -9846,7 +9970,7 @@ ReturnValue Game::beforeCreatureZoneChange(std::shared_ptr creature, c return RETURNVALUE_NOERROR; } -void Game::afterCreatureZoneChange(std::shared_ptr creature, const phmap::parallel_flat_hash_set> &fromZones, const phmap::parallel_flat_hash_set> &toZones) const { +void Game::afterCreatureZoneChange(std::shared_ptr creature, const phmap::flat_hash_set> &fromZones, const phmap::flat_hash_set> &toZones) const { if (!creature) { return; } diff --git a/src/game/game.hpp b/src/game/game.hpp index 9cf467b4b..a5c7cd116 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 { @@ -149,7 +153,9 @@ class Game { std::shared_ptr getPlayerByName(const std::string &s, bool allowOffline = false); - std::shared_ptr getPlayerByGUID(const uint32_t &guid); + std::shared_ptr getPlayerByGUID(const uint32_t &guid, bool allowOffline = false); + + std::string getPlayerNameByGUID(const uint32_t &guid); ReturnValue getPlayerByNameWildcard(const std::string &s, std::shared_ptr &player); @@ -210,6 +216,9 @@ class Game { ReturnValue checkMoveItemToCylinder(std::shared_ptr player, std::shared_ptr fromCylinder, std::shared_ptr toCylinder, std::shared_ptr item, Position toPos); ReturnValue internalMoveItem(std::shared_ptr fromCylinder, std::shared_ptr toCylinder, int32_t index, std::shared_ptr item, uint32_t count, std::shared_ptr* movedItem, uint32_t flags = 0, std::shared_ptr actor = nullptr, std::shared_ptr tradeItem = nullptr, bool checkTile = true); + std::tuple addItemBatch(const std::shared_ptr &toCylinder, const std::vector> &items, uint32_t flags = 0, bool dropOnMap = true, uint32_t autoContainerId = 0); + std::tuple createItemBatch(const std::shared_ptr &toCylinder, const std::vector> &itemCounts, uint32_t flags = 0, bool dropOnMap = true, uint32_t autoContainerId = 0); + std::tuple createItem(const std::shared_ptr &toCylinder, uint16_t itemId, uint32_t count, uint16_t subType, uint32_t flags = 0, bool dropOnMap = true, uint32_t autoContainerId = 0); ReturnValue internalAddItem(std::shared_ptr toCylinder, std::shared_ptr item, int32_t index = INDEX_WHEREEVER, uint32_t flags = 0, bool test = false); ReturnValue internalAddItem(std::shared_ptr toCylinder, std::shared_ptr item, int32_t index, uint32_t flags, bool test, uint32_t &remainderCount); ReturnValue internalRemoveItem(std::shared_ptr item, int32_t count = -1, bool test = false, uint32_t flags = 0, bool force = false); @@ -427,13 +436,12 @@ class Game { GameState_t getGameState() const; void setGameState(GameState_t newState); - void saveGameState(); // Events void checkCreatureWalk(uint32_t creatureId); void updateCreatureWalk(uint32_t creatureId); void checkCreatureAttack(uint32_t creatureId); - void checkCreatures(size_t index); + void checkCreatures(); void checkLight(); bool combatBlockHit(CombatDamage &damage, std::shared_ptr attacker, std::shared_ptr target, bool checkDefense, bool checkArmor, bool field); @@ -497,7 +505,10 @@ class Game { const std::map> &getItemsPrice() const { return itemsPriceMap; } - const phmap::flat_hash_map> &getPlayers() const { + const phmap::parallel_flat_hash_map> &getGuilds() const { + return guilds; + } + const phmap::parallel_flat_hash_map> &getPlayers() const { return players; } const std::map> &getMonsters() const { @@ -662,8 +673,8 @@ class Game { */ bool tryRetrieveStashItems(std::shared_ptr player, std::shared_ptr item); - ReturnValue beforeCreatureZoneChange(std::shared_ptr creature, const phmap::parallel_flat_hash_set> &fromZones, const phmap::parallel_flat_hash_set> &toZones, bool force = false) const; - void afterCreatureZoneChange(std::shared_ptr creature, const phmap::parallel_flat_hash_set> &fromZones, const phmap::parallel_flat_hash_set> &toZones) const; + ReturnValue beforeCreatureZoneChange(std::shared_ptr creature, const phmap::flat_hash_set> &fromZones, const phmap::flat_hash_set> &toZones, bool force = false) const; + void afterCreatureZoneChange(std::shared_ptr creature, const phmap::flat_hash_set> &fromZones, const phmap::flat_hash_set> &toZones) const; std::unique_ptr &getIOWheel(); const std::unique_ptr &getIOWheel() const; @@ -770,10 +781,11 @@ class Game { phmap::flat_hash_map highscoreCache; phmap::flat_hash_map> m_uniqueLoginPlayerNames; - phmap::flat_hash_map> players; + phmap::parallel_flat_hash_map> players; phmap::flat_hash_map> mappedPlayerNames; - phmap::flat_hash_map> guilds; + phmap::parallel_flat_hash_map> guilds; phmap::flat_hash_map> uniqueItems; + phmap::parallel_flat_hash_map m_playerNameCache; std::map stages; /* Items stored from the lua scripts positions @@ -892,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/game/scheduling/dispatcher.cpp b/src/game/scheduling/dispatcher.cpp index 9423c8799..1bb7512f1 100644 --- a/src/game/scheduling/dispatcher.cpp +++ b/src/game/scheduling/dispatcher.cpp @@ -9,66 +9,190 @@ #include "pch.hpp" -#include "lib/di/container.hpp" -#include "lib/thread/thread_pool.hpp" #include "game/scheduling/dispatcher.hpp" -#include "game/scheduling/task.hpp" +#include "lib/thread/thread_pool.hpp" +#include "lib/di/container.hpp" +#include "utils/tools.hpp" -Dispatcher::Dispatcher(ThreadPool &threadPool) : - threadPool(threadPool) { } +constexpr static auto ASYNC_TIME_OUT = std::chrono::seconds(15); +thread_local DispatcherContext Dispatcher::dispacherContext; Dispatcher &Dispatcher::getInstance() { return inject(); } -void Dispatcher::addTask(std::function f, const std::string &context) { - addTask(std::make_shared(std::move(f), context)); +void Dispatcher::init() { + updateClock(); + + threadPool.addLoad([this] { + std::unique_lock asyncLock(dummyMutex); + + while (!threadPool.getIoContext().stopped()) { + updateClock(); + + executeEvents(asyncLock); + executeScheduledEvents(); + mergeEvents(); + + if (!hasPendingTasks) { + signalSchedule.wait_for(asyncLock, timeUntilNextScheduledTask()); + } + } + }); } -void Dispatcher::addTask(std::function f, const std::string &context, uint32_t expiresAfterMs) { - addTask(std::make_shared(std::move(f), context), expiresAfterMs); +void Dispatcher::executeSerialEvents(std::vector &tasks) { + dispacherContext.group = TaskGroup::Serial; + dispacherContext.type = DispatcherType::Event; + + for (const auto &task : tasks) { + dispacherContext.taskName = task.getContext(); + if (task.execute()) { + ++dispatcherCycle; + } + } + tasks.clear(); + + dispacherContext.reset(); } -void Dispatcher::addTask(const std::shared_ptr task) { - addTask(task, 0); +void Dispatcher::executeParallelEvents(std::vector &tasks, const uint8_t groupId, std::unique_lock &asyncLock) { + const size_t totalTaskSize = tasks.size(); + std::atomic_uint_fast64_t executedTasks = 0; + + for (const auto &task : tasks) { + threadPool.addLoad([this, &task, &executedTasks, groupId, totalTaskSize] { + dispacherContext.type = DispatcherType::AsyncEvent; + dispacherContext.group = static_cast(groupId); + dispacherContext.taskName = task.getContext(); + + task.execute(); + + dispacherContext.reset(); + + executedTasks.fetch_add(1); + if (executedTasks.load() >= totalTaskSize) { + signalAsync.notify_one(); + } + }); + } + + if (signalAsync.wait_for(asyncLock, ASYNC_TIME_OUT) == std::cv_status::timeout) { + g_logger().warn("A timeout occurred when executing the async dispatch in the context({}). Executed Tasks: {}/{}.", groupId, executedTasks.load(), totalTaskSize); + } + tasks.clear(); } -void Dispatcher::addTask(const std::shared_ptr task, uint32_t expiresAfterMs) { - auto executeTask = [this, task]() { - std::lock_guard lockClass(threadSafetyMutex); +void Dispatcher::executeEvents(std::unique_lock &asyncLock) { + for (uint_fast8_t groupId = 0; groupId < static_cast(TaskGroup::Last); ++groupId) { + auto &tasks = m_tasks[groupId]; + if (tasks.empty()) { + return; + } - if (task->hasTraceableContext()) { - g_logger().trace("Executing task {}.", task->getContext()); + if (groupId == static_cast(TaskGroup::Serial)) { + executeSerialEvents(tasks); + mergeEvents(); // merge request, as there may be async event requests } else { - g_logger().debug("Executing task {}.", task->getContext()); + executeParallelEvents(tasks, groupId, asyncLock); } + } +} - ++dispatcherCycle; - (*task)(); - }; - - if (expiresAfterMs == 0) { - threadPool.addLoad(executeTask); +void Dispatcher::executeScheduledEvents() { + auto &threadScheduledTasks = getThreadTask()->scheduledTasks; - return; - }; + auto it = scheduledTasks.begin(); + while (it != scheduledTasks.end()) { + const auto &task = *it; + if (task->getTime() > Task::TIME_NOW) { + break; + } - auto timer = std::make_shared(threadPool.getIoContext()); - timer->expires_after(std::chrono::milliseconds(expiresAfterMs)); + dispacherContext.type = task->isCycle() ? DispatcherType::CycleEvent : DispatcherType::ScheduledEvent; + dispacherContext.group = TaskGroup::Serial; + dispacherContext.taskName = task->getContext(); - timer->async_wait([task, expiresAfterMs](const std::error_code &error) { - if (error == asio::error::operation_aborted) { - return; + if (task->execute() && task->isCycle()) { + task->updateTime(); + threadScheduledTasks.emplace_back(task); + } else { + scheduledTasksRef.erase(task->getId()); } - g_logger().info("Task '{}' was not executed within {} ms, so it was cancelled.", task->getContext(), expiresAfterMs); - }); + ++it; + } - threadPool.addLoad([timer, executeTask]() { - if (timer->cancel() <= 0) { - return; + if (it != scheduledTasks.begin()) { + scheduledTasks.erase(scheduledTasks.begin(), it); + } + + dispacherContext.reset(); +} + +// Merge thread events with main dispatch events +void Dispatcher::mergeEvents() { + for (const auto &thread : threads) { + std::scoped_lock lock(thread->mutex); + 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(); + } } - executeTask(); - }); + if (!thread->scheduledTasks.empty()) { + scheduledTasks.insert(make_move_iterator(thread->scheduledTasks.begin()), make_move_iterator(thread->scheduledTasks.end())); + thread->scheduledTasks.clear(); + } + } + + checkPendingTasks(); +} + +std::chrono::nanoseconds Dispatcher::timeUntilNextScheduledTask() const { + static constexpr auto CHRONO_NANO_0 = std::chrono::nanoseconds(0); + static constexpr auto CHRONO_MILI_MAX = std::chrono::milliseconds::max(); + + if (scheduledTasks.empty()) { + return CHRONO_MILI_MAX; + } + + 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 = 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 = getThreadTask(); + std::scoped_lock lock(thread->mutex); + + auto eventId = scheduledTasksRef + .emplace(task->getId(), thread->scheduledTasks.emplace_back(task)) + .first->first; + + notify(); + return eventId; +} + +void Dispatcher::asyncEvent(std::function &&f, TaskGroup group) { + 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) { + 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 7590773bd..73ba38ed2 100644 --- a/src/game/scheduling/dispatcher.hpp +++ b/src/game/scheduling/dispatcher.hpp @@ -9,11 +9,65 @@ #pragma once +#include "task.hpp" #include "lib/thread/thread_pool.hpp" -const int DISPATCHER_TASK_EXPIRATION = 2000; +static constexpr uint16_t DISPATCHER_TASK_EXPIRATION = 2000; +static constexpr uint16_t SCHEDULER_MINTICKS = 50; -class Task; +enum class TaskGroup : int8_t { + ThreadPool = -1, + Serial, + GenericParallel, + Last +}; + +enum class DispatcherType : uint8_t { + None, + Event, + AsyncEvent, + ScheduledEvent, + CycleEvent +}; + +struct DispatcherContext { + bool isOn() const { + return Task::TIME_NOW != SYSTEM_TIME_ZERO; + } + + bool isGroup(const TaskGroup _group) const { + return group == _group; + } + + bool isAsync() const { + return group != TaskGroup::Serial; + } + + auto getGroup() const { + return group; + } + + auto getName() const { + return taskName; + } + + auto getType() const { + return type; + } + +private: + void reset() { + group = TaskGroup::ThreadPool; + type = DispatcherType::None; + taskName = "ThreadPool::call"; + } + + DispatcherType type = DispatcherType::None; + TaskGroup group = TaskGroup::ThreadPool; + std::string_view taskName = ""; + + friend class Dispatcher; +}; /** * Dispatcher allow you to dispatch a task async to be executed @@ -22,7 +76,13 @@ class Task; */ class Dispatcher { public: - explicit Dispatcher(ThreadPool &threadPool); + explicit Dispatcher(ThreadPool &threadPool) : + threadPool(threadPool) { + threads.reserve(threadPool.getNumberOfThreads() + 1); + for (uint_fast16_t i = 0; i < threads.capacity(); ++i) { + threads.emplace_back(std::make_unique()); + } + }; // Ensures that we don't accidentally copy it Dispatcher(const Dispatcher &) = delete; @@ -30,20 +90,116 @@ class Dispatcher { static Dispatcher &getInstance(); - void addTask(std::function f, const std::string &context); - void addTask(std::function f, const std::string &context, uint32_t expiresAfterMs); + void addEvent(std::function &&f, std::string_view context, uint32_t expiresAfterMs = 0); + + uint64_t cycleEvent(uint32_t delay, std::function &&f, std::string_view context) { + return scheduleEvent(delay, std::move(f), context, true); + } + + uint64_t scheduleEvent(const std::shared_ptr &task); + uint64_t scheduleEvent(uint32_t delay, std::function &&f, std::string_view context) { + return scheduleEvent(delay, std::move(f), context, false); + } + + void asyncEvent(std::function &&f, TaskGroup group = TaskGroup::GenericParallel); - void addTask(const std::shared_ptr task); - void addTask(const std::shared_ptr task, uint32_t expiresAfterMs); + uint64_t asyncCycleEvent(uint32_t delay, std::function &&f, TaskGroup group = TaskGroup::GenericParallel) { + return scheduleEvent( + delay, [this, f = std::move(f), group] { asyncEvent([f] { f(); }, group); }, dispacherContext.taskName, true, false + ); + } + + uint64_t asyncScheduleEvent(uint32_t delay, std::function &&f, TaskGroup group = TaskGroup::GenericParallel) { + return scheduleEvent( + delay, [this, f = std::move(f), group] { asyncEvent([f] { f(); }, group); }, dispacherContext.taskName, false, false + ); + } [[nodiscard]] uint64_t getDispatcherCycle() const { return dispatcherCycle; } + void stopEvent(uint64_t eventId); + + const auto &context() const { + return dispacherContext; + } + private: + thread_local static DispatcherContext dispacherContext; + + // Update Time Cache + static void updateClock() { + Task::TIME_NOW = std::chrono::system_clock::now(); + } + + 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)); + } + + void init(); + void shutdown() { + signalAsync.notify_all(); + } + + inline void mergeEvents(); + inline void executeEvents(std::unique_lock &asyncLock); + inline void executeScheduledEvents(); + + inline void executeSerialEvents(std::vector &tasks); + inline void executeParallelEvents(std::vector &tasks, const uint8_t groupId, std::unique_lock &asyncLock); + inline std::chrono::nanoseconds timeUntilNextScheduledTask() const; + + inline void checkPendingTasks() { + hasPendingTasks = false; + for (uint_fast8_t i = 0; i < static_cast(TaskGroup::Last); ++i) { + if (!m_tasks[i].empty()) { + hasPendingTasks = true; + break; + } + } + } + + void notify() { + if (!hasPendingTasks) { + hasPendingTasks = true; + signalSchedule.notify_one(); + } + } + + uint_fast64_t dispatcherCycle = 0; + ThreadPool &threadPool; - uint64_t dispatcherCycle = 0; - std::mutex threadSafetyMutex; + std::condition_variable signalAsync; + std::condition_variable signalSchedule; + std::atomic_bool hasPendingTasks = false; + std::mutex dummyMutex; // This is only used for signaling the condition variable and not as an actual lock. + + // Thread Events + struct ThreadTask { + ThreadTask() { + for (auto &task : tasks) { + task.reserve(2000); + } + scheduledTasks.reserve(2000); + } + + std::array, static_cast(TaskGroup::Last)> tasks; + std::vector> scheduledTasks; + std::mutex mutex; + }; + std::vector> threads; + + // Main Events + std::array, static_cast(TaskGroup::Last)> m_tasks; + phmap::btree_multiset, Task::Compare> scheduledTasks; + phmap::parallel_flat_hash_map_m> scheduledTasksRef; + + friend class CanaryServer; }; constexpr auto g_dispatcher = Dispatcher::getInstance; diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp new file mode 100644 index 000000000..9a5d01145 --- /dev/null +++ b/src/game/scheduling/save_manager.cpp @@ -0,0 +1,144 @@ +#include "pch.hpp" + +#include "game/game.hpp" +#include "game/scheduling/save_manager.hpp" +#include "io/iologindata.hpp" + +SaveManager::SaveManager(ThreadPool &threadPool, KVStore &kvStore, Logger &logger, Game &game) : + threadPool(threadPool), kv(kvStore), logger(logger), game(game) { } + +SaveManager &SaveManager::getInstance() { + return inject(); +} + +void SaveManager::saveAll() { + Benchmark bm_saveAll; + logger.info("Saving server..."); + const auto players = game.getPlayers(); + + for (const auto &[_, player] : players) { + player->loginPosition = player->getPosition(); + doSavePlayer(player); + } + + auto guilds = game.getGuilds(); + for (const auto &[_, guild] : guilds) { + saveGuild(guild); + } + + saveMap(); + saveKV(); + logger.info("Server saved in {} milliseconds.", bm_saveAll.duration()); +} + +void SaveManager::scheduleAll() { + auto scheduledAt = std::chrono::steady_clock::now(); + m_scheduledAt = scheduledAt; + + threadPool.addLoad([this, scheduledAt]() { + if (m_scheduledAt.load() != scheduledAt) { + logger.warn("Skipping save for server because another save has been scheduled."); + return; + } + saveAll(); + }); +} + +void SaveManager::schedulePlayer(std::weak_ptr playerPtr) { + auto playerToSave = playerPtr.lock(); + if (!playerToSave) { + logger.debug("Skipping save for player because player is no longer online."); + return; + } + logger.debug("Scheduling player {} for saving.", playerToSave->getName()); + auto scheduledAt = std::chrono::steady_clock::now(); + m_playerMap[playerToSave->getGUID()] = scheduledAt; + threadPool.addLoad([this, playerPtr, scheduledAt]() { + auto player = playerPtr.lock(); + if (!player) { + logger.debug("Skipping save for player because player is no longer online."); + return; + } + if (m_playerMap[player->getGUID()] != scheduledAt) { + logger.warn("Skipping save for player because another save has been scheduled."); + return; + } + doSavePlayer(player); + }); +} + +bool SaveManager::doSavePlayer(std::shared_ptr player) { + if (!player) { + logger.debug("Failed to save player because player is null."); + return false; + } + Benchmark bm_savePlayer; + Player::PlayerLock lock(player); + m_playerMap.erase(player->getGUID()); + logger.debug("Saving player {}...", player->getName()); + bool saveSuccess = IOLoginData::savePlayer(player); + if (!saveSuccess) { + logger.error("Failed to save player {}.", player->getName()); + } + auto duration = bm_savePlayer.duration(); + if (duration > 100) { + logger.warn("Saving player {} took {} milliseconds.", player->getName(), duration); + } else { + logger.debug("Saving player {} took {} milliseconds.", player->getName(), duration); + } + return saveSuccess; +} + +bool SaveManager::savePlayer(std::shared_ptr player) { + if (player->isOnline()) { + schedulePlayer(player); + return true; + } + return doSavePlayer(player); +} + +void SaveManager::saveGuild(std::shared_ptr guild) { + if (!guild) { + logger.debug("Failed to save guild because guild is null."); + return; + } + Benchmark bm_saveGuild; + logger.debug("Saving guild {}...", guild->getName()); + IOGuild::saveGuild(guild); + auto duration = bm_saveGuild.duration(); + if (duration > 100) { + logger.warn("Saving guild {} took {} milliseconds.", guild->getName(), duration); + } else { + logger.debug("Saving guild {} took {} milliseconds.", guild->getName(), duration); + } +} + +void SaveManager::saveMap() { + Benchmark bm_saveMap; + logger.debug("Saving map..."); + bool saveSuccess = Map::save(); + if (!saveSuccess) { + logger.error("Failed to save map."); + } + auto duration = bm_saveMap.duration(); + if (duration > 100) { + logger.warn("Map saved in {} milliseconds.", bm_saveMap.duration()); + } else { + logger.debug("Map saved in {} milliseconds.", bm_saveMap.duration()); + } +} + +void SaveManager::saveKV() { + Benchmark bm_saveKV; + logger.debug("Saving key-value store..."); + bool saveSuccess = kv.saveAll(); + if (!saveSuccess) { + logger.error("Failed to save key-value store."); + } + auto duration = bm_saveKV.duration(); + if (duration > 100) { + logger.warn("Key-value store saved in {} milliseconds.", bm_saveKV.duration()); + } else { + logger.debug("Key-value store saved in {} milliseconds.", bm_saveKV.duration()); + } +} diff --git a/src/game/scheduling/save_manager.hpp b/src/game/scheduling/save_manager.hpp new file mode 100644 index 000000000..a809dd73f --- /dev/null +++ b/src/game/scheduling/save_manager.hpp @@ -0,0 +1,46 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include "lib/thread/thread_pool.hpp" +#include "kv/kv.hpp" + +class SaveManager { +public: + explicit SaveManager(ThreadPool &threadPool, KVStore &kvStore, Logger &logger, Game &game); + + SaveManager(const SaveManager &) = delete; + void operator=(const SaveManager &) = delete; + + static SaveManager &getInstance(); + + void saveAll(); + void scheduleAll(); + + bool savePlayer(std::shared_ptr player); + void saveGuild(std::shared_ptr guild); + +private: + void saveMap(); + void saveKV(); + + void schedulePlayer(std::weak_ptr player); + bool doSavePlayer(std::shared_ptr player); + + std::atomic m_scheduledAt; + phmap::parallel_flat_hash_map m_playerMap; + + ThreadPool &threadPool; + KVStore &kv; + Logger &logger; + Game &game; +}; + +constexpr auto g_saveManager = SaveManager::getInstance; diff --git a/src/game/scheduling/scheduler.cpp b/src/game/scheduling/scheduler.cpp deleted file mode 100644 index 4b351cd22..000000000 --- a/src/game/scheduling/scheduler.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR - * Repository: https://github.com/opentibiabr/canary - * License: https://github.com/opentibiabr/canary/blob/main/LICENSE - * Contributors: https://github.com/opentibiabr/canary/graphs/contributors - * Website: https://docs.opentibiabr.com/ - */ - -#include "pch.hpp" - -#include "lib/di/container.hpp" -#include "lib/thread/thread_pool.hpp" -#include "game/scheduling/dispatcher.hpp" -#include "game/scheduling/scheduler.hpp" -#include "game/scheduling/task.hpp" - -Scheduler::Scheduler(ThreadPool &threadPool) : - threadPool(threadPool) { } - -Scheduler &Scheduler::getInstance() { - return inject(); -} - -uint64_t Scheduler::addEvent(uint32_t delay, std::function f, std::string context) { - return addEvent(std::make_shared(std::move(f), std::move(context), delay)); -} - -uint64_t Scheduler::addEvent(const std::shared_ptr task) { - if (task->getEventId() == 0) { - task->setEventId(++lastEventId); - } - - threadPool.addLoad([this, task]() { - std::lock_guard lockAdd(threadSafetyMutex); - auto [item, wasAdded] = eventIds.try_emplace(task->getEventId(), threadPool.getIoContext()); - - asio::steady_timer &timer = item->second; - timer.expires_from_now(std::chrono::milliseconds(task->getDelay())); - - timer.async_wait([this, task](const asio::error_code &error) { - std::lock_guard lockAsyncCallback(threadSafetyMutex); - eventIds.erase(task->getEventId()); - - if (error == asio::error::operation_aborted || threadPool.getIoContext().stopped()) { - return; - } - - if (task->hasTraceableContext()) { - g_logger().trace("Dispatching scheduled task {}.", task->getContext()); - } else { - g_logger().debug("Dispatching scheduled task {}.", task->getContext()); - } - - g_dispatcher().addTask(task); - }); - }); - - return task->getEventId(); -} - -void Scheduler::stopEvent(uint64_t eventId) { - threadPool.addLoad([this, eventId]() { - std::lock_guard lockClass(threadSafetyMutex); - auto it = eventIds.find(eventId); - - if (it != eventIds.end()) { - it->second.cancel(); - } - }); -} diff --git a/src/game/scheduling/scheduler.hpp b/src/game/scheduling/scheduler.hpp deleted file mode 100644 index 79fad1e77..000000000 --- a/src/game/scheduling/scheduler.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Canary - A free and open-source MMORPG server emulator - * Copyright (©) 2019-2022 OpenTibiaBR - * Repository: https://github.com/opentibiabr/canary - * License: https://github.com/opentibiabr/canary/blob/main/LICENSE - * Contributors: https://github.com/opentibiabr/canary/graphs/contributors - * Website: https://docs.opentibiabr.com/ - */ - -#pragma once - -#include "lib/thread/thread_pool.hpp" - -static constexpr int32_t SCHEDULER_MINTICKS = 50; - -class Task; - -/** - * Scheduler allow you to schedule a task async to be executed after a - * given period. Once the time has passed, scheduler calls the task. - */ -class Scheduler { -public: - explicit Scheduler(ThreadPool &threadPool); - - // Ensures that we don't accidentally copy it - Scheduler(const Scheduler &) = delete; - Scheduler operator=(const Scheduler &) = delete; - - static Scheduler &getInstance(); - - uint64_t addEvent(uint32_t delay, std::function f, std::string context); - uint64_t addEvent(const std::shared_ptr task); - void stopEvent(uint64_t eventId); - -private: - ThreadPool &threadPool; - std::mutex threadSafetyMutex; - std::atomic lastEventId { 0 }; - phmap::flat_hash_map eventIds; -}; - -constexpr auto g_scheduler = Scheduler::getInstance; diff --git a/src/game/scheduling/task.cpp b/src/game/scheduling/task.cpp new file mode 100644 index 000000000..c9e6157ab --- /dev/null +++ b/src/game/scheduling/task.cpp @@ -0,0 +1,38 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#include "pch.hpp" +#include "task.hpp" +#include "lib/logging/log_with_spd_log.hpp" + +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 (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()); + } else { + g_logger().debug("Executing task {}.", getContext()); + } + } + + func(); + + return true; +} diff --git a/src/game/scheduling/task.hpp b/src/game/scheduling/task.hpp index a0d02a722..f42602242 100644 --- a/src/game/scheduling/task.hpp +++ b/src/game/scheduling/task.hpp @@ -8,45 +8,81 @@ */ #pragma once +#include "utils/tools.hpp" +#include + +static constexpr auto SYSTEM_TIME_ZERO = std::chrono::system_clock::time_point(std::chrono::milliseconds(0)); class Task { public: - // DO NOT allocate this class on the stack - Task(std::function &&f, std::string context) : - context(std::move(context)), func(std::move(f)) { + static std::chrono::system_clock::time_point TIME_NOW; + + Task(uint32_t expiresAfterMs, std::function &&f, std::string_view context) : + 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 context, uint32_t delay) : - delay(delay), context(std::move(context)), func(std::move(f)) { + Task(std::function &&f, std::string_view context, uint32_t delay, bool cycle = false, bool log = true) : + 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!"); } - virtual ~Task() = default; - void operator()() { - func(); - } + ~Task() = default; - void setEventId(uint64_t id) { - eventId = id; - } + uint64_t getId() { + if (id == 0) { + if (++LAST_EVENT_ID == 0) { + LAST_EVENT_ID = 1; + } - uint64_t getEventId() const { - return eventId; + id = LAST_EVENT_ID; + } + + return id; } uint32_t getDelay() const { return delay; } - std::string getContext() const { + std::string_view getContext() const { return context; } + auto getTime() const { + return utime; + } + + bool hasExpired() const { + return expiration != SYSTEM_TIME_ZERO && expiration < TIME_NOW; + } + + bool isCycle() const { + return cycle; + } + + bool isCanceled() const { + return func == nullptr; + } + + void cancel() { + func = nullptr; + } + + bool execute() const; + +private: + static std::atomic_uint_fast64_t LAST_EVENT_ID; + + void updateTime() { + utime = TIME_NOW + std::chrono::milliseconds(delay); + } + bool hasTraceableContext() const { - return std::set { + const static auto tasksContext = std::unordered_set({ "Creature::checkCreatureWalk", "Decay::checkDecay", + "Dispatcher::asyncEvent", "Game::checkCreatureAttack", "Game::checkCreatures", "Game::checkImbuements", @@ -66,14 +102,29 @@ class Task { "SpawnMonster::scheduleSpawn", "SpawnNpc::checkSpawnNpc", "Webhook::run", - "sendRecvMessageCallback", - } - .contains(context); + "Protocol::sendRecvMessageCallback", + }); + + return tasksContext.contains(context); } -private: + struct Compare { + bool operator()(const std::shared_ptr &a, const std::shared_ptr &b) const { + return a->utime < b->utime; + } + }; + + 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; + + uint64_t id = 0; uint32_t delay = 0; - uint64_t eventId = 0; - std::string context {}; - std::function func {}; + + bool cycle = false; + bool log = true; + + friend class Dispatcher; }; diff --git a/src/game/zones/zone.cpp b/src/game/zones/zone.cpp index f1aa300ef..fdeccb9bf 100644 --- a/src/game/zones/zone.cpp +++ b/src/game/zones/zone.cpp @@ -14,46 +14,51 @@ #include "creatures/monsters/monster.hpp" #include "creatures/npcs/npc.hpp" #include "creatures/players/player.hpp" +#include "utils/pugicast.hpp" phmap::parallel_flat_hash_map> Zone::zones = {}; +phmap::parallel_flat_hash_map> Zone::zonesByID = {}; const static std::shared_ptr nullZone = nullptr; -std::shared_ptr Zone::addZone(const std::string &name) { +std::shared_ptr Zone::addZone(const std::string &name, uint32_t zoneID /* = 0 */) { if (name == "default") { g_logger().error("Zone name {} is reserved", name); return nullZone; } + if (zoneID != 0 && zonesByID.contains(zoneID)) { + g_logger().debug("Found with ID {} while adding {}, linking them together...", zoneID, name); + auto zone = zonesByID[zoneID]; + zone->name = name; + zones[name] = zone; + return zone; + } + if (zones[name]) { g_logger().error("Zone {} already exists", name); return nullZone; } - zones[name] = std::make_shared(name); + zones[name] = std::make_shared(name, zoneID); + if (zoneID != 0) { + zonesByID[zoneID] = zones[name]; + } return zones[name]; } void Zone::addArea(Area area) { - for (const Position &pos : area) { - positions.insert(pos); + for (const auto &pos : area) { + addPosition(pos); } refresh(); } void Zone::subtractArea(Area area) { - for (const Position &pos : area) { - positions.erase(pos); - std::shared_ptr tile = g_game().map.getTile(pos); - if (tile) { - for (auto item : *tile->getItemList()) { - itemRemoved(item); - } - for (auto creature : *tile->getCreatures()) { - creatureRemoved(creature); - } - } + for (const auto &pos : area) { + removePosition(pos); } + refresh(); } -bool Zone::isPositionInZone(const Position &pos) const { +bool Zone::contains(const Position &pos) const { return positions.contains(pos); } @@ -74,115 +79,106 @@ std::shared_ptr Zone::getZone(const std::string &name) { return zones[name]; } -const phmap::parallel_flat_hash_set &Zone::getPositions() const { - return positions; +std::shared_ptr Zone::getZone(uint32_t zoneID) { + if (zoneID == 0) { + return nullZone; + } + if (zonesByID.contains(zoneID)) { + return zonesByID[zoneID]; + } + auto zone = std::make_shared(zoneID); + zonesByID[zoneID] = zone; + return zone; } -const phmap::parallel_flat_hash_set> &Zone::getTiles() const { - static phmap::parallel_flat_hash_set> tiles; - tiles.clear(); - for (const auto &position : positions) { - const auto tile = g_game().map.getTile(position); - if (tile) { - tiles.insert(tile); - } +std::vector Zone::getPositions() const { + std::vector result; + for (const auto &pos : positions) { + result.push_back(pos); } - return tiles; + return result; } -const phmap::parallel_flat_hash_set> &Zone::getCreatures() const { - static phmap::parallel_flat_hash_set> creatures; - creatures.clear(); - for (const auto creatureId : creaturesCache) { - const auto creature = g_game().getCreatureByID(creatureId); - if (creature) { - creatures.insert(creature); - } - } - return creatures; +std::vector> Zone::getCreatures() { + return weak::lock(creaturesCache); } -const phmap::parallel_flat_hash_set> &Zone::getPlayers() const { - static phmap::parallel_flat_hash_set> players; - players.clear(); - for (const auto playerId : playersCache) { - const auto player = g_game().getPlayerByID(playerId); - if (player) { - players.insert(player); - } - } - return players; +std::vector> Zone::getPlayers() { + return weak::lock(playersCache); } -const phmap::parallel_flat_hash_set> &Zone::getMonsters() const { - static phmap::parallel_flat_hash_set> monsters; - monsters.clear(); - for (const auto monsterId : monstersCache) { - const auto monster = g_game().getMonsterByID(monsterId); - if (monster) { - monsters.insert(monster); - } - } - return monsters; +std::vector> Zone::getMonsters() { + return weak::lock(monstersCache); } -const phmap::parallel_flat_hash_set> &Zone::getNpcs() const { - static phmap::parallel_flat_hash_set> npcs; - npcs.clear(); - for (const auto npcId : npcsCache) { - const auto npc = g_game().getNpcByID(npcId); - if (npc) { - npcs.insert(npc); - } - } - return npcs; +std::vector> Zone::getNpcs() { + return weak::lock(npcsCache); } -const phmap::parallel_flat_hash_set> &Zone::getItems() const { - return itemsCache; +std::vector> Zone::getItems() { + return weak::lock(itemsCache); } -void Zone::removePlayers() const { - for (auto player : getPlayers()) { +void Zone::removePlayers() { + for (const auto &player : getPlayers()) { g_game().internalTeleport(player, getRemoveDestination(player)); } } -void Zone::removeMonsters() const { - for (auto monster : getMonsters()) { - g_game().removeCreature(monster); +void Zone::removeMonsters() { + for (const auto &monster : getMonsters()) { + g_game().removeCreature(monster->getCreature()); } } -void Zone::removeNpcs() const { - for (auto npc : getNpcs()) { - g_game().removeCreature(npc); +void Zone::removeNpcs() { + for (const auto &npc : getNpcs()) { + g_game().removeCreature(npc->getCreature()); } } void Zone::clearZones() { + for (const auto &[_, zone] : zones) { + // do not clear zones loaded from the map (id > 0) + if (!zone || zone->isStatic()) { + continue; + } + zone->refresh(); + } zones.clear(); + for (const auto &[_, zone] : zonesByID) { + zones[zone->name] = zone; + } } -phmap::parallel_flat_hash_set> Zone::getZones(const Position postion) { - phmap::parallel_flat_hash_set> zonesSet; +std::vector> Zone::getZones(const Position position) { + Benchmark bm_getZones; + std::vector> result; for (const auto &[_, zone] : zones) { - if (zone && zone->isPositionInZone(postion)) { - zonesSet.insert(zone); + if (zone && zone->contains(position)) { + result.push_back(zone); } } - return zonesSet; + auto duration = bm_getZones.duration(); + if (duration > 100) { + g_logger().warn("Listed {} zones for position {} in {} milliseconds", result.size(), position.toString(), duration); + } + return result; } -const phmap::parallel_flat_hash_set> &Zone::getZones() { - static phmap::parallel_flat_hash_set> zonesSet; - zonesSet.clear(); +std::vector> Zone::getZones() { + Benchmark bm_getZones; + std::vector> result; for (const auto &[_, zone] : zones) { if (zone) { - zonesSet.insert(zone); + result.push_back(zone); } } - return zonesSet; + auto duration = bm_getZones.duration(); + if (duration > 100) { + g_logger().warn("Listed {} zones in {} milliseconds", result.size(), duration); + } + return result; } void Zone::creatureAdded(const std::shared_ptr &creature) { @@ -190,48 +186,25 @@ void Zone::creatureAdded(const std::shared_ptr &creature) { return; } - uint32_t id = 0; - if (creature->getPlayer()) { - id = creature->getPlayer()->getID(); - auto [_, playerInserted] = playersCache.insert(id); - if (playerInserted) { - g_logger().trace("Player {} (ID: {}) added to zone {}", creature->getName(), id, name); - } - } - if (creature->getMonster()) { - id = creature->getMonster()->getID(); - auto [_, monsterInserted] = monstersCache.insert(id); - if (monsterInserted) { - g_logger().trace("Monster {} (ID: {}) added to zone {}", creature->getName(), id, name); - } - } - if (creature->getNpc()) { - id = creature->getNpc()->getID(); - auto [_, npcInserted] = npcsCache.insert(id); - if (npcInserted) { - g_logger().trace("Npc {} (ID: {}) added to zone {}", creature->getName(), id, name); - } + if (const auto &player = creature->getPlayer()) { + playersCache.insert(player); + } else if (const auto &monster = creature->getMonster()) { + monstersCache.insert(monster); + } else if (const auto &npc = creature->getNpc()) { + npcsCache.insert(npc); } - if (id != 0) { - creaturesCache.insert(id); - } + creaturesCache.insert(creature); } void Zone::creatureRemoved(const std::shared_ptr &creature) { if (!creature) { return; } - creaturesCache.erase(creature->getID()); - if (creature->getPlayer() && playersCache.erase(creature->getID())) { - g_logger().trace("Player {} (ID: {}) removed from zone {}", creature->getName(), creature->getID(), name); - } - if (creature->getMonster() && monstersCache.erase(creature->getID())) { - g_logger().trace("Monster {} (ID: {}) removed from zone {}", creature->getName(), creature->getID(), name); - } - if (creature->getNpc() && npcsCache.erase(creature->getID())) { - g_logger().trace("Npc {} (ID: {}) removed from zone {}", creature->getName(), creature->getID(), name); - } + creaturesCache.erase(creature); + playersCache.erase(creature->getPlayer()); + monstersCache.erase(creature->getMonster()); + npcsCache.erase(creature->getNpc()); } void Zone::thingAdded(const std::shared_ptr &thing) { @@ -239,10 +212,10 @@ void Zone::thingAdded(const std::shared_ptr &thing) { return; } - if (thing->getItem()) { - itemAdded(thing->getItem()); - } else if (thing->getCreature()) { - creatureAdded(thing->getCreature()); + if (const auto &item = thing->getItem()) { + itemAdded(item); + } else if (const auto &creature = thing->getCreature()) { + creatureAdded(creature); } } @@ -261,30 +234,45 @@ void Zone::itemRemoved(const std::shared_ptr &item) { } void Zone::refresh() { + Benchmark bm_refresh; creaturesCache.clear(); monstersCache.clear(); npcsCache.clear(); playersCache.clear(); itemsCache.clear(); - for (const auto &position : positions) { - const auto tile = g_game().map.getTile(position); - if (!tile) { - continue; - } - const auto &items = tile->getItemList(); - if (!items) { - continue; - } - for (const auto &item : *items) { - itemAdded(item); - } - const auto &creatures = tile->getCreatures(); - if (!creatures) { + for (const auto &position : getPositions()) { + g_game().map.refreshZones(position); + } + g_logger().debug("Refreshed zone '{}' in {} milliseconds", name, bm_refresh.duration()); +} + +void Zone::setMonsterVariant(const std::string &variant) { + monsterVariant = variant; + g_logger().debug("Zone {} monster variant set to {}", name, variant); + for (auto &spawnMonster : g_game().map.spawnsMonster.getspawnMonsterList()) { + if (!contains(spawnMonster.getCenterPos())) { continue; } - for (const auto &creature : *creatures) { - creatureAdded(creature); - } + spawnMonster.setMonsterVariant(variant); + } + + removeMonsters(); +} + +bool Zone::loadFromXML(const std::string &fileName, uint16_t shiftID /* = 0 */) { + pugi::xml_document doc; + g_logger().debug("Loading zones from {}", fileName); + pugi::xml_parse_result result = doc.load_file(fileName.c_str()); + if (!result) { + printXMLError(__FUNCTION__, fileName, result); + return false; + } + + for (auto zoneNode : doc.child("zones").children()) { + auto name = zoneNode.attribute("name").value(); + auto zoneId = pugi::cast(zoneNode.attribute("zoneid").value()) << shiftID; + addZone(name, zoneId); } + return true; } diff --git a/src/game/zones/zone.hpp b/src/game/zones/zone.hpp index 0aa5e76fd..4a0c03180 100644 --- a/src/game/zones/zone.hpp +++ b/src/game/zones/zone.hpp @@ -9,8 +9,10 @@ #pragma once +#include #include "game/movement/position.hpp" #include "items/item.hpp" +#include "creatures/creature.hpp" class Tile; class Creature; @@ -40,6 +42,10 @@ struct Area { Position from; Position to; + std::string toString() const { + return fmt::format("Area(from: {}, to: {})", from.toString(), to.toString()); + } + class PositionIterator { public: PositionIterator(Position startPosition, const Area &refArea) : @@ -79,10 +85,69 @@ struct Area { } }; +namespace weak { + template + struct ThingHasher { + std::size_t operator()(std::weak_ptr thing) const { + if (thing.expired()) { + return 0; + } + return std::hash {}(thing.lock().get()); + } + }; + + template + struct ThingComparator { + bool operator()(const std::weak_ptr &lhs, const std::weak_ptr &rhs) const { + return lhs.lock() == rhs.lock(); + } + }; + + template <> + struct ThingHasher { + std::size_t operator()(const std::weak_ptr &weakCreature) const { + auto locked = weakCreature.lock(); + if (!locked) { + return 0; + } + return std::hash {}(locked->getID()); + } + }; + + template <> + struct ThingComparator { + bool operator()(const std::weak_ptr &lhs, const std::weak_ptr &rhs) const { + if (lhs.expired() || rhs.expired()) { + return false; + } + return lhs.lock()->getID() == rhs.lock()->getID(); + } + }; + + template + using set = std::unordered_set, ThingHasher, ThingComparator>; + + template + std::vector> lock(set &weakSet) { + std::vector> result; + for (auto it = weakSet.begin(); it != weakSet.end();) { + if (it->expired()) { + it = weakSet.erase(it); + } else { + result.push_back(it->lock()); + ++it; + } + } + return result; + } +} + class Zone { public: - explicit Zone(const std::string &name) : - name(name) { } + explicit Zone(const std::string &name, uint32_t id = 0) : + name(name), id(id) { } + explicit Zone(uint32_t id) : + id(id) { } // Deleted copy constructor and assignment operator. Zone(const Zone &) = delete; @@ -93,19 +158,23 @@ class Zone { } void addArea(Area area); void subtractArea(Area area); - bool isPositionInZone(const Position &position) const; + void addPosition(const Position &position) { + positions.emplace(position); + } + void removePosition(const Position &position) { + positions.erase(position); + } Position getRemoveDestination(const std::shared_ptr &creature = nullptr) const; void setRemoveDestination(const Position &position) { removeDestination = position; } - const phmap::parallel_flat_hash_set &getPositions() const; - const phmap::parallel_flat_hash_set> &getTiles() const; - const phmap::parallel_flat_hash_set> &getCreatures() const; - const phmap::parallel_flat_hash_set> &getPlayers() const; - const phmap::parallel_flat_hash_set> &getMonsters() const; - const phmap::parallel_flat_hash_set> &getNpcs() const; - const phmap::parallel_flat_hash_set> &getItems() const; + std::vector getPositions() const; + std::vector> getCreatures(); + std::vector> getPlayers(); + std::vector> getMonsters(); + std::vector> getNpcs(); + std::vector> getItems(); void creatureAdded(const std::shared_ptr &creature); void creatureRemoved(const std::shared_ptr &creature); @@ -113,28 +182,50 @@ class Zone { void itemAdded(const std::shared_ptr &item); void itemRemoved(const std::shared_ptr &item); - void removePlayers() const; - void removeMonsters() const; - void removeNpcs() const; + void removePlayers(); + void removeMonsters(); + void removeNpcs(); void refresh(); - static std::shared_ptr addZone(const std::string &name); + void setMonsterVariant(const std::string &variant); + const std::string &getMonsterVariant() const { + return monsterVariant; + } + + bool isStatic() const { + return id != 0; + } + + static std::shared_ptr addZone(const std::string &name, uint32_t id = 0); static std::shared_ptr getZone(const std::string &name); - static phmap::parallel_flat_hash_set> getZones(const Position position); - const static phmap::parallel_flat_hash_set> &getZones(); + static std::shared_ptr getZone(uint32_t id); + static std::vector> getZones(const Position position); + static std::vector> getZones(); + static void refreshAll() { + for (const auto &[_, zone] : zones) { + zone->refresh(); + } + } static void clearZones(); + static bool loadFromXML(const std::string &fileName, uint16_t shiftID = 0); + private: + bool contains(const Position &position) const; + Position removeDestination = Position(); std::string name; - phmap::parallel_flat_hash_set positions; + std::string monsterVariant; + std::unordered_set positions; + uint32_t id = 0; // ID 0 is used in zones created dynamically from lua. The map editor uses IDs starting from 1 (automatically generated). - phmap::parallel_flat_hash_set> itemsCache; - phmap::parallel_flat_hash_set creaturesCache; - phmap::parallel_flat_hash_set monstersCache; - phmap::parallel_flat_hash_set npcsCache; - phmap::parallel_flat_hash_set playersCache; + weak::set itemsCache; + weak::set creaturesCache; + weak::set monstersCache; + weak::set npcsCache; + weak::set playersCache; static phmap::parallel_flat_hash_map> zones; + static phmap::parallel_flat_hash_map> zonesByID; }; diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 0b1cf0c1f..f404a52b8 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -30,8 +30,6 @@ void IOLoginDataLoad::loadItems(ItemsMap &itemsMap, DBResult_ptr result, const s if (item) { if (!item->unserializeAttr(propStream)) { g_logger().warn("[{}] - Failed to deserialize item attributes {}, from player {}, from account id {}", __FUNCTION__, item->getID(), player->getName(), player->getAccountId()); - g_logger().info("[{}] - Deleting wrong item: {}", __FUNCTION__, item->getID()); - continue; } itemsMap[sid] = std::make_pair(item, pid); diff --git a/src/io/io_bosstiary.cpp b/src/io/io_bosstiary.cpp index cda3336af..e4aecee5f 100644 --- a/src/io/io_bosstiary.cpp +++ b/src/io/io_bosstiary.cpp @@ -69,6 +69,10 @@ void IOBosstiary::loadBoostedBoss() { while (true) { uint32_t randomIndex = uniform_random(0, static_cast(bossInfo.size())); auto it = std::next(bossInfo.begin(), randomIndex); + if (it == bossInfo.end()) { + break; + } + const auto &[randomBossId, randomBossName] = *it; if (randomBossId == oldBossRace) { continue; @@ -191,7 +195,6 @@ void IOBosstiary::addBosstiaryKill(std::shared_ptr player, const std::sh int32_t value = player->getStorageValue(STORAGEVALUE_PODIUM); if (value != 1 && newBossLevel == 2) { - auto returnValue = g_game().addItemStoreInbox(player, ITEM_PODIUM_OF_VIGOUR); if (!returnValue) { return; @@ -300,34 +303,6 @@ uint32_t IOBosstiary::calculteRemoveBoss(uint8_t removeTimes) const { return 300000 * removeTimes - 500000; } -std::vector IOBosstiary::getBosstiaryCooldownRaceId(std::shared_ptr player) const { - std::vector bossesCooldownRaceId; - if (!player) { - return bossesCooldownRaceId; - } - - for (std::map bossesMap = getBosstiaryMap(); - const auto &[bossId, bossName] : bossesMap) { - uint32_t bossKills = player->getBestiaryKillCount(bossId); - - const auto mType = g_monsters().getMonsterType(bossName); - if (!mType) { - continue; - } - - auto bossStorage = mType->info.bossStorageCooldown; - if (bossStorage == 0) { - continue; - } - - if (bossKills >= 1 || player->getStorageValue(bossStorage) > 0) { - bossesCooldownRaceId.push_back(bossId); - } - } - - return bossesCooldownRaceId; -} - const std::vector &IOBosstiary::getBossRaceKillStages(BosstiaryRarity_t race) const { auto it = levelInfos.find(race); if (it != levelInfos.end()) { diff --git a/src/io/io_bosstiary.hpp b/src/io/io_bosstiary.hpp index bbb1367e8..a253be3b4 100644 --- a/src/io/io_bosstiary.hpp +++ b/src/io/io_bosstiary.hpp @@ -66,7 +66,6 @@ class IOBosstiary { phmap::parallel_flat_hash_set getBosstiaryFinished(std::shared_ptr player, uint8_t level = 1) const; uint8_t getBossCurrentLevel(std::shared_ptr player, uint16_t bossId) const; uint32_t calculteRemoveBoss(uint8_t removeTimes) const; - std::vector getBosstiaryCooldownRaceId(std::shared_ptr player) const; const std::vector &getBossRaceKillStages(BosstiaryRarity_t race) const; private: diff --git a/src/io/io_definitions.hpp b/src/io/io_definitions.hpp index 5a7d9bea6..22d61883a 100644 --- a/src/io/io_definitions.hpp +++ b/src/io/io_definitions.hpp @@ -45,7 +45,8 @@ enum OTBM_AttrTypes_t { OTBM_ATTR_SLEEPERGUID = 20, OTBM_ATTR_SLEEPSTART = 21, OTBM_ATTR_CHARGES = 22, - OTBM_ATTR_EXT_SPAWN_NPC_FILE = 23 + OTBM_ATTR_EXT_SPAWN_NPC_FILE = 23, + OTBM_ATTR_EXT_ZONE_FILE = 24, }; enum OTBM_NodeTypes_t { @@ -65,6 +66,7 @@ enum OTBM_NodeTypes_t { OTBM_HOUSETILE = 14, OTBM_WAYPOINTS = 15, OTBM_WAYPOINT = 16, + OTBM_TILE_ZONE = 19 }; enum OTBM_TileFlag_t : uint32_t { diff --git a/src/io/iobestiary.cpp b/src/io/iobestiary.cpp index 079742889..c9fb933bc 100644 --- a/src/io/iobestiary.cpp +++ b/src/io/iobestiary.cpp @@ -403,7 +403,7 @@ phmap::parallel_flat_hash_set IOBestiary::getBestiaryFinished(std::sha for (const auto &[monsterTypeRaceId, monsterTypeName] : bestiaryMap) { uint32_t thisKilled = player->getBestiaryKillCount(monsterTypeRaceId); auto mtype = g_monsters().getMonsterType(monsterTypeName); - if (mtype && thisKilled >= mtype->info.bestiaryFirstUnlock) { + if (mtype && thisKilled >= mtype->info.bestiaryToUnlock) { finishedMonsters.insert(monsterTypeRaceId); } } diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index f32b90568..d454cd32b 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -362,7 +362,9 @@ void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::stri std::ostringstream query; query << "INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES (" << accountId << ',' << guid << ',' << db.escapeString(description) << ',' << icon << ',' << notify << ')'; - db.executeQuery(query.str()); + if (!db.executeQuery(query.str())) { + g_logger().error("Failed to add VIP entry for account %u. QUERY: %s", accountId, query.str().c_str()); + } } void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string &description, uint32_t icon, bool notify) { @@ -370,7 +372,9 @@ void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::str std::ostringstream query; query << "UPDATE `account_viplist` SET `description` = " << db.escapeString(description) << ", `icon` = " << icon << ", `notify` = " << notify << " WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; - db.executeQuery(query.str()); + if (!db.executeQuery(query.str())) { + g_logger().error("Failed to edit VIP entry for account %u. QUERY: %s", accountId, query.str().c_str()); + } } void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) { diff --git a/src/io/iomap.cpp b/src/io/iomap.cpp index 72eaed123..f11e33204 100644 --- a/src/io/iomap.cpp +++ b/src/io/iomap.cpp @@ -77,7 +77,7 @@ void IOMap::loadMap(Map* map, const Position &pos) { map->flush(); - g_logger().info("Map Loaded {} ({}x{}) in {} seconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); + g_logger().info("Map Loaded {} ({}x{}) in {} milliseconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); } void IOMap::parseMapDataAttributes(FileStream &stream, Map* map) { @@ -103,6 +103,11 @@ void IOMap::parseMapDataAttributes(FileStream &stream, Map* map) { map->housefile += stream.getString(); } break; + case OTBM_ATTR_EXT_ZONE_FILE: { + map->zonesfile = map->path.string().substr(0, map->path.string().rfind('/') + 1); + map->zonesfile += stream.getString(); + } break; + default: stream.back(); end = true; @@ -182,36 +187,50 @@ void IOMap::parseTileArea(FileStream &stream, Map &map, const Position &pos) { } while (stream.startNode()) { - if (stream.getU8() != OTBM_ITEM) { - throw IOMapException(fmt::format("[x:{}, y:{}, z:{}] Could not read item node.", x, y, z)); - } - - const uint16_t id = stream.getU16(); - - const auto &iType = Item::items[id]; - - if (iType.blockSolid) { - tileIsStatic = true; - } - - const auto item = std::make_shared(); - item->id = id; - - if (!item->unserializeItemNode(stream, x, y, z)) { - throw IOMapException(fmt::format("[x:{}, y:{}, z:{}] Failed to load item {}, Node Type.", x, y, z, id)); - } - - if (tile->isHouse() && iType.isBed()) { - // nothing - } else if (tile->isHouse() && iType.moveable) { - g_logger().warn("[IOMap::loadMap] - " - "Moveable item with ID: {}, in house: {}, " - "at position: x {}, y {}, z {}", - id, tile->houseId, x, y, z); - } else if (iType.isGroundTile()) { - tile->ground = map.tryReplaceItemFromCache(item); - } else { - tile->items.emplace_back(map.tryReplaceItemFromCache(item)); + auto type = stream.getU8(); + switch (type) { + case OTBM_ITEM: { + const uint16_t id = stream.getU16(); + + const auto &iType = Item::items[id]; + + if (iType.blockSolid) { + tileIsStatic = true; + } + + const auto item = std::make_shared(); + item->id = id; + + if (!item->unserializeItemNode(stream, x, y, z)) { + throw IOMapException(fmt::format("[x:{}, y:{}, z:{}] Failed to load item {}, Node Type.", x, y, z, id)); + } + + if (tile->isHouse() && iType.isBed()) { + // nothing + } else if (tile->isHouse() && iType.moveable) { + g_logger().warn("[IOMap::loadMap] - " + "Moveable item with ID: {}, in house: {}, " + "at position: x {}, y {}, z {}", + id, tile->houseId, x, y, z); + } else if (iType.isGroundTile()) { + tile->ground = map.tryReplaceItemFromCache(item); + } else { + tile->items.emplace_back(map.tryReplaceItemFromCache(item)); + } + } break; + case OTBM_TILE_ZONE: { + const auto zoneCount = stream.getU16(); + for (uint16_t i = 0; i < zoneCount; ++i) { + const auto zoneId = stream.getU16(); + if (!zoneId) { + throw IOMapException(fmt::format("[x:{}, y:{}, z:{}] Invalid zone id.", x, y, z)); + } + auto zone = Zone::getZone(zoneId); + zone->addPosition(Position(x, y, z)); + } + } break; + default: + throw IOMapException(fmt::format("[x:{}, y:{}, z:{}] Could not read item/zone node.", x, y, z)); } if (!stream.endNode()) { diff --git a/src/io/iomap.hpp b/src/io/iomap.hpp index d483ad58b..0c93b4a8e 100644 --- a/src/io/iomap.hpp +++ b/src/io/iomap.hpp @@ -17,6 +17,7 @@ #include "map/map.hpp" #include "creatures/monsters/spawns/spawn_monster.hpp" #include "creatures/npcs/spawns/spawn_npc.hpp" +#include "game/zones/zone.hpp" class IOMap { public: @@ -38,6 +39,22 @@ class IOMap { return map->spawnsMonster.loadFromXML(map->monsterfile); } + /** + * Load main map zones + * \param map Is the map class + * \returns true if the zones spawn map was loaded successfully + */ + static bool loadZones(Map* map) { + if (map->zonesfile.empty()) { + // OTBM file doesn't tell us about the zonesfile, + // Lets guess it is mapname-zone.xml. + map->zonesfile = g_configManager().getString(MAP_NAME); + map->zonesfile += "-zones.xml"; + } + + return Zone::loadFromXML(map->zonesfile); + } + /** * Load main map npcs * \param map Is the map class @@ -85,6 +102,21 @@ class IOMap { return map->spawnsMonsterCustomMaps[customMapIndex].loadFromXML(map->monsterfile); } + /** + * Load custom map zones + * \param map Is the map class + * \returns true if the zones spawn map custom was loaded successfully + */ + static bool loadZonesCustom(Map* map, const std::string &mapName, int customMapIndex) { + if (map->zonesfile.empty()) { + // OTBM file doesn't tell us about the zonesfile, + // Lets guess it is mapname-zones.xml. + map->zonesfile = mapName; + map->zonesfile += "-zones.xml"; + } + return Zone::loadFromXML(map->zonesfile, customMapIndex); + } + /** * Load custom map npcs * \param map Is the map class diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp index 94531a612..8e35f5acb 100644 --- a/src/io/iomapserialize.cpp +++ b/src/io/iomapserialize.cpp @@ -49,7 +49,7 @@ void IOMapSerialize::loadHouseItems(Map* map) { loadItem(propStream, tile, true); } } while (result->next()); - g_logger().info("Loaded house items in {} seconds", bm_context.duration()); + g_logger().info("Loaded house items in {} milliseconds", bm_context.duration()); } bool IOMapSerialize::saveHouseItems() { bool success = DBTransaction::executeWithinTransaction([]() { @@ -64,8 +64,6 @@ bool IOMapSerialize::saveHouseItems() { } bool IOMapSerialize::SaveHouseItemsGuard() { - Benchmark bm_context; - Database &db = Database::getInstance(); std::ostringstream query; @@ -79,7 +77,7 @@ bool IOMapSerialize::SaveHouseItemsGuard() { PropWriteStream stream; for (const auto &[key, house] : g_game().map.houses.getHouses()) { // save house items - for (std::shared_ptr tile : house->getTiles()) { + for (const auto &tile : house->getTiles()) { saveTile(stream, tile); size_t attributesSize; @@ -98,7 +96,6 @@ bool IOMapSerialize::SaveHouseItemsGuard() { return false; } - g_logger().info("Saved house items in {} seconds", bm_context.duration()); return true; } @@ -236,7 +233,12 @@ void IOMapSerialize::saveTile(PropWriteStream &stream, std::shared_ptr til std::forward_list> items; uint16_t count = 0; for (auto &item : *tileItems) { - if (!item->isSavedToHouses()) { + if (item->getID() == ITEM_BATHTUB_FILLED_NOTMOVABLE) { + std::shared_ptr tub = Item::CreateItem(ITEM_BATHTUB_FILLED); + items.push_front(tub); + ++count; + continue; + } else if (!item->isSavedToHouses()) { continue; } diff --git a/src/io/iomarket.cpp b/src/io/iomarket.cpp index 8feb7f38d..d6b610930 100644 --- a/src/io/iomarket.cpp +++ b/src/io/iomarket.cpp @@ -13,7 +13,8 @@ #include "database/databasetasks.hpp" #include "io/iologindata.hpp" #include "game/game.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" uint8_t IOMarket::getTierFromDatabaseTable(const std::string &string) { auto tier = static_cast(std::atoi(string.c_str())); @@ -135,7 +136,6 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) { if (!player) { player = std::make_shared(nullptr); if (!IOLoginData::loadPlayerById(player, playerId)) { - continue; } } @@ -168,7 +168,6 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) { for (uint16_t i = 0; i < amount; ++i) { std::shared_ptr item = Item::CreateItem(itemType.id, subType); if (g_game().internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - break; } @@ -179,7 +178,7 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool) { } if (player->isOffline()) { - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } } else { uint64_t totalPrice = result->getNumber("price") * amount; @@ -206,7 +205,7 @@ void IOMarket::checkExpiredOffers() { return; } - g_scheduler().addEvent(checkExpiredMarketOffersEachMinutes * 60 * 1000, IOMarket::checkExpiredOffers, __FUNCTION__); + g_dispatcher().scheduleEvent(checkExpiredMarketOffersEachMinutes * 60 * 1000, IOMarket::checkExpiredOffers, __FUNCTION__); } uint32_t IOMarket::getPlayerOfferCount(uint32_t playerId) { diff --git a/src/items/bed.cpp b/src/items/bed.cpp index 6c93e988a..289ae210a 100644 --- a/src/items/bed.cpp +++ b/src/items/bed.cpp @@ -12,7 +12,8 @@ #include "items/bed.hpp" #include "game/game.hpp" #include "io/iologindata.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" BedItem::BedItem(uint16_t id) : Item(id) { @@ -153,7 +154,7 @@ bool BedItem::sleep(std::shared_ptr player) { g_game().addMagicEffect(player->getPosition(), CONST_ME_SLEEP); // logout player after he sees himself walk onto the bed and it change id - g_scheduler().addEvent(SCHEDULER_MINTICKS, std::bind(&ProtocolGame::logout, player->client, false, false), "ProtocolGame::logout"); + g_dispatcher().scheduleEvent(SCHEDULER_MINTICKS, std::bind(&ProtocolGame::logout, player->client, false, false), "ProtocolGame::logout"); // change self and partner's appearance updateAppearance(player); @@ -178,7 +179,7 @@ void BedItem::wakeUp(std::shared_ptr player) { auto regenPlayer = std::make_shared(nullptr); if (IOLoginData::loadPlayerById(regenPlayer, sleeperGUID)) { regeneratePlayer(regenPlayer); - IOLoginData::savePlayer(regenPlayer); + g_saveManager().savePlayer(regenPlayer); } } else { regeneratePlayer(player); diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp index 67884eb4f..54ce00ace 100644 --- a/src/items/containers/container.cpp +++ b/src/items/containers/container.cpp @@ -58,7 +58,7 @@ std::shared_ptr Container::create(std::shared_ptr tile) { Container::~Container() { if (getID() == ITEM_BROWSEFIELD) { for (std::shared_ptr item : itemlist) { - item->setParent(m_parent); + item->setParent(getParent()); } } } @@ -361,6 +361,29 @@ bool Container::isHoldingItemWithId(const uint16_t id) { return false; } +bool Container::isInsideContainerWithId(const uint16_t id) { + auto nextParent = getParent(); + while (nextParent != nullptr && nextParent->getContainer()) { + if (nextParent->getContainer()->getID() == id) { + return true; + } + nextParent = nextParent->getRealParent(); + } + return false; +} + +bool Container::isAnyKindOfRewardChest() { + return getID() == ITEM_REWARD_CHEST || getID() == ITEM_REWARD_CONTAINER && getParent() && getParent()->getContainer() && getParent()->getContainer()->getID() == ITEM_REWARD_CHEST || isBrowseFieldAndHoldsRewardChest(); +} + +bool Container::isAnyKindOfRewardContainer() { + return getID() == ITEM_REWARD_CHEST || getID() == ITEM_REWARD_CONTAINER || isHoldingItemWithId(ITEM_REWARD_CONTAINER) || isInsideContainerWithId(ITEM_REWARD_CONTAINER); +} + +bool Container::isBrowseFieldAndHoldsRewardChest() { + return getID() == ITEM_BROWSEFIELD && isHoldingItemWithId(ITEM_REWARD_CHEST); +} + void Container::onAddContainerItem(std::shared_ptr item) { auto spectators = Spectators().find(getPosition(), false, 2, 2, 2, 2); diff --git a/src/items/containers/container.hpp b/src/items/containers/container.hpp index 815611200..0b014aee5 100644 --- a/src/items/containers/container.hpp +++ b/src/items/containers/container.hpp @@ -161,6 +161,11 @@ class Container : public Item, public Cylinder { virtual void removeItem(std::shared_ptr thing, bool sendUpdateToClient = false); + bool isAnyKindOfRewardChest(); + bool isAnyKindOfRewardContainer(); + bool isBrowseFieldAndHoldsRewardChest(); + bool isInsideContainerWithId(const uint16_t id); + protected: std::ostringstream &getContentDescription(std::ostringstream &os, bool oldProtocol); diff --git a/src/items/containers/mailbox/mailbox.cpp b/src/items/containers/mailbox/mailbox.cpp index 959ca818b..9253bdd74 100644 --- a/src/items/containers/mailbox/mailbox.cpp +++ b/src/items/containers/mailbox/mailbox.cpp @@ -12,6 +12,7 @@ #include "items/containers/mailbox/mailbox.hpp" #include "game/game.hpp" #include "io/iologindata.hpp" +#include "game/scheduling/save_manager.hpp" #include "map/spectators.hpp" ReturnValue Mailbox::queryAdd(int32_t, const std::shared_ptr &thing, uint32_t, uint32_t, std::shared_ptr) { @@ -107,7 +108,7 @@ bool Mailbox::sendItem(std::shared_ptr item) const { if (player->isOnline()) { player->onReceiveMail(); } else { - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } return true; } diff --git a/src/items/decay/decay.cpp b/src/items/decay/decay.cpp index 77c7eb9f3..a1b709b44 100644 --- a/src/items/decay/decay.cpp +++ b/src/items/decay/decay.cpp @@ -11,7 +11,7 @@ #include "items/decay/decay.hpp" #include "game/game.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" void Decay::startDecay(std::shared_ptr item) { if (!item) { @@ -41,11 +41,11 @@ void Decay::startDecay(std::shared_ptr item) { int64_t timestamp = OTSYS_TIME() + duration; if (decayMap.empty()) { - eventId = g_scheduler().addEvent(std::max(SCHEDULER_MINTICKS, duration), std::bind(&Decay::checkDecay, this), "Decay::checkDecay"); + eventId = g_dispatcher().scheduleEvent(std::max(SCHEDULER_MINTICKS, duration), std::bind(&Decay::checkDecay, this), "Decay::checkDecay"); } else { if (timestamp < decayMap.begin()->first) { - g_scheduler().stopEvent(eventId); - eventId = g_scheduler().addEvent(std::max(SCHEDULER_MINTICKS, duration), std::bind(&Decay::checkDecay, this), "Decay::checkDecay"); + g_dispatcher().stopEvent(eventId); + eventId = g_dispatcher().scheduleEvent(std::max(SCHEDULER_MINTICKS, duration), std::bind(&Decay::checkDecay, this), "Decay::checkDecay"); } } @@ -132,12 +132,24 @@ void Decay::checkDecay() { } if (it != end) { - eventId = g_scheduler().addEvent(std::max(SCHEDULER_MINTICKS, static_cast(it->first - timestamp)), std::bind(&Decay::checkDecay, this), "Decay::checkDecay"); + eventId = g_dispatcher().scheduleEvent(std::max(SCHEDULER_MINTICKS, static_cast(it->first - timestamp)), std::bind(&Decay::checkDecay, this), "Decay::checkDecay"); } } void Decay::internalDecayItem(std::shared_ptr item) { const ItemType &it = Item::items[item->getID()]; + // Remove the item and halt the decay process if a player triggers a bug where the item's decay ID matches its equip or de-equip transformation ID + if (it.id == it.transformEquipTo || it.id == it.transformDeEquipTo) { + g_game().internalRemoveItem(item); + auto player = item->getHoldingPlayer(); + if (player) { + g_logger().error("[{}] - internalDecayItem failed to player {}, item id is same from transform equip/deequip, " + " item id: {}, equip to id: '{}', deequip to id '{}'", + __FUNCTION__, player->getName(), it.id, it.transformEquipTo, it.transformDeEquipTo); + } + return; + } + if (it.decayTo != 0) { std::shared_ptr player = item->getHoldingPlayer(); if (player) { diff --git a/src/items/functions/item/custom_attribute.cpp b/src/items/functions/item/custom_attribute.cpp index 596ac55d1..f7cfa925e 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: diff --git a/src/items/functions/item/item_parse.cpp b/src/items/functions/item/item_parse.cpp index 986b1f532..8d29dfb71 100644 --- a/src/items/functions/item/item_parse.cpp +++ b/src/items/functions/item/item_parse.cpp @@ -384,11 +384,20 @@ void ItemParse::parseTransform(const std::string &tmpStrValue, pugi::xml_attribu std::string stringValue = tmpStrValue; if (stringValue == "transformequipto") { itemType.transformEquipTo = pugi::cast(valueAttribute.value()); + if (itemType.transformEquipTo == itemType.decayTo) { + g_logger().warn("[{}] item with id {} is transforming on equip to the same id of decay to '{}'", __FUNCTION__, itemType.id, itemType.decayTo); + itemType.decayTo = 0; + } if (ItemType &transform = Item::items.getItemType(itemType.transformEquipTo); transform.type == ITEM_TYPE_NONE) { transform.type = itemType.type; } } else if (stringValue == "transformdeequipto") { + if (itemType.transformDeEquipTo == itemType.decayTo) { + g_logger().warn("[{}] item with id {} is transforming on de-equip to the same id of decay to '{}'", __FUNCTION__, itemType.id, itemType.decayTo); + itemType.decayTo = 0; + } + itemType.transformDeEquipTo = pugi::cast(valueAttribute.value()); } else if (stringValue == "transformto") { itemType.transformToFree = pugi::cast(valueAttribute.value()); @@ -561,7 +570,7 @@ void ItemParse::parseAbsorbPercent(const std::string &tmpStrValue, pugi::xml_att itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); } else if (stringValue == "absorbpercentfire") { itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - } else if (stringValue == "absorbpercentpoison") { + } else if (stringValue == "absorbpercentpoison" || stringValue == "absorbpercentearth") { itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); } else if (stringValue == "absorbpercentice") { itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); diff --git a/src/items/item.cpp b/src/items/item.cpp index b6902575b..1632cba95 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -185,7 +185,8 @@ Item::Item(const uint16_t itemId, uint16_t itemCount /*= 0*/) : const ItemType &it = items[id]; auto itemCharges = it.charges; if (it.isFluidContainer() || it.isSplash()) { - setAttribute(ItemAttribute_t::FLUIDTYPE, itemCount); + auto fluidType = std::clamp(itemCount, 1, FLUID_INK); + setAttribute(ItemAttribute_t::FLUIDTYPE, fluidType); } else if (it.stackable) { if (itemCount != 0) { setItemCount(static_cast(itemCount)); @@ -3081,7 +3082,6 @@ void Item::startDecaying() { } void Item::stopDecaying() { - g_logger().debug("Item::stopDecaying"); g_decay().stopDecay(static_self_cast()); } diff --git a/src/items/thing.hpp b/src/items/thing.hpp index 0a6161783..5b75e716c 100644 --- a/src/items/thing.hpp +++ b/src/items/thing.hpp @@ -16,6 +16,7 @@ class Cylinder; class Item; class Creature; class Container; +class Player; class Thing { public: @@ -47,6 +48,9 @@ class Thing { virtual int32_t getThrowRange() const = 0; virtual bool isPushable() = 0; + virtual std::shared_ptr getPlayer() { + return nullptr; + } virtual std::shared_ptr getContainer() { return nullptr; } diff --git a/src/items/tile.cpp b/src/items/tile.cpp index 0d9a5db0e..92beb20d5 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -461,7 +461,7 @@ void Tile::onRemoveTileItem(const CreatureVector &spectators, const std::vector< } } } - for (const auto zone : getZones()) { + for (auto &zone : getZones()) { zone->itemRemoved(item); } @@ -1732,6 +1732,47 @@ std::shared_ptr Tile::getDoorItem() const { return nullptr; } -const phmap::parallel_flat_hash_set> Tile::getZones() { - return Zone::getZones(getPosition()); +phmap::flat_hash_set> Tile::getZones() { + return zones; +} + +void Tile::addZone(std::shared_ptr zone) { + zones.insert(zone); + const auto &items = getItemList(); + if (items) { + for (const auto &item : *items) { + zone->itemAdded(item); + } + } + const auto &creatures = getCreatures(); + if (creatures) { + for (const auto &creature : *creatures) { + zone->creatureAdded(creature); + } + } +} + +void Tile::clearZones() { + phmap::flat_hash_set> zonesToRemove; + for (const auto &zone : zones) { + if (zone->isStatic()) { + continue; + } + zonesToRemove.insert(zone); + const auto &items = getItemList(); + if (items) { + for (const auto &item : *items) { + zone->itemRemoved(item); + } + } + const auto &creatures = getCreatures(); + if (creatures) { + for (const auto &creature : *creatures) { + zone->creatureRemoved(creature); + } + } + } + for (const auto &zone : zonesToRemove) { + zones.erase(zone); + } } diff --git a/src/items/tile.hpp b/src/items/tile.hpp index 2158d635d..de5dd4f0e 100644 --- a/src/items/tile.hpp +++ b/src/items/tile.hpp @@ -172,8 +172,10 @@ class Tile : public Cylinder, public SharedObject { void resetFlag(uint32_t flag) { this->flags &= ~flag; } + void addZone(std::shared_ptr zone); + void clearZones(); - const phmap::parallel_flat_hash_set> getZones(); + phmap::flat_hash_set> getZones(); ZoneType_t getZoneType() const { if (hasFlag(TILESTATE_PROTECTIONZONE)) { @@ -261,7 +263,7 @@ class Tile : public Cylinder, public SharedObject { std::shared_ptr ground = nullptr; Position tilePos; uint32_t flags = 0; - std::shared_ptr zone; + phmap::flat_hash_set> zones; }; // Used for walkable tiles, where there is high likeliness of diff --git a/src/kv/value_wrapper.hpp b/src/kv/value_wrapper.hpp index a4e899adc..3ff804402 100644 --- a/src/kv/value_wrapper.hpp +++ b/src/kv/value_wrapper.hpp @@ -49,7 +49,19 @@ class ValueWrapper { template T get() const { - return std::get(data_); + if (std::holds_alternative(data_)) { + return std::get(data_); + } + return T {}; + } + + double getNumber() const { + if (std::holds_alternative(data_)) { + return static_cast(std::get(data_)); + } else if (std::holds_alternative(data_)) { + return std::get(data_); + } + return 0.0; } const ValueVariant &getVariant() const { diff --git a/src/lib/logging/log_with_spd_log.cpp b/src/lib/logging/log_with_spd_log.cpp index d06e5e195..9b58317b8 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/lib/thread/thread_pool.cpp b/src/lib/thread/thread_pool.cpp index a88d73fc6..93b730b0b 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 90a57bae1..8ad60f146 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; }; diff --git a/src/lua/creature/actions.cpp b/src/lua/creature/actions.cpp index 6a79d697e..ba9236976 100644 --- a/src/lua/creature/actions.cpp +++ b/src/lua/creature/actions.cpp @@ -310,6 +310,10 @@ ReturnValue Actions::internalUseItem(std::shared_ptr player, const Posit // reward chest if (container->getRewardChest() != nullptr && container->getParent()) { + if (!player->hasOtherRewardContainerOpen(container->getParent()->getContainer())) { + player->removeEmptyRewards(); + } + std::shared_ptr playerRewardChest = player->getRewardChest(); if (playerRewardChest->empty()) { return RETURNVALUE_REWARDCHESTISEMPTY; diff --git a/src/lua/creature/raids.cpp b/src/lua/creature/raids.cpp index 93335b0c7..1770a7b36 100644 --- a/src/lua/creature/raids.cpp +++ b/src/lua/creature/raids.cpp @@ -12,7 +12,7 @@ #include "lua/creature/raids.hpp" #include "utils/pugicast.hpp" #include "game/game.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" #include "creatures/monsters/monster.hpp" #include "server/network/webhook/webhook.hpp" @@ -102,7 +102,7 @@ bool Raids::startup() { setLastRaidEnd(OTSYS_TIME()); - checkRaidsEvent = g_scheduler().addEvent(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this), "Raids::checkRaids"); + checkRaidsEvent = g_dispatcher().scheduleEvent(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this), "Raids::checkRaids"); started = true; return started; @@ -131,11 +131,11 @@ void Raids::checkRaids() { } } - checkRaidsEvent = g_scheduler().addEvent(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this), "Raids::checkRaids"); + checkRaidsEvent = g_dispatcher().scheduleEvent(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this), "Raids::checkRaids"); } void Raids::clear() { - g_scheduler().stopEvent(checkRaidsEvent); + g_dispatcher().stopEvent(checkRaidsEvent); checkRaidsEvent = 0; for (const auto &raid : raidList) { @@ -213,7 +213,7 @@ void Raid::startRaid() { const auto raidEvent = getNextRaidEvent(); if (raidEvent) { state = RAIDSTATE_EXECUTING; - nextEventEvent = g_scheduler().addEvent(raidEvent->getDelay(), std::bind(&Raid::executeRaidEvent, this, raidEvent), "Raid::executeRaidEvent"); + nextEventEvent = g_dispatcher().scheduleEvent(raidEvent->getDelay(), std::bind(&Raid::executeRaidEvent, this, raidEvent), "Raid::executeRaidEvent"); } else { g_logger().warn("[raids] Raid {} has no events", name); resetRaid(); @@ -227,7 +227,7 @@ void Raid::executeRaidEvent(const std::shared_ptr raidEvent) { if (newRaidEvent) { uint32_t ticks = static_cast(std::max(RAID_MINTICKS, newRaidEvent->getDelay() - raidEvent->getDelay())); - nextEventEvent = g_scheduler().addEvent(ticks, std::bind(&Raid::executeRaidEvent, this, newRaidEvent), __FUNCTION__); + nextEventEvent = g_dispatcher().scheduleEvent(ticks, std::bind(&Raid::executeRaidEvent, this, newRaidEvent), __FUNCTION__); } else { resetRaid(); } @@ -245,7 +245,7 @@ void Raid::resetRaid() { void Raid::stopEvents() { if (nextEventEvent != 0) { - g_scheduler().stopEvent(nextEventEvent); + g_dispatcher().stopEvent(nextEventEvent); nextEventEvent = 0; } } @@ -372,7 +372,6 @@ bool SingleSpawnEvent::executeEvent() { } if (!g_game().placeCreature(monster, position, false, true)) { - g_logger().error("{} - Cant create monster {}", __FUNCTION__, monsterName); return false; } diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index ec59b8b1c..99a277490 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -30,23 +30,47 @@ // Game int GameFunctions::luaGameCreateMonsterType(lua_State* L) { - // Game.createMonsterType(name) + // Game.createMonsterType(name[, variant = ""[, alternateName = ""]]) if (isString(L, 1)) { - std::string name = getString(L, 1); + const auto name = getString(L, 1); + std::string uniqueName = name; + auto variant = getString(L, 2, ""); + const auto alternateName = getString(L, 3, ""); + std::set names; auto monsterType = std::make_shared(name); - if (!g_monsters().tryAddMonsterType(name, monsterType)) { - lua_pushstring(L, fmt::format("The monster with name {} already registered", name).c_str()); - lua_error(L); - return 1; - } - if (!monsterType) { lua_pushstring(L, "MonsterType is nullptr"); lua_error(L); return 1; } + // if variant starts with !, then it's the only variant for this monster, so we register it with both names + if (variant.starts_with("!")) { + names.insert(name); + variant = variant.substr(1); + } + if (!variant.empty()) { + uniqueName = variant + "|" + name; + } + names.insert(uniqueName); + + monsterType->name = name; + if (!alternateName.empty()) { + names.insert(alternateName); + monsterType->name = alternateName; + } + + monsterType->variantName = variant; monsterType->nameDescription = "a " + name; + + for (const auto &alternateName : names) { + if (!g_monsters().tryAddMonsterType(alternateName, monsterType)) { + lua_pushstring(L, fmt::format("The monster with name {} already registered", alternateName).c_str()); + lua_error(L); + return 1; + } + } + pushUserdata(L, monsterType); setMetatable(L, -1, "MonsterType"); } else { @@ -152,7 +176,7 @@ int GameFunctions::luaGameGetPlayers(lua_State* L) { int GameFunctions::luaGameLoadMap(lua_State* L) { // Game.loadMap(path) const std::string &path = getString(L, 1); - g_dispatcher().addTask([path]() { g_game().loadMap(path); }, "GameFunctions::luaGameLoadMap"); + g_dispatcher().addEvent([path]() { g_game().loadMap(path); }, "GameFunctions::luaGameLoadMap"); return 0; } @@ -160,7 +184,7 @@ int GameFunctions::luaGameloadMapChunk(lua_State* L) { // Game.loadMapChunk(path, position, remove) const std::string &path = getString(L, 1); const Position &position = getPosition(L, 2); - g_dispatcher().addTask([path, position]() { g_game().loadMap(path, position); }, "GameFunctions::luaGameloadMapChunk"); + g_dispatcher().addEvent([path, position]() { g_game().loadMap(path, position); }, "GameFunctions::luaGameloadMapChunk"); return 0; } @@ -414,12 +438,6 @@ int GameFunctions::luaGameCreateMonster(lua_State* L) { if (mtype && mtype->info.raceid > 0 && mtype->info.bosstiaryRace == BosstiaryRarity_t::RARITY_ARCHFOE) { for (const auto &spectator : Spectators().find(monster->getPosition(), true)) { if (const auto &tmpPlayer = spectator->getPlayer()) { - const auto &bossesOnTracker = g_ioBosstiary().getBosstiaryCooldownRaceId(tmpPlayer); - // If not have boss to update, then kill loop for economize resources - if (bossesOnTracker.size() == 0) { - break; - } - tmpPlayer->sendBosstiaryCooldownTimer(); } } diff --git a/src/lua/functions/core/game/global_functions.cpp b/src/lua/functions/core/game/global_functions.cpp index 8bc9b55b0..7abf3b2c6 100644 --- a/src/lua/functions/core/game/global_functions.cpp +++ b/src/lua/functions/core/game/global_functions.cpp @@ -11,7 +11,8 @@ #include "creatures/interactions/chat.hpp" #include "game/game.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" #include "lua/functions/core/game/global_functions.hpp" #include "lua/scripts/lua_environment.hpp" #include "lua/scripts/script_environment.hpp" @@ -678,7 +679,7 @@ int GlobalFunctions::luaAddEvent(lua_State* L) { eventDesc.scriptName = getScriptEnv()->getScriptInterface()->getLoadingScriptName(); auto &lastTimerEventId = g_luaEnvironment().lastEventTimerId; - eventDesc.eventId = g_scheduler().addEvent( + eventDesc.eventId = g_dispatcher().scheduleEvent( delay, std::bind(&LuaEnvironment::executeTimerEvent, &g_luaEnvironment(), lastTimerEventId), "LuaEnvironment::executeTimerEvent" @@ -710,7 +711,7 @@ int GlobalFunctions::luaStopEvent(lua_State* L) { LuaTimerEventDesc timerEventDesc = std::move(it->second); timerEvents.erase(it); - g_scheduler().stopEvent(timerEventDesc.eventId); + g_dispatcher().stopEvent(timerEventDesc.eventId); luaL_unref(globalState, LUA_REGISTRYINDEX, timerEventDesc.function); for (auto parameter : timerEventDesc.parameters) { @@ -722,7 +723,7 @@ int GlobalFunctions::luaStopEvent(lua_State* L) { } int GlobalFunctions::luaSaveServer(lua_State* L) { - g_game().saveGameState(); + g_saveManager().scheduleAll(); pushBoolean(L, true); return 1; } diff --git a/src/lua/functions/core/game/zone_functions.cpp b/src/lua/functions/core/game/zone_functions.cpp index a624f36fe..5471d0189 100644 --- a/src/lua/functions/core/game/zone_functions.cpp +++ b/src/lua/functions/core/game/zone_functions.cpp @@ -121,27 +121,6 @@ int ZoneFunctions::luaZoneGetPositions(lua_State* L) { return 1; } -int ZoneFunctions::luaZoneGetTiles(lua_State* L) { - // Zone:getTiles() - auto zone = getUserdataShared(L, 1); - if (!zone) { - reportErrorFunc(getErrorDesc(LUA_ERROR_ZONE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - auto tiles = zone->getTiles(); - lua_createtable(L, static_cast(tiles.size()), 0); - - int index = 0; - for (auto tile : tiles) { - index++; - pushUserdata(L, tile.get()); - setMetatable(L, -1, "Tile"); - lua_rawseti(L, -2, index); - } - return 1; -} - int ZoneFunctions::luaZoneGetCreatures(lua_State* L) { // Zone:getCreatures() auto zone = getUserdataShared(L, 1); @@ -284,6 +263,24 @@ int ZoneFunctions::luaZoneRemoveNpcs(lua_State* L) { return 1; } +int ZoneFunctions::luaZoneSetMonsterVariant(lua_State* L) { + // Zone:setMonsterVariant(variant) + auto zone = getUserdataShared(L, 1); + if (!zone) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ZONE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + auto variant = getString(L, 2); + if (variant.empty()) { + pushBoolean(L, false); + return 1; + } + zone->setMonsterVariant(variant); + pushBoolean(L, true); + return 1; +} + int ZoneFunctions::luaZoneGetByName(lua_State* L) { // Zone.getByName(name) auto name = getString(L, 1); @@ -306,7 +303,7 @@ int ZoneFunctions::luaZoneGetByPosition(lua_State* L) { return 1; } int index = 0; - const auto zones = tile->getZones(); + auto zones = tile->getZones(); lua_createtable(L, static_cast(zones.size()), 0); for (auto zone : zones) { index++; @@ -319,7 +316,7 @@ int ZoneFunctions::luaZoneGetByPosition(lua_State* L) { int ZoneFunctions::luaZoneGetAll(lua_State* L) { // Zone.getAll() - const auto zones = Zone::getZones(); + auto zones = Zone::getZones(); lua_createtable(L, static_cast(zones.size()), 0); int index = 0; for (auto zone : zones) { diff --git a/src/lua/functions/core/game/zone_functions.hpp b/src/lua/functions/core/game/zone_functions.hpp index c8ce215f6..2a3bbd0f8 100644 --- a/src/lua/functions/core/game/zone_functions.hpp +++ b/src/lua/functions/core/game/zone_functions.hpp @@ -16,7 +16,6 @@ class ZoneFunctions final : LuaScriptInterface { registerMethod(L, "Zone", "getRemoveDestination", ZoneFunctions::luaZoneGetRemoveDestination); registerMethod(L, "Zone", "setRemoveDestination", ZoneFunctions::luaZoneSetRemoveDestination); registerMethod(L, "Zone", "getPositions", ZoneFunctions::luaZoneGetPositions); - registerMethod(L, "Zone", "getTiles", ZoneFunctions::luaZoneGetTiles); registerMethod(L, "Zone", "getCreatures", ZoneFunctions::luaZoneGetCreatures); registerMethod(L, "Zone", "getPlayers", ZoneFunctions::luaZoneGetPlayers); registerMethod(L, "Zone", "getMonsters", ZoneFunctions::luaZoneGetMonsters); @@ -28,6 +27,8 @@ class ZoneFunctions final : LuaScriptInterface { registerMethod(L, "Zone", "removeNpcs", ZoneFunctions::luaZoneRemoveNpcs); registerMethod(L, "Zone", "refresh", ZoneFunctions::luaZoneRefresh); + registerMethod(L, "Zone", "setMonsterVariant", ZoneFunctions::luaZoneSetMonsterVariant); + // static methods registerMethod(L, "Zone", "getByPosition", ZoneFunctions::luaZoneGetByPosition); registerMethod(L, "Zone", "getByName", ZoneFunctions::luaZoneGetByName); @@ -45,7 +46,6 @@ class ZoneFunctions final : LuaScriptInterface { static int luaZoneSetRemoveDestination(lua_State* L); static int luaZoneRefresh(lua_State* L); static int luaZoneGetPositions(lua_State* L); - static int luaZoneGetTiles(lua_State* L); static int luaZoneGetCreatures(lua_State* L); static int luaZoneGetPlayers(lua_State* L); static int luaZoneGetMonsters(lua_State* L); @@ -56,6 +56,8 @@ class ZoneFunctions final : LuaScriptInterface { static int luaZoneRemoveMonsters(lua_State* L); static int luaZoneRemoveNpcs(lua_State* L); + static int luaZoneSetMonsterVariant(lua_State* L); + static int luaZoneGetByPosition(lua_State* L); static int luaZoneGetByName(lua_State* L); static int luaZoneGetAll(lua_State* L); diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index 1beea8de6..e08d32218 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -280,6 +280,20 @@ int MonsterTypeFunctions::luaMonsterTypeCanPushCreatures(lua_State* L) { return 1; } +int MonsterTypeFunctions::luaMonsterTypeCritChance(lua_State* L) { + // get: monsterType:critChance() set: monsterType:critChance(int) + const auto monsterType = getUserdataShared(L, 1); + if (monsterType) { + if (lua_gettop(L) == 2) { + monsterType->info.critChance = getNumber(L, 2); + } + lua_pushnumber(L, monsterType->info.critChance); + } else { + lua_pushnil(L); + } + return 1; +} + int32_t MonsterTypeFunctions::luaMonsterTypeName(lua_State* L) { // get: monsterType:name() set: monsterType:name(name) const auto monsterType = getUserdataShared(L, 1); @@ -1556,27 +1570,6 @@ int MonsterTypeFunctions::luaMonsterTypeBossRaceId(lua_State* L) { return 1; } -int MonsterTypeFunctions::luaMonsterTypeBossStorageCooldown(lua_State* L) { - // set: monsterType:bossStorageCooldown(storage) - // get: monsterType:bossStorageCooldown() - const auto monsterType = getUserdataShared(L, 1); - if (!monsterType) { - pushBoolean(L, false); - reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_TYPE_NOT_FOUND)); - return 0; - } - - auto bossStorageCooldown = getNumber(L, 2, 0); - if (lua_gettop(L) == 1) { - lua_pushnumber(L, static_cast(monsterType->info.bossStorageCooldown)); - } else { - monsterType->info.bossStorageCooldown = bossStorageCooldown; - pushBoolean(L, true); - } - - return 1; -} - int MonsterTypeFunctions::luaMonsterTypeSoundChance(lua_State* L) { // get: monsterType:soundChance() set: monsterType:soundChance(chance) const auto monsterType = getUserdataShared(L, 1); @@ -1664,3 +1657,22 @@ int MonsterTypeFunctions::luaMonsterTypedeathSound(lua_State* L) { return 1; } + +int MonsterTypeFunctions::luaMonsterTypeVariant(lua_State* L) { + // get: monsterType:variant() set: monsterType:variant(variantName) + const auto monsterType = getUserdataShared(L, 1); + if (!monsterType) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (lua_gettop(L) == 1) { + pushString(L, monsterType->variantName); + } else { + monsterType->variantName = getString(L, 2); + pushBoolean(L, true); + } + + return 1; +} diff --git a/src/lua/functions/creatures/monster/monster_type_functions.hpp b/src/lua/functions/creatures/monster/monster_type_functions.hpp index d9c513d2b..f1b2a98bf 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.hpp @@ -35,6 +35,8 @@ class MonsterTypeFunctions final : LuaScriptInterface { registerMethod(L, "MonsterType", "canPushItems", MonsterTypeFunctions::luaMonsterTypeCanPushItems); registerMethod(L, "MonsterType", "canPushCreatures", MonsterTypeFunctions::luaMonsterTypeCanPushCreatures); + registerMethod(L, "MonsterType", "critChance", MonsterTypeFunctions::luaMonsterTypeCritChance); + registerMethod(L, "MonsterType", "name", MonsterTypeFunctions::luaMonsterTypeName); registerMethod(L, "MonsterType", "nameDescription", MonsterTypeFunctions::luaMonsterTypeNameDescription); @@ -131,13 +133,14 @@ class MonsterTypeFunctions final : LuaScriptInterface { registerMethod(L, "MonsterType", "bossRace", MonsterTypeFunctions::luaMonsterTypeBossRace); registerMethod(L, "MonsterType", "bossRaceId", MonsterTypeFunctions::luaMonsterTypeBossRaceId); - registerMethod(L, "MonsterType", "bossStorageCooldown", MonsterTypeFunctions::luaMonsterTypeBossStorageCooldown); registerMethod(L, "MonsterType", "soundChance", MonsterTypeFunctions::luaMonsterTypeSoundChance); registerMethod(L, "MonsterType", "soundSpeedTicks", MonsterTypeFunctions::luaMonsterTypeSoundSpeedTicks); registerMethod(L, "MonsterType", "addSound", MonsterTypeFunctions::luaMonsterTypeAddSound); registerMethod(L, "MonsterType", "getSounds", MonsterTypeFunctions::luaMonsterTypeGetSounds); registerMethod(L, "MonsterType", "deathSound", MonsterTypeFunctions::luaMonsterTypedeathSound); + + registerMethod(L, "MonsterType", "variant", MonsterTypeFunctions::luaMonsterTypeVariant); } private: @@ -253,17 +256,13 @@ class MonsterTypeFunctions final : LuaScriptInterface { static int luaMonsterTypeBossRace(lua_State* L); static int luaMonsterTypeBossRaceId(lua_State* L); - static int luaMonsterTypeBossStorageCooldown(lua_State* L); static int luaMonsterTypeSoundChance(lua_State* L); static int luaMonsterTypeSoundSpeedTicks(lua_State* L); static int luaMonsterTypeAddSound(lua_State* L); static int luaMonsterTypeGetSounds(lua_State* L); static int luaMonsterTypedeathSound(lua_State* L); + static int luaMonsterTypeCritChance(lua_State* L); - // Hazard system - static int luaMonsterTypeHazardSystemCrit(lua_State* L); - static int luaMonsterTypeHazardSystemDodge(lua_State* L); - static int luaMonsterTypeHazardSystemSpawnPod(lua_State* L); - static int luaMonsterTypeHazardSystemDamageBoost(lua_State* L); + static int luaMonsterTypeVariant(lua_State* L); }; diff --git a/src/lua/functions/creatures/npc/npc_functions.cpp b/src/lua/functions/creatures/npc/npc_functions.cpp index fc8c8272c..e0ae3c2bf 100644 --- a/src/lua/functions/creatures/npc/npc_functions.cpp +++ b/src/lua/functions/creatures/npc/npc_functions.cpp @@ -524,85 +524,7 @@ int NpcFunctions::luaNpcSellItem(lua_State* L) { } } - uint32_t itemsPurchased = 0; - uint8_t backpacksPurchased = 0; - uint8_t internalCount = it.stackable ? it.stackSize : 1; - auto remainingAmount = static_cast(amount); - if (inBackpacks) { - while (remainingAmount > 0) { - std::shared_ptr container = Item::CreateItem(ITEM_SHOPPING_BAG); - if (!container) { - break; - } - - if (g_game().internalPlayerAddItem(player, container, ignoreCap, CONST_SLOT_WHEREEVER) != RETURNVALUE_NOERROR) { - break; - } - - backpacksPurchased++; - uint8_t internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - const ItemType &iType = Item::items[itemId]; - std::shared_ptr item; - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - if (actionId != 0) { - item->setAttribute(ItemAttribute_t::ACTIONID, actionId); - } - - while (remainingAmount > 0) { - if (g_game().internalAddItem(container->getContainer(), item, INDEX_WHEREEVER, 0) != RETURNVALUE_NOERROR) { - break; - } - - itemsPurchased += internalAmount; - remainingAmount -= internalAmount; - internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - } - } - } else { - uint8_t internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - const ItemType &iType = Item::items[itemId]; - std::shared_ptr item; - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - if (actionId != 0) { - item->setAttribute(ItemAttribute_t::ACTIONID, actionId); - } - - while (remainingAmount > 0) { - if (g_game().internalPlayerAddItem(player, item, ignoreCap, CONST_SLOT_WHEREEVER) != RETURNVALUE_NOERROR) { - break; - } - - itemsPurchased += internalAmount; - remainingAmount -= internalAmount; - internalAmount = (remainingAmount > internalCount) ? internalCount : static_cast(remainingAmount); - if (iType.isWrappable()) { - item = Item::CreateItem(ITEM_DECORATION_KIT, subType); - item->setAttribute(ItemAttribute_t::DESCRIPTION, "Unwrap this item in your own house to create a <" + iType.name + ">."); - item->setCustomAttribute("unWrapId", static_cast(itemId)); - } else { - item = Item::CreateItem(itemId, it.stackable ? internalAmount : subType); - } - } - } + const auto &[_, itemsPurchased, backpacksPurchased] = g_game().createItem(player, itemId, amount, subType, actionId, ignoreCap, inBackpacks ? ITEM_SHOPPING_BAG : 0); std::stringstream ss; uint64_t itemCost = itemsPurchased * pricePerUnit; @@ -610,7 +532,7 @@ int NpcFunctions::luaNpcSellItem(lua_State* L) { if (npc->getCurrency() == ITEM_GOLD_COIN) { if (!g_game().removeMoney(player, itemCost + backpackCost, 0, true)) { g_logger().error("[NpcFunctions::luaNpcSellItem (removeMoney)] - Player {} have a problem for buy item {} on shop for npc {}", player->getName(), itemId, npc->getName()); - g_logger().debug("[Information] Player {} buyed item {} on shop for npc {}, at position {}", player->getName(), itemId, npc->getName(), player->getPosition().toString()); + g_logger().debug("[Information] Player {} bought {} x item {} on shop for npc {}, at position {}", player->getName(), itemsPurchased, itemId, npc->getName(), player->getPosition().toString()); } else if (backpacksPurchased > 0) { ss << "Bought " << std::to_string(itemsPurchased) << "x " << it.name << " and " << std::to_string(backpacksPurchased); if (backpacksPurchased > 1) { diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 7145525c3..12e7379a2 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -19,6 +19,7 @@ #include "io/ioprey.hpp" #include "items/item.hpp" #include "lua/functions/creatures/player/player_functions.hpp" +#include "game/scheduling/save_manager.hpp" #include "map/spectators.hpp" int PlayerFunctions::luaPlayerSendInventory(lua_State* L) { @@ -1379,10 +1380,26 @@ int PlayerFunctions::luaPlayerSetVocation(lua_State* L) { } player->setVocation(vocation->getId()); + player->sendSkills(); + player->sendStats(); + player->sendBasicData(); + player->wheel()->sendGiftOfLifeCooldown(); + g_game().reloadCreature(player); pushBoolean(L, true); return 1; } +int PlayerFunctions::luaPlayerIsPromoted(lua_State* L) { + // player:isPromoted() + std::shared_ptr player = getUserdataShared(L, 1); + if (player) { + pushBoolean(L, player->isPromoted()); + } else { + lua_pushnil(L); + } + return 1; +} + int PlayerFunctions::luaPlayerGetSex(lua_State* L) { // player:getSex() std::shared_ptr player = getUserdataShared(L, 1); @@ -2824,10 +2841,7 @@ int PlayerFunctions::luaPlayerSave(lua_State* L) { if (!player->isOffline()) { player->loginPosition = player->getPosition(); } - pushBoolean(L, IOLoginData::savePlayer(player)); - if (player->isOffline()) { - // avoiding memory leak - } + pushBoolean(L, g_saveManager().savePlayer(player)); } else { lua_pushnil(L); } diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index 14075cb69..89bf59b27 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -130,6 +130,7 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "getVocation", PlayerFunctions::luaPlayerGetVocation); registerMethod(L, "Player", "setVocation", PlayerFunctions::luaPlayerSetVocation); + registerMethod(L, "Player", "isPromoted", PlayerFunctions::luaPlayerIsPromoted); registerMethod(L, "Player", "getSex", PlayerFunctions::luaPlayerGetSex); registerMethod(L, "Player", "setSex", PlayerFunctions::luaPlayerSetSex); @@ -469,6 +470,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerGetVocation(lua_State* L); static int luaPlayerSetVocation(lua_State* L); + static int luaPlayerIsPromoted(lua_State* L); static int luaPlayerGetSex(lua_State* L); static int luaPlayerSetSex(lua_State* L); diff --git a/src/lua/functions/items/item_functions.cpp b/src/lua/functions/items/item_functions.cpp index f943c52c8..c1f253612 100644 --- a/src/lua/functions/items/item_functions.cpp +++ b/src/lua/functions/items/item_functions.cpp @@ -14,6 +14,7 @@ #include "game/game.hpp" #include "items/item.hpp" #include "items/decay/decay.hpp" +#include "game/scheduling/save_manager.hpp" class Imbuement; diff --git a/src/lua/functions/lua_functions_loader.cpp b/src/lua/functions/lua_functions_loader.cpp index cf3775929..0973876c2 100644 --- a/src/lua/functions/lua_functions_loader.cpp +++ b/src/lua/functions/lua_functions_loader.cpp @@ -16,6 +16,7 @@ #include "creatures/players/grouping/guild.hpp" #include "game/zones/zone.hpp" #include "game/game.hpp" +#include "game/scheduling/dispatcher.hpp" #include "game/movement/teleport.hpp" #include "lua/functions/core/core_functions.hpp" #include "lua/functions/creatures/creature_functions.hpp" @@ -90,6 +91,10 @@ std::string LuaFunctionsLoader::getErrorDesc(ErrorCode_t code) { } int LuaFunctionsLoader::protectedCall(lua_State* L, int nargs, int nresults) { + if (const int ret = validateDispatcherContext(__FUNCTION__); ret != 0) { + return ret; + } + int error_index = lua_gettop(L) - nargs; lua_pushcfunction(L, luaErrorHandler); lua_insert(L, error_index); @@ -120,6 +125,10 @@ int LuaFunctionsLoader::luaErrorHandler(lua_State* L) { } void LuaFunctionsLoader::pushVariant(lua_State* L, const LuaVariant &var) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_createtable(L, 0, 4); setField(L, "type", var.type); switch (var.type) { @@ -144,6 +153,10 @@ void LuaFunctionsLoader::pushVariant(lua_State* L, const LuaVariant &var) { } void LuaFunctionsLoader::pushThing(lua_State* L, std::shared_ptr thing) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + if (!thing) { lua_createtable(L, 0, 4); setField(L, "uid", 0); @@ -165,6 +178,10 @@ void LuaFunctionsLoader::pushThing(lua_State* L, std::shared_ptr thing) { } void LuaFunctionsLoader::pushCylinder(lua_State* L, std::shared_ptr cylinder) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + if (std::shared_ptr creature = cylinder->getCreature()) { pushUserdata(L, creature); setCreatureMetatable(L, -1, creature); @@ -182,10 +199,18 @@ void LuaFunctionsLoader::pushCylinder(lua_State* L, std::shared_ptr cy } void LuaFunctionsLoader::pushString(lua_State* L, const std::string &value) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_pushlstring(L, value.c_str(), value.length()); } void LuaFunctionsLoader::pushCallback(lua_State* L, int32_t callback) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_rawgeti(L, LUA_REGISTRYINDEX, callback); } @@ -205,11 +230,19 @@ int32_t LuaFunctionsLoader::popCallback(lua_State* L) { // Metatables void LuaFunctionsLoader::setMetatable(lua_State* L, int32_t index, const std::string &name) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + luaL_getmetatable(L, name.c_str()); lua_setmetatable(L, index - 1); } void LuaFunctionsLoader::setWeakMetatable(lua_State* L, int32_t index, const std::string &name) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + static std::set weakObjectTypes; const std::string &weakName = name + "_weak"; @@ -244,6 +277,10 @@ void LuaFunctionsLoader::setWeakMetatable(lua_State* L, int32_t index, const std } void LuaFunctionsLoader::setItemMetatable(lua_State* L, int32_t index, std::shared_ptr item) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + if (item && item->getContainer()) { luaL_getmetatable(L, "Container"); } else if (item && item->getTeleport()) { @@ -255,6 +292,10 @@ void LuaFunctionsLoader::setItemMetatable(lua_State* L, int32_t index, std::shar } void LuaFunctionsLoader::setCreatureMetatable(lua_State* L, int32_t index, std::shared_ptr creature) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + if (creature && creature->getPlayer()) { luaL_getmetatable(L, "Player"); } else if (creature && creature->getMonster()) { @@ -492,10 +533,18 @@ std::string LuaFunctionsLoader::getUserdataTypeName(LuaData_t userType) { // Push void LuaFunctionsLoader::pushBoolean(lua_State* L, bool value) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_pushboolean(L, value ? 1 : 0); } void LuaFunctionsLoader::pushCombatDamage(lua_State* L, const CombatDamage &damage) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_pushnumber(L, damage.primary.value); lua_pushnumber(L, damage.primary.type); lua_pushnumber(L, damage.secondary.value); @@ -504,6 +553,10 @@ void LuaFunctionsLoader::pushCombatDamage(lua_State* L, const CombatDamage &dama } void LuaFunctionsLoader::pushInstantSpell(lua_State* L, const InstantSpell &spell) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_createtable(L, 0, 6); setField(L, "name", spell.getName()); @@ -517,6 +570,10 @@ void LuaFunctionsLoader::pushInstantSpell(lua_State* L, const InstantSpell &spel } void LuaFunctionsLoader::pushPosition(lua_State* L, const Position &position, int32_t stackpos /* = 0*/) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_createtable(L, 0, 4); setField(L, "x", position.x); @@ -528,6 +585,10 @@ void LuaFunctionsLoader::pushPosition(lua_State* L, const Position &position, in } void LuaFunctionsLoader::pushOutfit(lua_State* L, const Outfit_t &outfit) { + if (validateDispatcherContext(__FUNCTION__)) { + return; + } + lua_createtable(L, 0, 13); setField(L, "lookType", outfit.lookType); setField(L, "lookTypeEx", outfit.lookTypeEx); @@ -691,3 +752,12 @@ int LuaFunctionsLoader::luaGarbageCollection(lua_State* L) { } return 0; } + +int LuaFunctionsLoader::validateDispatcherContext(std::string_view fncName) { + if (g_dispatcher().context().isOn() && g_dispatcher().context().isAsync()) { + g_logger().warn("[{}] The call to lua was ignored because the '{}' task is trying to communicate while in async mode.", fncName, g_dispatcher().context().getName()); + return LUA_ERRRUN; + } + + return 0; +} diff --git a/src/lua/functions/lua_functions_loader.hpp b/src/lua/functions/lua_functions_loader.hpp index 49fe5e624..89baf958e 100644 --- a/src/lua/functions/lua_functions_loader.hpp +++ b/src/lua/functions/lua_functions_loader.hpp @@ -101,6 +101,13 @@ class LuaFunctionsLoader { static std::string getFormatedLoggerMessage(lua_State* L); static std::string getString(lua_State* L, int32_t arg); + static std::string getString(lua_State* L, int32_t arg, std::string defaultValue) { + const auto parameters = lua_gettop(L); + if (parameters == 0 || arg > parameters) { + return defaultValue; + } + return getString(L, arg); + } static CombatDamage getCombatDamage(lua_State* L); static Position getPosition(lua_State* L, int32_t arg, int32_t &stackpos); static Position getPosition(lua_State* L, int32_t arg); @@ -219,4 +226,5 @@ class LuaFunctionsLoader { static ScriptEnvironment scriptEnv[16]; static int32_t scriptEnvIndex; + static int validateDispatcherContext(std::string_view fncName); }; diff --git a/src/lua/global/globalevent.cpp b/src/lua/global/globalevent.cpp index a8acb01bb..c50eba9b8 100644 --- a/src/lua/global/globalevent.cpp +++ b/src/lua/global/globalevent.cpp @@ -12,16 +12,16 @@ #include "lua/global/globalevent.hpp" #include "utils/tools.hpp" #include "game/game.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" GlobalEvents::GlobalEvents() = default; GlobalEvents::~GlobalEvents() = default; void GlobalEvents::clear() { // Stop events - g_scheduler().stopEvent(thinkEventId); + g_dispatcher().stopEvent(thinkEventId); thinkEventId = 0; - g_scheduler().stopEvent(timerEventId); + g_dispatcher().stopEvent(timerEventId); timerEventId = 0; // Clear maps @@ -35,7 +35,7 @@ bool GlobalEvents::registerLuaEvent(const std::shared_ptr globalEve auto result = timerMap.emplace(globalEvent->getName(), globalEvent); if (result.second) { if (timerEventId == 0) { - timerEventId = g_scheduler().addEvent(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this), "GlobalEvents::timer"); + timerEventId = g_dispatcher().scheduleEvent(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this), "GlobalEvents::timer"); } return true; } @@ -48,7 +48,7 @@ bool GlobalEvents::registerLuaEvent(const std::shared_ptr globalEve auto result = thinkMap.emplace(globalEvent->getName(), globalEvent); if (result.second) { if (thinkEventId == 0) { - thinkEventId = g_scheduler().addEvent( + thinkEventId = g_dispatcher().scheduleEvent( SCHEDULER_MINTICKS, [this] { think(); }, "GlobalEvents::think" ); } @@ -99,7 +99,7 @@ void GlobalEvents::timer() { } if (nextScheduledTime != std::numeric_limits::max()) { - timerEventId = g_scheduler().addEvent(std::max(1000, nextScheduledTime * 1000), std::bind(&GlobalEvents::timer, this), __FUNCTION__); + timerEventId = g_dispatcher().scheduleEvent(std::max(1000, nextScheduledTime * 1000), std::bind(&GlobalEvents::timer, this), __FUNCTION__); } } @@ -136,7 +136,7 @@ void GlobalEvents::think() { if (nextScheduledTime != std::numeric_limits::max()) { auto delay = static_cast(nextScheduledTime); - thinkEventId = g_scheduler().addEvent(delay, std::bind(&GlobalEvents::think, this), "GlobalEvents::think"); + thinkEventId = g_dispatcher().scheduleEvent(delay, std::bind(&GlobalEvents::think, this), "GlobalEvents::think"); } } diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index a623a3e52..8bb26bcfb 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -14,6 +14,7 @@ #include "io/iologindata.hpp" #include "game/game.hpp" #include "items/bed.hpp" +#include "game/scheduling/save_manager.hpp" House::House(uint32_t houseId) : id(houseId) { } @@ -285,7 +286,7 @@ bool House::transferToDepot(std::shared_ptr player) const { g_logger().debug("[{}] moving item '{}' to depot", __FUNCTION__, item->getName()); g_game().internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); } - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); return true; } @@ -501,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; @@ -821,7 +835,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const { } } - IOLoginData::savePlayer(player); + g_saveManager().savePlayer(player); } } diff --git a/src/map/map.cpp b/src/map/map.cpp index 39fbe4787..53f7c98db 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -32,7 +32,7 @@ void Map::load(const std::string &identifier, const Position &pos) { } } -void Map::loadMap(const std::string &identifier, bool mainMap /*= false*/, bool loadHouses /*= false*/, bool loadMonsters /*= false*/, bool loadNpcs /*= false*/, const Position &pos /*= Position()*/) { +void Map::loadMap(const std::string &identifier, bool mainMap /*= false*/, bool loadHouses /*= false*/, bool loadMonsters /*= false*/, bool loadNpcs /*= false*/, bool loadZones /*= false*/, const Position &pos /*= Position()*/) { // Only download map if is loading the main map and it is not already downloaded if (mainMap && g_configManager().getBoolean(TOGGLE_DOWNLOAD_MAP) && !std::filesystem::exists(identifier)) { const auto mapDownloadUrl = g_configManager().getString(MAP_DOWNLOAD_URL); @@ -86,6 +86,10 @@ void Map::loadMap(const std::string &identifier, bool mainMap /*= false*/, bool IOMap::loadNpcs(this); } + if (loadZones) { + IOMap::loadZones(this); + } + // Files need to be cleaned up if custom map is enabled to open, or will try to load main map files if (g_configManager().getBoolean(TOGGLE_MAP_CUSTOM)) { monsterfile.clear(); @@ -94,7 +98,7 @@ void Map::loadMap(const std::string &identifier, bool mainMap /*= false*/, bool } } -void Map::loadMapCustom(const std::string &mapName, bool loadHouses, bool loadMonsters, bool loadNpcs, int customMapIndex) { +void Map::loadMapCustom(const std::string &mapName, bool loadHouses, bool loadMonsters, bool loadNpcs, bool loadZones, int customMapIndex) { // Load the map load(g_configManager().getString(DATA_DIRECTORY) + "/world/custom/" + mapName + ".otbm"); @@ -110,6 +114,10 @@ void Map::loadMapCustom(const std::string &mapName, bool loadHouses, bool loadMo g_logger().warn("Failed to load npc custom spawn data"); } + if (loadZones && !IOMap::loadZonesCustom(this, mapName, customMapIndex)) { + g_logger().warn("Failed to load zones custom data"); + } + // Files need to be cleaned up or will try to load previous map files again monsterfile.clear(); housefile.clear(); @@ -149,6 +157,25 @@ std::shared_ptr Map::getOrCreateTile(uint16_t x, uint16_t y, uint8_t z, bo return tile; } +std::shared_ptr Map::getLoadedTile(uint16_t x, uint16_t y, uint8_t z) { + if (z >= MAP_MAX_LAYERS) { + return nullptr; + } + + const auto leaf = getQTNode(x, y); + if (!leaf) { + return nullptr; + } + + const auto &floor = leaf->getFloor(z); + if (!floor) { + return nullptr; + } + + const auto tile = floor->getTile(x, y); + return tile; +} + std::shared_ptr Map::getTile(uint16_t x, uint16_t y, uint8_t z) { if (z >= MAP_MAX_LAYERS) { return nullptr; @@ -168,6 +195,19 @@ std::shared_ptr Map::getTile(uint16_t x, uint16_t y, uint8_t z) { return tile ? tile : getOrCreateTileFromCache(floor, x, y); } +void Map::refreshZones(uint16_t x, uint16_t y, uint8_t z) { + const auto tile = getLoadedTile(x, y, z); + if (!tile) { + return; + } + + tile->clearZones(); + const auto &zones = Zone::getZones(tile->getPosition()); + for (const auto &zone : zones) { + tile->addZone(zone); + } +} + void Map::setTile(uint16_t x, uint16_t y, uint8_t z, std::shared_ptr newTile) { if (z >= MAP_MAX_LAYERS) { g_logger().error("Attempt to set tile on invalid coordinate: {}", Position(x, y, z).toString()); diff --git a/src/map/map.hpp b/src/map/map.hpp index 501f7578b..ac0211c7d 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -50,7 +50,7 @@ class Map : protected MapCache { * \param loadNpcs if true, the main map npcs is loaded * \returns true if the main map was loaded successfully */ - void loadMap(const std::string &identifier, bool mainMap = false, bool loadHouses = false, bool loadMonsters = false, bool loadNpcs = false, const Position &pos = Position()); + void loadMap(const std::string &identifier, bool mainMap = false, bool loadHouses = false, bool loadMonsters = false, bool loadNpcs = false, bool loadZones = false, const Position &pos = Position()); /** * Load the custom map * \param identifier Is the map custom folder @@ -59,7 +59,7 @@ class Map : protected MapCache { * \param loadNpcs if true, the map custom npcs is loaded * \returns true if the custom map was loaded successfully */ - void loadMapCustom(const std::string &mapName, bool loadHouses, bool loadMonsters, bool loadNpcs, const int customMapIndex); + void loadMapCustom(const std::string &mapName, bool loadHouses, bool loadMonsters, bool loadNpcs, bool loadZones, const int customMapIndex); void loadHouseInfo(); @@ -78,6 +78,11 @@ class Map : protected MapCache { return getTile(pos.x, pos.y, pos.z); } + void refreshZones(uint16_t x, uint16_t y, uint8_t z); + void refreshZones(const Position &pos) { + refreshZones(pos.x, pos.y, pos.z); + } + std::shared_ptr getOrCreateTile(uint16_t x, uint16_t y, uint8_t z, bool isDynamic = false); std::shared_ptr getOrCreateTile(const Position &pos, bool isDynamic = false) { return getOrCreateTile(pos.x, pos.y, pos.z, isDynamic); @@ -147,11 +152,13 @@ class Map : protected MapCache { void setTile(const Position &pos, std::shared_ptr newTile) { setTile(pos.x, pos.y, pos.z, newTile); } + std::shared_ptr getLoadedTile(uint16_t x, uint16_t y, uint8_t z); std::filesystem::path path; std::string monsterfile; std::string housefile; std::string npcfile; + std::string zonesfile; uint32_t width = 0; uint32_t height = 0; diff --git a/src/map/mapcache.cpp b/src/map/mapcache.cpp index dfbbc58c3..c39629806 100644 --- a/src/map/mapcache.cpp +++ b/src/map/mapcache.cpp @@ -16,6 +16,7 @@ #include "io/iologindata.hpp" #include "items/item.hpp" #include "game/game.hpp" +#include "game/zones/zone.hpp" #include "map/map.hpp" #include "utils/hash.hpp" #include "io/filestream.hpp" @@ -132,6 +133,9 @@ std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptrsetFlag(static_cast(cachedTile->flags)); + for (const auto &zone : Zone::getZones(pos)) { + tile->addZone(zone); + } floor->setTile(x, y, tile); diff --git a/src/pch.hpp b/src/pch.hpp index c2308989b..ab57f178e 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -18,6 +18,8 @@ #include "utils/definitions.hpp" #include "utils/simd.hpp" #include "utils/vectorset.hpp" +#include "utils/arraylist.hpp" +#include "utils/vectorsort.hpp" // -------------------- // STL Includes diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index c2df08b28..8e637c053 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -12,35 +12,29 @@ #include "server/network/connection/connection.hpp" #include "server/network/message/outputmessage.hpp" #include "server/network/protocol/protocol.hpp" -#include "game/scheduling/scheduler.hpp" #include "game/scheduling/dispatcher.hpp" #include "server/server.hpp" Connection_ptr ConnectionManager::createConnection(asio::io_service &io_service, ConstServicePort_ptr servicePort) { - std::lock_guard lockClass(connectionManagerLock); - auto connection = std::make_shared(io_service, servicePort); - connections.insert(connection); + connections.emplace(connection); return connection; } void ConnectionManager::releaseConnection(const Connection_ptr &connection) { - std::lock_guard lockClass(connectionManagerLock); - connections.erase(connection); } void ConnectionManager::closeAll() { - std::lock_guard lockClass(connectionManagerLock); - - for (const auto &connection : connections) { + connections.for_each([](const Connection_ptr &connection) { try { std::error_code error; connection->socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); } catch (const std::system_error &systemError) { g_logger().error("[ConnectionManager::closeAll] - Failed to close connection, system error code {}", systemError.what()); } - } + }); + connections.clear(); } @@ -60,13 +54,14 @@ void Connection::close(bool force) { ConnectionManager::getInstance().releaseConnection(shared_from_this()); std::lock_guard lockClass(connectionLock); + ip = 0; if (connectionState == CONNECTION_STATE_CLOSED) { return; } connectionState = CONNECTION_STATE_CLOSED; if (protocol) { - g_dispatcher().addTask(std::bind_front(&Protocol::release, protocol), "Protocol::release", 1000); + g_dispatcher().addEvent(std::bind_front(&Protocol::release, protocol), "Protocol::release", 1000); } if (messageQueue.empty() || force) { @@ -93,7 +88,7 @@ void Connection::closeSocket() { void Connection::accept(Protocol_ptr protocolPtr) { this->connectionState = CONNECTION_STATE_IDENTIFYING; this->protocol = protocolPtr; - g_dispatcher().addTask(std::bind_front(&Protocol::onConnect, protocolPtr), "Protocol::onConnect", 1000); + g_dispatcher().addEvent(std::bind_front(&Protocol::onConnect, protocolPtr), "Protocol::onConnect", 1000); // Call second accept for not duplicate code accept(false); @@ -325,16 +320,17 @@ void Connection::internalWorker() { } uint32_t Connection::getIP() { + if (ip != 1) { + return ip; + } + std::lock_guard lockClass(connectionLock); // IP-address is expressed in network byte order std::error_code error; const asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error); - if (error) { - return 0; - } - - return htonl(endpoint.address().to_v4().to_ulong()); + ip = error ? 0 : htonl(endpoint.address().to_v4().to_uint()); + return ip; } void Connection::internalSend(const OutputMessage_ptr &outputMessage) { diff --git a/src/server/network/connection/connection.hpp b/src/server/network/connection/connection.hpp index 903d60763..3a80bf6ca 100644 --- a/src/server/network/connection/connection.hpp +++ b/src/server/network/connection/connection.hpp @@ -42,8 +42,7 @@ class ConnectionManager { void closeAll(); private: - phmap::flat_hash_set connections; - std::mutex connectionManagerLock; + phmap::parallel_flat_hash_set_m connections; }; class Connection : public std::enable_shared_from_this { @@ -102,6 +101,7 @@ class Connection : public std::enable_shared_from_this { time_t timeConnected; uint32_t packetsSent = 0; + uint32_t ip = 1; std::underlying_type_t connectionState = CONNECTION_STATE_OPEN; bool receivedFirst = false; diff --git a/src/server/network/message/outputmessage.cpp b/src/server/network/message/outputmessage.cpp index 3579178f2..d15cd4b36 100644 --- a/src/server/network/message/outputmessage.cpp +++ b/src/server/network/message/outputmessage.cpp @@ -11,13 +11,13 @@ #include "outputmessage.hpp" #include "server/network/protocol/protocol.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" const std::chrono::milliseconds OUTPUTMESSAGE_AUTOSEND_DELAY { 10 }; void OutputMessagePool::scheduleSendAll() { auto function = std::bind_front(&OutputMessagePool::sendAll, this); - g_scheduler().addEvent(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), function, "OutputMessagePool::sendAll"); + g_dispatcher().scheduleEvent(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), function, "OutputMessagePool::sendAll"); } void OutputMessagePool::sendAll() { diff --git a/src/server/network/protocol/protocol.cpp b/src/server/network/protocol/protocol.cpp index 05a6d1451..686a8f7e4 100644 --- a/src/server/network/protocol/protocol.cpp +++ b/src/server/network/protocol/protocol.cpp @@ -44,16 +44,14 @@ bool Protocol::sendRecvMessageCallback(NetworkMessage &msg) { return false; } - auto protocolWeak = std::weak_ptr(shared_from_this()); - std::function callback = [protocolWeak, &msg]() { + g_dispatcher().addEvent([&msg, protocolWeak = std::weak_ptr(shared_from_this())]() { if (auto protocol = protocolWeak.lock()) { if (auto protocolConnection = protocol->getConnection()) { protocol->parsePacket(msg); protocolConnection->resumeWork(); } - } - }; - g_dispatcher().addTask(callback, __FUNCTION__); + } }, __FUNCTION__); + return true; } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 59c2867e5..6da9e9418 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -28,7 +28,6 @@ #include "creatures/players/grouping/familiars.hpp" #include "server/network/protocol/protocolgame.hpp" #include "game/scheduling/dispatcher.hpp" -#include "game/scheduling/scheduler.hpp" #include "creatures/combat/spells.hpp" #include "creatures/players/management/waitlist.hpp" #include "items/weapons/weapons.hpp" @@ -241,12 +240,12 @@ ProtocolGame::ProtocolGame(Connection_ptr initConnection) : template void ProtocolGame::addGameTask(Callable function, Args &&... args) { - g_dispatcher().addTask(std::bind(function, &g_game(), std::forward(args)...), "ProtocolGame::addGameTask"); + g_dispatcher().addEvent(std::bind(function, &g_game(), std::forward(args)...), "ProtocolGame::addGameTask"); } template -void ProtocolGame::addGameTaskTimed(uint32_t delay, std::string context, Callable function, Args &&... args) { - g_dispatcher().addTask(std::bind(function, &g_game(), std::forward(args)...), context, delay); +void ProtocolGame::addGameTaskTimed(uint32_t delay, std::string_view context, Callable function, Args &&... args) { + g_dispatcher().addEvent(std::bind(function, &g_game(), std::forward(args)...), context, delay); } void ProtocolGame::AddItem(NetworkMessage &msg, uint16_t id, uint8_t count, uint8_t tier) { @@ -585,7 +584,7 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS foundPlayer->disconnect(); foundPlayer->isConnecting = true; - eventConnect = g_scheduler().addEvent(1000, std::bind(&ProtocolGame::connect, getThis(), foundPlayer->getName(), operatingSystem), "ProtocolGame::connect"); + eventConnect = g_dispatcher().scheduleEvent(1000, std::bind(&ProtocolGame::connect, getThis(), foundPlayer->getName(), operatingSystem), "ProtocolGame::connect"); } else { connect(foundPlayer->getName(), operatingSystem); } @@ -793,11 +792,11 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) { output->addByte(0x14); output->addString(ss.str()); send(output); - g_scheduler().addEvent(1000, std::bind(&ProtocolGame::disconnect, getThis()), "ProtocolGame::disconnect"); + g_dispatcher().scheduleEvent(1000, std::bind(&ProtocolGame::disconnect, getThis()), "ProtocolGame::disconnect"); return; } - g_dispatcher().addTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem), "ProtocolGame::login"); + g_dispatcher().addEvent(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem), "ProtocolGame::login"); } void ProtocolGame::onConnect() { @@ -862,16 +861,16 @@ void ProtocolGame::parsePacket(NetworkMessage &msg) { m_playerDeathTime++; } - g_dispatcher().addTask(std::bind(&ProtocolGame::parsePacketDead, getThis(), recvbyte), "ProtocolGame::parsePacketDead"); + g_dispatcher().addEvent(std::bind(&ProtocolGame::parsePacketDead, getThis(), recvbyte), "ProtocolGame::parsePacketDead"); return; } // Modules system if (player && recvbyte != 0xD3) { - g_dispatcher().addTask(std::bind(&Modules::executeOnRecvbyte, &g_modules(), player->getID(), msg, recvbyte), "Modules::executeOnRecvbyte"); + g_dispatcher().addEvent(std::bind(&Modules::executeOnRecvbyte, &g_modules(), player->getID(), msg, recvbyte), "Modules::executeOnRecvbyte"); } - g_dispatcher().addTask(std::bind(&ProtocolGame::parsePacketFromDispatcher, getThis(), msg, recvbyte), "ProtocolGame::parsePacketFromDispatcher"); + g_dispatcher().addEvent(std::bind(&ProtocolGame::parsePacketFromDispatcher, getThis(), msg, recvbyte), "ProtocolGame::parsePacketFromDispatcher"); } void ProtocolGame::parsePacketDead(uint8_t recvbyte) { @@ -881,7 +880,7 @@ void ProtocolGame::parsePacketDead(uint8_t recvbyte) { g_game().removePlayerUniqueLogin(player->getName()); } disconnect(); - g_dispatcher().addTask(std::bind(&IOLoginData::updateOnlineStatus, player->getGUID(), false), "IOLoginData::updateOnlineStatus"); + g_dispatcher().addEvent(std::bind(&IOLoginData::updateOnlineStatus, player->getGUID(), false), "IOLoginData::updateOnlineStatus"); return; } @@ -890,7 +889,7 @@ void ProtocolGame::parsePacketDead(uint8_t recvbyte) { return; } - g_scheduler().addEvent(100, std::bind(&ProtocolGame::sendPing, getThis()), "ProtocolGame::sendPing"); + g_dispatcher().scheduleEvent(100, std::bind(&ProtocolGame::sendPing, getThis()), "ProtocolGame::sendPing"); if (!player->spawn()) { disconnect(); @@ -898,15 +897,15 @@ void ProtocolGame::parsePacketDead(uint8_t recvbyte) { return; } - g_dispatcher().addTask(std::bind(&ProtocolGame::sendAddCreature, getThis(), player, player->getPosition(), 0, false), "ProtocolGame::sendAddCreature"); - g_dispatcher().addTask(std::bind(&ProtocolGame::addBless, getThis()), "ProtocolGame::addBless"); + g_dispatcher().addEvent(std::bind(&ProtocolGame::sendAddCreature, getThis(), player, player->getPosition(), 0, false), "ProtocolGame::sendAddCreature"); + g_dispatcher().addEvent(std::bind(&ProtocolGame::addBless, getThis()), "ProtocolGame::addBless"); resetPlayerDeathTime(); return; } if (recvbyte == 0x1D) { // keep the connection alive - g_scheduler().addEvent(100, std::bind(&ProtocolGame::sendPingBack, getThis()), "ProtocolGame::sendPingBack"); + g_dispatcher().scheduleEvent(100, std::bind(&ProtocolGame::sendPingBack, getThis()), "ProtocolGame::sendPingBack"); return; } } @@ -920,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); @@ -941,7 +940,7 @@ void ProtocolGame::parsePacketFromDispatcher(NetworkMessage msg, uint8_t recvbyt switch (recvbyte) { case 0x14: - g_dispatcher().addTask(std::bind(&ProtocolGame::logout, getThis(), true, false), "ProtocolGame::logout"); + g_dispatcher().addEvent(std::bind(&ProtocolGame::logout, getThis(), true, false), "ProtocolGame::logout"); break; case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); @@ -1211,9 +1210,9 @@ void ProtocolGame::parsePacketFromDispatcher(NetworkMessage msg, uint8_t recvbyt case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break; - // g_dispatcher().addTask(std::bind(&Modules::executeOnRecvbyte, g_modules, player, msg, recvbyte)); + // g_dispatcher().addEvent(std::bind(&Modules::executeOnRecvbyte, g_modules, player, msg, recvbyte)); case 0xD3: - g_dispatcher().addTask(std::bind(&ProtocolGame::parseSetOutfit, getThis(), msg), "ProtocolGame::parseSetOutfit"); + g_dispatcher().addEvent(std::bind(&ProtocolGame::parseSetOutfit, getThis(), msg), "ProtocolGame::parseSetOutfit"); break; case 0xD4: parseToggleMount(msg); @@ -2067,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; } @@ -2146,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); @@ -3974,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 } @@ -4565,7 +4563,7 @@ void ProtocolGame::updateCoinBalance() { return; } - g_dispatcher().addTask( + g_dispatcher().addEvent( std::bind( [](uint32_t playerId) { auto threadPlayer = g_game().getPlayerByID(playerId); @@ -8432,20 +8430,30 @@ void ProtocolGame::sendBosstiaryCooldownTimer() { NetworkMessage msg; msg.addByte(0xBD); - auto bossesOnTracker = g_ioBosstiary().getBosstiaryCooldownRaceId(player); - auto bossesOnTrackerSize = static_cast(bossesOnTracker.size()); - msg.add(bossesOnTrackerSize); // Number of bosses on timer - for (const auto &bossRaceId : bossesOnTracker) { + auto startBosses = msg.getBufferPosition(); + msg.skipBytes(2); // Boss count + uint16_t bossesCount = 0; + for (std::map bossesMap = g_ioBosstiary().getBosstiaryMap(); + const auto &[bossRaceId, _] : bossesMap) { const auto mType = g_ioBosstiary().getMonsterTypeByBossRaceId(bossRaceId); if (!mType) { continue; } - int32_t timer = player->getStorageValue(mType->info.bossStorageCooldown); + auto timerValue = player->kv()->scoped("boss.cooldown")->get(toKey(std::to_string(bossRaceId))); + if (!timerValue || !timerValue.has_value()) { + continue; + } + auto timer = timerValue->getNumber(); uint64_t sendTimer = timer > 0 ? static_cast(timer) : 0; msg.add(bossRaceId); // bossRaceId msg.add(sendTimer); // Boss cooldown in seconds + bossesCount++; } + auto endBosses = msg.getBufferPosition(); + msg.setBufferPosition(startBosses); + msg.add(bossesCount); + msg.setBufferPosition(endBosses); writeToOutputBuffer(msg); } diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index 60eac6f18..2295c1036 100644 --- a/src/server/network/protocol/protocolgame.hpp +++ b/src/server/network/protocol/protocolgame.hpp @@ -72,7 +72,7 @@ class ProtocolGame final : public Protocol { template void addGameTask(Callable function, Args &&... args); template - void addGameTaskTimed(uint32_t delay, std::string context, Callable function, Args &&... args); + void addGameTaskTimed(uint32_t delay, std::string_view context, Callable function, Args &&... args); ProtocolGame_ptr getThis() { return std::static_pointer_cast(shared_from_this()); @@ -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); diff --git a/src/server/network/protocol/protocollogin.cpp b/src/server/network/protocol/protocollogin.cpp index 756dc07a1..1f0a3b57a 100644 --- a/src/server/network/protocol/protocollogin.cpp +++ b/src/server/network/protocol/protocollogin.cpp @@ -175,5 +175,5 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage &msg) { } auto thisPtr = std::static_pointer_cast(shared_from_this()); - g_dispatcher().addTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountDescriptor, password), "ProtocolLogin::getCharacterList"); + g_dispatcher().addEvent(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountDescriptor, password), "ProtocolLogin::getCharacterList"); } diff --git a/src/server/network/protocol/protocolstatus.cpp b/src/server/network/protocol/protocolstatus.cpp index 3d2ce459c..315e49a35 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 = "OTX Server"; +std::string ProtocolStatus::SERVER_VERSION = "6.2"; +std::string ProtocolStatus::SERVER_DEVELOPERS = "Canary Base - OpenTibiaBR Organization and data editor: Mattyx14"; + std::map ProtocolStatus::ipConnectMap; const uint64_t ProtocolStatus::start = OTSYS_TIME(); @@ -38,7 +42,7 @@ void ProtocolStatus::onRecvFirstMessage(NetworkMessage &msg) { // XML info protocol case 0xFF: { if (msg.getString(4) == "info") { - g_dispatcher().addTask(std::bind(&ProtocolStatus::sendStatusString, std::static_pointer_cast(shared_from_this())), "ProtocolStatus::sendStatusString"); + g_dispatcher().addEvent(std::bind(&ProtocolStatus::sendStatusString, std::static_pointer_cast(shared_from_this())), "ProtocolStatus::sendStatusString"); return; } break; @@ -51,7 +55,7 @@ void ProtocolStatus::onRecvFirstMessage(NetworkMessage &msg) { if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { characterName = msg.getString(); } - g_dispatcher().addTask(std::bind(&ProtocolStatus::sendInfo, std::static_pointer_cast(shared_from_this()), requestedInfo, characterName), "ProtocolStatus::sendInfo"); + g_dispatcher().addEvent(std::bind(&ProtocolStatus::sendInfo, std::static_pointer_cast(shared_from_this()), requestedInfo, characterName), "ProtocolStatus::sendInfo"); return; } @@ -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 be8b35462..36d83f77d 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/src/server/network/webhook/webhook.cpp b/src/server/network/webhook/webhook.cpp index 06bc8eb9e..69d05070a 100644 --- a/src/server/network/webhook/webhook.cpp +++ b/src/server/network/webhook/webhook.cpp @@ -11,7 +11,7 @@ #include "server/network/webhook/webhook.hpp" #include "config/configmanager.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" #include "utils/tools.hpp" Webhook::Webhook(ThreadPool &threadPool) : @@ -38,7 +38,7 @@ Webhook &Webhook::getInstance() { void Webhook::run() { threadPool.addLoad([this] { sendWebhook(); }); - g_scheduler().addEvent( + g_dispatcher().scheduleEvent( g_configManager().getNumber(DISCORD_WEBHOOK_DELAY_MS), [this] { run(); }, "Webhook::run" ); } @@ -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); diff --git a/src/server/server.cpp b/src/server/server.cpp index 19ccf0ed5..dc6fa3bd9 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -12,7 +12,7 @@ #include "server/network/message/outputmessage.hpp" #include "server/server.hpp" #include "config/configmanager.hpp" -#include "game/scheduling/scheduler.hpp" +#include "game/scheduling/dispatcher.hpp" #include "creatures/players/management/ban.hpp" ServiceManager::~ServiceManager() { @@ -108,7 +108,7 @@ void ServicePort::onAccept(Connection_ptr connection, const std::error_code &err if (!pendingStart) { close(); pendingStart = true; - g_scheduler().addEvent(15000, std::bind_front(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort), "ServicePort::openAcceptor"); + g_dispatcher().scheduleEvent(15000, std::bind_front(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort), "ServicePort::openAcceptor"); } } } @@ -157,7 +157,7 @@ void ServicePort::open(uint16_t port) { g_logger().warn("[ServicePort::open] - Error code: {}", e.what()); pendingStart = true; - g_scheduler().addEvent(15000, std::bind_front(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port), "ServicePort::openAcceptor"); + g_dispatcher().scheduleEvent(15000, std::bind_front(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port), "ServicePort::openAcceptor"); } } diff --git a/src/server/signals.cpp b/src/server/signals.cpp index 8fb718386..bb40bbcd8 100644 --- a/src/server/signals.cpp +++ b/src/server/signals.cpp @@ -11,6 +11,7 @@ #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" +#include "game/scheduling/save_manager.hpp" #include "lib/thread/thread_pool.hpp" #include "lua/creature/events.hpp" #include "lua/scripts/lua_environment.hpp" @@ -52,21 +53,21 @@ void Signals::asyncWait() { void Signals::dispatchSignalHandler(int signal) { switch (signal) { case SIGINT: // Shuts the server down - g_dispatcher().addTask(sigintHandler, "sigintHandler"); + g_dispatcher().addEvent(sigintHandler, "sigintHandler"); break; case SIGTERM: // Shuts the server down - g_dispatcher().addTask(sigtermHandler, "sigtermHandler"); + g_dispatcher().addEvent(sigtermHandler, "sigtermHandler"); break; #ifndef _WIN32 case SIGHUP: // Reload config/data - g_dispatcher().addTask(sighupHandler, "sighupHandler"); + g_dispatcher().addEvent(sighupHandler, "sighupHandler"); break; case SIGUSR1: // Saves game state - g_dispatcher().addTask(sigusr1Handler, "sigusr1Handler"); + g_dispatcher().addEvent(sigusr1Handler, "sigusr1Handler"); break; #else case SIGBREAK: // Shuts the server down - g_dispatcher().addTask(sigbreakHandler, "sigbreakHandler"); + g_dispatcher().addEvent(sigbreakHandler, "sigbreakHandler"); // hold the thread until other threads end inject().shutdown(); break; @@ -91,7 +92,7 @@ void Signals::sigtermHandler() { void Signals::sigusr1Handler() { // Dispatcher thread g_logger().info("SIGUSR1 received, saving the game state..."); - g_game().saveGameState(); + g_saveManager().scheduleAll(); } void Signals::sighupHandler() { diff --git a/src/utils/arraylist.hpp b/src/utils/arraylist.hpp new file mode 100644 index 000000000..ea803ab8c --- /dev/null +++ b/src/utils/arraylist.hpp @@ -0,0 +1,158 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include +#include + +// # Mehah +// Arraylist is a very fast container for adding to the front and back, +// it uses two vectors to do this juggling and doesn't allow you to remove the front, as it is slow, +// use std::list for this case. + +namespace stdext { + template + class arraylist { + public: + bool contains(const T &v) { + update(); + return std::ranges::find(backContainer, v) != backContainer.end(); + } + + bool erase(const T &v) { + update(); + + const auto &it = std::ranges::find(backContainer, v); + if (it == backContainer.end()) { + return false; + } + backContainer.erase(it); + + return true; + } + + bool erase(const size_t begin, const size_t end) { + update(); + + if (begin > size() || end > size()) { + return false; + } + + backContainer.erase(backContainer.begin() + begin, backContainer.begin() + end); + return true; + } + + template + bool erase_if(F fnc) { + update(); + return std::erase_if(backContainer, std::move(fnc)) > 0; + } + + auto &front() { + update(); + return backContainer.front(); + } + + void pop_back() { + update(); + backContainer.pop_back(); + } + + auto &back() { + update(); + return backContainer.back(); + } + + void push_front(const T &v) { + needUpdate = true; + frontContainer.push_back(v); + } + + void push_front(T &&_Val) { + needUpdate = true; + frontContainer.push_back(std::move(_Val)); + } + + template + decltype(auto) emplace_front(_Valty &&... v) { + needUpdate = true; + return frontContainer.emplace_back(std::forward<_Valty>(v)...); + } + + void push_back(const T &v) { + backContainer.push_back(v); + } + + void push_back(T &&_Val) { + backContainer.push_back(std::move(_Val)); + } + + template + decltype(auto) emplace_back(_Valty &&... v) { + return backContainer.emplace_back(std::forward<_Valty>(v)...); + } + + bool empty() const noexcept { + return backContainer.empty() && frontContainer.empty(); + } + + size_t size() const noexcept { + return backContainer.size() + frontContainer.size(); + } + + auto begin() noexcept { + update(); + return backContainer.begin(); + } + + auto end() noexcept { + return backContainer.end(); + } + + void clear() noexcept { + frontContainer.clear(); + return backContainer.clear(); + } + + void reserve(size_t newCap) noexcept { + backContainer.reserve(newCap); + frontContainer.reserve(newCap); + } + + const auto &data() noexcept { + update(); + return backContainer.data(); + } + + T &operator[](const size_t i) { + update(); + return backContainer[i]; + } + + private: + inline void update() noexcept { + if (!needUpdate) { + return; + } + + needUpdate = false; + std::ranges::reverse(frontContainer); + frontContainer.insert(frontContainer.end(), make_move_iterator(backContainer.begin()), make_move_iterator(backContainer.end())); + backContainer.clear(); + backContainer.insert(backContainer.end(), make_move_iterator(frontContainer.begin()), make_move_iterator(frontContainer.end())); + frontContainer.clear(); + } + + std::vector backContainer; + std::vector frontContainer; + + bool needUpdate = false; + }; +} diff --git a/src/utils/benchmark.hpp b/src/utils/benchmark.hpp index 8d968ca70..7ec007cea 100644 --- a/src/utils/benchmark.hpp +++ b/src/utils/benchmark.hpp @@ -75,7 +75,7 @@ class Benchmark { private: int64_t time() const noexcept { - return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); } int64_t startTime = -1; diff --git a/src/utils/const.hpp b/src/utils/const.hpp index c7c8ca829..b9020a177 100644 --- a/src/utils/const.hpp +++ b/src/utils/const.hpp @@ -26,7 +26,6 @@ static constexpr int32_t EVENT_IMBUEMENT_INTERVAL = 1000; static constexpr uint8_t IMBUEMENT_MAX_TIER = 3; static constexpr int32_t STORAGEVALUE_EMOTE = 30008; -static constexpr int32_t STORAGEVALUE_PROMOTION = 30018; static constexpr int32_t STORAGEVALUE_PODIUM = 30020; static constexpr int32_t STORAGEVALUE_AUTO_LOOT = 30063; static constexpr int32_t STORAGEVALUE_DAILYREWARD = 14898; diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index f6b5d9ff1..ea927bc51 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -834,7 +834,6 @@ AmmoTypeNames ammoTypeNames = { { "throwingknife", AMMO_THROWINGKNIFE }, { "diamondarrow", AMMO_ARROW }, { "spectralbolt", AMMO_BOLT }, - }; WeaponActionNames weaponActionNames = { @@ -1512,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; @@ -1795,3 +1783,19 @@ std::string formatNumber(uint64_t number) { } return formattedNumber; } + +void sleep_for(uint64_t ms) { + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +} + +/** + * @brief Formats a string to be used as KV key (lowercase, spaces replaced with -, no whitespace) + * @param str The string to format + * @return The formatted string + */ +std::string toKey(const std::string &str) { + std::string key = asLowerCaseString(str); + std::replace(key.begin(), key.end(), ' ', '-'); + key.erase(std::remove_if(key.begin(), key.end(), [](char c) { return std::isspace(c); }), key.end()); + return key; +} diff --git a/src/utils/tools.hpp b/src/utils/tools.hpp index 636aa365e..14e9fd394 100644 --- a/src/utils/tools.hpp +++ b/src/utils/tools.hpp @@ -123,9 +123,9 @@ ItemAttribute_t stringToItemAttribute(const std::string &str); 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); @@ -186,3 +186,5 @@ std::string getPlayerObjectPronoun(PlayerPronoun_t pronoun, PlayerSex_t sex, con std::string getPlayerPossessivePronoun(PlayerPronoun_t pronoun, PlayerSex_t sex, const std::string &name); std::string getPlayerReflexivePronoun(PlayerPronoun_t pronoun, PlayerSex_t sex, const std::string &name); std::string getVerbForPronoun(PlayerPronoun_t pronoun, bool pastTense = false); + +std::string toKey(const std::string &str); diff --git a/src/utils/utils_definitions.hpp b/src/utils/utils_definitions.hpp index ee6b8da2f..e84dcf58a 100644 --- a/src/utils/utils_definitions.hpp +++ b/src/utils/utils_definitions.hpp @@ -655,6 +655,9 @@ enum ItemID_t : uint16_t { ITEM_PRIMAL_POD = 39176, ITEM_DIVINE_EMPOWERMENT = 40450, + ITEM_BATHTUB_FILLED = 26077, + ITEM_BATHTUB_FILLED_NOTMOVABLE = 26100, + ITEM_NONE = 0 }; diff --git a/src/utils/vectorsort.hpp b/src/utils/vectorsort.hpp new file mode 100644 index 000000000..7069e5463 --- /dev/null +++ b/src/utils/vectorsort.hpp @@ -0,0 +1,178 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2022 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include +#include + +// # Mehah +// vector_sort is a container that contains sorted objects. + +namespace stdext { + template + class vector_sort { + public: + bool contains(const T &v) { + update(); + return std::ranges::binary_search(container, v); + } + + bool erase(const T &v) { + update(); + + const auto &it = std::ranges::lower_bound(container, v); + if (it == container.end()) { + return false; + } + container.erase(it); + + return true; + } + + bool erase(const size_t begin, const size_t end) { + update(); + + if (begin > size() || end > size()) { + return false; + } + + container.erase(container.begin() + begin, container.begin() + end); + return true; + } + + template + bool erase_if(F fnc) { + update(); + return std::erase_if(container, std::move(fnc)) > 0; + } + + auto &front() { + update(); + return container.front(); + } + + void pop_back() { + update(); + container.pop_back(); + } + + auto &back() { + update(); + return container.back(); + } + + void push_back(const T &v) { + needUpdate = true; + container.push_back(v); + } + + void push_back(T &&_Val) { + needUpdate = true; + container.push_back(std::move(_Val)); + } + + // Copy all content list to this + auto insert_all(const vector_sort &list) { + needUpdate = true; + return container.insert(container.end(), list.begin(), list.end()); + } + + // Copy all content list to this + auto insert_all(const std::vector &list) { + needUpdate = true; + return container.insert(container.end(), list.begin(), list.end()); + } + + // Move all content list to this + auto join(vector_sort &list) { + needUpdate = true; + auto res = container.insert(container.end(), make_move_iterator(list.begin()), make_move_iterator(list.end())); + list.clear(); + return res; + } + + // Move all content list to this + auto join(std::vector &list) { + needUpdate = true; + auto res = container.insert(container.end(), make_move_iterator(list.begin()), make_move_iterator(list.end())); + list.clear(); + return res; + } + + template + decltype(auto) emplace_back(_Valty &&... v) { + needUpdate = true; + return container.emplace_back(std::forward<_Valty>(v)...); + } + + void partial_sort(size_t begin, size_t end = 0) { + partial_begin = begin; + if (end > begin) { + partial_end = size() - end; + } + } + + void notify_sort() { + needUpdate = true; + } + + bool empty() const noexcept { + return container.empty(); + } + + size_t size() const noexcept { + return container.size(); + } + + auto begin() noexcept { + update(); + return container.begin(); + } + + auto end() noexcept { + return container.end(); + } + + void clear() noexcept { + partial_begin = partial_end = 0; + return container.clear(); + } + + void reserve(size_t newCap) noexcept { + container.reserve(newCap); + } + + const auto &data() noexcept { + update(); + return container.data(); + } + + T &operator[](const size_t i) { + update(); + return container[i]; + } + + private: + inline void update() noexcept { + if (!needUpdate) { + return; + } + + needUpdate = false; + std::ranges::sort(container.begin() + partial_begin, container.end() - partial_end, std::less()); + } + + std::vector container; + + bool needUpdate = false; + size_t partial_begin { 0 }; + size_t partial_end { 0 }; + }; +} diff --git a/tests/fixture/account/in_memory_account_repository.hpp b/tests/fixture/account/in_memory_account_repository.hpp index dd4e77881..3996b7d11 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 91fd28a30..5b4055af7 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)); }); diff --git a/vcproj/otxserver.vcxproj b/vcproj/otxserver.vcxproj index bd2a76d48..00da63152 100644 --- a/vcproj/otxserver.vcxproj +++ b/vcproj/otxserver.vcxproj @@ -59,9 +59,9 @@ - + @@ -212,6 +212,7 @@ + @@ -221,6 +222,7 @@ + @@ -257,11 +259,12 @@ + + - @@ -470,7 +473,7 @@ $(ProjectName)-sln - false + true false @@ -548,4 +551,4 @@ - \ No newline at end of file + diff --git a/vcproj/settings.props b/vcproj/settings.props index aa958c24b..9f01a7890 100644 --- a/vcproj/settings.props +++ b/vcproj/settings.props @@ -51,7 +51,7 @@ Level3 true true - MultiThreaded + MultiThreadedDLL $(CANARY_LIBDEPS)