From 9519cee4baac18a85ff0b529ade351b17400072b Mon Sep 17 00:00:00 2001 From: Matt Gomez Date: Wed, 13 Dec 2023 10:17:56 -0600 Subject: [PATCH] update : https://github.com/opentibiabr/canary/commit/e570944aee0fa517a9167aa8771d69b8075b70ba --- metrics/README.md | 65 +++ metrics/docker-compose.yml | 43 ++ metrics/prometheus/prometheus.yml | 9 + src/account/account.cpp | 3 +- src/account/account.hpp | 3 +- src/account/account_repository_db.cpp | 2 +- src/canary_server.cpp | 13 +- src/config/config_definitions.hpp | 467 +++++++++--------- src/config/configmanager.cpp | 12 +- src/creatures/combat/combat.cpp | 7 +- src/creatures/combat/condition.cpp | 32 +- src/creatures/combat/spells.cpp | 40 +- src/creatures/combat/spells.hpp | 10 +- src/creatures/creature.cpp | 66 ++- src/creatures/monsters/monsters.cpp | 3 +- .../monsters/spawns/spawn_monster.cpp | 260 +++++++--- .../monsters/spawns/spawn_monster.hpp | 19 +- src/creatures/npcs/npc.cpp | 3 + src/creatures/npcs/npcs.cpp | 4 +- src/creatures/players/player.cpp | 107 +++- src/creatures/players/player.hpp | 1 + src/database/database.cpp | 12 +- src/game/bank/bank.cpp | 10 +- src/game/functions/game_reload.cpp | 1 + src/game/game.cpp | 210 ++++---- src/game/game.hpp | 9 +- src/game/scheduling/task.cpp | 4 +- src/game/scheduling/task.hpp | 3 +- src/game/zones/zone.cpp | 4 +- src/io/functions/iologindata_save_player.cpp | 1 + src/io/io_wheel.cpp | 4 +- src/io/iobestiary.cpp | 8 +- src/io/iologindata.cpp | 5 +- src/io/iomarket.cpp | 29 ++ src/io/iomarket.hpp | 1 + src/io/ioprey.cpp | 6 + src/items/tile.cpp | 21 + src/items/weapons/weapons.cpp | 9 +- src/kv/kv.cpp | 21 +- src/kv/kv.hpp | 10 +- src/kv/kv_sql.cpp | 20 +- src/kv/kv_sql.hpp | 1 + src/kv/value_wrapper.cpp | 17 +- src/lib/CMakeLists.txt | 1 + src/lib/metrics/metrics.cpp | 105 ++++ src/lib/metrics/metrics.hpp | 171 +++++++ src/lua/functions/core/CMakeLists.txt | 1 + .../functions/core/game/game_functions.cpp | 20 +- .../core/libs/core_libs_functions.hpp | 2 + src/lua/functions/core/libs/kv_functions.cpp | 40 +- src/lua/functions/core/libs/kv_functions.hpp | 6 + .../functions/core/libs/metrics_functions.cpp | 40 ++ .../functions/core/libs/metrics_functions.hpp | 21 + .../network/network_message_functions.cpp | 5 +- .../creatures/combat/spell_functions.cpp | 4 +- .../functions/creatures/npc/npc_functions.cpp | 10 +- .../creatures/player/player_functions.cpp | 1 + src/lua/scripts/luascript.cpp | 30 ++ src/lua/scripts/luascript.hpp | 2 + src/map/house/house.cpp | 2 + src/pch.hpp | 1 + src/server/network/connection/connection.cpp | 207 ++++---- src/server/network/connection/connection.hpp | 3 +- src/server/network/message/networkmessage.cpp | 8 +- src/server/network/message/networkmessage.hpp | 2 +- src/server/network/protocol/protocolgame.cpp | 369 +++++++------- src/server/network/protocol/protocollogin.cpp | 12 +- .../network/protocol/protocolstatus.cpp | 28 +- src/server/server.cpp | 2 +- src/server/server.hpp | 1 + 70 files changed, 1856 insertions(+), 813 deletions(-) create mode 100644 metrics/README.md create mode 100644 metrics/docker-compose.yml create mode 100644 metrics/prometheus/prometheus.yml create mode 100644 src/lib/metrics/metrics.cpp create mode 100644 src/lib/metrics/metrics.hpp create mode 100644 src/lua/functions/core/libs/metrics_functions.cpp create mode 100644 src/lua/functions/core/libs/metrics_functions.hpp diff --git a/metrics/README.md b/metrics/README.md new file mode 100644 index 000000000..551fc6d87 --- /dev/null +++ b/metrics/README.md @@ -0,0 +1,65 @@ +# Canary Metrics (OpenTelemetry) + +By default, no metrics are collected or exported. To enable metrics, you must setup a metrics exporter. The following example shows how to setup a Prometheus exporter. + +config.lua + +```lua +metricsEnablePrometheus = true +metricsPrometheusAddress = "0.0.0.0:9464" +``` + +This, in and of itself will expose a Prometheus endpoint at `http://localhost:9464/metrics`. However, you will need to configure Prometheus to scrape this endpoint. + +The easiest, batteries included way, to do this is using the provided `docker-compose.yml` file provided in this `metrics` directory. Simply run `docker-compose up` and you will have a Prometheus instance running and scraping the Canary metrics endpoint. + +The `docker-compose.yml` file also includes a Grafana instance that is preconfigured to use the Prometheus instance as a data source. The Grafana instance is exposed at `http://localhost:3000` and the default username and password are `admin` and `admin` respectively (you will be prompted to change the password on first login). + +## Usage + +This is an **advanced** feature. While you can simply enable OStream and get metrics in your logs, that is not recommended to do in production. Prometheus can be run efficiently in production with minimal impact to server performance. + +_Enabling OStream:_ + +```config.lua +metricsEnableOstream = true +metricsOstreamInterval = 1000 +``` + +If you **don't** how what Prometheus and Grafana are, you need to learn that first: https://prometheus.io/ is your starting point. You can come back to this feature once you've understood how to install and run this software. + +## Metrics + +We export all kinds of metrics, but the most important ones are: + +Here's an interactive demo of a dashboard from a real production server: https://snapshots.raintank.io/dashboard/snapshot/bpiq45inK3I2Xixa2d7oNHWekdiDE6zr + +- Latency metrics for C++ methods +- Latency metrics for Lua functions +- Latency metrics for SQL queries +- Latency metrics for Dispatcher tasks +- Latency metrics for DB Lock contention + +**Screenshot** +![grafana](https://github.com/opentibiabr/canary/assets/223760/b307c335-9af9-4c1a-bf7e-5c3dc86a016d) + +## Analytics + +We also export analytic event, counters and other useful data. This is useful for debugging and understanding the behavior of the server. Some interesting ones are: + +- Stats around monsters killed (per monster type, player, etc) +- Stats around raw exp and total exp gained +- Stats around wealth gained (based on gold and item drops, with their NPC value) + +### Examples: + +_Note: you can normally see player names here, I've hidden those for privacy._ + +**Raw exp/h** +![exp-per-hour](https://github.com/opentibiabr/canary/assets/223760/3a873aca-f2e4-4d19-8e61-ed20c176a30f) + +**Raw gold/h** +![gold-per-hour](https://github.com/opentibiabr/canary/assets/223760/1c0d1e99-c4b9-4d9a-aced-75ac376b4673) + +**Monsters killed/h** +![monsters-per-hour](https://github.com/opentibiabr/canary/assets/223760/4d8c9e19-d579-4405-a018-fc69c79a11c2) diff --git a/metrics/docker-compose.yml b/metrics/docker-compose.yml new file mode 100644 index 000000000..28ce37108 --- /dev/null +++ b/metrics/docker-compose.yml @@ -0,0 +1,43 @@ +--- +version: "3" + +services: + prometheus: + image: prom/prometheus:latest + restart: unless-stopped + volumes: + - ./prometheus:/etc/prometheus + - prometheus-data:/prometheus + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.enable-lifecycle" + - "--log.level=debug" + ports: + - "9090:9090" + extra_hosts: + - "host.docker.internal:host-gateway" + networks: + - monitoring-net + + grafana: + image: grafana/grafana:latest + restart: unless-stopped + volumes: + - grafana-data:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + depends_on: + - prometheus + ports: + - "4444:3000" + networks: + - monitoring-net + +volumes: + prometheus-data: + grafana-data: + +networks: + monitoring-net: diff --git a/metrics/prometheus/prometheus.yml b/metrics/prometheus/prometheus.yml new file mode 100644 index 000000000..97a21bc19 --- /dev/null +++ b/metrics/prometheus/prometheus.yml @@ -0,0 +1,9 @@ +--- +global: + scrape_interval: 5s + scrape_timeout: 2s + evaluation_interval: 5s +scrape_configs: + - job_name: canary + static_configs: + - targets: ['host.docker.internal:9464'] diff --git a/src/account/account.cpp b/src/account/account.cpp index 881b94914..2179482ef 100644 --- a/src/account/account.cpp +++ b/src/account/account.cpp @@ -170,8 +170,9 @@ namespace account { } void Account::addPremiumDays(const int32_t &days) { - auto timeLeft = static_cast((m_account.premiumLastDay - getTimeNow()) % 86400); + auto timeLeft = std::max(0, static_cast((m_account.premiumLastDay - getTimeNow()) % 86400)); setPremiumDays(m_account.premiumRemainingDays + days); + m_account.premiumDaysPurchased += days; if (timeLeft > 0) { m_account.premiumLastDay += timeLeft; diff --git a/src/account/account.hpp b/src/account/account.hpp index 839b99283..061f7dcdc 100644 --- a/src/account/account.hpp +++ b/src/account/account.hpp @@ -13,6 +13,7 @@ #include "config/configmanager.hpp" #include "utils/definitions.hpp" #include "security/argon.hpp" +#include "utils/tools.hpp" namespace account { class Account { @@ -106,7 +107,7 @@ namespace account { void addPremiumDays(const int32_t &days); void setPremiumDays(const int32_t &days); [[nodiscard]] inline uint32_t getPremiumRemainingDays() const { - return m_account.premiumRemainingDays; + return m_account.premiumLastDay > getTimeNow() ? static_cast((m_account.premiumLastDay - getTimeNow()) / 86400) : 0; } [[nodiscard]] inline uint32_t getPremiumDaysPurchased() const { diff --git a/src/account/account_repository_db.cpp b/src/account/account_repository_db.cpp index c4a960e62..321741bd7 100644 --- a/src/account/account_repository_db.cpp +++ b/src/account/account_repository_db.cpp @@ -160,11 +160,11 @@ namespace account { acc.id = result->getNumber("id"); acc.accountType = static_cast(result->getNumber("type")); - acc.premiumRemainingDays = result->getNumber("premdays"); acc.premiumLastDay = result->getNumber("lastday"); acc.sessionExpires = result->getNumber("expires"); acc.premiumDaysPurchased = result->getNumber("premdays_purchased"); acc.creationTime = result->getNumber("creation"); + acc.premiumRemainingDays = acc.premiumLastDay > getTimeNow() ? (acc.premiumLastDay - getTimeNow()) / 86400 : 0; setupLoyaltyInfo(acc); diff --git a/src/canary_server.cpp b/src/canary_server.cpp index e1efbca62..1747a8e0e 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -61,6 +61,16 @@ int CanaryServer::run() { loadConfigLua(); logger.info("Server protocol: {}.{}{}", CLIENT_VERSION_UPPER, CLIENT_VERSION_LOWER, g_configManager().getBoolean(OLD_PROTOCOL, __FUNCTION__) ? " and 10x allowed!" : ""); + metrics::Options metricsOptions; + metricsOptions.enablePrometheusExporter = g_configManager().getBoolean(METRICS_ENABLE_PROMETHEUS, __FUNCTION__); + if (metricsOptions.enablePrometheusExporter) { + metricsOptions.prometheusOptions.url = g_configManager().getString(METRICS_PROMETHEUS_ADDRESS, __FUNCTION__); + } + metricsOptions.enableOStreamExporter = g_configManager().getBoolean(METRICS_ENABLE_OSTREAM, __FUNCTION__); + if (metricsOptions.enableOStreamExporter) { + metricsOptions.ostreamOptions.export_interval_millis = std::chrono::milliseconds(g_configManager().getNumber(METRICS_OSTREAM_INTERVAL, __FUNCTION__)); + } + g_metrics().init(metricsOptions); rsa.start(); initializeDatabase(); @@ -209,7 +219,7 @@ void CanaryServer::logInfos() { /** *It is preferable to keep the close button off as it closes the server without saving (this can cause the player to lose items from houses and others informations, since windows automatically closes the process in five seconds, when forcing the close) * Choose to use "CTROL + C" or "CTROL + BREAK" for security close - * To activate/desactivate window; + * To activate/deactivate window; * \param MF_GRAYED Disable the "x" (force close) button * \param MF_ENABLED Enable the "x" (force close) button */ @@ -376,4 +386,5 @@ void CanaryServer::modulesLoadHelper(bool loaded, std::string moduleName) { void CanaryServer::shutdown() { inject().shutdown(); g_dispatcher().shutdown(); + g_metrics().shutdown(); } diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index dc7f1caac..541da6793 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -11,268 +11,277 @@ // Enum enum ConfigKey_t : uint16_t { - ALLOW_CHANGEOUTFIT, - ONE_PLAYER_ON_ACCOUNT, + ACTIONS_DELAY_INTERVAL, + ADVENTURERSBLESSING_LEVEL, AIMBOT_HOTKEY_ENABLED, - REMOVE_RUNE_CHARGES, - EXPERIENCE_FROM_PLAYERS, - FREE_PREMIUM, - REPLACE_KICK_ON_LOGIN, - BIND_ONLY_GLOBAL_ADDRESS, - OPTIMIZE_DATABASE, - MARKET_PREMIUM, - EMOTE_SPELLS, - STAMINA_SYSTEM, - WARN_UNSAFE_SCRIPTS, - CONVERT_UNSAFE_SCRIPTS, - CLASSIC_ATTACK_SPEED, - SCRIPTS_CONSOLE_LOGS, - REMOVE_WEAPON_AMMO, - REMOVE_WEAPON_CHARGES, - REMOVE_POTION_CHARGES, - GLOBAL_SERVER_SAVE_NOTIFY_MESSAGE, - GLOBAL_SERVER_SAVE_CLEAN_MAP, - GLOBAL_SERVER_SAVE_CLOSE, - GLOBAL_SERVER_SAVE_SHUTDOWN, - FORCE_MONSTERTYPE_LOAD, - HOUSE_OWNED_BY_ACCOUNT, - CLEAN_PROTECTION_ZONES, ALLOW_BLOCK_SPAWN, - HOUSE_PURSHASED_SHOW_PRICE, - ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, - WEATHER_RAIN, - WEATHER_THUNDER, - TOGGLE_FREE_QUEST, - ONLY_PREMIUM_ACCOUNT, - TOGGLE_MAP_CUSTOM, + ALLOW_CHANGEOUTFIT, + ALLOW_RELOAD, ALL_CONSOLE_LOG, - STAMINA_TRAINER, - STAMINA_PZ, - PUSH_WHEN_ATTACKING, - SORT_LOOT_BY_CHANCE, - TOGGLE_SAVE_INTERVAL, - TOGGLE_SAVE_INTERVAL_CLEAN_MAP, - PREY_ENABLED, - PREY_FREE_THIRD_SLOT, - TASK_HUNTING_ENABLED, - TASK_HUNTING_FREE_THIRD_SLOT, - STASH_MOVING, - TOGGLE_IMBUEMENT_SHRINE_STORAGE, - TOGGLE_IMBUEMENT_NON_AGGRESSIVE_FIGHT_ONLY, - AUTOLOOT, + AUTH_TYPE, AUTOBANK, - RATE_USE_STAGES, - INVENTORY_GLOW, - TELEPORT_SUMMONS, - TOGGLE_DOWNLOAD_MAP, - USE_ANY_DATAPACK_FOLDER, - ALLOW_RELOAD, + AUTOLOOT, + BESTIARY_KILL_MULTIPLIER, + BESTIARY_RATE_CHARM_SHOP_PRICE, + BIND_ONLY_GLOBAL_ADDRESS, + BLACK_SKULL_DURATION, + BOOSTED_BOSS_KILL_BONUS, + BOOSTED_BOSS_LOOT_BONUS, BOOSTED_BOSS_SLOT, - XP_DISPLAY_MODE, - TOGGLE_GOLD_POUCH_ALLOW_ANYTHING, - TOGGLE_GOLD_POUCH_QUICKLOOT_ONLY, - TOGGLE_SERVER_IS_RETRO, - TOGGLE_TRAVELS_FREE, - TELEPORT_PLAYER_TO_VOCATION_ROOM, - OLD_PROTOCOL, - TOGGLE_HAZARDSYSTEM, - LOYALTY_ENABLED, - PARTY_AUTO_SHARE_EXPERIENCE, - PARTY_SHARE_LOOT_BOOSTS, - RESET_SESSIONS_ON_STARTUP, - TOGGLE_WHEELSYSTEM, - TOGGLE_ATTACK_SPEED_ONFIST, - VIP_SYSTEM_ENABLED, - VIP_AUTOLOOT_VIP_ONLY, - VIP_KEEP_HOUSE, - VIP_STAY_ONLINE, - REWARD_CHEST_COLLECT_ENABLED, - TOGGLE_MOUNT_IN_PZ, - TOGGLE_HOUSE_TRANSFER_ON_SERVER_RESTART, - TOGGLE_RECEIVE_REWARD, - TOGGLE_MAINTAIN_MODE, - MAP_NAME, - MAP_DOWNLOAD_URL, - MAP_AUTHOR, - HOUSE_RENT_PERIOD, - SERVER_NAME, - SERVER_MOTD, - OWNER_NAME, - OWNER_EMAIL, - URL, - LOCATION, - IP, - WORLD_TYPE, - MYSQL_HOST, - MYSQL_USER, - MYSQL_PASS, - MYSQL_DB, - MYSQL_SOCK, - AUTH_TYPE, - DEFAULT_PRIORITY, - STORE_IMAGES_URL, - MAP_CUSTOM_NAME, - MAP_CUSTOM_AUTHOR, - DISCORD_WEBHOOK_URL, - SAVE_INTERVAL_TYPE, - GLOBAL_SERVER_SAVE_TIME, - DATA_DIRECTORY, - CORE_DIRECTORY, - FORGE_FIENDISH_INTERVAL_TYPE, - FORGE_FIENDISH_INTERVAL_TIME, - TIBIADROME_CONCOCTION_TICK_TYPE, - M_CONST, - MAINTAIN_MODE_MESSAGE, - SQL_PORT, - MAX_PLAYERS, - PZ_LOCKED, - DEFAULT_DESPAWNRANGE, - DEFAULT_DESPAWNRADIUS, - RATE_EXPERIENCE, - RATE_SKILL, - RATE_LOOT, - RATE_MAGIC, - RATE_SPAWN, - RATE_KILLING_IN_THE_NAME_OF_POINTS, - HOUSE_PRICE_PER_SQM, - HOUSE_BUY_LEVEL, - HOUSE_LOSE_AFTER_INACTIVITY, - MAX_MESSAGEBUFFER, - ACTIONS_DELAY_INTERVAL, - EX_ACTIONS_DELAY_INTERVAL, - KICK_AFTER_MINUTES, - PROTECTION_LEVEL, - DEATH_LOSE_PERCENT, - STATUSQUERY_TIMEOUT, - FRAG_TIME, - WHITE_SKULL_TIME, - GAME_PORT, - LOGIN_PORT, - STATUS_PORT, - STAIRHOP_DELAY, - MAX_CONTAINER, - MAX_CONTAINER_ITEM, - MARKET_OFFER_DURATION, - DEPOT_BOXES, - FREE_DEPOT_LIMIT, - PREMIUM_DEPOT_LIMIT, + BOSSTIARY_KILL_MULTIPLIER, + BOSS_DEFAULT_TIME_TO_DEFEAT, + BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN, + BUY_AOL_COMMAND_FEE, + BUY_BLESS_COMMAND_FEE, CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, - MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, - EXP_FROM_PLAYERS_LEVEL_RANGE, - MAX_PACKETS_PER_SECOND, + CLASSIC_ATTACK_SPEED, + CLEAN_PROTECTION_ZONES, COMPRESSION_LEVEL, - STORE_COIN_PACKET, + CONVERT_UNSAFE_SCRIPTS, + CORE_DIRECTORY, + CRITICALCHANCE, + DATA_DIRECTORY, DAY_KILLS_TO_RED, - WEEK_KILLS_TO_RED, - MONTH_KILLS_TO_RED, - RED_SKULL_DURATION, - BLACK_SKULL_DURATION, - ORANGE_SKULL_DURATION, - GLOBAL_SERVER_SAVE_NOTIFY_DURATION, - PUSH_DELAY, - PUSH_DISTANCE_DELAY, - STASH_ITEMS, - PARTY_LIST_MAX_DISTANCE, - STAMINA_ORANGE_DELAY, - STAMINA_GREEN_DELAY, - STAMINA_TRAINER_DELAY, - STAMINA_PZ_GAIN, - STAMINA_TRAINER_GAIN, - SAVE_INTERVAL_TIME, - PREY_REROLL_PRICE_LEVEL, - PREY_SELECTION_LIST_PRICE, - PREY_BONUS_TIME, - PREY_BONUS_REROLL_PRICE, - PREY_FREE_REROLL_TIME, - TASK_HUNTING_LIMIT_EXHAUST, - TASK_HUNTING_REROLL_PRICE_LEVEL, - TASK_HUNTING_SELECTION_LIST_PRICE, - TASK_HUNTING_BONUS_REROLL_PRICE, - TASK_HUNTING_FREE_REROLL_TIME, - MAX_ALLOWED_ON_A_DUMMY, - FREE_QUEST_STAGE, + DEATH_LOSE_PERCENT, + DEFAULT_DESPAWNRADIUS, + DEFAULT_DESPAWNRANGE, + DEFAULT_PRIORITY, DEPOTCHEST, - CRITICALCHANCE, - ADVENTURERSBLESSING_LEVEL, - FORGE_MAX_ITEM_TIER, - FORGE_COST_ONE_SLIVER, - FORGE_SLIVER_AMOUNT, - FORGE_CORE_COST, - FORGE_MAX_DUST, - FORGE_FUSION_DUST_COST, - FORGE_TRANSFER_DUST_COST, + DEPOT_BOXES, + DISCORD_WEBHOOK_DELAY_MS, + DISCORD_WEBHOOK_URL, + EMOTE_SPELLS, + EXPERIENCE_FROM_PLAYERS, + EXP_FROM_PLAYERS_LEVEL_RANGE, + EX_ACTIONS_DELAY_INTERVAL, + FAMILIAR_TIME, + FORCE_MONSTERTYPE_LOAD, + FORGE_AMOUNT_MULTIPLIER, FORGE_BASE_SUCCESS_RATE, FORGE_BONUS_SUCCESS_RATE, - FORGE_TIER_LOSS_REDUCTION, - FORGE_AMOUNT_MULTIPLIER, - FORGE_MIN_SLIVERS, - FORGE_MAX_SLIVERS, - FORGE_INFLUENCED_CREATURES_LIMIT, + FORGE_CORE_COST, + FORGE_COST_ONE_SLIVER, FORGE_FIENDISH_CREATURES_LIMIT, - BESTIARY_KILL_MULTIPLIER, - BOSSTIARY_KILL_MULTIPLIER, - BOOSTED_BOSS_LOOT_BONUS, - BOOSTED_BOSS_KILL_BONUS, - FAMILIAR_TIME, - BUY_AOL_COMMAND_FEE, - BUY_BLESS_COMMAND_FEE, - HAZARD_CRITICAL_INTERVAL, + FORGE_FIENDISH_INTERVAL_TIME, + FORGE_FIENDISH_INTERVAL_TYPE, + FORGE_FUSION_DUST_COST, + FORGE_INFLUENCED_CREATURES_LIMIT, + FORGE_MAX_DUST, + FORGE_MAX_ITEM_TIER, + FORGE_MAX_SLIVERS, + FORGE_MIN_SLIVERS, + FORGE_SLIVER_AMOUNT, + FORGE_TIER_LOSS_REDUCTION, + FORGE_TRANSFER_DUST_COST, + FRAG_TIME, + FREE_DEPOT_LIMIT, + FREE_PREMIUM, + FREE_QUEST_STAGE, + GAME_PORT, + GLOBAL_SERVER_SAVE_CLEAN_MAP, + GLOBAL_SERVER_SAVE_CLOSE, + GLOBAL_SERVER_SAVE_NOTIFY_DURATION, + GLOBAL_SERVER_SAVE_NOTIFY_MESSAGE, + GLOBAL_SERVER_SAVE_SHUTDOWN, + GLOBAL_SERVER_SAVE_TIME, HAZARD_CRITICAL_CHANCE, + HAZARD_CRITICAL_INTERVAL, HAZARD_CRITICAL_MULTIPLIER, HAZARD_DAMAGE_MULTIPLIER, HAZARD_DODGE_MULTIPLIER, - HAZARD_PODS_DROP_MULTIPLIER, - HAZARD_PODS_TIME_TO_DAMAGE, HAZARD_EXP_BONUS_MULTIPLIER, HAZARD_LOOT_BONUS_MULTIPLIER, HAZARD_PODS_DAMAGE, + HAZARD_PODS_DROP_MULTIPLIER, + HAZARD_PODS_TIME_TO_DAMAGE, HAZARD_PODS_TIME_TO_SPAWN, HAZARD_SPAWN_PLUNDER_MULTIPLIER, + HOUSE_BUY_LEVEL, + HOUSE_LOSE_AFTER_INACTIVITY, + HOUSE_OWNED_BY_ACCOUNT, + HOUSE_PRICE_PER_SQM, + HOUSE_PRICE_RENT_MULTIPLIER, + HOUSE_PURSHASED_SHOW_PRICE, + HOUSE_RENT_PERIOD, + HOUSE_RENT_RATE, + INVENTORY_GLOW, + IP, + KICK_AFTER_MINUTES, + LOCATION, + LOGIN_PORT, + LOGLEVEL, LOW_LEVEL_BONUS_EXP, + LOYALTY_BONUS_PERCENTAGE_MULTIPLIER, + LOYALTY_ENABLED, LOYALTY_POINTS_PER_CREATION_DAY, - LOYALTY_POINTS_PER_PREMIUM_DAY_SPENT, LOYALTY_POINTS_PER_PREMIUM_DAY_PURCHASED, - WHEEL_POINTS_PER_LEVEL, - MULTIPLIER_ATTACKONFIST, + LOYALTY_POINTS_PER_PREMIUM_DAY_SPENT, + MAINTAIN_MODE_MESSAGE, + MAP_AUTHOR, + MAP_CUSTOM_AUTHOR, + MAP_CUSTOM_NAME, + MAP_DOWNLOAD_URL, + MAP_NAME, + MARKET_OFFER_DURATION, + MARKET_PREMIUM, + MAX_ALLOWED_ON_A_DUMMY, + MAX_CONTAINER, + MAX_CONTAINER_ITEM, + MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, + MAX_MESSAGEBUFFER, + MAX_PACKETS_PER_SECOND, + MAX_PLAYERS, + MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, + MAX_PLAYERS_PER_ACCOUNT, MAX_SPEED_ATTACKONFIST, - TIBIADROME_CONCOCTION_COOLDOWN, - TIBIADROME_CONCOCTION_DURATION, - T_CONST, + METRICS_ENABLE_OSTREAM, + METRICS_ENABLE_PROMETHEUS, + METRICS_OSTREAM_INTERVAL, + METRICS_PROMETHEUS_ADDRESS, + MONTH_KILLS_TO_RED, + MULTIPLIER_ATTACKONFIST, + MYSQL_DB, + MYSQL_HOST, + MYSQL_PASS, + MYSQL_SOCK, + MYSQL_USER, + M_CONST, + OLD_PROTOCOL, + ONE_PLAYER_ON_ACCOUNT, + ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, + ONLY_PREMIUM_ACCOUNT, + OPTIMIZE_DATABASE, + ORANGE_SKULL_DURATION, + OWNER_EMAIL, + OWNER_NAME, PARALLELISM, - BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN, - BOSS_DEFAULT_TIME_TO_DEFEAT, - VIP_BONUS_EXP, - VIP_BONUS_LOOT, - VIP_BONUS_SKILL, - VIP_FAMILIAR_TIME_COOLDOWN_REDUCTION, - REWARD_CHEST_MAX_COLLECT_ITEMS, - DISCORD_WEBHOOK_DELAY_MS, + PARTY_AUTO_SHARE_EXPERIENCE, + PARTY_LIST_MAX_DISTANCE, + PARTY_SHARE_LOOT_BOOSTS, + PARTY_SHARE_LOOT_BOOSTS_DIMINISHING_FACTOR, + PREMIUM_DEPOT_LIMIT, + PREY_BONUS_REROLL_PRICE, + PREY_BONUS_TIME, + PREY_ENABLED, + PREY_FREE_REROLL_TIME, + PREY_FREE_THIRD_SLOT, + PREY_REROLL_PRICE_LEVEL, + PREY_SELECTION_LIST_PRICE, + PROTECTION_LEVEL, + PUSH_DELAY, + PUSH_DISTANCE_DELAY, + PUSH_WHEN_ATTACKING, PVP_MAX_LEVEL_DIFFERENCE, - BESTIARY_RATE_CHARM_SHOP_PRICE, + PVP_RATE_DAMAGE_REDUCTION_PER_LEVEL, + PVP_RATE_DAMAGE_TAKEN_PER_LEVEL, + PZ_LOCKED, + RANDOM_MONSTER_SPAWN, + RATE_ATTACK_SPEED, + RATE_BOSS_ATTACK, + RATE_BOSS_DEFENSE, + RATE_BOSS_HEALTH, + RATE_EXERCISE_TRAINING_SPEED, + RATE_EXPERIENCE, RATE_HEALTH_REGEN, RATE_HEALTH_REGEN_SPEED, + RATE_KILLING_IN_THE_NAME_OF_POINTS, + RATE_LOOT, + RATE_MAGIC, RATE_MANA_REGEN, RATE_MANA_REGEN_SPEED, - RATE_SOUL_REGEN, - RATE_SOUL_REGEN_SPEED, - RATE_SPELL_COOLDOWN, - RATE_ATTACK_SPEED, - RATE_OFFLINE_TRAINING_SPEED, - RATE_EXERCISE_TRAINING_SPEED, - RATE_MONSTER_HEALTH, RATE_MONSTER_ATTACK, RATE_MONSTER_DEFENSE, - RATE_BOSS_HEALTH, - RATE_BOSS_ATTACK, - RATE_BOSS_DEFENSE, - RATE_NPC_HEALTH, + RATE_MONSTER_HEALTH, RATE_NPC_ATTACK, RATE_NPC_DEFENSE, - LOYALTY_BONUS_PERCENTAGE_MULTIPLIER, - PARTY_SHARE_LOOT_BOOSTS_DIMINISHING_FACTOR, - PVP_RATE_DAMAGE_TAKEN_PER_LEVEL, - PVP_RATE_DAMAGE_REDUCTION_PER_LEVEL, - HOUSE_PRICE_RENT_MULTIPLIER, - HOUSE_RENT_RATE, - LOGLEVEL, + RATE_NPC_HEALTH, + RATE_OFFLINE_TRAINING_SPEED, + RATE_SKILL, + RATE_SOUL_REGEN, + RATE_SOUL_REGEN_SPEED, + RATE_SPAWN, + RATE_SPELL_COOLDOWN, + RATE_USE_STAGES, + RED_SKULL_DURATION, + REFUND_BEGINNING_WEAPON_MANA, + REMOVE_BEGINNING_WEAPON_AMMO, + REMOVE_POTION_CHARGES, + REMOVE_RUNE_CHARGES, + REMOVE_WEAPON_AMMO, + REMOVE_WEAPON_CHARGES, + REPLACE_KICK_ON_LOGIN, + RESET_SESSIONS_ON_STARTUP, + REWARD_CHEST_COLLECT_ENABLED, + REWARD_CHEST_MAX_COLLECT_ITEMS, + SAVE_INTERVAL_TIME, + SAVE_INTERVAL_TYPE, + SCRIPTS_CONSOLE_LOGS, + SERVER_MOTD, + SERVER_NAME, + SORT_LOOT_BY_CHANCE, + SQL_PORT, + STAIRHOP_DELAY, + STAMINA_GREEN_DELAY, + STAMINA_ORANGE_DELAY, + STAMINA_PZ, + STAMINA_PZ_GAIN, + STAMINA_SYSTEM, + STAMINA_TRAINER, + STAMINA_TRAINER_DELAY, + STAMINA_TRAINER_GAIN, + STASH_ITEMS, + STASH_MOVING, + STATUSQUERY_TIMEOUT, + STATUS_PORT, + STORE_COIN_PACKET, + STORE_IMAGES_URL, + TASK_HUNTING_BONUS_REROLL_PRICE, + TASK_HUNTING_ENABLED, + TASK_HUNTING_FREE_REROLL_TIME, + TASK_HUNTING_FREE_THIRD_SLOT, + TASK_HUNTING_LIMIT_EXHAUST, + TASK_HUNTING_REROLL_PRICE_LEVEL, + TASK_HUNTING_SELECTION_LIST_PRICE, + TELEPORT_PLAYER_TO_VOCATION_ROOM, + TELEPORT_SUMMONS, + TIBIADROME_CONCOCTION_COOLDOWN, + TIBIADROME_CONCOCTION_DURATION, + TIBIADROME_CONCOCTION_TICK_TYPE, + TOGGLE_ATTACK_SPEED_ONFIST, + TOGGLE_DOWNLOAD_MAP, + TOGGLE_FREE_QUEST, + TOGGLE_GOLD_POUCH_ALLOW_ANYTHING, + TOGGLE_GOLD_POUCH_QUICKLOOT_ONLY, + TOGGLE_HAZARDSYSTEM, + TOGGLE_HOUSE_TRANSFER_ON_SERVER_RESTART, + TOGGLE_IMBUEMENT_NON_AGGRESSIVE_FIGHT_ONLY, + TOGGLE_IMBUEMENT_SHRINE_STORAGE, + TOGGLE_MAINTAIN_MODE, + TOGGLE_MAP_CUSTOM, + TOGGLE_MOUNT_IN_PZ, + TOGGLE_RECEIVE_REWARD, + TOGGLE_SAVE_INTERVAL, + TOGGLE_SAVE_INTERVAL_CLEAN_MAP, + TOGGLE_SERVER_IS_RETRO, + TOGGLE_TRAVELS_FREE, + TOGGLE_WHEELSYSTEM, + T_CONST, + URL, + USE_ANY_DATAPACK_FOLDER, + VIP_AUTOLOOT_VIP_ONLY, + VIP_BONUS_EXP, + VIP_BONUS_LOOT, + VIP_BONUS_SKILL, + VIP_FAMILIAR_TIME_COOLDOWN_REDUCTION, + VIP_KEEP_HOUSE, + VIP_STAY_ONLINE, + VIP_SYSTEM_ENABLED, + WARN_UNSAFE_SCRIPTS, + WEATHER_RAIN, + WEATHER_THUNDER, + WEEK_KILLS_TO_RED, + WHEEL_POINTS_PER_LEVEL, + WHITE_SKULL_TIME, + WORLD_TYPE, + XP_DISPLAY_MODE, }; diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 293146427..97d1052ad 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -44,6 +44,7 @@ bool ConfigManager::load() { loadBoolConfig(L, OPTIMIZE_DATABASE, "startupDatabaseOptimization", true); loadBoolConfig(L, TOGGLE_MAP_CUSTOM, "toggleMapCustom", true); loadBoolConfig(L, TOGGLE_MAINTAIN_MODE, "toggleMaintainMode", false); + loadBoolConfig(L, RANDOM_MONSTER_SPAWN, "randomMonsterSpawn", false); loadStringConfig(L, MAINTAIN_MODE_MESSAGE, "maintainModeMessage", ""); loadStringConfig(L, IP, "ip", "127.0.0.1"); @@ -82,7 +83,8 @@ bool ConfigManager::load() { } loadBoolConfig(L, ALLOW_CHANGEOUTFIT, "allowChangeOutfit", true); - loadBoolConfig(L, ONE_PLAYER_ON_ACCOUNT, "onePlayerOnlinePerAccount", true); + loadIntConfig(L, MAX_PLAYERS_PER_ACCOUNT, "maxPlayersOnlinePerAccount", 1); + loadIntConfig(L, MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, "maxPlayersOutsidePZPerAccount", 1); loadBoolConfig(L, AIMBOT_HOTKEY_ENABLED, "hotkeyAimbotEnabled", true); loadBoolConfig(L, REMOVE_RUNE_CHARGES, "removeChargesFromRunes", true); loadBoolConfig(L, EXPERIENCE_FROM_PLAYERS, "experienceByKillingPlayers", false); @@ -102,6 +104,8 @@ bool ConfigManager::load() { loadBoolConfig(L, ALLOW_BLOCK_SPAWN, "allowBlockSpawn", true); loadBoolConfig(L, REMOVE_WEAPON_AMMO, "removeWeaponAmmunition", true); loadBoolConfig(L, REMOVE_WEAPON_CHARGES, "removeWeaponCharges", true); + loadBoolConfig(L, REMOVE_BEGINNING_WEAPON_AMMO, "removeBeginningWeaponAmmunition", true); + loadBoolConfig(L, REFUND_BEGINNING_WEAPON_MANA, "refundBeginningWeaponMana", false); loadBoolConfig(L, REMOVE_POTION_CHARGES, "removeChargesFromPotions", true); loadBoolConfig(L, GLOBAL_SERVER_SAVE_NOTIFY_MESSAGE, "globalServerSaveNotifyMessage", true); loadBoolConfig(L, GLOBAL_SERVER_SAVE_CLEAN_MAP, "globalServerSaveCleanMap", false); @@ -347,6 +351,12 @@ bool ConfigManager::load() { loadBoolConfig(L, TOGGLE_RECEIVE_REWARD, "toggleReceiveReward", false); + loadBoolConfig(L, METRICS_ENABLE_PROMETHEUS, "metricsEnablePrometheus", false); + loadStringConfig(L, METRICS_PROMETHEUS_ADDRESS, "metricsPrometheusAddress", "localhost:9464"); + + loadBoolConfig(L, METRICS_ENABLE_OSTREAM, "metricsEnableOstream", false); + loadIntConfig(L, METRICS_OSTREAM_INTERVAL, "metricsOstreamInterval", 1000); + loaded = true; lua_close(L); return true; diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 151e5458a..a348f3640 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -14,11 +14,13 @@ #include "lua/creature/events.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "game/game.hpp" +#include "game/scheduling/dispatcher.hpp" #include "io/iobestiary.hpp" #include "creatures/monsters/monster.hpp" #include "creatures/monsters/monsters.hpp" #include "items/weapons/weapons.hpp" #include "map/spectators.hpp" +#include "lib/metrics/metrics.hpp" int32_t Combat::getLevelFormula(std::shared_ptr player, const std::shared_ptr wheelSpell, const CombatDamage &damage) const { if (!player) { @@ -584,7 +586,7 @@ void Combat::CombatHealthFunc(std::shared_ptr caster, std::shared_ptr< } damage.damageMultiplier += attackerPlayer->wheel()->getMajorStatConditional("Divine Empowerment", WheelMajor_t::DAMAGE); - g_logger().debug("Wheel Divine Empowerment damage multiplier {}", damage.damageMultiplier); + g_logger().trace("Wheel Divine Empowerment damage multiplier {}", damage.damageMultiplier); } if (g_game().combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { @@ -919,6 +921,7 @@ void Combat::doChainEffect(const Position &origin, const Position &dest, uint8_t } bool Combat::doCombatChain(std::shared_ptr caster, std::shared_ptr target, bool aggressive) const { + metrics::method_latency measure(__METHOD_NAME__); if (!params.chainCallback) { return false; } @@ -1309,6 +1312,7 @@ void Combat::setRuneSpellName(const std::string &value) { } std::vector>> Combat::pickChainTargets(std::shared_ptr caster, const CombatParams ¶ms, uint8_t chainDistance, uint8_t maxTargets, bool backtracking, bool aggressive, std::shared_ptr initialTarget /* = nullptr */) { + metrics::method_latency measure(__METHOD_NAME__); if (!caster) { return {}; } @@ -2016,6 +2020,7 @@ void MagicField::onStepInField(const std::shared_ptr &creature) { } void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptr target, CombatDamage &damage, const CombatParams ¶ms) { + metrics::method_latency measure(__METHOD_NAME__); if (damage.extension || !caster || damage.primary.type == COMBAT_HEALING) { return; } diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 1c4c636ba..2320ad99e 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -2085,8 +2085,9 @@ void ConditionSpeed::setFormulaVars(float NewMina, float NewMinb, float NewMaxa, } void ConditionSpeed::getFormulaValues(int32_t var, int32_t &min, int32_t &max) const { - min = (var * mina) + minb; - max = (var * maxa) + maxb; + int32_t difference = var - 40; + min = mina * difference + minb; + max = maxa * difference + maxb; } bool ConditionSpeed::setParam(ConditionParam_t param, int32_t value) { @@ -2145,9 +2146,19 @@ bool ConditionSpeed::startCondition(std::shared_ptr creature) { } if (speedDelta == 0) { - int32_t min, max; - getFormulaValues(creature->getBaseSpeed(), min, max); + int32_t min; + int32_t max; + auto baseSpeed = creature->getBaseSpeed(); + getFormulaValues(baseSpeed, min, max); speedDelta = uniform_random(min, max); + + if (conditionType == CONDITION_HASTE) { + speedDelta = speedDelta - baseSpeed; + } + + if (conditionType == CONDITION_PARALYZE && speedDelta < baseSpeed - 40) { + speedDelta = 40 - baseSpeed; + } } g_game().changeSpeed(creature, speedDelta); @@ -2173,7 +2184,7 @@ void ConditionSpeed::addCondition(std::shared_ptr creature, const std: setTicks(addCondition->getTicks()); - const std::shared_ptr &conditionSpeed = addCondition->static_self_cast(); + const auto &conditionSpeed = addCondition->static_self_cast(); int32_t oldSpeedDelta = speedDelta; speedDelta = conditionSpeed->speedDelta; mina = conditionSpeed->mina; @@ -2184,8 +2195,17 @@ void ConditionSpeed::addCondition(std::shared_ptr creature, const std: if (speedDelta == 0) { int32_t min; int32_t max; - getFormulaValues(creature->getBaseSpeed(), min, max); + auto baseSpeed = creature->getBaseSpeed(); + getFormulaValues(baseSpeed, min, max); speedDelta = uniform_random(min, max); + + if (conditionType == CONDITION_HASTE) { + speedDelta = speedDelta - baseSpeed; + } + + if (conditionType == CONDITION_PARALYZE && speedDelta < baseSpeed - 40) { + speedDelta = 40 - baseSpeed; + } } int32_t newSpeedChange = (speedDelta - oldSpeedDelta); diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp index 4ae9d69ae..d684b5433 100644 --- a/src/creatures/combat/spells.cpp +++ b/src/creatures/combat/spells.cpp @@ -20,6 +20,25 @@ Spells::Spells() = default; Spells::~Spells() = default; TalkActionResult_t Spells::playerSaySpell(std::shared_ptr player, std::string &words) { + auto maxOnline = g_configManager().getNumber(MAX_PLAYERS_PER_ACCOUNT, __FUNCTION__); + auto tile = player->getTile(); + if (maxOnline > 1 && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && tile && !tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + auto maxOutsizePZ = g_configManager().getNumber(MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, __FUNCTION__); + auto accountPlayers = g_game().getPlayersByAccount(player->getAccount()); + int countOutsizePZ = 0; + for (const auto &accountPlayer : accountPlayers) { + if (accountPlayer == player || accountPlayer->isOffline()) { + continue; + } + if (accountPlayer->getTile() && !accountPlayer->getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { + ++countOutsizePZ; + } + } + if (countOutsizePZ >= maxOutsizePZ) { + player->sendTextMessage(MESSAGE_FAILURE, fmt::format("You cannot cast spells while you have {} character(s) outside of a protection zone.", maxOutsizePZ)); + return TALKACTION_FAILED; + } + } std::string str_words = words; if (player->hasCondition(CONDITION_FEARED)) { @@ -143,7 +162,7 @@ std::list Spells::getSpellsByVocation(uint16_t vocationId) { if (vocSpellsIt != vocSpells.end() && vocSpellsIt->second) { - spellsList.push_back(it.second->getId()); + spellsList.push_back(it.second->getSpellId()); } } @@ -162,7 +181,7 @@ std::shared_ptr Spells::getRuneSpell(uint16_t id) { auto it = runes.find(id); if (it == runes.end()) { for (auto &rune : runes) { - if (rune.second->getId() == id) { + if (rune.second->getRuneItemId() == id) { return rune.second; } } @@ -216,7 +235,7 @@ std::shared_ptr Spells::getInstantSpell(const std::string &words) std::shared_ptr Spells::getInstantSpellById(uint16_t spellId) { for (auto &it : instants) { - if (it.second->getId() == spellId) { + if (it.second->getSpellId() == spellId) { return it.second; } } @@ -391,7 +410,7 @@ bool Spell::playerSpellCheck(std::shared_ptr player) const { return false; } - if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId) || (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) { + if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, m_spellId) || (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) { player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); if (isInstant()) { @@ -609,14 +628,19 @@ void Spell::setWheelOfDestinyBoost(WheelSpellBoost_t boost, WheelSpellGrade_t gr void Spell::applyCooldownConditions(std::shared_ptr player) const { WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName()); bool isUpgraded = getWheelOfDestinyUpgraded() && static_cast(spellGrade) > 0; - auto rate_cooldown = (int32_t)g_configManager().getFloat(RATE_SPELL_COOLDOWN, __FUNCTION__); + // Safety check to prevent division by zero + auto rateCooldown = g_configManager().getFloat(RATE_SPELL_COOLDOWN, __FUNCTION__); + if (std::abs(rateCooldown) < std::numeric_limits::epsilon()) { + rateCooldown = 0.1; // Safe minimum value + } + if (cooldown > 0) { int32_t spellCooldown = cooldown; if (isUpgraded) { spellCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::COOLDOWN, spellGrade); } if (spellCooldown > 0) { - std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, spellCooldown / rate_cooldown, 0, false, spellId); + std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, spellCooldown / rateCooldown, 0, false, m_spellId); player->addCondition(condition); } } @@ -627,7 +651,7 @@ void Spell::applyCooldownConditions(std::shared_ptr player) const { spellGroupCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::GROUP_COOLDOWN, spellGrade); } if (spellGroupCooldown > 0) { - std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellGroupCooldown / rate_cooldown, 0, false, group); + std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellGroupCooldown / rateCooldown, 0, false, group); player->addCondition(condition); } } @@ -638,7 +662,7 @@ void Spell::applyCooldownConditions(std::shared_ptr player) const { spellSecondaryGroupCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::SECONDARY_GROUP_COOLDOWN, spellGrade); } if (spellSecondaryGroupCooldown > 0) { - std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellSecondaryGroupCooldown / rate_cooldown, 0, false, secondaryGroup); + std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, spellSecondaryGroupCooldown / rateCooldown, 0, false, secondaryGroup); player->addCondition(condition); } } diff --git a/src/creatures/combat/spells.hpp b/src/creatures/combat/spells.hpp index f95f8aa40..4a237ff39 100644 --- a/src/creatures/combat/spells.hpp +++ b/src/creatures/combat/spells.hpp @@ -128,11 +128,11 @@ class Spell : public BaseSpell { void setName(std::string n) { name = std::move(n); } - [[nodiscard]] uint16_t getId() const { - return spellId; + [[nodiscard]] uint16_t getSpellId() const { + return m_spellId; } - void setId(uint16_t id) { - spellId = id; + void setSpellId(uint16_t id) { + m_spellId = id; } void postCastSpell(std::shared_ptr player, bool finishedCast = true, bool payCost = true) const; @@ -355,7 +355,7 @@ class Spell : public BaseSpell { uint32_t magLevel = 0; int32_t range = -1; - uint16_t spellId = 0; + uint16_t m_spellId = 0; bool selfTarget = false; bool needTarget = false; diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index e9b674c54..4720b9032 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -16,6 +16,7 @@ #include "creatures/monsters/monster.hpp" #include "game/zones/zone.hpp" #include "map/spectators.hpp" +#include "lib/metrics/metrics.hpp" Creature::Creature() { onIdleStatus(); @@ -29,6 +30,7 @@ Creature::~Creature() { } bool Creature::canSee(const Position &myPos, const Position &pos, int32_t viewRangeX, int32_t viewRangeY) { + metrics::method_latency measure(__METHOD_NAME__); if (myPos.z <= MAP_INIT_SURFACE_LAYER) { // we are on ground level or above (7 -> 0) // view is from 7 -> 0 @@ -88,6 +90,7 @@ int32_t Creature::getWalkSize() { } void Creature::onThink(uint32_t interval) { + metrics::method_latency measure(__METHOD_NAME__); if (!isMapLoaded && useCacheMap()) { isMapLoaded = true; updateMapCache(); @@ -158,6 +161,7 @@ void Creature::onIdleStatus() { } void Creature::onCreatureWalk() { + metrics::method_latency measure(__METHOD_NAME__); if (getWalkDelay() <= 0) { Direction dir; uint32_t flags = FLAG_IGNOREFIELDDAMAGE; @@ -268,6 +272,7 @@ void Creature::stopEventWalk() { } void Creature::updateMapCache() { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr newTile; const Position &myPos = getPosition(); Position pos(0, 0, myPos.z); @@ -283,6 +288,7 @@ void Creature::updateMapCache() { } void Creature::updateTileCache(std::shared_ptr newTile, int32_t dx, int32_t dy) { + metrics::method_latency measure(__METHOD_NAME__); if (std::abs(dx) <= maxWalkCacheWidth && std::abs(dy) <= maxWalkCacheHeight) { localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = newTile && newTile->queryAdd(0, getCreature(), 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) == RETURNVALUE_NOERROR; } @@ -298,6 +304,7 @@ void Creature::updateTileCache(std::shared_ptr upTile, const Position &pos } int32_t Creature::getWalkCache(const Position &pos) { + metrics::method_latency measure(__METHOD_NAME__); if (!useCacheMap()) { return 2; } @@ -354,6 +361,7 @@ void Creature::onRemoveTileItem(std::shared_ptr updateTile, const Position } void Creature::onCreatureAppear(std::shared_ptr creature, bool isLogin) { + metrics::method_latency measure(__METHOD_NAME__); if (creature == getCreature()) { if (useCacheMap()) { isMapLoaded = true; @@ -371,6 +379,7 @@ void Creature::onCreatureAppear(std::shared_ptr creature, bool isLogin } void Creature::onRemoveCreature(std::shared_ptr creature, bool) { + metrics::method_latency measure(__METHOD_NAME__); onCreatureDisappear(creature, true); if (creature != getCreature() && isMapLoaded) { if (creature->getPosition().z == getPosition().z) { @@ -386,6 +395,7 @@ void Creature::onRemoveCreature(std::shared_ptr creature, bool) { } void Creature::onCreatureDisappear(std::shared_ptr creature, bool isLogout) { + metrics::method_latency measure(__METHOD_NAME__); if (getAttackedCreature() == creature) { setAttackedCreature(nullptr); onAttackedCreatureDisappear(isLogout); @@ -398,6 +408,7 @@ void Creature::onCreatureDisappear(std::shared_ptr creature, bool isLo } void Creature::onChangeZone(ZoneType_t zone) { + metrics::method_latency measure(__METHOD_NAME__); auto attackedCreature = getAttackedCreature(); if (attackedCreature && zone == ZONE_PROTECTION) { onCreatureDisappear(attackedCreature, false); @@ -405,6 +416,7 @@ void Creature::onChangeZone(ZoneType_t zone) { } void Creature::onAttackedCreatureChangeZone(ZoneType_t zone) { + metrics::method_latency measure(__METHOD_NAME__); if (zone == ZONE_PROTECTION) { auto attackedCreature = getAttackedCreature(); if (attackedCreature) { @@ -414,6 +426,7 @@ void Creature::onAttackedCreatureChangeZone(ZoneType_t zone) { } void Creature::checkSummonMove(const Position &newPos, bool teleportSummon) { + metrics::method_latency measure(__METHOD_NAME__); if (hasSummons()) { std::vector> despawnMonsterList; for (const auto &summon : getSummons()) { @@ -456,6 +469,7 @@ void Creature::checkSummonMove(const Position &newPos, bool teleportSummon) { } void Creature::onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) { + metrics::method_latency measure(__METHOD_NAME__); if (creature == getCreature()) { lastStep = OTSYS_TIME(); lastStepCost = 1; @@ -612,6 +626,7 @@ void Creature::onCreatureMove(const std::shared_ptr &creature, const s } void Creature::onDeath() { + metrics::method_latency measure(__METHOD_NAME__); bool lastHitUnjustified = false; bool mostDamageUnjustified = false; std::shared_ptr lastHitCreature = g_game().getCreatureByID(lastHitCreatureId); @@ -687,13 +702,47 @@ void Creature::onDeath() { /** * @deprecated -- This is here to trigger the deprecated onKill events in lua */ + auto mostDamageCreatureMaster = mostDamageCreature ? mostDamageCreature->getMaster() : nullptr; if (mostDamageCreature && (mostDamageCreature != lastHitCreature || getMonster()) && mostDamageCreature != lastHitCreatureMaster) { - auto mostDamageCreatureMaster = mostDamageCreature->getMaster(); if (lastHitCreature != mostDamageCreatureMaster && (lastHitCreatureMaster == nullptr || mostDamageCreatureMaster != lastHitCreatureMaster)) { mostDamageUnjustified = mostDamageCreature->deprecatedOnKilledCreature(getCreature(), false); } } + bool killedByPlayer = mostDamageCreature && mostDamageCreature->getPlayer() || mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer(); + if (getPlayer()) { + g_metrics().addCounter( + "player_death", + 1, + { + { "name", getNameDescription() }, + { "level", std::to_string(getPlayer()->getLevel()) }, + { "most_damage_creature", mostDamageCreature ? mostDamageCreature->getName() : "(none)" }, + { "last_hit_creature", lastHitCreature ? lastHitCreature->getName() : "(none)" }, + { "most_damage_dealt", std::to_string(mostDamage) }, + { "most_damage_creature_master", mostDamageCreatureMaster ? mostDamageCreatureMaster->getName() : "(none)" }, + { "most_damage_unjustified", std::to_string(mostDamageUnjustified) }, + { "last_hit_unjustified", std::to_string(lastHitUnjustified) }, + { "by_player", std::to_string(killedByPlayer) }, + } + ); + } else { + std::string killerName = mostDamageCreature ? mostDamageCreature->getName() : "(none)"; + if (mostDamageCreatureMaster) { + killerName = mostDamageCreatureMaster->getName(); + } + g_metrics().addCounter( + "monster_death", + 1, + { + { "name", getName() }, + { "killer", killerName }, + { "is_summon", std::to_string(getMaster() ? true : false) }, + { "by_player", std::to_string(killedByPlayer) }, + } + ); + } + bool droppedCorpse = dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); death(lastHitCreature); @@ -707,6 +756,7 @@ void Creature::onDeath() { } bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared_ptr mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) { + metrics::method_latency measure(__METHOD_NAME__); if (!lootDrop && getMonster()) { if (getMaster()) { // Scripting event onDeath @@ -974,6 +1024,7 @@ void Creature::getPathSearchParams(const std::shared_ptr &, FindPathPa } void Creature::goToFollowCreature_async(std::function &&onComplete) { + metrics::method_latency measure(__METHOD_NAME__); if (pathfinderRunning.load()) { return; } @@ -990,6 +1041,7 @@ void Creature::goToFollowCreature_async(std::function &&onComplete) { } void Creature::goToFollowCreature() { + metrics::method_latency measure(__METHOD_NAME__); const auto &followCreature = getFollowCreature(); if (!followCreature) { return; @@ -1043,6 +1095,7 @@ bool Creature::canFollowMaster() { } bool Creature::setFollowCreature(std::shared_ptr creature) { + metrics::method_latency measure(__METHOD_NAME__); if (creature) { if (getFollowCreature() == creature) { return true; @@ -1187,6 +1240,7 @@ void Creature::onAttackedCreatureDrainHealth(std::shared_ptr target, i } void Creature::onAttackedCreatureKilled(std::shared_ptr target) { + metrics::method_latency measure(__METHOD_NAME__); if (target != getCreature()) { uint64_t gainExp = target->getGainedExperience(static_self_cast()); onGainExperience(gainExp, target); @@ -1194,6 +1248,7 @@ void Creature::onAttackedCreatureKilled(std::shared_ptr target) { } bool Creature::deprecatedOnKilledCreature(std::shared_ptr target, bool lastHit) { + metrics::method_latency measure(__METHOD_NAME__); auto master = getMaster(); if (master) { master->deprecatedOnKilledCreature(target, lastHit); @@ -1208,6 +1263,7 @@ bool Creature::deprecatedOnKilledCreature(std::shared_ptr target, bool } void Creature::onGainExperience(uint64_t gainExp, std::shared_ptr target) { + metrics::method_latency measure(__METHOD_NAME__); auto master = getMaster(); if (gainExp == 0 || !master) { return; @@ -1238,6 +1294,7 @@ void Creature::onGainExperience(uint64_t gainExp, std::shared_ptr targ } bool Creature::setMaster(std::shared_ptr newMaster, bool reloadCreature /* = false*/) { + metrics::method_latency measure(__METHOD_NAME__); // Persists if this creature has ever been a summon this->summoned = true; auto oldMaster = getMaster(); @@ -1270,6 +1327,7 @@ bool Creature::setMaster(std::shared_ptr newMaster, bool reloadCreatur } bool Creature::addCondition(std::shared_ptr condition) { + metrics::method_latency measure(__METHOD_NAME__); if (condition == nullptr) { return false; } @@ -1302,6 +1360,7 @@ bool Creature::addCombatCondition(std::shared_ptr condition) { } void Creature::removeCondition(ConditionType_t type) { + metrics::method_latency measure(__METHOD_NAME__); auto it = conditions.begin(), end = conditions.end(); while (it != end) { std::shared_ptr condition = *it; @@ -1319,6 +1378,7 @@ void Creature::removeCondition(ConditionType_t type) { } void Creature::removeCondition(ConditionType_t conditionType, ConditionId_t conditionId, bool force /* = false*/) { + metrics::method_latency measure(__METHOD_NAME__); auto it = conditions.begin(), end = conditions.end(); while (it != end) { std::shared_ptr condition = *it; @@ -1382,6 +1442,7 @@ std::shared_ptr Creature::getCondition(ConditionType_t type) const { } std::shared_ptr Creature::getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId /* = 0*/) const { + metrics::method_latency measure(__METHOD_NAME__); for (const auto &condition : conditions) { if (condition->getType() == type && condition->getId() == conditionId && condition->getSubId() == subId) { return condition; @@ -1401,6 +1462,7 @@ std::vector> Creature::getConditionsByType(ConditionT } void Creature::executeConditions(uint32_t interval) { + metrics::method_latency measure(__METHOD_NAME__); auto it = conditions.begin(), end = conditions.end(); while (it != end) { std::shared_ptr condition = *it; @@ -1419,6 +1481,7 @@ void Creature::executeConditions(uint32_t interval) { } bool Creature::hasCondition(ConditionType_t type, uint32_t subId /* = 0*/) const { + metrics::method_latency measure(__METHOD_NAME__); if (isSuppress(type)) { return false; } @@ -1652,6 +1715,7 @@ bool Creature::isInvisible() const { } bool Creature::getPathTo(const Position &targetPos, stdext::arraylist &dirList, const FindPathParams &fpp) { + metrics::method_latency measure(__METHOD_NAME__); return g_game().map.getPathMatching(getCreature(), dirList, FrozenPathingConditionCall(targetPos), fpp); } diff --git a/src/creatures/monsters/monsters.cpp b/src/creatures/monsters/monsters.cpp index ba05f5293..463238e91 100644 --- a/src/creatures/monsters/monsters.cpp +++ b/src/creatures/monsters/monsters.cpp @@ -144,7 +144,8 @@ bool Monsters::deserializeSpell(const std::shared_ptr spell, spell } std::shared_ptr condition = Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)->static_self_cast(); - condition->setFormulaVars(speedChange / 1000.0, 0, speedChange / 1000.0, 0); + float multiplier = 1.0f + static_cast(speedChange) / 1000.0f; + condition->setFormulaVars(multiplier / 2, 40, multiplier, 40); combatPtr->addCondition(condition); } else if (spellName == "outfit") { int32_t duration = 10000; diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 9549ae593..05435d6b4 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -95,17 +95,13 @@ bool SpawnsMonster::loadFromXML(const std::string &filemonstername) { boostedrate = 2; } - uint32_t interval = pugi::cast(childMonsterNode.attribute("spawntime").value()) * 1000 * 100 / std::max((uint32_t)1, (g_configManager().getNumber(RATE_SPAWN, __FUNCTION__) * boostedrate * eventschedule)); - if (interval >= MONSTER_MINSPAWN_INTERVAL && interval <= MONSTER_MAXSPAWN_INTERVAL) { - spawnMonster.addMonster(nameAttribute.as_string(), pos, dir, static_cast(interval)); - } else { - if (interval <= MONSTER_MINSPAWN_INTERVAL) { - g_logger().warn("[SpawnsMonster::loadFromXml] - {} {} spawntime cannot be less than {} seconds, set to {} by default.", nameAttribute.as_string(), pos.toString(), MONSTER_MINSPAWN_INTERVAL / 1000, MONSTER_MINSPAWN_INTERVAL / 1000); - spawnMonster.addMonster(nameAttribute.as_string(), pos, dir, MONSTER_MINSPAWN_INTERVAL); - } else { - g_logger().warn("[SpawnsMonster::loadFromXml] - {} {} spawntime can not be more than {} seconds", nameAttribute.as_string(), pos.toString(), MONSTER_MAXSPAWN_INTERVAL / 1000); - } + pugi::xml_attribute weightAttribute = childMonsterNode.attribute("weight"); + uint32_t weight = 1; + if (weightAttribute) { + weight = pugi::cast(weightAttribute.value()); } + + spawnMonster.addMonster(nameAttribute.as_string(), pos, dir, pugi::cast(childMonsterNode.attribute("spawntime").value()) * 1000, weight); } } } @@ -150,8 +146,7 @@ void SpawnMonster::startSpawnMonsterCheck() { } SpawnMonster::~SpawnMonster() { - for (const auto &it : spawnedMonsterMap) { - std::shared_ptr monster = it.second; + for (const auto &[_, monster] : spawnedMonsterMap) { monster->setSpawnMonster(nullptr); } } @@ -170,72 +165,100 @@ bool SpawnMonster::isInSpawnMonsterZone(const Position &pos) { return SpawnsMonster::isInZone(centerPos, radius, pos); } -bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, const std::shared_ptr monsterType, const Position &pos, Direction dir, bool startup /*= false*/) { +bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, spawnBlock_t &sb, const std::shared_ptr monsterType, bool startup /*= false*/) { + if (spawnedMonsterMap.contains(spawnMonsterId)) { + return false; + } auto monster = std::make_shared(monsterType); if (startup) { // No need to send out events to the surrounding since there is no one out there to listen! - if (!g_game().internalPlaceCreature(monster, pos, true)) { + if (!g_game().internalPlaceCreature(monster, sb.pos, true)) { return false; } } else { - if (!g_game().placeCreature(monster, pos, false, true)) { + if (!g_game().placeCreature(monster, sb.pos, false, true)) { return false; } } - monster->setDirection(dir); + monster->setDirection(sb.direction); monster->setSpawnMonster(this); - monster->setMasterPos(pos); + monster->setMasterPos(sb.pos); - spawnedMonsterMap.insert(spawned_pair(spawnMonsterId, monster)); - spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME(); - g_events().eventMonsterOnSpawn(monster, pos); - g_callbacks().executeCallback(EventCallback_t::monsterOnSpawn, &EventCallback::monsterOnSpawn, monster, pos); + spawnedMonsterMap[spawnMonsterId] = monster; + sb.lastSpawn = OTSYS_TIME(); + g_events().eventMonsterOnSpawn(monster, sb.pos); + g_callbacks().executeCallback(EventCallback_t::monsterOnSpawn, &EventCallback::monsterOnSpawn, monster, sb.pos); return true; } -void SpawnMonster::startup() { - for (const auto &it : spawnMonsterMap) { - uint32_t spawnMonsterId = it.first; - const spawnBlock_t &sb = it.second; - spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction, true); +void SpawnMonster::startup(bool delayed) { + if (g_configManager().getBoolean(RANDOM_MONSTER_SPAWN, __FUNCTION__)) { + for (auto it = spawnMonsterMap.begin(); it != spawnMonsterMap.end(); ++it) { + auto &[spawnMonsterId, sb] = *it; + for (auto &[monsterType, weight] : sb.monsterTypes) { + if (monsterType->isBoss()) { + continue; + } + for (auto otherIt = std::next(it); otherIt != spawnMonsterMap.end(); ++otherIt) { + auto &[id, otherSb] = *otherIt; + if (id == spawnMonsterId) { + continue; + } + if (otherSb.hasBoss()) { + continue; + } + if (otherSb.monsterTypes.contains(monsterType)) { + weight += otherSb.monsterTypes[monsterType]; + } + otherSb.monsterTypes.emplace(monsterType, weight); + sb.monsterTypes.emplace(monsterType, weight); + } + } + } + } + for (auto &[spawnMonsterId, sb] : spawnMonsterMap) { + const auto &mType = sb.getMonsterType(); + if (!mType) { + continue; + } + if (delayed) { + g_dispatcher().addEvent(std::bind(&SpawnMonster::scheduleSpawn, this, spawnMonsterId, sb, mType, 0, true), "SpawnMonster::startup"); + } else { + scheduleSpawn(spawnMonsterId, sb, mType, 0, true); + } } } void SpawnMonster::checkSpawnMonster() { - checkSpawnMonsterEvent = 0; + if (checkSpawnMonsterEvent == 0) { + return; + } + checkSpawnMonsterEvent = 0; cleanup(); - uint32_t spawnMonsterCount = 0; - - for (auto &it : spawnMonsterMap) { - uint32_t spawnMonsterId = it.first; - if (spawnedMonsterMap.find(spawnMonsterId) != spawnedMonsterMap.end()) { + for (auto &[spawnMonsterId, sb] : spawnMonsterMap) { + if (spawnedMonsterMap.contains(spawnMonsterId)) { continue; } - spawnBlock_t &sb = it.second; - if (!sb.monsterType->canSpawn(sb.pos)) { + const auto &mType = sb.getMonsterType(); + if (!mType) { + continue; + } + if (!mType->canSpawn(sb.pos) || (mType->info.isBlockable && findPlayer(sb.pos))) { sb.lastSpawn = OTSYS_TIME(); continue; } + if (OTSYS_TIME() < sb.lastSpawn + sb.interval) { + continue; + } - if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) { - if (sb.monsterType->info.isBlockable && findPlayer(sb.pos)) { - sb.lastSpawn = OTSYS_TIME(); - continue; - } - - if (sb.monsterType->info.isBlockable) { - spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction); - } else { - scheduleSpawn(spawnMonsterId, sb, 3 * NONBLOCKABLE_SPAWN_MONSTER_INTERVAL); - } - - if (++spawnMonsterCount >= static_cast(g_configManager().getNumber(RATE_SPAWN, __FUNCTION__))) { - break; - } + if (mType->info.isBlockable) { + spawnMonster(spawnMonsterId, sb, mType, true); + } else { + scheduleSpawn(spawnMonsterId, sb, mType, 3 * NONBLOCKABLE_SPAWN_MONSTER_INTERVAL); } } @@ -244,30 +267,29 @@ void SpawnMonster::checkSpawnMonster() { } } -void SpawnMonster::scheduleSpawn(uint32_t spawnMonsterId, spawnBlock_t &sb, uint16_t interval) { +void SpawnMonster::scheduleSpawn(uint32_t spawnMonsterId, spawnBlock_t &sb, const std::shared_ptr mType, uint16_t interval, bool startup /*= false*/) { if (interval <= 0) { - spawnMonster(spawnMonsterId, sb.monsterType, sb.pos, sb.direction); + spawnMonster(spawnMonsterId, sb, mType, startup); } else { g_game().addMagicEffect(sb.pos, CONST_ME_TELEPORT); - g_dispatcher().scheduleEvent(1400, std::bind(&SpawnMonster::scheduleSpawn, this, spawnMonsterId, sb, interval - NONBLOCKABLE_SPAWN_MONSTER_INTERVAL), "SpawnMonster::scheduleSpawn"); + g_dispatcher().scheduleEvent(NONBLOCKABLE_SPAWN_MONSTER_INTERVAL, std::bind(&SpawnMonster::scheduleSpawn, this, spawnMonsterId, sb, mType, interval - NONBLOCKABLE_SPAWN_MONSTER_INTERVAL, startup), "SpawnMonster::scheduleSpawn"); } } void SpawnMonster::cleanup() { - auto it = spawnedMonsterMap.begin(); - while (it != spawnedMonsterMap.end()) { - uint32_t spawnMonsterId = it->first; - std::shared_ptr monster = it->second; - if (!monster || monster->isRemoved()) { - spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME(); - it = spawnedMonsterMap.erase(it); - } else { - ++it; + std::vector removeList; + for (const auto &[spawnMonsterId, monster] : spawnedMonsterMap) { + if (monster == nullptr || monster->isRemoved()) { + removeList.push_back(spawnMonsterId); } } + for (const auto &spawnMonsterId : removeList) { + spawnMonsterMap[spawnMonsterId].lastSpawn = OTSYS_TIME(); + spawnedMonsterMap.erase(spawnMonsterId); + } } -bool SpawnMonster::addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t scheduleInterval) { +bool SpawnMonster::addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t scheduleInterval, uint32_t weight /*= 1*/) { std::string variant = ""; for (const auto &zone : Zone::getZones(pos)) { if (!zone->getMonsterVariant().empty()) { @@ -281,34 +303,77 @@ bool SpawnMonster::addMonster(const std::string &name, const Position &pos, Dire return false; } - this->interval = std::min(this->interval, scheduleInterval); - - spawnBlock_t sb; - sb.monsterType = monsterType; - sb.pos = pos; - sb.direction = dir; - sb.interval = scheduleInterval; - sb.lastSpawn = 0; + uint32_t eventschedule = g_eventsScheduler().getSpawnMonsterSchedule(); + std::string boostedMonster = g_game().getBoostedMonsterName(); + int32_t boostedrate = 1; + if (name == boostedMonster) { + boostedrate = 2; + } + // eventschedule is a whole percentage, so we need to multiply by 100 to match the order of magnitude of the other values + scheduleInterval = scheduleInterval * 100 / std::max((uint32_t)1, (g_configManager().getNumber(RATE_SPAWN, __FUNCTION__) * boostedrate * eventschedule)); + if (scheduleInterval < MONSTER_MINSPAWN_INTERVAL) { + g_logger().warn("[SpawnsMonster::addMonster] - {} {} spawntime cannot be less than {} seconds, set to {} by default.", name, pos.toString(), MONSTER_MINSPAWN_INTERVAL / 1000, MONSTER_MINSPAWN_INTERVAL / 1000); + scheduleInterval = MONSTER_MINSPAWN_INTERVAL; + } else if (scheduleInterval > MONSTER_MAXSPAWN_INTERVAL) { + g_logger().warn("[SpawnsMonster::addMonster] - {} {} spawntime can not be more than {} seconds, set to {} by default", name, pos.toString(), MONSTER_MAXSPAWN_INTERVAL / 1000, MONSTER_MAXSPAWN_INTERVAL / 1000); + scheduleInterval = MONSTER_MAXSPAWN_INTERVAL; + } + this->interval = std::gcd(this->interval, scheduleInterval); + spawnBlock_t* sb = nullptr; uint32_t spawnMonsterId = spawnMonsterMap.size() + 1; - spawnMonsterMap[spawnMonsterId] = sb; + for (auto &[id, maybeSb] : spawnMonsterMap) { + if (maybeSb.pos == pos) { + sb = &maybeSb; + spawnMonsterId = id; + break; + } + } + if (sb) { + if (sb->monsterTypes.contains(monsterType)) { + g_logger().error("[SpawnMonster] Monster {} already exists in spawn block at {}", name, pos.toString()); + return false; + } + if (monsterType->isBoss() && sb->monsterTypes.size() > 0) { + g_logger().error("[SpawnMonster] Boss monster {} has been added to spawn block with other monsters. This is not allowed.", name); + return false; + } + if (sb->hasBoss()) { + g_logger().error("[SpawnMonster] Monster {} has been added to spawn block with a boss. This is not allowed.", name); + return false; + } + } + if (!sb) { + sb = &spawnMonsterMap.emplace(spawnMonsterId, spawnBlock_t()).first->second; + } + sb->monsterTypes.emplace(monsterType, weight); + sb->pos = pos; + sb->direction = dir; + sb->interval = scheduleInterval; + sb->lastSpawn = 0; return true; } void SpawnMonster::removeMonster(std::shared_ptr monster) { - for (auto it = spawnedMonsterMap.begin(), end = spawnedMonsterMap.end(); it != end; ++it) { - if (it->second == monster) { - spawnedMonsterMap.erase(it); + uint32_t spawnMonsterId = 0; + for (const auto &[id, m] : spawnedMonsterMap) { + if (m == monster) { + spawnMonsterId = id; break; } } + spawnedMonsterMap.erase(spawnMonsterId); } 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; + std::unordered_map, uint32_t> monsterTypes; + for (const auto &[monsterType, weight] : it.second.monsterTypes) { + auto variantName = variant + monsterType->typeName; + auto variantType = g_monsters().getMonsterType(variantName, false); + monsterTypes.emplace(variantType, weight); + } + it.second.monsterTypes = monsterTypes; } } @@ -318,3 +383,44 @@ void SpawnMonster::stopEvent() { checkSpawnMonsterEvent = 0; } } + +std::shared_ptr spawnBlock_t::getMonsterType() const { + if (monsterTypes.empty()) { + return nullptr; + } + uint32_t totalWeight = 0; + for (const auto &[mType, weight] : monsterTypes) { + if (!mType) { + continue; + } + if (mType->isBoss()) { + if (monsterTypes.size() > 1) { + g_logger().warn("[SpawnMonster] Boss monster {} has been added to spawn block with other monsters. This is not allowed.", mType->name); + } + return mType; + } + totalWeight += weight; + } + uint32_t randomWeight = uniform_random(0, totalWeight - 1); + // order monsters by weight DESC + std::vector, uint32_t>> orderedMonsterTypes(monsterTypes.begin(), monsterTypes.end()); + std::sort(orderedMonsterTypes.begin(), orderedMonsterTypes.end(), [](const auto &a, const auto &b) { + return a.second > b.second; + }); + for (const auto &[mType, weight] : orderedMonsterTypes) { + if (randomWeight < weight) { + return mType; + } + randomWeight -= weight; + } + return nullptr; +} + +bool spawnBlock_t::hasBoss() const { + for (const auto &[monsterType, weight] : monsterTypes) { + if (monsterType->isBoss()) { + return true; + } + } + return false; +} diff --git a/src/creatures/monsters/spawns/spawn_monster.hpp b/src/creatures/monsters/spawns/spawn_monster.hpp index 749856525..9ea29e4f3 100644 --- a/src/creatures/monsters/spawns/spawn_monster.hpp +++ b/src/creatures/monsters/spawns/spawn_monster.hpp @@ -17,10 +17,13 @@ class MonsterType; struct spawnBlock_t { Position pos; - std::shared_ptr monsterType; + std::unordered_map, uint32_t> monsterTypes; int64_t lastSpawn; uint32_t interval; Direction direction; + + std::shared_ptr getMonsterType() const; + bool hasBoss() const; }; class SpawnMonster { @@ -33,13 +36,13 @@ class SpawnMonster { SpawnMonster(const SpawnMonster &) = delete; SpawnMonster &operator=(const SpawnMonster &) = delete; - bool addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t interval); + bool addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t interval, uint32_t weight = 1); void removeMonster(std::shared_ptr monster); uint32_t getInterval() const { return interval; } - void startup(); + void startup(bool delayed = false); void startSpawnMonsterCheck(); void stopEvent(); @@ -55,12 +58,10 @@ class SpawnMonster { private: // map of the spawned creatures - using SpawnedMap = std::multimap>; - using spawned_pair = SpawnedMap::value_type; - SpawnedMap spawnedMonsterMap; + phmap::parallel_flat_hash_map_m> spawnedMonsterMap; // map of creatures in the spawn - std::map spawnMonsterMap; + phmap::parallel_flat_hash_map_m spawnMonsterMap; Position centerPos; int32_t radius; @@ -69,9 +70,9 @@ class SpawnMonster { uint32_t checkSpawnMonsterEvent = 0; static bool findPlayer(const Position &pos); - bool spawnMonster(uint32_t spawnMonsterId, const std::shared_ptr monsterType, const Position &pos, Direction dir, bool startup = false); + bool spawnMonster(uint32_t spawnMonsterId, spawnBlock_t &sb, const std::shared_ptr monsterType, bool startup = false); void checkSpawnMonster(); - void scheduleSpawn(uint32_t spawnMonsterId, spawnBlock_t &sb, uint16_t interval); + void scheduleSpawn(uint32_t spawnMonsterId, spawnBlock_t &sb, const std::shared_ptr monsterType, uint16_t interval, bool startup = false); }; class SpawnsMonster { diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index 3991d01c1..5602f1458 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -16,6 +16,7 @@ #include "lua/callbacks/creaturecallback.hpp" #include "game/scheduling/dispatcher.hpp" #include "map/spectators.hpp" +#include "lib/metrics/metrics.hpp" int32_t Npc::despawnRange; int32_t Npc::despawnRadius; @@ -279,6 +280,7 @@ void Npc::onPlayerBuyItem(std::shared_ptr player, uint16_t itemId, uint8 if (getCurrency() == ITEM_GOLD_COIN && (player->getMoney() + player->getBankBalance()) < totalCost) { g_logger().error("[Npc::onPlayerBuyItem (getMoney)] - Player {} have a problem for buy item {} on shop for npc {}", player->getName(), itemId, getName()); g_logger().debug("[Information] Player {} tried to buy item {} on shop for npc {}, at position {}", player->getName(), itemId, getName(), player->getPosition().toString()); + g_metrics().addCounter("balance_decrease", totalCost, { { "player", player->getName() }, { "context", "npc_purchase" } }); return; } else if (getCurrency() != ITEM_GOLD_COIN && (player->getItemTypeCount(getCurrency()) < totalCost || ((player->getMoney() + player->getBankBalance()) < bagsCost))) { g_logger().error("[Npc::onPlayerBuyItem (getItemTypeCount)] - Player {} have a problem for buy item {} on shop for npc {}", player->getName(), itemId, getName()); @@ -416,6 +418,7 @@ void Npc::onPlayerSellItem(std::shared_ptr player, uint16_t itemId, uint } else { g_game().addMoney(player, totalCost); } + g_metrics().addCounter("balance_increase", totalCost, { { "player", player->getName() }, { "context", "npc_sale" } }); } else { std::shared_ptr newItem = Item::CreateItem(getCurrency(), totalCost); if (newItem) { diff --git a/src/creatures/npcs/npcs.cpp b/src/creatures/npcs/npcs.cpp index 0af1b0902..50fff3aa7 100644 --- a/src/creatures/npcs/npcs.cpp +++ b/src/creatures/npcs/npcs.cpp @@ -97,10 +97,8 @@ void NpcType::loadShop(const std::shared_ptr &npcType, ShopBlock shopBl shopBlock.childShop.push_back(child); } } - npcType->info.shopItemVector.push_back(shopBlock); - } else { - npcType->info.shopItemVector.push_back(shopBlock); } + npcType->info.shopItemVector.push_back(shopBlock); info.speechBubble = SPEECHBUBBLE_TRADE; } diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 0c68ec3a6..47789f5ba 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -31,6 +31,7 @@ #include "items/weapons/weapons.hpp" #include "core.hpp" #include "map/spectators.hpp" +#include "lib/metrics/metrics.hpp" MuteCountMap Player::muteCountMap; @@ -1107,6 +1108,23 @@ void Player::checkLootContainers(std::shared_ptr item) { } void Player::sendLootStats(std::shared_ptr item, uint8_t count) { + uint64_t value = 0; + if (item->getID() == ITEM_GOLD_COIN || item->getID() == ITEM_PLATINUM_COIN || item->getID() == ITEM_CRYSTAL_COIN) { + if (item->getID() == ITEM_PLATINUM_COIN) { + value = count * 100; + } else if (item->getID() == ITEM_CRYSTAL_COIN) { + value = count * 10000; + } else { + value = count; + } + } else if ( + auto npc = g_game().getNpcByName("The Lootmonger") + ) { + const auto &iType = Item::items.getItemType(item->getID()); + value = iType.sellPrice * count; + } + g_metrics().addCounter("player_loot", value, { { "player", getName() } }); + if (client) { client->sendLootStats(item, count); } @@ -1256,6 +1274,10 @@ void Player::sendStats() { } void Player::updateSupplyTracker(std::shared_ptr item) { + const auto &iType = Item::items.getItemType(item->getID()); + auto value = iType.buyPrice; + g_metrics().addCounter("player_supply", value, { { "player", getName() } }); + if (client) { client->sendUpdateSupplyTracker(item); } @@ -1382,6 +1404,8 @@ void Player::onApplyImbuement(Imbuement* imbuement, std::shared_ptr item, return; } + g_metrics().addCounter("balance_decrease", price, { { "player", getName() }, { "context", "apply_imbuement" } }); + for (auto &[key, value] : items) { std::stringstream withdrawItemMessage; @@ -1444,6 +1468,7 @@ void Player::onClearImbuement(std::shared_ptr item, uint8_t slot) { this->openImbuementWindow(item); return; } + g_metrics().addCounter("balance_decrease", baseImbuement->removeCost, { { "player", getName() }, { "context", "clear_imbuement" } }); if (item->getParent() == getPlayer()) { removeItemImbuementStats(imbuementInfo.imbuement); @@ -1660,6 +1685,12 @@ void Player::onCreatureAppear(std::shared_ptr creature, bool isLogin) } sendBlessStatus(); } + + if (getCurrentMount() != 0) { + toggleMount(true); + } + + g_game().changePlayerSpeed(static_self_cast(), 0); } } @@ -1692,6 +1723,12 @@ void Player::onChangeZone(ZoneType_t zone) { wasMounted = true; } } else { + int32_t ticks = g_configManager().getNumber(STAIRHOP_DELAY, __FUNCTION__); + if (ticks > 0) { + if (const auto &condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_PACIFIED, ticks, 0)) { + addCondition(condition); + } + } if (wasMounted) { toggleMount(true); wasMounted = false; @@ -2236,6 +2273,16 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool return; } + auto rate = exp / rawExp; + std::map attrs({ { "player", getName() }, { "level", std::to_string(getLevel()) }, { "rate", std::to_string(rate) } }); + if (sendText) { + g_metrics().addCounter("player_experience_raw", rawExp, attrs); + g_metrics().addCounter("player_experience_actual", exp, attrs); + } else { + g_metrics().addCounter("player_experience_bonus_raw", rawExp, attrs); + g_metrics().addCounter("player_experience_bonus_actual", exp, attrs); + } + // Hazard system experience std::shared_ptr monster = target && target->getMonster() ? target->getMonster() : nullptr; bool handleHazardExperience = monster && monster->getHazard() && getHazardSystemPoints() > 0; @@ -3942,9 +3989,7 @@ std::map &Player::getAllItemTypeCount(std::map &Player::getAllSaleItemIdAndCount(std::map &countMap) const { for (const auto item : getAllInventoryItems(false, true)) { - if (!item->hasImbuements()) { - countMap[item->getID()] += item->getItemCount(); - } + countMap[item->getID()] += item->getItemCount(); } return countMap; @@ -4360,6 +4405,7 @@ void Player::onAddCondition(ConditionType_t type) { if (type == CONDITION_OUTFIT && isMounted()) { dismount(); + wasMounted = true; } sendIcons(); @@ -4433,6 +4479,10 @@ void Player::onEndCondition(ConditionType_t type) { } } + if (type == CONDITION_OUTFIT && wasMounted) { + toggleMount(true); + } + sendIcons(); } @@ -4642,7 +4692,7 @@ bool Player::onKilledMonster(const std::shared_ptr &monster) { if (hasFlag(PlayerFlags_t::NotGenerateLoot)) { monster->setDropLoot(false); } - if (monster->isSummon()) { + if (monster->hasBeenSummoned()) { return false; } auto mType = monster->getMonsterType(); @@ -5577,6 +5627,14 @@ void Player::sendUnjustifiedPoints() { } } +uint8_t Player::getLastMount() const { + int32_t value = getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT); + if (value > 0) { + return value; + } + return static_cast(kv()->get("last-mount")->get()); +} + uint8_t Player::getCurrentMount() const { int32_t value = getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT); if (value > 0) { @@ -5635,7 +5693,7 @@ bool Player::toggleMount(bool mount) { return false; } - uint8_t currentMountId = getCurrentMount(); + uint8_t currentMountId = getLastMount(); if (currentMountId == 0) { sendOutfitWindow(); return false; @@ -5652,6 +5710,7 @@ bool Player::toggleMount(bool mount) { if (!hasMount(currentMount)) { setCurrentMount(0); + kv()->set("last-mount", 0); sendOutfitWindow(); return false; } @@ -5668,6 +5727,7 @@ bool Player::toggleMount(bool mount) { defaultOutfit.lookMount = currentMount->clientId; setCurrentMount(currentMount->id); + kv()->set("last-mount", currentMount->id); if (currentMount->speed != 0) { g_game().changeSpeed(static_self_cast(), currentMount->speed); @@ -5727,6 +5787,7 @@ bool Player::untameMount(uint8_t mountId) { } setCurrentMount(0); + kv()->set("last-mount", 0); } return true; @@ -5758,6 +5819,7 @@ void Player::dismount() { } defaultOutfit.lookMount = 0; + setCurrentMount(0); } bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) { @@ -5925,32 +5987,28 @@ void Player::clearModalWindows() { } uint16_t Player::getHelpers() const { - uint16_t helpers; - if (guild && party) { - phmap::flat_hash_set> helperSet; + const auto &guildMembers = guild->getMembersOnline(); - const auto guildMembers = guild->getMembersOnline(); - helperSet.insert(guildMembers.begin(), guildMembers.end()); + stdext::vector_set> helperSet; + helperSet.insert(helperSet.end(), guildMembers.begin(), guildMembers.end()); + helperSet.insertAll(party->getMembers()); + helperSet.insertAll(party->getInvitees()); - const auto partyMembers = party->getMembers(); - helperSet.insert(partyMembers.begin(), partyMembers.end()); + helperSet.emplace(party->getLeader()); - const auto partyInvitees = party->getInvitees(); - helperSet.insert(partyInvitees.begin(), partyInvitees.end()); + return static_cast(helperSet.size()); + } - helperSet.insert(party->getLeader()); + if (guild) { + return static_cast(guild->getMemberCountOnline()); + } - helpers = helperSet.size(); - } else if (guild) { - helpers = guild->getMemberCountOnline(); - } else if (party) { - helpers = party->getMemberCount() + party->getInvitationCount() + 1; - } else { - helpers = 0; + if (party) { + return static_cast(party->getMemberCount() + party->getInvitationCount() + 1); } - return helpers; + return 0u; } void Player::sendClosePrivate(uint16_t channelId) { @@ -6963,6 +7021,7 @@ void Player::forgeFuseItems(uint16_t itemId, uint8_t tier, bool success, bool re sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } + g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_fuse" } }); history.cost = cost; } @@ -7036,6 +7095,7 @@ void Player::forgeFuseItems(uint16_t itemId, uint8_t tier, bool success, bool re sendForgeError(RETURNVALUE_CONTACTADMINISTRATOR); return; } + g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_fuse" } }); history.cost = cost; } @@ -7167,6 +7227,7 @@ void Player::forgeTransferItemTier(uint16_t donorItemId, uint8_t tier, uint16_t return; } history.cost = cost; + g_metrics().addCounter("balance_decrease", cost, { { "player", getName() }, { "context", "forge_transfer" } }); returnValue = g_game().internalAddItem(static_self_cast(), exaltationContainer, INDEX_WHEREEVER); if (returnValue != RETURNVALUE_NOERROR) { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 9f6c01252..a33c538c8 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -159,6 +159,7 @@ class Player final : public Creature, public Cylinder, public Bankable { return CREATURETYPE_PLAYER; } + uint8_t getLastMount() const; uint8_t getCurrentMount() const; void setCurrentMount(uint8_t mountId); bool isMounted() const { diff --git a/src/database/database.cpp b/src/database/database.cpp index a097c9f9b..916ffd1ee 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -12,6 +12,7 @@ #include "config/configmanager.hpp" #include "database/database.hpp" #include "lib/di/container.hpp" +#include "lib/metrics/metrics.hpp" Database::~Database() { if (handle != nullptr) { @@ -60,7 +61,10 @@ bool Database::beginTransaction() { if (!executeQuery("BEGIN")) { return false; } + metrics::lock_latency measureLock("database"); databaseLock.lock(); + measureLock.stop(); + return true; } @@ -121,11 +125,14 @@ bool Database::executeQuery(const std::string_view &query) { g_logger().trace("Executing Query: {}", query); + metrics::lock_latency measureLock("database"); std::scoped_lock lock { databaseLock }; + measureLock.stop(); + metrics::query_latency measure(query.substr(0, 50)); bool success = retryQuery(query, 10); - mysql_free_result(mysql_store_result(handle)); + return success; } @@ -136,8 +143,11 @@ DBResult_ptr Database::storeQuery(const std::string_view &query) { } g_logger().trace("Storing Query: {}", query); + metrics::lock_latency measureLock("database"); std::scoped_lock lock { databaseLock }; + measureLock.stop(); + metrics::query_latency measure(query.substr(0, 50)); retry: if (mysql_query(handle, query.data()) != 0) { g_logger().error("Query: {}", query); diff --git a/src/game/bank/bank.cpp b/src/game/bank/bank.cpp index f1a700388..713ee6457 100644 --- a/src/game/bank/bank.cpp +++ b/src/game/bank/bank.cpp @@ -14,6 +14,7 @@ #include "creatures/players/player.hpp" #include "io/iologindata.hpp" #include "game/scheduling/save_manager.hpp" +#include "lib/metrics/metrics.hpp" Bank::Bank(const std::shared_ptr bankable) : m_bankable(bankable) { @@ -110,7 +111,12 @@ bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) } } - return debit(amount) && destination->credit(amount); + if (!(debit(amount) && destination->credit(amount))) { + return false; + } + g_metrics().addCounter("balance_increase", amount, { { "player", destination->getBankable()->getPlayer()->getName() }, { "context", "bank_transfer" } }); + g_metrics().addCounter("balance_decrease", amount, { { "player", getBankable()->getPlayer()->getName() }, { "context", "bank_transfer" } }); + return true; } bool Bank::withdraw(std::shared_ptr player, uint64_t amount) { @@ -118,6 +124,7 @@ bool Bank::withdraw(std::shared_ptr player, uint64_t amount) { return false; } g_game().addMoney(player, amount); + g_metrics().addCounter("balance_decrease", amount, { { "player", player->getName() }, { "context", "bank_withdraw" } }); return true; } @@ -144,5 +151,6 @@ bool Bank::deposit(const std::shared_ptr destination, uint64_t amount) { if (!g_game().removeMoney(bankable->getPlayer(), amount)) { return false; } + g_metrics().addCounter("balance_increase", amount, { { "player", bankable->getPlayer()->getName() }, { "context", "bank_deposit" } }); return destination->credit(amount); } diff --git a/src/game/functions/game_reload.cpp b/src/game/functions/game_reload.cpp index 873cd60ec..f40e9cf90 100644 --- a/src/game/functions/game_reload.cpp +++ b/src/game/functions/game_reload.cpp @@ -72,6 +72,7 @@ bool GameReload::reloadAll() const { reloadResults.reserve(magic_enum::enum_count()); for (auto value : magic_enum::enum_values()) { + g_logger().info("Reloading: {}", magic_enum::enum_name(value)); if (value == Reload_t::RELOAD_TYPE_ALL) { continue; } diff --git a/src/game/game.cpp b/src/game/game.cpp index 5dcba79ad..d741b24b1 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -296,6 +296,7 @@ void Game::start(ServiceManager* manager) { g_dispatcher().cycleEvent( EVENT_LUA_GARBAGE_COLLECTION, [this] { g_luaEnvironment().collectGarbage(); }, "Calling GC" ); + g_dispatcher().cycleEvent(EVENT_REFRESH_MARKET_PRICES, std::bind_front(&Game::loadItemsPrice, this), "Game::loadItemsPrice"); } GameState_t Game::getGameState() const { @@ -388,7 +389,7 @@ void Game::setGameState(GameState_t newState) { } bool Game::loadItemsPrice() { - itemsSaleCount = 0; + IOMarket::getInstance().updateStatistics(); std::ostringstream query, marketQuery; query << "SELECT DISTINCT `itemtype` FROM `market_offers`;"; @@ -398,20 +399,19 @@ bool Game::loadItemsPrice() { return false; } - do { - marketQuery.str(std::string()); - uint16_t itemId = result->getNumber("itemtype"); - marketQuery << "SELECT `price`, `tier` FROM `market_offers` WHERE `itemtype` = " << itemId << " ORDER BY `price` DESC LIMIT 1"; - DBResult_ptr marketOffersResult = db.storeQuery(marketQuery.str()); - if (marketOffersResult) { - std::map tierAndCount; - auto tier = marketOffersResult->getNumber("tier"); - auto price = marketOffersResult->getNumber("price"); - tierAndCount[tier] = price; - itemsPriceMap[itemId] = tierAndCount; - itemsSaleCount++; - } - } while (result->next()); + auto stats = IOMarket::getInstance().getPurchaseStatistics(); + for (const auto &[itemId, itemStats] : stats) { + std::map tierToPrice; + for (const auto &[tier, tierStats] : itemStats) { + auto averagePrice = tierStats.totalPrice / tierStats.numTransactions; + tierToPrice[tier] = averagePrice; + } + itemsPriceMap[itemId] = tierToPrice; + } + auto offers = IOMarket::getInstance().getActiveOffers(MARKETACTION_BUY); + for (const auto &offer : offers) { + itemsPriceMap[offer.itemId][offer.tier] = std::max(itemsPriceMap[offer.itemId][offer.tier], offer.price); + } return true; } @@ -691,13 +691,13 @@ std::shared_ptr Game::getNpcByID(uint32_t id) { return it->second; } -std::shared_ptr Game::getPlayerByID(uint32_t id, bool loadTmp /* = false */) { +std::shared_ptr Game::getPlayerByID(uint32_t id, bool allowOffline /* = false */) { auto playerMap = players.find(id); if (playerMap != players.end()) { return playerMap->second; } - if (!loadTmp) { + if (!allowOffline) { return nullptr; } std::shared_ptr tmpPlayer = std::make_shared(nullptr); @@ -748,14 +748,14 @@ std::shared_ptr Game::getNpcByName(const std::string &s) { return nullptr; } -std::shared_ptr Game::getPlayerByName(const std::string &s, bool loadTmp /* = false */) { +std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOffline /* = false */) { if (s.empty()) { return nullptr; } auto it = mappedPlayerNames.find(asLowerCaseString(s)); if (it == mappedPlayerNames.end() || it->second.expired()) { - if (!loadTmp) { + if (!allowOffline) { return nullptr; } std::shared_ptr tmpPlayer = std::make_shared(nullptr); @@ -769,7 +769,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, bool loadTmp /* = false */) { +std::shared_ptr Game::getPlayerByGUID(const uint32_t &guid, bool allowOffline /* = false */) { if (guid == 0) { return nullptr; } @@ -778,7 +778,7 @@ std::shared_ptr Game::getPlayerByGUID(const uint32_t &guid, bool loadTmp return it.second; } } - if (!loadTmp) { + if (!allowOffline) { return nullptr; } std::shared_ptr tmpPlayer = std::make_shared(nullptr); @@ -830,13 +830,19 @@ ReturnValue Game::getPlayerByNameWildcard(const std::string &s, std::shared_ptr< return RETURNVALUE_NOERROR; } -std::shared_ptr Game::getPlayerByAccount(uint32_t acc) { - for (const auto &it : players) { - if (it.second->getAccountId() == acc) { - return it.second; +std::vector> Game::getPlayersByAccount(std::shared_ptr acc, bool allowOffline /* = false */) { + auto [accountPlayers, error] = acc->getAccountPlayers(); + if (error != account::ERROR_NO) { + return {}; + } + std::vector> ret; + for (const auto &[name, _] : accountPlayers) { + auto player = getPlayerByName(name, allowOffline); + if (player) { + ret.push_back(player); } } - return nullptr; + return ret; } bool Game::internalPlaceCreature(std::shared_ptr creature, const Position &pos, bool extendedPos /*=false*/, bool forced /*= false*/, bool creatureCheck /*= false*/) { @@ -869,6 +875,7 @@ bool Game::internalPlaceCreature(std::shared_ptr creature, const Posit } bool Game::placeCreature(std::shared_ptr creature, const Position &pos, bool extendedPos /*=false*/, bool forced /*= false*/) { + metrics::method_latency measure(__METHOD_NAME__); if (!internalPlaceCreature(creature, pos, extendedPos, forced)) { return false; } @@ -895,6 +902,7 @@ bool Game::placeCreature(std::shared_ptr creature, const Position &pos } bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = true*/) { + metrics::method_latency measure(__METHOD_NAME__); if (!creature || creature->isRemoved()) { return false; } @@ -962,6 +970,7 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = } void Game::executeDeath(uint32_t creatureId) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr creature = getCreatureByID(creatureId); if (creature && !creature->isRemoved()) { afterCreatureZoneChange(creature, creature->getZones(), {}); @@ -970,6 +979,7 @@ void Game::executeDeath(uint32_t creatureId) { } void Game::playerTeleport(uint32_t playerId, const Position &newPosition) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr player = getPlayerByID(playerId); if (!player || !player->hasFlag(PlayerFlags_t::CanMapClickTeleport)) { return; @@ -982,6 +992,7 @@ void Game::playerTeleport(uint32_t playerId, const Position &newPosition) { } void Game::playerInspectItem(std::shared_ptr player, const Position &pos) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr thing = internalGetThing(player, pos, 0, 0, STACKPOS_TOPDOWN_ITEM); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); @@ -998,6 +1009,7 @@ void Game::playerInspectItem(std::shared_ptr player, const Position &pos } void Game::playerInspectItem(std::shared_ptr player, uint16_t itemId, uint8_t itemCount, bool cyclopedia) { + metrics::method_latency measure(__METHOD_NAME__); player->sendItemInspection(itemId, itemCount, nullptr, cyclopedia); } @@ -1051,6 +1063,7 @@ FILELOADER_ERRORS Game::loadAppearanceProtobuf(const std::string &file) { } void Game::playerMoveThing(uint32_t playerId, const Position &fromPos, uint16_t itemId, uint8_t fromStackPos, const Position &toPos, uint8_t count) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; @@ -1133,6 +1146,7 @@ void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, } void Game::playerMoveCreature(std::shared_ptr player, std::shared_ptr movingCreature, const Position &movingCreatureOrigPos, std::shared_ptr toTile) { + metrics::method_latency measure(__METHOD_NAME__); if (!player->canDoAction()) { uint32_t delay = 600; std::shared_ptr task = createPlayerTask(delay, std::bind(&Game::playerMoveCreatureByID, this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()), "Game::playerMoveCreatureByID"); @@ -1272,6 +1286,7 @@ ReturnValue Game::internalMoveCreature(std::shared_ptr creature, Direc } ReturnValue Game::internalMoveCreature(const std::shared_ptr &creature, const std::shared_ptr &toTile, uint32_t flags /*= 0*/) { + metrics::method_latency measure(__METHOD_NAME__); if (creature->hasCondition(CONDITION_ROOTED)) { return RETURNVALUE_NOTPOSSIBLE; } @@ -1668,6 +1683,7 @@ ReturnValue Game::checkMoveItemToCylinder(std::shared_ptr player, std::s } ReturnValue Game::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*/) { + metrics::method_latency measure(__METHOD_NAME__); if (fromCylinder == nullptr) { g_logger().error("[{}] fromCylinder is nullptr", __FUNCTION__); return RETURNVALUE_NOTPOSSIBLE; @@ -1918,6 +1934,7 @@ ReturnValue Game::internalAddItem(std::shared_ptr toCylinder, std::sha } ReturnValue Game::internalAddItem(std::shared_ptr toCylinder, std::shared_ptr item, int32_t index, uint32_t flags, bool test, uint32_t &remainderCount) { + metrics::method_latency measure(__METHOD_NAME__); if (toCylinder == nullptr) { g_logger().error("[{}] fromCylinder is nullptr", __FUNCTION__); return RETURNVALUE_NOTPOSSIBLE; @@ -2004,6 +2021,7 @@ ReturnValue Game::internalAddItem(std::shared_ptr toCylinder, std::sha } ReturnValue Game::internalRemoveItem(std::shared_ptr item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/, bool force /*= false*/) { + metrics::method_latency measure(__METHOD_NAME__); if (item == nullptr) { g_logger().debug("{} - Item is nullptr", __FUNCTION__); return RETURNVALUE_NOTPOSSIBLE; @@ -2063,6 +2081,7 @@ ReturnValue Game::internalRemoveItem(std::shared_ptr item, int32_t count / } std::tuple Game::addItemBatch(const std::shared_ptr &toCylinder, const std::vector> &items, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + metrics::method_latency measure(__METHOD_NAME__); const auto player = toCylinder->getPlayer(); bool dropping = false; ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; @@ -2130,6 +2149,7 @@ std::tuple Game::addItemBatch(const std::shared } std::tuple Game::createItemBatch(const std::shared_ptr &toCylinder, const std::vector> &itemCounts, uint32_t flags /* = 0 */, bool dropOnMap /* = true */, uint32_t autoContainerId /* = 0 */) { + metrics::method_latency measure(__METHOD_NAME__); std::vector> items; for (const auto &[itemId, count, subType] : itemCounts) { const auto &itemType = Item::items[itemId]; @@ -2163,6 +2183,7 @@ std::tuple Game::createItem(const std::shared_p } ReturnValue Game::internalPlayerAddItem(std::shared_ptr player, std::shared_ptr item, bool dropOnMap /*= true*/, Slots_t slot /*= CONST_SLOT_WHEREEVER*/) { + metrics::method_latency measure(__METHOD_NAME__); uint32_t remainderCount = 0; ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); if (remainderCount != 0) { @@ -2185,6 +2206,7 @@ ReturnValue Game::internalPlayerAddItem(std::shared_ptr player, std::sha } std::shared_ptr Game::findItemOfType(std::shared_ptr cylinder, uint16_t itemId, bool depthSearch /*= true*/, int32_t subType /*= -1*/) const { + metrics::method_latency measure(__METHOD_NAME__); if (cylinder == nullptr) { g_logger().error("[{}] Cylinder is nullptr", __FUNCTION__); return nullptr; @@ -2360,6 +2382,7 @@ void Game::addMoney(std::shared_ptr cylinder, uint64_t money, uint32_t } std::shared_ptr Game::transformItem(std::shared_ptr item, uint16_t newId, int32_t newCount /*= -1*/) { + metrics::method_latency measure(__METHOD_NAME__); if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) { // chargeless item placed on map = infinite return item; } @@ -2490,7 +2513,8 @@ std::shared_ptr Game::transformItem(std::shared_ptr item, uint16_t n return newItem; } -ReturnValue Game::internalTeleport(std::shared_ptr thing, const Position &newPos, bool pushMove /* = true*/, uint32_t flags /*= 0*/) { +ReturnValue Game::internalTeleport(const std::shared_ptr &thing, const Position &newPos, bool pushMove /* = true*/, uint32_t flags /*= 0*/) { + metrics::method_latency measure(__METHOD_NAME__); if (thing == nullptr) { g_logger().error("[{}] thing is nullptr", __FUNCTION__); return RETURNVALUE_NOTPOSSIBLE; @@ -2781,6 +2805,7 @@ ReturnValue Game::internalCollectLootItems(std::shared_ptr player, std:: return RETURNVALUE_NOTPOSSIBLE; } player->setBankBalance(player->getBankBalance() + money); + g_metrics().addCounter("balance_increase", money, { { "player", player->getName() }, { "context", "loot" } }); return RETURNVALUE_NOERROR; } } @@ -3239,6 +3264,7 @@ void Game::playerStopAutoWalk(uint32_t playerId) { } void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t fromStackPos, uint16_t fromItemId, const Position &toPos, uint8_t toStackPos, uint16_t toItemId) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; @@ -3368,6 +3394,7 @@ void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t f } void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPos, uint8_t index, uint16_t itemId) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; @@ -3462,6 +3489,7 @@ void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPo } void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t itemId) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; @@ -4671,6 +4699,7 @@ void Game::internalCloseTrade(std::shared_ptr player) { } void Game::playerBuyItem(uint32_t playerId, uint16_t itemId, uint8_t count, uint16_t amount, bool ignoreCap /* = false*/, bool inBackpacks /* = false*/) { + metrics::method_latency measure(__METHOD_NAME__); if (amount == 0) { return; } @@ -4709,6 +4738,7 @@ void Game::playerBuyItem(uint32_t playerId, uint16_t itemId, uint8_t count, uint } void Game::playerSellItem(uint32_t playerId, uint16_t itemId, uint8_t count, uint16_t amount, bool ignoreEquipped) { + metrics::method_latency measure(__METHOD_NAME__); if (amount == 0) { return; } @@ -5455,7 +5485,7 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, uint8_t isMoun auto deltaSpeedChange = mount->speed; if (player->isMounted()) { - const auto prevMount = mounts.getMountByID(player->getCurrentMount()); + const auto prevMount = mounts.getMountByID(player->getLastMount()); if (prevMount) { deltaSpeedChange -= prevMount->speed; } @@ -5786,12 +5816,14 @@ void Game::addCreatureCheck(const std::shared_ptr &creature) { } void Game::removeCreatureCheck(const std::shared_ptr &creature) { + metrics::method_latency measure(__METHOD_NAME__); if (creature->inCheckCreaturesVector) { creature->creatureCheck = false; } } void Game::checkCreatures() { + metrics::method_latency measure(__METHOD_NAME__); static size_t index = 0; auto &checkCreatureList = checkCreatureLists[index]; @@ -5990,6 +6022,31 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack std::shared_ptr targetPlayer = target->getPlayer(); if (damage.primary.type != COMBAT_NONE) { + + damage.primary.value = -damage.primary.value; + // Damage healing primary + if (attacker) { + if (target->getMonster()) { + uint32_t primaryHealing = target->getMonster()->getHealingCombatValue(damage.primary.type); + if (primaryHealing > 0) { + damageHeal.primary.value = std::ceil((damage.primary.value) * (primaryHealing / 100.)); + canHeal = true; + } + } + if (targetPlayer && attacker->getAbsorbPercent(damage.primary.type) != 0) { + damageAbsorbMessage = true; + } + if (attacker->getPlayer() && attacker->getIncreasePercent(damage.primary.type) != 0) { + damageIncreaseMessage = true; + } + damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.; + } + damage.primary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.; + + primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); + + damage.primary.value = -damage.primary.value; + InternalGame::sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition(), attacker); // Damage reflection primary if (!damage.extension && attacker) { if (targetPlayer && attacker->getMonster() && damage.primary.type != COMBAT_HEALING) { @@ -6028,57 +6085,12 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack } } } - damage.primary.value = -damage.primary.value; - // Damage healing primary - if (attacker) { - if (target->getMonster()) { - uint32_t primaryHealing = target->getMonster()->getHealingCombatValue(damage.primary.type); - if (primaryHealing > 0) { - damageHeal.primary.value = std::ceil((damage.primary.value) * (primaryHealing / 100.)); - canHeal = true; - } - } - if (targetPlayer && attacker->getAbsorbPercent(damage.primary.type) != 0) { - damageAbsorbMessage = true; - } - if (attacker->getPlayer() && attacker->getIncreasePercent(damage.primary.type) != 0) { - damageIncreaseMessage = true; - } - damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.; - } - damage.primary.value *= target->getBuff(BUFF_DAMAGERECEIVED) / 100.; - - primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); - - damage.primary.value = -damage.primary.value; - InternalGame::sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition(), attacker); } else { primaryBlockType = BLOCK_NONE; } if (damage.secondary.type != COMBAT_NONE) { - // Damage reflection secondary - if (!damage.extension && attacker && target->getMonster()) { - uint32_t secondaryReflectPercent = target->getReflectPercent(damage.secondary.type, true); - uint32_t secondaryReflectFlat = target->getReflectFlat(damage.secondary.type, true); - if (secondaryReflectPercent > 0 || secondaryReflectFlat > 0) { - if (!canReflect) { - damageReflected.primary.type = damage.secondary.type; - damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); - if (!damageReflected.exString.empty()) { - damageReflected.exString += ", "; - } - damageReflected.extension = true; - damageReflected.exString += "damage reflection"; - damageReflectedParams.combatType = damage.primary.type; - damageReflectedParams.aggressive = true; - canReflect = true; - } else { - damageReflected.secondary.type = damage.secondary.type; - damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); - } - } - } + damage.secondary.value = -damage.secondary.value; // Damage healing secondary if (attacker && target->getMonster()) { @@ -6102,9 +6114,32 @@ bool Game::combatBlockHit(CombatDamage &damage, std::shared_ptr attack damage.secondary.value = -damage.secondary.value; InternalGame::sendBlockEffect(secondaryBlockType, damage.secondary.type, target->getPosition(), attacker); + + if (!damage.extension && attacker && target->getMonster()) { + int32_t secondaryReflectPercent = target->getReflectPercent(damage.secondary.type, true); + int32_t secondaryReflectFlat = target->getReflectFlat(damage.secondary.type, true); + if (secondaryReflectPercent > 0 || secondaryReflectFlat > 0) { + if (!canReflect) { + damageReflected.primary.type = damage.secondary.type; + damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); + if (!damageReflected.exString.empty()) { + damageReflected.exString += ", "; + } + damageReflected.extension = true; + damageReflected.exString += "damage reflection"; + damageReflectedParams.combatType = damage.primary.type; + damageReflectedParams.aggressive = true; + canReflect = true; + } else { + damageReflected.secondary.type = damage.secondary.type; + damageReflected.primary.value = std::ceil(damage.secondary.value * secondaryReflectPercent / 100.) + std::max(-static_cast(std::ceil(attacker->getMaxHealth() * 0.01)), std::max(damage.secondary.value, -(static_cast(secondaryReflectFlat)))); + } + } + } } else { secondaryBlockType = BLOCK_NONE; } + // Damage reflection secondary if (damage.primary.type == COMBAT_HEALING) { damage.primary.value *= target->getBuff(BUFF_HEALINGRECEIVED) / 100.; @@ -7223,13 +7258,7 @@ bool Game::combatChangeMana(std::shared_ptr attacker, std::shared_ptr< } target->drainMana(attacker, manaLoss); - if (targetPlayer) { - std::string cause = "(other)"; - if (attacker) { - cause = attacker->getName(); - } - targetPlayer->updateInputAnalyzer(damage.primary.type, damage.primary.value * -1, cause); - } + std::stringstream ss; std::string damageString = std::to_string(manaLoss); @@ -8470,6 +8499,7 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t ite } g_game().removeMoney(player, fee, 0, true); + g_metrics().addCounter("balance_decrease", fee, { { "player", player->getName() }, { "context", "market_fee" } }); } else { uint64_t totalPrice = price * amount; totalPrice += fee; @@ -8479,6 +8509,7 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t ite } g_game().removeMoney(player, totalPrice, 0, true); + g_metrics().addCounter("balance_decrease", totalPrice, { { "player", player->getName() }, { "context", "market_offer" } }); } // Send market window again for update item stats and avoid item clone @@ -8498,17 +8529,6 @@ void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t ite IOMarket::createOffer(player->getGUID(), static_cast(type), it.id, amount, price, tier, anonymous); - // uint8_t = tier, uint64_t price - std::map tierAndPriceMap; - tierAndPriceMap[tier] = price; - auto ColorItem = itemsPriceMap.find(it.id); - if (ColorItem == itemsPriceMap.end()) { - itemsPriceMap[it.id] = tierAndPriceMap; - itemsSaleCount++; - } else if (auto priceIt = ColorItem->second.find(tier); priceIt->second < price) { - itemsPriceMap[it.id] = tierAndPriceMap; - } - const MarketOfferList &buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id, tier); const MarketOfferList &sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id, tier); player->sendMarketBrowseItem(it.id, buyOffers, sellOffers, tier); @@ -8540,6 +8560,7 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 if (offer.type == MARKETACTION_BUY) { player->setBankBalance(player->getBankBalance() + offer.price * offer.amount); + g_metrics().addCounter("balance_decrease", offer.price * offer.amount, { { "player", player->getName() }, { "context", "market_purchase" } }); // Send market window again for update stats player->sendMarketEnter(player->getLastDepotId()); } else { @@ -8700,6 +8721,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 } player->setBankBalance(player->getBankBalance() + totalPrice); + g_metrics().addCounter("balance_increase", totalPrice, { { "player", player->getName() }, { "context", "market_sale" } }); if (it.id == ITEM_STORE_COIN) { buyerPlayer->getAccount()->addCoins(account::CoinType::TRANSFERABLE, amount, "Purchased on Market"); @@ -8770,6 +8792,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 player->setBankBalance(0); g_game().removeMoney(player, remainsPrice); } + g_metrics().addCounter("balance_decrease", totalPrice, { { "player", player->getName() }, { "context", "market_purchase" } }); if (it.id == ITEM_STORE_COIN) { player->getAccount()->addCoins(account::CoinType::TRANSFERABLE, amount, "Purchased on Market"); @@ -8824,6 +8847,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16 } sellerPlayer->setBankBalance(sellerPlayer->getBankBalance() + totalPrice); + g_metrics().addCounter("balance_increase", totalPrice, { { "player", sellerPlayer->getName() }, { "context", "market_sale" } }); if (it.id == ITEM_STORE_COIN) { sellerPlayer->getAccount()->registerCoinTransaction(account::CoinTransactionType::REMOVE, account::CoinType::TRANSFERABLE, amount, "Sold on Market"); } @@ -8932,6 +8956,7 @@ void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, ui } void Game::playerForgeFuseItems(uint32_t playerId, uint16_t itemId, uint8_t tier, bool usedCore, bool reduceTierLoss) { + metrics::method_latency measure(__METHOD_NAME__); std::shared_ptr player = getPlayerByID(playerId); if (!player) { return; @@ -9015,6 +9040,7 @@ void Game::playerBosstiarySlot(uint32_t playerId, uint8_t slotId, uint32_t selec uint8_t removeTimes = player->getRemoveTimes(); uint32_t removePrice = g_ioBosstiary().calculteRemoveBoss(removeTimes); g_game().removeMoney(player, removePrice, 0, true); + g_metrics().addCounter("balance_decrease", removePrice, { { "player", player->getName() }, { "context", "bosstiary_remove" } }); player->addRemoveTime(); } diff --git a/src/game/game.hpp b/src/game/game.hpp index d1676e198..f446615e2 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -45,6 +45,7 @@ static constexpr int32_t EVENT_DECAYINTERVAL = 250; static constexpr int32_t EVENT_DECAY_BUCKETS = 4; static constexpr int32_t EVENT_FORGEABLEMONSTERCHECKINTERVAL = 300000; static constexpr int32_t EVENT_LUA_GARBAGE_COLLECTION = 60000 * 10; // 10min +static constexpr int32_t EVENT_REFRESH_MARKET_PRICES = 60000; // 1min static constexpr std::chrono::minutes CACHE_EXPIRATION_TIME { 10 }; // 10min static constexpr std::chrono::minutes HIGHSCORE_CACHE_EXPIRATION_TIME { 10 }; // 10min @@ -160,7 +161,7 @@ class Game { ReturnValue getPlayerByNameWildcard(const std::string &s, std::shared_ptr &player); - std::shared_ptr getPlayerByAccount(uint32_t acc); + std::vector> getPlayersByAccount(std::shared_ptr acc, bool allowOffline = false); bool internalPlaceCreature(std::shared_ptr creature, const Position &pos, bool extendedPos = false, bool forced = false, bool creatureCheck = false); @@ -184,9 +185,6 @@ class Game { uint32_t getPlayersRecord() const { return playersRecord; } - uint16_t getItemsPriceCount() const { - return itemsSaleCount; - } void addItemsClassification(ItemClassification* itemsClassification) { itemsClassifications.push_back(itemsClassification); @@ -236,7 +234,7 @@ class Game { std::shared_ptr transformItem(std::shared_ptr item, uint16_t newId, int32_t newCount = -1); - ReturnValue internalTeleport(std::shared_ptr thing, const Position &newPos, bool pushMove = true, uint32_t flags = 0); + ReturnValue internalTeleport(const std::shared_ptr &thing, const Position &newPos, bool pushMove = true, uint32_t flags = 0); bool internalCreatureTurn(std::shared_ptr creature, Direction dir); @@ -860,7 +858,6 @@ class Game { uint32_t motdNum = 0; std::map> itemsPriceMap; - uint16_t itemsSaleCount; std::vector itemsClassifications; diff --git a/src/game/scheduling/task.cpp b/src/game/scheduling/task.cpp index 28418a4a1..1cc4c4cba 100644 --- a/src/game/scheduling/task.cpp +++ b/src/game/scheduling/task.cpp @@ -10,15 +10,17 @@ #include "pch.hpp" #include "task.hpp" #include "lib/logging/log_with_spd_log.hpp" +#include "lib/metrics/metrics.hpp" std::atomic_uint_fast64_t Task::LAST_EVENT_ID = 0; bool Task::execute() const { + metrics::task_latency measure(context); if (isCanceled()) { return false; } if (hasExpired()) { - g_logger().info("The task '{}' has expired, it has not been executed in {} ms.", getContext(), expiration - utime); + g_logger().info("The task '{}' has expired, it has not been executed in {}.", getContext(), expiration - utime); return false; } diff --git a/src/game/scheduling/task.hpp b/src/game/scheduling/task.hpp index 18dfd5d39..e02a1ce40 100644 --- a/src/game/scheduling/task.hpp +++ b/src/game/scheduling/task.hpp @@ -96,6 +96,7 @@ class Task { "Raids::checkRaids", "SpawnMonster::checkSpawnMonster", "SpawnMonster::scheduleSpawn", + "SpawnMonster::startup", "SpawnNpc::checkSpawnNpc", "Webhook::run", "Protocol::sendRecvMessageCallback", @@ -111,7 +112,7 @@ class Task { }; std::function func = nullptr; - std::string_view context; + std::string context; int64_t utime = 0; int64_t expiration = 0; diff --git a/src/game/zones/zone.cpp b/src/game/zones/zone.cpp index fdeccb9bf..97ef72cf2 100644 --- a/src/game/zones/zone.cpp +++ b/src/game/zones/zone.cpp @@ -26,7 +26,7 @@ std::shared_ptr Zone::addZone(const std::string &name, uint32_t zoneID /* return nullZone; } if (zoneID != 0 && zonesByID.contains(zoneID)) { - g_logger().debug("Found with ID {} while adding {}, linking them together...", zoneID, name); + g_logger().trace("[Zone::addZone] Found with ID {} while adding {}, linking them together...", zoneID, name); auto zone = zonesByID[zoneID]; zone->name = name; zones[name] = zone; @@ -244,7 +244,7 @@ void Zone::refresh() { for (const auto &position : getPositions()) { g_game().map.refreshZones(position); } - g_logger().debug("Refreshed zone '{}' in {} milliseconds", name, bm_refresh.duration()); + g_logger().trace("Refreshed zone '{}' in {} milliseconds", name, bm_refresh.duration()); } void Zone::setMonsterVariant(const std::string &variant) { diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index 3f2e7a233..e6adeb2f4 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -177,6 +177,7 @@ bool IOLoginDataSave::savePlayerFirst(std::shared_ptr player) { // First, an UPDATE query to write the player itself query.str(""); query << "UPDATE `players` SET "; + query << "`name` = " << db.escapeString(player->name) << ","; query << "`level` = " << player->level << ","; query << "`group_id` = " << player->group->id << ","; query << "`vocation` = " << player->getVocationId() << ","; diff --git a/src/io/io_wheel.cpp b/src/io/io_wheel.cpp index 3db059a80..3a8d69d7d 100644 --- a/src/io/io_wheel.cpp +++ b/src/io/io_wheel.cpp @@ -49,7 +49,7 @@ namespace InternalPlayerWheel { void registerWheelSpellTable(const T &spellData, const std::string &name, WheelSpellGrade_t gradeType) { if (name == "Any_Focus_Mage_Spell") { for (const std::string &focusSpellName : m_focusSpells) { - g_logger().debug("[{}] registered any spell: {}", __FUNCTION__, focusSpellName); + g_logger().trace("[{}] registered any spell: {}", __FUNCTION__, focusSpellName); registerWheelSpellTable(spellData, focusSpellName, gradeType); } return; @@ -57,7 +57,7 @@ namespace InternalPlayerWheel { auto spell = g_spells().getInstantSpellByName(name); if (spell) { - g_logger().debug("[{}] registering instant spell with name {}", __FUNCTION__, spell->getName()); + g_logger().trace("[{}] registering instant spell with name {}", __FUNCTION__, spell->getName()); // Increase data const auto increaseData = spellData.increase; if (increaseData.damage > 0) { diff --git a/src/io/iobestiary.cpp b/src/io/iobestiary.cpp index 2f66c53ad..a16c6164a 100644 --- a/src/io/iobestiary.cpp +++ b/src/io/iobestiary.cpp @@ -14,6 +14,7 @@ #include "io/iobestiary.hpp" #include "creatures/monsters/monsters.hpp" #include "creatures/players/player.hpp" +#include "lib/metrics/metrics.hpp" SoftSingleton IOBestiary::instanceTracker("IOBestiary"); @@ -26,7 +27,7 @@ bool IOBestiary::parseCharmCombat(const std::shared_ptr charm, std::share if (charm->type == CHARM_OFFENSIVE) { if (charm->id == CHARM_CRIPPLE) { std::shared_ptr cripple = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_PARALYZE, 10000, 0)->static_self_cast(); - cripple->setFormulaVars(-1, 81, -1, 81); + cripple->setFormulaVars(-1, 0, -1, 0); target->addCondition(cripple); player->sendCancelMessage(charm->cancelMsg); return false; @@ -70,14 +71,14 @@ bool IOBestiary::parseCharmCombat(const std::shared_ptr charm, std::share } case CHARM_ADRENALINE: { std::shared_ptr adrenaline = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_HASTE, 10000, 0)->static_self_cast(); - adrenaline->setFormulaVars(1.5, -0, 1.5, -0); + adrenaline->setFormulaVars(2.5, 40, 2.5, 40); player->addCondition(adrenaline); player->sendCancelMessage(charm->cancelMsg); return false; } case CHARM_NUMB: { std::shared_ptr numb = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_PARALYZE, 10000, 0)->static_self_cast(); - numb->setFormulaVars(-1, 81, -1, 81); + numb->setFormulaVars(-1, 0, -1, 0); target->addCondition(numb); player->sendCancelMessage(charm->cancelMsg); return false; @@ -336,6 +337,7 @@ void IOBestiary::sendBuyCharmRune(std::shared_ptr player, charmRune_t ru resetCharmRuneCreature(player, charm); player->sendFYIBox("You successfully removed the creature."); player->BestiarysendCharms(); + g_metrics().addCounter("balance_decrease", fee, { { "player", player->getName() }, { "context", "charm_removal" } }); return; } player->sendFYIBox("You don't have enough gold."); diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 584f36e20..3ca34026d 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -15,6 +15,7 @@ #include "game/game.hpp" #include "creatures/monsters/monster.hpp" #include "creatures/players/wheel/player_wheel.hpp" +#include "lib/metrics/metrics.hpp" bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, const std::string &password, std::string &characterName, uint32_t &accountId, bool oldProtocol) { account::Account account(accountDescriptor); @@ -74,16 +75,18 @@ void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) { std::ostringstream query; if (login) { + g_metrics().addUpDownCounter("players_online", 1); query << "INSERT INTO `players_online` VALUES (" << guid << ')'; updateOnline[guid] = true; } else { + g_metrics().addUpDownCounter("players_online", -1); query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; updateOnline.erase(guid); } Database::getInstance().executeQuery(query.str()); } -// The boolean "disableIrrelevantInfo" will desactivate the loading of information that is not relevant to the preload, for example, forge, bosstiary, etc. None of this we need to access if the player is offline +// The boolean "disableIrrelevantInfo" will deactivate the loading of information that is not relevant to the preload, for example, forge, bosstiary, etc. None of this we need to access if the player is offline bool IOLoginData::loadPlayerById(std::shared_ptr player, uint32_t id, bool disableIrrelevantInfo /* = true*/) { Database &db = Database::getInstance(); std::ostringstream query; diff --git a/src/io/iomarket.cpp b/src/io/iomarket.cpp index ad0459983..f308e10e6 100644 --- a/src/io/iomarket.cpp +++ b/src/io/iomarket.cpp @@ -26,6 +26,35 @@ uint8_t IOMarket::getTierFromDatabaseTable(const std::string &string) { return tier; } +MarketOfferList IOMarket::getActiveOffers(MarketAction_t action) { + MarketOfferList offerList; + + std::ostringstream query; + query << "SELECT `id`, `amount`, `price`, `tier`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = " << action; + + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + if (!result) { + return offerList; + } + + const int32_t marketOfferDuration = g_configManager().getNumber(MARKET_OFFER_DURATION, __FUNCTION__); + + do { + MarketOffer offer; + offer.amount = result->getNumber("amount"); + offer.price = result->getNumber("price"); + offer.timestamp = result->getNumber("created") + marketOfferDuration; + offer.counter = result->getNumber("id") & 0xFFFF; + if (result->getNumber("anonymous") == 0) { + offer.playerName = result->getString("player_name"); + } else { + offer.playerName = "Anonymous"; + } + offer.tier = getTierFromDatabaseTable(result->getString("tier")); + offerList.push_back(offer); + } while (result->next()); + return offerList; +} MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId, uint8_t tier) { MarketOfferList offerList; diff --git a/src/io/iomarket.hpp b/src/io/iomarket.hpp index f192acca5..b02fba0ce 100644 --- a/src/io/iomarket.hpp +++ b/src/io/iomarket.hpp @@ -23,6 +23,7 @@ class IOMarket { return inject(); } + static MarketOfferList getActiveOffers(MarketAction_t action); static MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId, uint8_t tier); static MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId); static HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId); diff --git a/src/io/ioprey.cpp b/src/io/ioprey.cpp index b953d1807..2a1c0eebd 100644 --- a/src/io/ioprey.cpp +++ b/src/io/ioprey.cpp @@ -14,6 +14,7 @@ #include "config/configmanager.hpp" #include "game/game.hpp" #include "io/ioprey.hpp" +#include "lib/metrics/metrics.hpp" // Prey class PreySlot::PreySlot(PreySlot_t id) : @@ -301,6 +302,8 @@ void IOPrey::parsePreyAction(std::shared_ptr player, PreySlot_t slotId, return; } else if (slot->freeRerollTimeStamp <= OTSYS_TIME()) { slot->freeRerollTimeStamp = OTSYS_TIME() + g_configManager().getNumber(PREY_FREE_REROLL_TIME, __FUNCTION__) * 1000; + } else { + g_metrics().addCounter("balance_decrease", player->getPreyRerollPrice(), { { "player", player->getName() }, { "context", "prey_reroll" } }); } slot->eraseBonus(true); @@ -406,6 +409,8 @@ void IOPrey::parseTaskHuntingAction(std::shared_ptr player, PreySlot_t s return; } else if (slot->freeRerollTimeStamp <= OTSYS_TIME()) { slot->freeRerollTimeStamp = OTSYS_TIME() + g_configManager().getNumber(TASK_HUNTING_FREE_REROLL_TIME, __FUNCTION__) * 1000; + } else { + g_metrics().addCounter("balance_decrease", player->getTaskHuntingRerollPrice(), { { "player", player->getName() }, { "context", "hunting_task_reroll" } }); } slot->eraseTask(); @@ -465,6 +470,7 @@ void IOPrey::parseTaskHuntingAction(std::shared_ptr player, PreySlot_t s return; } + g_metrics().addCounter("balance_decrease", player->getTaskHuntingRerollPrice(), { { "player", player->getName() }, { "context", "hunting_task_cancel" } }); slot->eraseTask(); slot->reloadReward(); slot->state = PreyTaskDataState_Selection; diff --git a/src/items/tile.cpp b/src/items/tile.cpp index 787c5ed9b..2b9527095 100644 --- a/src/items/tile.cpp +++ b/src/items/tile.cpp @@ -668,6 +668,27 @@ ReturnValue Tile::queryAdd(int32_t, const std::shared_ptr &thing, uint32_ } const auto playerTile = player->getTile(); + // moving from a pz tile to a non-pz tile + if (playerTile && playerTile->hasFlag(TILESTATE_PROTECTIONZONE)) { + auto maxOnline = g_configManager().getNumber(MAX_PLAYERS_PER_ACCOUNT, __FUNCTION__); + if (maxOnline > 1 && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && !hasFlag(TILESTATE_PROTECTIONZONE)) { + auto maxOutsizePZ = g_configManager().getNumber(MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, __FUNCTION__); + auto accountPlayers = g_game().getPlayersByAccount(player->getAccount()); + int countOutsizePZ = 0; + for (const auto &accountPlayer : accountPlayers) { + if (accountPlayer == player || accountPlayer->isOffline()) { + continue; + } + if (accountPlayer->getTile() && !accountPlayer->getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { + ++countOutsizePZ; + } + } + if (countOutsizePZ >= maxOutsizePZ) { + player->sendCreatureSay(player, TALKTYPE_MONSTER_SAY, fmt::format("You can only have {} character{} from your account outside of a protection zone.", maxOutsizePZ == 1 ? "one" : std::to_string(maxOutsizePZ), maxOutsizePZ > 1 ? "s" : ""), &getPosition()); + return RETURNVALUE_NOTPOSSIBLE; + } + } + } if (playerTile && player->isPzLocked()) { if (!playerTile->hasFlag(TILESTATE_PVPZONE)) { // player is trying to enter a pvp zone while being pz-locked diff --git a/src/items/weapons/weapons.cpp b/src/items/weapons/weapons.cpp index 0e5d1e949..796aea212 100644 --- a/src/items/weapons/weapons.cpp +++ b/src/items/weapons/weapons.cpp @@ -252,6 +252,10 @@ void Weapon::onUsedWeapon(std::shared_ptr player, std::shared_ptr if (manaCost != 0) { player->addManaSpent(manaCost); player->changeMana(-static_cast(manaCost)); + + if (g_configManager().getBoolean(REFUND_BEGINNING_WEAPON_MANA, __FUNCTION__) && (item->getName() == "wand of vortex" || item->getName() == "snakebite rod")) { + player->changeMana(static_cast(manaCost)); + } } uint32_t healthCost = getHealthCost(player); @@ -263,7 +267,8 @@ void Weapon::onUsedWeapon(std::shared_ptr player, std::shared_ptr player->changeSoul(-static_cast(soul)); } - if (breakChance != 0 && uniform_random(1, 100) <= breakChance) { + bool skipRemoveBeginningWeaponAmmo = !g_configManager().getBoolean(REMOVE_BEGINNING_WEAPON_AMMO, __FUNCTION__) && (item->getName() == "arrow" || item->getName() == "bolt" || item->getName() == "spear"); + if (!skipRemoveBeginningWeaponAmmo && breakChance != 0 && uniform_random(1, 100) <= breakChance) { Weapon::decrementItemCount(item); player->updateSupplyTracker(item); return; @@ -271,7 +276,7 @@ void Weapon::onUsedWeapon(std::shared_ptr player, std::shared_ptr switch (action) { case WEAPONACTION_REMOVECOUNT: - if (g_configManager().getBoolean(REMOVE_WEAPON_AMMO, __FUNCTION__)) { + if (!skipRemoveBeginningWeaponAmmo && g_configManager().getBoolean(REMOVE_WEAPON_AMMO, __FUNCTION__)) { Weapon::decrementItemCount(item); player->updateSupplyTracker(item); } diff --git a/src/kv/kv.cpp b/src/kv/kv.cpp index 0e2f4b2f8..f3133dec9 100644 --- a/src/kv/kv.cpp +++ b/src/kv/kv.cpp @@ -32,7 +32,7 @@ void KVStore::set(const std::string &key, const ValueWrapper &value) { } void KVStore::setLocked(const std::string &key, const ValueWrapper &value) { - logger.debug("KVStore::set({})", key); + logger.trace("KVStore::set({})", key); auto it = store_.find(key); if (it != store_.end()) { it->second.first = value; @@ -53,7 +53,7 @@ void KVStore::setLocked(const std::string &key, const ValueWrapper &value) { } std::optional KVStore::get(const std::string &key, bool forceLoad /*= false */) { - logger.debug("KVStore::get({})", key); + logger.trace("KVStore::get({})", key); std::scoped_lock lock(mutex_); if (forceLoad || !store_.contains(key)) { auto value = load(key); @@ -72,11 +72,26 @@ std::optional KVStore::get(const std::string &key, bool forceLoad return value; } +std::unordered_set KVStore::keys(const std::string &prefix /*= ""*/) { + std::scoped_lock lock(mutex_); + std::unordered_set keys; + for (const auto &[key, value] : store_) { + if (key.find(prefix) == 0) { + std::string suffix = key.substr(prefix.size()); + keys.insert(suffix); + } + } + for (const auto &key : loadPrefix(prefix)) { + keys.insert(key); + } + return keys; +} + void KV::remove(const std::string &key) { set(key, ValueWrapper::deleted()); } std::shared_ptr KVStore::scoped(const std::string &scope) { - logger.debug("KVStore::scoped({})", scope); + logger.trace("KVStore::scoped({})", scope); return std::make_shared(logger, *this, scope); } diff --git a/src/kv/kv.hpp b/src/kv/kv.hpp index 40fe44983..99bc1f695 100644 --- a/src/kv/kv.hpp +++ b/src/kv/kv.hpp @@ -32,6 +32,8 @@ class KV : public std::enable_shared_from_this { virtual std::shared_ptr scoped(const std::string &scope) = 0; + virtual std::unordered_set keys(const std::string &prefix = "") = 0; + void remove(const std::string &key); virtual void flush() { @@ -60,6 +62,7 @@ class KVStore : public KV { } std::shared_ptr scoped(const std::string &scope) override final; + std::unordered_set keys(const std::string &prefix = ""); protected: phmap::parallel_flat_hash_map::iterator>> getStore() { @@ -76,6 +79,7 @@ class KVStore : public KV { virtual std::optional load(const std::string &key) = 0; virtual bool save(const std::string &key, const ValueWrapper &value) = 0; + virtual std::vector loadPrefix(const std::string &prefix = "") = 0; private: void setLocked(const std::string &key, const ValueWrapper &value); @@ -118,10 +122,14 @@ class ScopedKV final : public KV { } std::shared_ptr scoped(const std::string &scope) override final { - logger.debug("ScopedKV::scoped({})", buildKey(scope)); + logger.trace("ScopedKV::scoped({})", buildKey(scope)); return std::make_shared(logger, rootKV_, buildKey(scope)); } + std::unordered_set keys(const std::string &prefix = "") override { + return rootKV_.keys(buildKey(prefix)); + } + private: std::string buildKey(const std::string &key) const { return fmt::format("{}.{}", prefix_, key); diff --git a/src/kv/kv_sql.cpp b/src/kv/kv_sql.cpp index a0f1623ed..4784efa5c 100644 --- a/src/kv/kv_sql.cpp +++ b/src/kv/kv_sql.cpp @@ -41,6 +41,24 @@ std::optional KVSQL::load(const std::string &key) { return std::nullopt; } +std::vector KVSQL::loadPrefix(const std::string &prefix /* = ""*/) { + std::vector keys; + std::string keySearch = db.escapeString(prefix + "%"); + auto query = fmt::format("SELECT `key_name` FROM `kv_store` WHERE `key_name` LIKE {}", keySearch); + auto result = db.storeQuery(query); + if (result == nullptr) { + return keys; + } + + do { + std::string key = result->getString("key_name"); + replaceString(key, prefix, ""); + keys.push_back(key); + } while (result->next()); + + return keys; +} + bool KVSQL::save(const std::string &key, const ValueWrapper &value) { auto update = dbUpdate(); prepareSave(key, value, update); @@ -58,7 +76,7 @@ bool KVSQL::prepareSave(const std::string &key, const ValueWrapper &value, DBIns return db.executeQuery(query); } - update.addRow(fmt::format("{}, {}, {}", db.escapeString(key), getTimeMsNow(), db.escapeString(data))); + update.addRow(fmt::format("{}, {}, {}", db.escapeString(key), value.getTimestamp(), db.escapeString(data))); return true; } diff --git a/src/kv/kv_sql.hpp b/src/kv/kv_sql.hpp index 8cb4dce89..fd181b1ce 100644 --- a/src/kv/kv_sql.hpp +++ b/src/kv/kv_sql.hpp @@ -25,6 +25,7 @@ class KVSQL final : public KVStore { bool saveAll() override; private: + std::vector loadPrefix(const std::string &prefix = "") override; std::optional load(const std::string &key) override; bool save(const std::string &key, const ValueWrapper &value) override; bool prepareSave(const std::string &key, const ValueWrapper &value, DBInsert &update); diff --git a/src/kv/value_wrapper.cpp b/src/kv/value_wrapper.cpp index 023beba15..af304eeac 100644 --- a/src/kv/value_wrapper.cpp +++ b/src/kv/value_wrapper.cpp @@ -1,30 +1,31 @@ #include "kv/value_wrapper.hpp" +#include "utils/tools.hpp" ValueWrapper::ValueWrapper(uint64_t timestamp) : - timestamp_(timestamp) { } + timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } ValueWrapper::ValueWrapper(const ValueVariant &value, uint64_t timestamp) : - data_(value), timestamp_(timestamp) { } + data_(value), timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } ValueWrapper::ValueWrapper(const std::string &value, uint64_t timestamp) : - data_(value), timestamp_(timestamp) { } + data_(value), timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } ValueWrapper::ValueWrapper(bool value, uint64_t timestamp) : - data_(value), timestamp_(timestamp) { } + data_(value), timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } ValueWrapper::ValueWrapper(int value, uint64_t timestamp) : - data_(value), timestamp_(timestamp) { } + data_(value), timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } ValueWrapper::ValueWrapper(double value, uint64_t timestamp) : - data_(value), timestamp_(timestamp) { } + data_(value), timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } ValueWrapper::ValueWrapper(const phmap::flat_hash_map &value, uint64_t timestamp) : data_(createMapFromRange(value.begin(), value.end(), timestamp)), - timestamp_(timestamp) { } + timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } ValueWrapper::ValueWrapper(const std::initializer_list> &init_list, uint64_t timestamp) : data_(createMapFromRange(init_list.begin(), init_list.end(), timestamp)), - timestamp_(timestamp) { } + timestamp_(timestamp == 0 ? getTimeMsNow() : timestamp) { } std::optional ValueWrapper::get(const std::string &key) const { auto pval = std::get_if(&data_); diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 00b8329f4..402c6a34a 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources(${PROJECT_NAME}_lib PRIVATE di/soft_singleton.cpp logging/log_with_spd_log.cpp + metrics/metrics.cpp thread/thread_pool.cpp ) diff --git a/src/lib/metrics/metrics.cpp b/src/lib/metrics/metrics.cpp new file mode 100644 index 000000000..77a4bb8b3 --- /dev/null +++ b/src/lib/metrics/metrics.cpp @@ -0,0 +1,105 @@ +/** + * 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 "metrics.hpp" +#include "lib/di/container.hpp" + +using namespace metrics; + +Metrics &Metrics::getInstance() { + return inject(); +} + +void Metrics::init(Options opts) { + if (!opts.enableOStreamExporter && !opts.enablePrometheusExporter) { + return; + } + + auto provider = metrics_sdk::MeterProviderFactory::Create(); + auto* p = static_cast(provider.get()); + + if (opts.enableOStreamExporter) { + opts.ostreamOptions.export_timeout_millis = std::chrono::milliseconds(1000); + auto ostreamExporter = metrics_exporter::OStreamMetricExporterFactory::Create(); + auto reader = metrics_sdk::PeriodicExportingMetricReaderFactory::Create(std::move(ostreamExporter), opts.ostreamOptions); + p->AddMetricReader(std::move(reader)); + } + + if (opts.enablePrometheusExporter) { + g_logger().info("Starting Prometheus exporter at {}", opts.prometheusOptions.url); + auto prometheusExporter = metrics_exporter::PrometheusExporterFactory::Create(opts.prometheusOptions); + p->AddMetricReader(std::move(prometheusExporter)); + } + + for (auto name : latencyNames) { + auto instrumentSelector = metrics_sdk::InstrumentSelectorFactory::Create(metrics_sdk::InstrumentType::kHistogram, name, "us"); + auto meterSelector = metrics_sdk::MeterSelectorFactory::Create("performance", otelVersion, otelSchema); + + auto aggregationConfig = std::make_unique(); + // TODO: migrate to ExponentialHistogramIndexer when that's available + // clang-format off + aggregationConfig->boundaries_ = { + // Ultra-fine granularity below 10µs + 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, + 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0, + 35.0, 40.0, 45.0, 50.0, 55.0, 60.0, 65.0, 70.0, 75.0, 80.0, + 85.0, 90.0, 95.0, 100.0, + // Fine granularity between 100µs and 500µs + 120.0, 140.0, 160.0, 180.0, 200.0, 225.0, 250.0, 275.0, 300.0, 325.0, 350.0, 375.0, 400.0, 425.0, 450.0, 475.0, 500.0, + // Moderate granularity from 500µs to 1ms (1000µs) + 550.0, 600.0, 650.0, 700.0, 750.0, 800.0, 850.0, 900.0, 950.0, 1000.0, + // Coarser granularity for higher latencies (in microseconds) + 1100.0, 1200.0, 1300.0, 1400.0, 1500.0, 2000.0, 2500.0, 3000.0, 3500.0, 4000.0, 4500.0, 5000.0, 10000.0, + // Very coarse granularity for latencies in milliseconds + 20000.0, 30000.0, 40000.0, 50000.0, 60000.0,70000.0, 80000.0, 90000.0, 100000.0, + 200000.0, 300000.0, 400000.0, 500000.0, 600000.0,700000.0, 800000.0, 900000.0, 1000000.0, + // Even coarser granularity for latencies in seconds + 2000000.0, 3000000.0, 4000000.0, 5000000.0, 6000000.0,7000000.0, 8000000.0, 9000000.0, 10000000.0, + 20000000.0, 30000000.0, 40000000.0, 50000000.0, 60000000.0,70000000.0, 80000000.0, 90000000.0, 100000000.0, + // And finally a catch-all for anything else + std::numeric_limits::infinity(), + }; + // clang-format on + + auto view = metrics_sdk::ViewFactory::Create(name, "Latency", "us", metrics_sdk::AggregationType::kHistogram, std::move(aggregationConfig)); + p->AddView(std::move(instrumentSelector), std::move(meterSelector), std::move(view)); + + latencyHistograms[name] = getMeter()->CreateDoubleHistogram(name, "Latency", "us"); + } + + metrics_api::Provider::SetMeterProvider(std::move(provider)); +} + +void Metrics::shutdown() { + std::shared_ptr none; + metrics_api::Provider::SetMeterProvider(none); +} + +ScopedLatency::ScopedLatency(std::string_view name, const std::string &histogramName, const std::string &scopeKey) : + ScopedLatency(name, g_metrics().latencyHistograms[histogramName], { { scopeKey, std::string(name) } }, g_metrics().defaultContext) { + if (histogram == nullptr) { + stopped = true; + return; + } +} + +ScopedLatency::~ScopedLatency() { + stop(); +} + +void ScopedLatency::stop() { + if (stopped) { + return; + } + stopped = true; + auto end = std::chrono::steady_clock::now(); + double elapsed = static_cast(std::chrono::duration_cast(end - begin).count()) / 1000; + auto attrskv = opentelemetry::common::KeyValueIterableView { attrs }; + histogram->Record(elapsed, attrskv, context); +} diff --git a/src/lib/metrics/metrics.hpp b/src/lib/metrics/metrics.hpp new file mode 100644 index 000000000..116676fb3 --- /dev/null +++ b/src/lib/metrics/metrics.hpp @@ -0,0 +1,171 @@ +/** + * 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 "game/scheduling/dispatcher.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace metrics_sdk = opentelemetry::sdk::metrics; +namespace common = opentelemetry::common; +namespace metrics_exporter = opentelemetry::exporter::metrics; +namespace metrics_api = opentelemetry::metrics; + +constexpr std::string_view methodName(const char* s) { + std::string_view prettyFunction(s); + size_t bracket = prettyFunction.rfind("("); + size_t space = prettyFunction.rfind(" ", bracket) + 1; + return prettyFunction.substr(space, bracket - space); +} + +#if defined(__GNUC__) || defined(__clang__) + #define __METHOD_NAME__ methodName(__PRETTY_FUNCTION__) +#elif defined(_MSC_VER) + #define __METHOD_NAME__ methodName(__FUNCSIG__) +#else + #error "Compiler not supported" +#endif + +namespace metrics { + using Meter = opentelemetry::nostd::shared_ptr; + + template + using Histogram = opentelemetry::nostd::unique_ptr>; + + template + using Counter = opentelemetry::nostd::unique_ptr>; + + template + using UpDownCounter = opentelemetry::nostd::unique_ptr>; + + struct Options { + bool enablePrometheusExporter; + bool enableOStreamExporter; + + metrics_sdk::PeriodicExportingMetricReaderOptions ostreamOptions; + metrics_exporter::PrometheusExporterOptions prometheusOptions; + }; + + class ScopedLatency { + public: + explicit ScopedLatency(std::string_view name, const std::string &histogramName, const std::string &scopeKey); + explicit ScopedLatency(std::string_view name, Histogram &histogram, std::map attrs = {}, opentelemetry::context::Context context = opentelemetry::context::Context()) : + begin(std::chrono::steady_clock::now()), histogram(histogram), attrs(attrs), context(context) { + } + + void stop(); + + ~ScopedLatency(); + + private: + opentelemetry::context::Context context; + Histogram &histogram; + std::chrono::steady_clock::time_point begin; + std::map attrs; + bool stopped { false }; + }; + +#define DEFINE_LATENCY_CLASS(class_name, histogram_name, category) \ + class class_name##_latency final : public ScopedLatency { \ + public: \ + class_name##_latency(std::string_view name) : \ + ScopedLatency(name, histogram_name "_latency", category) { } \ + } + + DEFINE_LATENCY_CLASS(method, "method", "method"); + DEFINE_LATENCY_CLASS(lua, "lua", "scope"); + DEFINE_LATENCY_CLASS(query, "query", "truncated_query"); + DEFINE_LATENCY_CLASS(task, "task", "task"); + DEFINE_LATENCY_CLASS(lock, "lock", "scope"); + + const std::vector latencyNames { + "method_latency", + "lua_latency", + "query_latency", + "task_latency", + "lock_latency", + }; + + class Metrics final { + public: + Metrics() { } + ~Metrics() = default; + + void init(Options opts); + void shutdown(); + + static Metrics &getInstance(); + + void addCounter(std::string_view name, double value, std::map attrs = {}) { + std::scoped_lock lock(mutex_); + if (!getMeter()) { + return; + } + if (counters.find(name) == counters.end()) { + std::string nameStr(name); + counters[name] = getMeter()->CreateDoubleCounter(nameStr); + } + auto attrskv = opentelemetry::common::KeyValueIterableView { attrs }; + counters[name]->Add(value, attrskv); + } + + void addUpDownCounter(std::string_view name, int value, std::map attrs = {}) { + std::scoped_lock lock(mutex_); + if (!getMeter()) { + return; + } + if (upDownCounters.find(name) == upDownCounters.end()) { + std::string nameStr(name); + upDownCounters[name] = getMeter()->CreateInt64UpDownCounter(nameStr); + } + auto attrskv = opentelemetry::common::KeyValueIterableView { attrs }; + upDownCounters[name]->Add(value, attrskv); + } + + friend class ScopedLatency; + + protected: + opentelemetry::context::Context defaultContext {}; + phmap::parallel_flat_hash_map> latencyHistograms; + phmap::flat_hash_map> upDownCounters; + phmap::flat_hash_map> counters; + + Meter getMeter() { + auto provider = metrics_api::Provider::GetMeterProvider(); + if (provider == nullptr) { + return {}; + } + return provider->GetMeter(meterName, otelVersion); + } + + private: + std::mutex mutex_; + + std::string meterName { "stats" }; + std::string otelVersion { "1.2.0" }; + std::string otelSchema { "https://opentelemetry.io/schemas/1.2.0" }; + }; +} + +constexpr auto g_metrics + = metrics::Metrics::getInstance; diff --git a/src/lua/functions/core/CMakeLists.txt b/src/lua/functions/core/CMakeLists.txt index 6f4168f0f..1cf919da3 100644 --- a/src/lua/functions/core/CMakeLists.txt +++ b/src/lua/functions/core/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(${PROJECT_NAME}_lib PRIVATE libs/db_functions.cpp libs/result_functions.cpp libs/logger_functions.cpp + libs/metrics_functions.cpp libs/kv_functions.cpp network/network_message_functions.cpp network/webhook_functions.cpp diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index cab8c599a..633f39489 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -611,13 +611,23 @@ int GameFunctions::luaGameHasDistanceEffect(lua_State* L) { } int GameFunctions::luaGameGetOfflinePlayer(lua_State* L) { - uint32_t playerId = getNumber(L, 1); - - auto offlinePlayer = std::make_shared(nullptr); - if (!IOLoginData::loadPlayerById(offlinePlayer, playerId)) { + // Game.getOfflinePlayer(name or id) + std::shared_ptr player = nullptr; + if (isNumber(L, 1)) { + uint32_t id = getNumber(L, 1); + if (id >= Player::getFirstID() && id <= Player::getLastID()) { + player = g_game().getPlayerByID(id, true); + } else { + player = g_game().getPlayerByGUID(id, true); + } + } else if (isString(L, 1)) { + auto name = getString(L, 1); + player = g_game().getPlayerByName(name, true); + } + if (!player) { lua_pushnil(L); } else { - pushUserdata(L, offlinePlayer); + pushUserdata(L, player); setMetatable(L, -1, "Player"); } diff --git a/src/lua/functions/core/libs/core_libs_functions.hpp b/src/lua/functions/core/libs/core_libs_functions.hpp index 29b8c8896..4badabe26 100644 --- a/src/lua/functions/core/libs/core_libs_functions.hpp +++ b/src/lua/functions/core/libs/core_libs_functions.hpp @@ -14,6 +14,7 @@ #include "lua/functions/core/libs/db_functions.hpp" #include "lua/functions/core/libs/result_functions.hpp" #include "lua/functions/core/libs/logger_functions.hpp" +#include "lua/functions/core/libs/metrics_functions.hpp" #include "lua/functions/core/libs/kv_functions.hpp" class CoreLibsFunctions final : LuaScriptInterface { @@ -23,6 +24,7 @@ class CoreLibsFunctions final : LuaScriptInterface { DBFunctions::init(L); ResultFunctions::init(L); LoggerFunctions::init(L); + MetricsFunctions::init(L); KVFunctions::init(L); } diff --git a/src/lua/functions/core/libs/kv_functions.cpp b/src/lua/functions/core/libs/kv_functions.cpp index 769c20bff..dbef91636 100644 --- a/src/lua/functions/core/libs/kv_functions.cpp +++ b/src/lua/functions/core/libs/kv_functions.cpp @@ -72,7 +72,7 @@ int KVFunctions::luaKVGet(lua_State* L) { valueWrapper = g_kv().get(key, forceLoad); } - if (valueWrapper) { + if (valueWrapper.has_value()) { pushValueWrapper(L, *valueWrapper); } else { lua_pushnil(L); @@ -80,6 +80,44 @@ int KVFunctions::luaKVGet(lua_State* L) { return 1; } +int KVFunctions::luaKVRemove(lua_State* L) { + // KV.remove(key) | scopedKV:remove(key) + auto key = getString(L, -1); + if (isUserdata(L, 1)) { + auto scopedKV = getUserdataShared(L, 1); + scopedKV->remove(key); + } else { + g_kv().remove(key); + } + lua_pushnil(L); + return 1; +} + +int KVFunctions::luaKVKeys(lua_State* L) { + // KV.keys([prefix = ""]) | scopedKV:keys([prefix = ""]) + std::unordered_set keys; + std::string prefix = ""; + + if (isString(L, -1)) { + prefix = getString(L, -1); + } + + if (isUserdata(L, 1)) { + auto scopedKV = getUserdataShared(L, 1); + keys = scopedKV->keys(); + } else { + keys = g_kv().keys(prefix); + } + + int index = 0; + lua_createtable(L, static_cast(keys.size()), 0); + for (const auto &key : keys) { + pushString(L, key); + lua_rawseti(L, -2, ++index); + } + return 1; +} + std::optional KVFunctions::getValueWrapper(lua_State* L) { if (isBoolean(L, -1)) { return ValueWrapper(getBoolean(L, -1)); diff --git a/src/lua/functions/core/libs/kv_functions.hpp b/src/lua/functions/core/libs/kv_functions.hpp index 243a0ed36..3abddfb0b 100644 --- a/src/lua/functions/core/libs/kv_functions.hpp +++ b/src/lua/functions/core/libs/kv_functions.hpp @@ -18,17 +18,23 @@ class KVFunctions final : LuaScriptInterface { registerMethod(L, "kv", "scoped", KVFunctions::luaKVScoped); registerMethod(L, "kv", "set", KVFunctions::luaKVSet); registerMethod(L, "kv", "get", KVFunctions::luaKVGet); + registerMethod(L, "kv", "keys", KVFunctions::luaKVKeys); + registerMethod(L, "kv", "remove", KVFunctions::luaKVRemove); registerClass(L, "KV", ""); registerMethod(L, "KV", "scoped", KVFunctions::luaKVScoped); registerMethod(L, "KV", "set", KVFunctions::luaKVSet); registerMethod(L, "KV", "get", KVFunctions::luaKVGet); + registerMethod(L, "KV", "keys", KVFunctions::luaKVKeys); + registerMethod(L, "KV", "remove", KVFunctions::luaKVRemove); } private: static int luaKVScoped(lua_State* L); static int luaKVSet(lua_State* L); static int luaKVGet(lua_State* L); + static int luaKVKeys(lua_State* L); + static int luaKVRemove(lua_State* L); static std::optional getValueWrapper(lua_State* L); static void pushStringValue(lua_State* L, const StringType &value); diff --git a/src/lua/functions/core/libs/metrics_functions.cpp b/src/lua/functions/core/libs/metrics_functions.cpp new file mode 100644 index 000000000..4c0b916c9 --- /dev/null +++ b/src/lua/functions/core/libs/metrics_functions.cpp @@ -0,0 +1,40 @@ +/** + * 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 "lua/functions/core/libs/metrics_functions.hpp" +#include "lib/metrics/metrics.hpp" + +void MetricsFunctions::init(lua_State* L) { + registerTable(L, "metrics"); + registerMethod(L, "metrics", "addCounter", MetricsFunctions::luaMetricsAddCounter); +} + +// Metrics +int MetricsFunctions::luaMetricsAddCounter(lua_State* L) { + // metrics.addCounter(name, value, attributes) + auto name = getString(L, 1); + auto value = getNumber(L, 2); + auto attributes = getAttributes(L, 3); + g_metrics().addCounter(name, value, attributes); + return 1; +} + +std::map MetricsFunctions::getAttributes(lua_State* L, int32_t index) { + std::map attributes; + if (isTable(L, index)) { + lua_pushnil(L); + while (lua_next(L, index) != 0) { + attributes[getString(L, -2)] = getString(L, -1); + lua_pop(L, 1); + } + } + return attributes; +} diff --git a/src/lua/functions/core/libs/metrics_functions.hpp b/src/lua/functions/core/libs/metrics_functions.hpp new file mode 100644 index 000000000..47d492c3d --- /dev/null +++ b/src/lua/functions/core/libs/metrics_functions.hpp @@ -0,0 +1,21 @@ +/** + * 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 "lua/scripts/luascript.hpp" + +class MetricsFunctions final : public LuaScriptInterface { +public: + static void init(lua_State* L); + +private: + static int luaMetricsAddCounter(lua_State* L); + static std::map getAttributes(lua_State* L, int32_t index); +}; diff --git a/src/lua/functions/core/network/network_message_functions.cpp b/src/lua/functions/core/network/network_message_functions.cpp index 61567b1e1..fc1c16886 100644 --- a/src/lua/functions/core/network/network_message_functions.cpp +++ b/src/lua/functions/core/network/network_message_functions.cpp @@ -191,11 +191,12 @@ int NetworkMessageFunctions::luaNetworkMessageAdd64(lua_State* L) { } int NetworkMessageFunctions::luaNetworkMessageAddString(lua_State* L) { - // networkMessage:addString(string) + // networkMessage:addString(string, function) const std::string &string = getString(L, 2); + const std::string &function = getString(L, 3); const auto &message = getUserdataShared(L, 1); if (message) { - message->addString(string); + message->addString(string, function); pushBoolean(L, true); } else { lua_pushnil(L); diff --git a/src/lua/functions/creatures/combat/spell_functions.cpp b/src/lua/functions/creatures/combat/spell_functions.cpp index 3691645e2..c76df1163 100644 --- a/src/lua/functions/creatures/combat/spell_functions.cpp +++ b/src/lua/functions/creatures/combat/spell_functions.cpp @@ -179,9 +179,9 @@ int SpellFunctions::luaSpellId(lua_State* L) { return 1; } if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getId()); + lua_pushnumber(L, spell->getSpellId()); } else { - spell->setId(getNumber(L, 2)); + spell->setSpellId(getNumber(L, 2)); pushBoolean(L, true); } } else { diff --git a/src/lua/functions/creatures/npc/npc_functions.cpp b/src/lua/functions/creatures/npc/npc_functions.cpp index 0df0b5504..c4f447c88 100644 --- a/src/lua/functions/creatures/npc/npc_functions.cpp +++ b/src/lua/functions/creatures/npc/npc_functions.cpp @@ -384,7 +384,6 @@ int NpcFunctions::luaNpcOpenShopWindowTable(lua_State* L) { lua_pushnil(L); while (lua_next(L, 3) != 0) { const auto tableIndex = lua_gettop(L); - ShopBlock item; auto itemId = getField(L, tableIndex, "clientId"); auto subType = getField(L, tableIndex, "subType"); @@ -397,10 +396,11 @@ int NpcFunctions::luaNpcOpenShopWindowTable(lua_State* L) { auto sellPrice = getField(L, tableIndex, "sell"); auto storageKey = getField(L, tableIndex, "storageKey"); auto storageValue = getField(L, tableIndex, "storageValue"); - auto realName = getFieldString(L, tableIndex, "name"); - g_logger().debug("[{}] item '{}' sell price '{}', buyprice '{}'", __FUNCTION__, realName, sellPrice, buyPrice); - - items.emplace_back(itemId, subType, buyPrice, sellPrice, storageKey, storageValue, std::move(realName)); + auto itemName = getFieldString(L, tableIndex, "itemName"); + if (itemName.empty()) { + itemName = Item::items[itemId].name; + } + items.emplace_back(itemId, subType, buyPrice, sellPrice, storageKey, storageValue, itemName); lua_pop(L, 8); } lua_pop(L, 3); diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 5b646264d..df80a42b1 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -20,6 +20,7 @@ #include "items/item.hpp" #include "lua/functions/creatures/player/player_functions.hpp" #include "game/scheduling/save_manager.hpp" +#include "game/scheduling/dispatcher.hpp" #include "map/spectators.hpp" int PlayerFunctions::luaPlayerSendInventory(lua_State* L) { diff --git a/src/lua/scripts/luascript.cpp b/src/lua/scripts/luascript.cpp index e4f230a5d..adefef958 100644 --- a/src/lua/scripts/luascript.cpp +++ b/src/lua/scripts/luascript.cpp @@ -11,6 +11,7 @@ #include "lua/scripts/luascript.hpp" #include "lua/scripts/lua_environment.hpp" +#include "lib/metrics/metrics.hpp" ScriptEnvironment::DBResultMap ScriptEnvironment::tempResults; uint32_t ScriptEnvironment::lastResultId = 0; @@ -236,7 +237,35 @@ bool LuaScriptInterface::closeState() { return true; } +std::string LuaScriptInterface::getMetricsScope() { + metrics::method_latency measure(__METHOD_NAME__); + int32_t scriptId; + int32_t callbackId; + bool timerEvent; + LuaScriptInterface* scriptInterface; + getScriptEnv()->getEventInfo(scriptId, scriptInterface, callbackId, timerEvent); + + std::string name; + if (scriptId == EVENT_ID_LOADING) { + name = "loading"; + } else if (scriptId == EVENT_ID_USER) { + name = "user"; + } else { + name = scriptInterface->getFileById(scriptId); + if (name.empty()) { + return "unknown"; + } + auto pos = name.find("data"); + if (pos != std::string::npos) { + name = name.substr(pos); + } + } + + return fmt::format("{}:{}", name, timerEvent ? "timer" : ""); +} + bool LuaScriptInterface::callFunction(int params) { + metrics::lua_latency measure(getMetricsScope()); bool result = false; int size = lua_gettop(luaState); if (protectedCall(luaState, params, 1) != 0) { @@ -255,6 +284,7 @@ bool LuaScriptInterface::callFunction(int params) { } void LuaScriptInterface::callVoidFunction(int params) { + metrics::lua_latency measure(getMetricsScope()); int size = lua_gettop(luaState); if (protectedCall(luaState, params, 0) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(luaState)); diff --git a/src/lua/scripts/luascript.hpp b/src/lua/scripts/luascript.hpp index 8f0b3b36c..b7845c845 100644 --- a/src/lua/scripts/luascript.hpp +++ b/src/lua/scripts/luascript.hpp @@ -73,6 +73,8 @@ class LuaScriptInterface : public LuaFunctionsLoader { std::map cacheFiles; private: + std::string getMetricsScope(); + std::string lastLuaError; std::string interfaceName; std::string loadingFile; diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index c4bf34c95..fb2430e05 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -15,6 +15,7 @@ #include "game/game.hpp" #include "items/bed.hpp" #include "game/scheduling/save_manager.hpp" +#include "lib/metrics/metrics.hpp" House::House(uint32_t houseId) : id(houseId) { } @@ -788,6 +789,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const { if (player->getBankBalance() >= rent) { g_game().removeMoney(player, rent, 0, true); + g_metrics().addCounter("balance_decrease", rent, { { "player", player->getName() }, { "context", "house_rent" } }); time_t paidUntil = currentTime; switch (rentPeriod) { diff --git a/src/pch.hpp b/src/pch.hpp index 04f692a0b..ea0233ee1 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -41,6 +41,7 @@ #include #include #include +#include // -------------------- // System Includes diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index 8508052c3..f0046f0aa 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -30,98 +30,93 @@ void ConnectionManager::closeAll() { try { std::error_code error; connection->socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); + if (error) { + g_logger().error("[ConnectionManager::closeAll] - Failed to close connection, system error code {}", error.message()); + } } catch (const std::system_error &systemError) { - g_logger().error("[ConnectionManager::closeAll] - Failed to close connection, system error code {}", systemError.what()); + g_logger().error("[ConnectionManager::closeAll] - Exception caught: {}", systemError.what()); } }); connections.clear(); } -// Connection -// Constructor Connection::Connection(asio::io_service &initIoService, ConstServicePort_ptr initservicePort) : readTimer(initIoService), writeTimer(initIoService), service_port(std::move(initservicePort)), - socket(initIoService) { - timeConnected = time(nullptr); + socket(initIoService), + timeConnected(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())) { } -// Constructor end void Connection::close(bool force) { - // any thread ConnectionManager::getInstance().releaseConnection(shared_from_this()); - std::scoped_lock lockClass(connectionLock); + std::scoped_lock lock(connectionLock); ip = 0; + if (connectionState == CONNECTION_STATE_CLOSED) { return; } connectionState = CONNECTION_STATE_CLOSED; if (protocol) { - g_dispatcher().addEvent(std::bind_front(&Protocol::release, protocol), "Protocol::release", 1000); + g_dispatcher().addEvent(std::bind_front(&Protocol::release, protocol), "Protocol::release", std::chrono::milliseconds(1000).count()); } if (messageQueue.empty() || force) { closeSocket(); - } else { - // will be closed by the destructor or onWriteOperation } } void Connection::closeSocket() { - if (socket.is_open()) { - try { - readTimer.cancel(); - writeTimer.cancel(); - std::error_code error; - socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); - socket.close(error); - } catch (const std::system_error &e) { - g_logger().error("[Connection::closeSocket] - error: {}", e.what()); - } + if (!socket.is_open()) { + return; + } + + readTimer.cancel(); + writeTimer.cancel(); + socket.cancel(); + + std::error_code error; + socket.shutdown(asio::ip::tcp::socket::shutdown_both, error); + if (error) { + g_logger().error("[Connection::closeSocket] - Failed to shutdown socket: {}", error.message()); + } + + socket.close(error); + if (error) { + g_logger().error("[Connection::closeSocket] - Failed to close socket: {}", error.message()); } } void Connection::accept(Protocol_ptr protocolPtr) { - this->connectionState = CONNECTION_STATE_IDENTIFYING; - this->protocol = protocolPtr; - g_dispatcher().addEvent(std::bind_front(&Protocol::onConnect, protocolPtr), "Protocol::onConnect", 1000); + connectionState = CONNECTION_STATE_IDENTIFYING; + protocol = std::move(protocolPtr); + g_dispatcher().addEvent(std::bind_front(&Protocol::onConnect, protocol), "Protocol::onConnect", std::chrono::milliseconds(1000).count()); - // Call second accept for not duplicate code - accept(false); + acceptInternal(false); } -void Connection::accept(bool toggleParseHeader /* = true */) { - try { - readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); - readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); +void Connection::acceptInternal(bool toggleParseHeader) { + readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); - // If toggleParseHeader is true, execute the parseHeader, if not, execute parseProxyIdentification - if (toggleParseHeader) { - // Read size of the first packet - asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); - } else { - // Read header bytes to identify if it is proxy identification - asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseProxyIdentification, shared_from_this(), std::placeholders::_1)); - } - } catch (const std::system_error &e) { - g_logger().error("[Connection::accept] - error: {}", e.what()); - close(FORCE_CLOSE); - } + auto readCallback = toggleParseHeader ? std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1) + : std::bind(&Connection::parseProxyIdentification, shared_from_this(), std::placeholders::_1); + asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), readCallback); } void Connection::parseProxyIdentification(const std::error_code &error) { - std::scoped_lock lockClass(connectionLock); + std::scoped_lock lock(connectionLock); readTimer.cancel(); - if (error) { + if (error || connectionState == CONNECTION_STATE_CLOSED) { + if (error) { + g_logger().error("[Connection::parseProxyIdentification] - Read error: {}", error.message()); + } close(FORCE_CLOSE); return; - } else if (connectionState == CONNECTION_STATE_CLOSED) { - return; } uint8_t* msgBuffer = msg.getBuffer(); @@ -163,23 +158,24 @@ void Connection::parseProxyIdentification(const std::error_code &error) { } } - accept(true); + acceptInternal(true); } void Connection::parseHeader(const std::error_code &error) { - std::scoped_lock lockClass(connectionLock); + std::scoped_lock lock(connectionLock); readTimer.cancel(); - if (error) { + if (error || connectionState == CONNECTION_STATE_CLOSED) { + if (error != asio::error::operation_aborted) { + g_logger().error("[Connection::parseHeader] - Read error: {}", error.message()); + } close(FORCE_CLOSE); return; - } else if (connectionState == CONNECTION_STATE_CLOSED) { - return; } uint32_t timePassed = std::max(1, (time(nullptr) - timeConnected) + 1); if ((++packetsSent / timePassed) > static_cast(g_configManager().getNumber(MAX_PACKETS_PER_SECOND, __FUNCTION__))) { - g_logger().warn("{} disconnected for exceeding packet per second limit.", convertIPToString(getIP())); + g_logger().warn("[Connection::parseHeader] - {} disconnected for exceeding packet per second limit.", convertIPToString(getIP())); close(); return; } @@ -209,14 +205,15 @@ void Connection::parseHeader(const std::error_code &error) { } void Connection::parsePacket(const std::error_code &error) { - std::scoped_lock lockClass(connectionLock); + std::scoped_lock lock(connectionLock); readTimer.cancel(); - if (error) { + if (error || connectionState == CONNECTION_STATE_CLOSED) { + if (error) { + g_logger().error("[Connection::parsePacket] - Read error: {}", error.message()); + } close(FORCE_CLOSE); return; - } else if (connectionState == CONNECTION_STATE_CLOSED) { - return; } bool skipReadingNextPacket = false; @@ -275,19 +272,14 @@ void Connection::parsePacket(const std::error_code &error) { } void Connection::resumeWork() { - std::scoped_lock lockClass(connectionLock); + readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); - try { - // Wait to the next packet - asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); - } catch (const std::system_error &e) { - g_logger().error("[Connection::resumeWork] - error: {}", e.what()); - close(FORCE_CLOSE); - } + asio::async_read(socket, asio::buffer(msg.getBuffer(), HEADER_LENGTH), std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); } void Connection::send(const OutputMessage_ptr &outputMessage) { - std::scoped_lock lockClass(connectionLock); + std::scoped_lock lock(connectionLock); if (connectionState == CONNECTION_STATE_CLOSED) { return; } @@ -295,71 +287,74 @@ void Connection::send(const OutputMessage_ptr &outputMessage) { bool noPendingWrite = messageQueue.empty(); messageQueue.emplace_back(outputMessage); if (noPendingWrite) { - // Make asio thread handle xtea encryption instead of dispatcher - try { + if (socket.is_open()) { asio::post(socket.get_executor(), std::bind(&Connection::internalWorker, shared_from_this())); - } catch (const std::system_error &e) { - g_logger().error("[Connection::send] - error: {}", e.what()); - messageQueue.clear(); + } else { + g_logger().error("[Connection::send] - Socket is not open for writing."); close(FORCE_CLOSE); } } } void Connection::internalWorker() { - std::unique_lock lockClass(connectionLock); - if (!messageQueue.empty()) { - const OutputMessage_ptr &outputMessage = messageQueue.front(); - lockClass.unlock(); - protocol->onSendMessage(outputMessage); - lockClass.lock(); - internalSend(outputMessage); - } else if (connectionState == CONNECTION_STATE_CLOSED) { - closeSocket(); + std::unique_lock lock(connectionLock); + if (messageQueue.empty()) { + if (connectionState == CONNECTION_STATE_CLOSED) { + closeSocket(); + } + return; } + + const auto &outputMessage = messageQueue.front(); + lock.unlock(); + protocol->onSendMessage(outputMessage); + lock.lock(); + + internalSend(outputMessage); } uint32_t Connection::getIP() { - if (ip != 1) { - return ip; + std::scoped_lock lock(connectionLock); + + if (ip == 1) { + std::error_code error; + asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error); + if (error) { + g_logger().error("[Connection::getIP] - Failed to get remote endpoint: {}", error.message()); + ip = 0; + } else { + ip = htonl(endpoint.address().to_v4().to_uint()); + } } - std::scoped_lock lockClass(connectionLock); - - // IP-address is expressed in network byte order - std::error_code error; - const asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error); - ip = error ? 0 : htonl(endpoint.address().to_v4().to_uint()); return ip; } void Connection::internalSend(const OutputMessage_ptr &outputMessage) { - try { - writeTimer.expires_from_now(std::chrono::seconds(CONNECTION_WRITE_TIMEOUT)); - writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); + writeTimer.expires_from_now(std::chrono::seconds(CONNECTION_WRITE_TIMEOUT)); + writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); - asio::async_write(socket, asio::buffer(outputMessage->getOutputBuffer(), outputMessage->getLength()), std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); - } catch (const std::system_error &e) { - g_logger().error("[Connection::internalSend] - error: {}", e.what()); - } + asio::async_write(socket, asio::buffer(outputMessage->getOutputBuffer(), outputMessage->getLength()), std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); } void Connection::onWriteOperation(const std::error_code &error) { - std::unique_lock lockClass(connectionLock); + std::unique_lock lock(connectionLock); writeTimer.cancel(); - messageQueue.pop_front(); if (error) { + g_logger().error("[Connection::onWriteOperation] - Write error: {}", error.message()); messageQueue.clear(); close(FORCE_CLOSE); return; } + messageQueue.pop_front(); + if (!messageQueue.empty()) { - const OutputMessage_ptr &outputMessage = messageQueue.front(); - lockClass.unlock(); + const auto &outputMessage = messageQueue.front(); + lock.unlock(); protocol->onSendMessage(outputMessage); - lockClass.lock(); + lock.lock(); internalSend(outputMessage); } else if (connectionState == CONNECTION_STATE_CLOSED) { closeSocket(); @@ -367,12 +362,12 @@ void Connection::onWriteOperation(const std::error_code &error) { } void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const std::error_code &error) { - if (error == asio::error::operation_aborted) { - // The timer has been manually cancelled - return; - } - - if (auto connection = connectionWeak.lock()) { - connection->close(FORCE_CLOSE); + if (error) { + if (error != asio::error::operation_aborted) { + g_logger().warn("[Connection::handleTimeout] - Timeout or error: {}", error.message()); + if (auto connection = connectionWeak.lock()) { + connection->close(FORCE_CLOSE); + } + } } } diff --git a/src/server/network/connection/connection.hpp b/src/server/network/connection/connection.hpp index 3a80bf6ca..d7d0f4b00 100644 --- a/src/server/network/connection/connection.hpp +++ b/src/server/network/connection/connection.hpp @@ -61,9 +61,10 @@ class Connection : public std::enable_shared_from_this { void close(bool force = false); // Used by protocols that require server to send first void accept(Protocol_ptr protocolPtr); - void accept(bool toggleParseHeader = true); + void acceptInternal(bool toggleParseHeader = true); void resumeWork(); + void send(const OutputMessage_ptr &outputMessage); uint32_t getIP(); diff --git a/src/server/network/message/networkmessage.cpp b/src/server/network/message/networkmessage.cpp index 457e3b32b..9c10b4907 100644 --- a/src/server/network/message/networkmessage.cpp +++ b/src/server/network/message/networkmessage.cpp @@ -40,17 +40,17 @@ Position NetworkMessage::getPosition() { return pos; } -void NetworkMessage::addString(const std::string &value) { +void NetworkMessage::addString(const std::string &value, const std::string &function) { size_t stringLen = value.length(); if (value.empty()) { - g_logger().debug("[NetworkMessage::addString] - Value string is empty"); + g_logger().debug("[NetworkMessage::addString] - Value string is empty, function '{}'", function); } if (!canAdd(stringLen + 2)) { - g_logger().error("[NetworkMessage::addString] - NetworkMessage size is wrong: {}", stringLen); + g_logger().error("[NetworkMessage::addString] - NetworkMessage size is wrong: {}, function '{}'", stringLen, function); return; } if (stringLen > NETWORKMESSAGE_MAXSIZE) { - g_logger().error("[NetworkMessage::addString] - Exceded NetworkMessage max size: {}, actually size: {}", NETWORKMESSAGE_MAXSIZE, stringLen); + g_logger().error("[NetworkMessage::addString] - Exceded NetworkMessage max size: {}, actually size: {}, function '{}'", NETWORKMESSAGE_MAXSIZE, stringLen, function); return; } diff --git a/src/server/network/message/networkmessage.hpp b/src/server/network/message/networkmessage.hpp index 3635475a8..3051e87bb 100644 --- a/src/server/network/message/networkmessage.hpp +++ b/src/server/network/message/networkmessage.hpp @@ -90,7 +90,7 @@ class NetworkMessage { void addBytes(const char* bytes, size_t size); void addPaddingBytes(size_t n); - void addString(const std::string &value); + void addString(const std::string &value, const std::string &function); void addDouble(double value, uint8_t precision = 2); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 958d19f2a..357c85521 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -228,7 +228,7 @@ namespace { g_logger().debug("Sendding category number '{}', category name '{}'", static_cast(value), magic_enum::enum_name(value).data()); msg.addByte(static_cast(value)); - msg.addString(toStartCaseWithSpace(magic_enum::enum_name(value).data())); + msg.addString(toStartCaseWithSpace(magic_enum::enum_name(value).data()), "void sendContainerCategory - toStartCaseWithSpace(magic_enum::enum_name(value).data())"); } } } // namespace @@ -515,9 +515,11 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS return; } - if (g_configManager().getBoolean(ONE_PLAYER_ON_ACCOUNT, __FUNCTION__) && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && g_game().getPlayerByAccount(player->getAccountId())) { + auto onlineCount = g_game().getPlayersByAccount(player->getAccount()).size(); + auto maxOnline = g_configManager().getNumber(MAX_PLAYERS_PER_ACCOUNT, __FUNCTION__); + if (player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && onlineCount >= maxOnline) { g_game().removePlayerUniqueLogin(player); - disconnectClient("You may only login with one character\nof your account at the same time."); + disconnectClient(fmt::format("You may only login with {} character{}\nof your account at the same time.", maxOnline, maxOnline > 1 ? "s" : "")); return; } @@ -553,7 +555,7 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS auto output = OutputMessagePool::getOutputMessage(); output->addByte(0x16); - output->addString(ss.str()); + output->addString(ss.str(), "ProtocolGame::login - ss.str()"); output->addByte(retryTime); send(output); disconnect(); @@ -570,6 +572,24 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS player->setOperatingSystem(operatingSystem); + const auto tile = g_game().map.getOrCreateTile(player->getLoginPosition()); + // moving from a pz tile to a non-pz tile + if (maxOnline > 1 && player->getAccountType() < account::ACCOUNT_TYPE_GAMEMASTER && !tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + auto maxOutsizePZ = g_configManager().getNumber(MAX_PLAYERS_OUTSIDE_PZ_PER_ACCOUNT, __FUNCTION__); + auto accountPlayers = g_game().getPlayersByAccount(player->getAccount()); + int countOutsizePZ = 0; + for (const auto &accountPlayer : accountPlayers) { + if (accountPlayer != player && accountPlayer->getTile() && !accountPlayer->getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { + ++countOutsizePZ; + } + } + if (countOutsizePZ >= maxOutsizePZ) { + g_game().removePlayerUniqueLogin(player); + disconnectClient(fmt::format("You can only have {} character{} from your account outside of a protection zone.", maxOutsizePZ == 1 ? "one" : std::to_string(maxOutsizePZ), maxOutsizePZ > 1 ? "s" : "")); + return; + } + } + if (!g_game().placeCreature(player, player->getLoginPosition()) && !g_game().placeCreature(player, player->getTemplePosition(), false, true)) { g_game().removePlayerUniqueLogin(player); disconnectClient("Temple position is wrong. Please, contact the administrator."); @@ -802,7 +822,7 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) { auto output = OutputMessagePool::getOutputMessage(); output->addByte(0x14); - output->addString(ss.str()); + output->addString(ss.str(), "ProtocolGame::onRecvFirstMessage - ss.str()"); send(output); g_dispatcher().scheduleEvent(1000, std::bind(&ProtocolGame::disconnect, getThis()), "ProtocolGame::disconnect"); return; @@ -842,7 +862,7 @@ void ProtocolGame::onConnect() { void ProtocolGame::disconnectClient(const std::string &message) const { auto output = OutputMessagePool::getOutputMessage(); output->addByte(0x14); - output->addString(message); + output->addString(message, "ProtocolGame::disconnectClient - message"); send(output); disconnect(); } @@ -1994,10 +2014,10 @@ void ProtocolGame::sendItemInspection(uint16_t itemId, uint8_t itemCount, std::s const ItemType &it = Item::items[itemId]; if (item) { - msg.addString(item->getName()); + msg.addString(item->getName(), "ProtocolGame::sendItemInspection - item->getName()"); AddItem(msg, item); } else { - msg.addString(it.name); + msg.addString(it.name, "ProtocolGame::sendItemInspection - it.name"); AddItem(msg, it.id, itemCount, 0); } msg.addByte(0); @@ -2005,8 +2025,8 @@ void ProtocolGame::sendItemInspection(uint16_t itemId, uint8_t itemCount, std::s auto descriptions = Item::getDescriptions(it, item); msg.addByte(descriptions.size()); for (const auto &description : descriptions) { - msg.addString(description.first); - msg.addString(description.second); + msg.addString(description.first, "ProtocolGame::sendItemInspection - description.first"); + msg.addString(description.second, "ProtocolGame::sendItemInspection - description.second"); } writeToOutputBuffer(msg); } @@ -2088,8 +2108,8 @@ void ProtocolGame::sendHighscores(const std::vector &charact msg.addByte(0x00); // No data available msg.addByte(1); // Worlds - msg.addString(g_configManager().getString(SERVER_NAME, __FUNCTION__)); // First World - msg.addString(g_configManager().getString(SERVER_NAME, __FUNCTION__)); // Selected World + msg.addString(g_configManager().getString(SERVER_NAME, __FUNCTION__), "ProtocolGame::sendHighscores - g_configManager().getString(SERVER_NAME)"); // First World + msg.addString(g_configManager().getString(SERVER_NAME, __FUNCTION__), "ProtocolGame::sendHighscores - g_configManager().getString(SERVER_NAME)"); // Selected World msg.addByte(0); // Game World Category: 0xFF(-1) - Selected World msg.addByte(0); // BattlEye World Type @@ -2099,7 +2119,7 @@ void ProtocolGame::sendHighscores(const std::vector &charact msg.skipBytes(1); // Vocation Count msg.add(0xFFFFFFFF); // All Vocations - hardcoded - msg.addString("(all)"); // All Vocations - hardcoded + msg.addString("(all)", "ProtocolGame::sendHighscores - (all)"); // All Vocations - hardcoded uint32_t selectedVocation = 0xFFFFFFFF; const auto vocationsMap = g_vocations().getVocations(); @@ -2107,7 +2127,7 @@ void ProtocolGame::sendHighscores(const std::vector &charact const Vocation &vocation = it.second; if (vocation.getFromVocation() == static_cast(vocation.getId())) { msg.add(vocation.getFromVocation()); // Vocation Id - msg.addString(vocation.getVocName()); // Vocation Name + msg.addString(vocation.getVocName(), "ProtocolGame::sendHighscores - vocation.getVocName()"); // Vocation Name ++vocations; if (vocation.getFromVocation() == vocationId) { selectedVocation = vocationId; @@ -2132,7 +2152,7 @@ void ProtocolGame::sendHighscores(const std::vector &charact msg.addByte(sizeof(highscoreCategories) / sizeof(HighscoreCategory)); // Category Count for (HighscoreCategory &category : highscoreCategories) { msg.addByte(category.id); // Category Id - msg.addString(category.name); // Category Name + msg.addString(category.name, "ProtocolGame::sendHighscores - category.name"); // Category Name if (category.id == categoryId) { selectedCategory = categoryId; } @@ -2145,10 +2165,10 @@ void ProtocolGame::sendHighscores(const std::vector &charact msg.addByte(characters.size()); // Character Count for (const HighscoreCharacter &character : characters) { msg.add(character.rank); // Rank - msg.addString(character.name); // Character Name - msg.addString(""); // Probably Character Title(not visible in window) + msg.addString(character.name, "ProtocolGame::sendHighscores - character.name"); // Character Name + msg.addString("", "ProtocolGame::sendHighscores - empty"); // Probably Character Title(not visible in window) msg.addByte(character.vocation); // Vocation Id - msg.addString(g_configManager().getString(SERVER_NAME, __FUNCTION__)); // World + msg.addString(g_configManager().getString(SERVER_NAME, __FUNCTION__), "ProtocolGame::sendHighscores - g_configManager().getString(SERVER_NAME)"); // World msg.add(character.level); // Level msg.addByte((player->getGUID() == character.id)); // Player Indicator Boolean msg.add(character.points); // Points @@ -2212,7 +2232,7 @@ void ProtocolGame::parseBestiarysendRaces() { BestClass = mtype->info.bestiaryClass; } } - msg.addString(BestClass); + msg.addString(BestClass, "ProtocolGame::parseBestiarysendRaces - BestClass"); msg.add(count); uint16_t unlockedCount = g_iobestiary().getBestiaryRaceUnlocked(player, static_cast(i)); msg.add(unlockedCount); @@ -2264,7 +2284,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) { NetworkMessage newmsg; newmsg.addByte(0xd7); newmsg.add(raceId); - newmsg.addString(Class); + newmsg.addString(Class, "ProtocolGame::parseBestiarysendMonsterData - Class"); newmsg.addByte(currentLevel); newmsg.add(killCounter); @@ -2305,7 +2325,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) { newmsg.addByte(difficult); newmsg.addByte(0); // 1 if special event - 0 if regular loot (?) if (shouldAddItem == true) { - newmsg.addString(loot.name); + newmsg.addString(loot.name, "ProtocolGame::parseBestiarysendMonsterData - loot.name"); newmsg.addByte(loot.countmax > 0 ? 0x1 : 0x0); } } @@ -2338,7 +2358,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) { } newmsg.add(1); - newmsg.addString(mtype->info.bestiaryLocations); + newmsg.addString(mtype->info.bestiaryLocations, "ProtocolGame::parseBestiarysendMonsterData - mtype->info.bestiaryLocations"); } if (currentLevel > 3) { @@ -2416,7 +2436,7 @@ void ProtocolGame::sendTeamFinderList() { uint8_t status = 0; uint16_t membersSize = 0; msg.add(leader->getGUID()); - msg.addString(leader->getName()); + msg.addString(leader->getName(), "ProtocolGame::sendTeamFinderList - leader->getName()"); msg.add(teamAssemble->minLevel); msg.add(teamAssemble->maxLevel); msg.addByte(teamAssemble->vocationIDs); @@ -2522,7 +2542,7 @@ void ProtocolGame::sendLeaderTeamFinder(bool reset) { } msg.add(leader->getGUID()); - msg.addString(leader->getName()); + msg.addString(leader->getName(), "ProtocolGame::sendLeaderTeamFinder - leader->getName()"); msg.add(leader->getLevel()); msg.addByte(leader->getVocation()->getClientId()); msg.addByte(3); @@ -2533,7 +2553,7 @@ void ProtocolGame::sendLeaderTeamFinder(bool reset) { continue; } msg.add(member->getGUID()); - msg.addString(member->getName()); + msg.addString(member->getName(), "ProtocolGame::sendLeaderTeamFinder - member->getName()"); msg.add(member->getLevel()); msg.addByte(member->getVocation()->getClientId()); msg.addByte(memberPair.second); @@ -2799,8 +2819,8 @@ void ProtocolGame::BestiarysendCharms() { msg.addByte(charmList.size()); for (const auto &c_type : charmList) { msg.addByte(c_type->id); - msg.addString(c_type->name); - msg.addString(c_type->description); + msg.addString(c_type->name, "ProtocolGame::BestiarysendCharms - c_type->name"); + msg.addString(c_type->description, "ProtocolGame::BestiarysendCharms - c_type->description"); msg.addByte(0); // Unknown msg.add(c_type->points); if (g_iobestiary().hasCharmUnlockedRuneBit(c_type, player->getUnlockedRunesBit())) { @@ -2872,7 +2892,7 @@ void ProtocolGame::parseBestiarysendCreatures(NetworkMessage &msg) { } NetworkMessage newmsg; newmsg.addByte(0xd6); - newmsg.addString(text); + newmsg.addString(text, "ProtocolGame::parseBestiarysendCreatures - text"); newmsg.add(race.size()); std::map creaturesKilled = g_iobestiary().getBestiaryKillCountByMonsterIDs(player, race); @@ -3092,7 +3112,7 @@ void ProtocolGame::parseSeekInContainer(NetworkMessage &msg) { void ProtocolGame::sendOpenPrivateChannel(const std::string &receiver) { NetworkMessage msg; msg.addByte(0xAD); - msg.addString(receiver); + msg.addString(receiver, "ProtocolGame::sendOpenPrivateChannel - receiver"); writeToOutputBuffer(msg); } @@ -3112,7 +3132,7 @@ void ProtocolGame::sendChannelEvent(uint16_t channelId, const std::string &playe NetworkMessage msg; msg.addByte(0xF3); msg.add(channelId); - msg.addString(playerName); + msg.addString(playerName, "ProtocolGame::sendChannelEvent - playerName"); msg.addByte(channelEvent); writeToOutputBuffer(msg); } @@ -3306,7 +3326,7 @@ void ProtocolGame::sendAddMarker(const Position &pos, uint8_t markType, const st msg.addPosition(pos); msg.addByte(markType); - msg.addString(desc); + msg.addString(desc, "ProtocolGame::sendAddMarker - desc"); writeToOutputBuffer(msg); } @@ -3331,13 +3351,13 @@ void ProtocolGame::sendCyclopediaCharacterBaseInformation() { msg.addByte(0xDA); msg.addByte(CYCLOPEDIA_CHARACTERINFO_BASEINFORMATION); msg.addByte(0x00); - msg.addString(player->getName()); - msg.addString(player->getVocation()->getVocName()); + msg.addString(player->getName(), "ProtocolGame::sendCyclopediaCharacterBaseInformation - player->getName()"); + msg.addString(player->getVocation()->getVocName(), "ProtocolGame::sendCyclopediaCharacterBaseInformation - player->getVocation()->getVocName()"); msg.add(player->getLevel()); AddOutfit(msg, player->getDefaultOutfit(), false); msg.addByte(0x00); // hide stamina - msg.addString(""); // character title + msg.addString("", "ProtocolGame::sendCyclopediaCharacterBaseInformation - empty"); // character title writeToOutputBuffer(msg); } @@ -3577,7 +3597,7 @@ void ProtocolGame::sendCyclopediaCharacterRecentDeaths(uint16_t page, uint16_t p msg.add(entries.size()); for (const RecentDeathEntry &entry : entries) { msg.add(entry.timestamp); - msg.addString(entry.cause); + msg.addString(entry.cause, "ProtocolGame::sendCyclopediaCharacterRecentDeaths - entry.cause"); } writeToOutputBuffer(msg); } @@ -3596,7 +3616,7 @@ void ProtocolGame::sendCyclopediaCharacterRecentPvPKills(uint16_t page, uint16_t msg.add(entries.size()); for (const RecentPvPKillEntry &entry : entries) { msg.add(entry.timestamp); - msg.addString(entry.description); + msg.addString(entry.description, "ProtocolGame::sendCyclopediaCharacterRecentPvPKills - entry.description"); msg.addByte(entry.status); } writeToOutputBuffer(msg); @@ -3661,7 +3681,7 @@ void ProtocolGame::sendCyclopediaCharacterOutfitsMounts() { ++outfitSize; msg.add(outfit->lookType); - msg.addString(outfit->name); + msg.addString(outfit->name, "ProtocolGame::sendCyclopediaCharacterOutfitsMounts - outfit->name"); msg.addByte(addons); if (from == "store") { msg.addByte(CYCLOPEDIA_CHARACTERINFO_OUTFITTYPE_STORE); @@ -3692,7 +3712,7 @@ void ProtocolGame::sendCyclopediaCharacterOutfitsMounts() { ++mountSize; msg.add(mount->clientId); - msg.addString(mount->name); + msg.addString(mount->name, "ProtocolGame::sendCyclopediaCharacterOutfitsMounts - mount->name"); if (type == "store") { msg.addByte(CYCLOPEDIA_CHARACTERINFO_OUTFITTYPE_STORE); } else if (type == "quest") { @@ -3721,7 +3741,7 @@ void ProtocolGame::sendCyclopediaCharacterOutfitsMounts() { } ++familiarsSize; msg.add(familiar.lookType); - msg.addString(familiar.name); + msg.addString(familiar.name, "ProtocolGame::sendCyclopediaCharacterOutfitsMounts - familiar.name"); if (type == "quest") { msg.addByte(CYCLOPEDIA_CHARACTERINFO_OUTFITTYPE_QUEST); } else { @@ -3782,19 +3802,19 @@ void ProtocolGame::sendCyclopediaCharacterInspection() { ++inventoryItems; msg.addByte(slot); - msg.addString(inventoryItem->getName()); + msg.addString(inventoryItem->getName(), "ProtocolGame::sendCyclopediaCharacterInspection - inventoryItem->getName()"); AddItem(msg, inventoryItem); msg.addByte(0); auto descriptions = Item::getDescriptions(Item::items[inventoryItem->getID()], inventoryItem); msg.addByte(descriptions.size()); for (const auto &description : descriptions) { - msg.addString(description.first); - msg.addString(description.second); + msg.addString(description.first, "ProtocolGame::sendCyclopediaCharacterInspection - description.first"); + msg.addString(description.second, "ProtocolGame::sendCyclopediaCharacterInspection - description.second"); } } } - msg.addString(player->getName()); + msg.addString(player->getName(), "ProtocolGame::sendCyclopediaCharacterInspection - player->getName()"); AddOutfit(msg, player->getDefaultOutfit(), false); // Player overall summary @@ -3804,28 +3824,28 @@ void ProtocolGame::sendCyclopediaCharacterInspection() { // Level description playerDescriptionSize++; - msg.addString("Level"); + msg.addString("Level", "ProtocolGame::sendCyclopediaCharacterInspection - Level"); // Vocation description playerDescriptionSize++; - msg.addString(std::to_string(player->getLevel())); - msg.addString("Vocation"); - msg.addString(player->getVocation()->getVocName()); + msg.addString(std::to_string(player->getLevel()), "ProtocolGame::sendCyclopediaCharacterInspection - std::to_string(player->getLevel())"); + msg.addString("Vocation", "ProtocolGame::sendCyclopediaCharacterInspection - Vocation"); + msg.addString(player->getVocation()->getVocName(), "ProtocolGame::sendCyclopediaCharacterInspection - player->getVocation()->getVocName()"); // Loyalty title if (player->getLoyaltyTitle().length() != 0) { playerDescriptionSize++; - msg.addString("Loyalty Title"); - msg.addString(player->getLoyaltyTitle()); + msg.addString("Loyalty Title", "ProtocolGame::sendCyclopediaCharacterInspection - Loyalty Title"); + msg.addString(player->getLoyaltyTitle(), "ProtocolGame::sendCyclopediaCharacterInspection - player->getLoyaltyTitle()"); } // Outfit description playerDescriptionSize++; - msg.addString("Outfit"); + msg.addString("Outfit", "ProtocolGame::sendCyclopediaCharacterInspection - Outfit"); if (const auto outfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), player->getDefaultOutfit().lookType)) { - msg.addString(outfit->name); + msg.addString(outfit->name, "ProtocolGame::sendCyclopediaCharacterInspection - outfit->name"); } else { - msg.addString("unknown"); + msg.addString("unknown", "ProtocolGame::sendCyclopediaCharacterInspection - unknown"); } msg.setBufferPosition(startInventory); @@ -3855,7 +3875,7 @@ void ProtocolGame::sendCyclopediaCharacterBadges() { // IsPremium (GOD has always 'Premium') msg.addByte(player->isPremium() ? 0x01 : 0x00); // Character loyalty title - msg.addString(player->getLoyaltyTitle()); + msg.addString(player->getLoyaltyTitle(), "ProtocolGame::sendCyclopediaCharacterBadges - player->getLoyaltyTitle()"); // Enable badges msg.addByte(0x00); // Todo badges loop @@ -3917,7 +3937,7 @@ void ProtocolGame::sendBasicData() { std::vector> validSpells; for (uint16_t sid : spellsList) { auto spell = g_spells().getInstantSpellById(sid); - if (spell && spell->getId() > 0) { + if (spell && spell->getSpellId() > 0) { validSpells.push_back(spell); } } @@ -3932,7 +3952,7 @@ void ProtocolGame::sendBasicData() { // Only send valid spells to old client if (oldProtocol) { - msg.addByte(spell->getId()); + msg.addByte(spell->getSpellId()); continue; } @@ -3944,10 +3964,10 @@ void ProtocolGame::sendBasicData() { if (static_cast(grade) == 0) { msg.add(0); } else { - msg.add(spell->getId()); + msg.add(spell->getSpellId()); } } else { - msg.add(spell->getId()); + msg.add(spell->getSpellId()); } } @@ -4101,7 +4121,7 @@ void ProtocolGame::sendTextMessage(const TextMessage &message) { default: break; } - msg.addString(message.text); + msg.addString(message.text, "ProtocolGame::sendTextMessage - message.text"); writeToOutputBuffer(msg); } @@ -4116,9 +4136,9 @@ void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::strin NetworkMessage msg; msg.addByte(0xB2); msg.add(channelId); - msg.addString(channelName); + msg.addString(channelName, "ProtocolGame::sendCreatePrivateChannel - channelName"); msg.add(0x01); - msg.addString(player->getName()); + msg.addString(player->getName(), "ProtocolGame::sendCreatePrivateChannel - player->getName()"); msg.add(0x00); writeToOutputBuffer(msg); } @@ -4131,7 +4151,7 @@ void ProtocolGame::sendChannelsDialog() { msg.addByte(list.size()); for (const auto &channel : list) { msg.add(channel->getId()); - msg.addString(channel->getName()); + msg.addString(channel->getName(), "ProtocolGame::sendChannelsDialog - channel->getName()"); } writeToOutputBuffer(msg); @@ -4142,12 +4162,12 @@ void ProtocolGame::sendChannel(uint16_t channelId, const std::string &channelNam msg.addByte(0xAC); msg.add(channelId); - msg.addString(channelName); + msg.addString(channelName, "ProtocolGame::sendChannel - channelName"); if (channelUsers) { msg.add(channelUsers->size()); for (const auto &it : *channelUsers) { - msg.addString(it.second->getName()); + msg.addString(it.second->getName(), "ProtocolGame::sendChannel - it.second->getName()"); } } else { msg.add(0x00); @@ -4156,7 +4176,7 @@ void ProtocolGame::sendChannel(uint16_t channelId, const std::string &channelNam if (invitedUsers) { msg.add(invitedUsers->size()); for (const auto &it : *invitedUsers) { - msg.addString(it.second->getName()); + msg.addString(it.second->getName(), "ProtocolGame::sendChannel - it.second->getName()"); } } else { msg.add(0x00); @@ -4168,11 +4188,11 @@ void ProtocolGame::sendChannelMessage(const std::string &author, const std::stri NetworkMessage msg; msg.addByte(0xAA); msg.add(0x00); - msg.addString(author); + msg.addString(author, "ProtocolGame::sendChannelMessage - author"); msg.add(0x00); msg.addByte(type); msg.add(channel); - msg.addString(text); + msg.addString(text, "ProtocolGame::sendChannelMessage - text"); writeToOutputBuffer(msg); } @@ -4213,10 +4233,10 @@ void ProtocolGame::sendContainer(uint8_t cid, std::shared_ptr contain if (container->getID() == ITEM_BROWSEFIELD) { AddItem(msg, ITEM_BAG, 1, container->getTier()); - msg.addString("Browse Field"); + msg.addString("Browse Field", "ProtocolGame::sendContainer - Browse Field"); } else { AddItem(msg, container); - msg.addString(container->getName()); + msg.addString(container->getName(), "ProtocolGame::sendContainer - container->getName()"); } const auto itemsStoreInboxToSend = container->getStoreInboxFilteredItems(); @@ -4343,7 +4363,7 @@ void ProtocolGame::sendLootStats(std::shared_ptr item, uint8_t count) { NetworkMessage msg; msg.addByte(0xCF); AddItem(msg, lootedItem); - msg.addString(lootedItem->getName()); + msg.addString(lootedItem->getName(), "ProtocolGame::sendLootStats - lootedItem->getName()"); item->setIsLootTrackeable(false); writeToOutputBuffer(msg); @@ -4353,11 +4373,11 @@ void ProtocolGame::sendLootStats(std::shared_ptr item, uint8_t count) { void ProtocolGame::sendShop(std::shared_ptr npc) { NetworkMessage msg; msg.addByte(0x7A); - msg.addString(npc->getName()); + msg.addString(npc->getName(), "ProtocolGame::sendShop - npc->getName()"); if (!oldProtocol) { msg.add(npc->getCurrency()); - msg.addString(std::string()); // Currency name + msg.addString(std::string(), "ProtocolGame::sendShop - std::string()"); // Currency name } std::vector shoplist = npc->getShopItemVector(player->getGUID()); @@ -4622,7 +4642,7 @@ void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList & } else { msg.add(static_cast(offer.price)); } - msg.addString(offer.playerName); + msg.addString(offer.playerName, "ProtocolGame::sendMarketBrowseItem - offer.playerName"); } msg.add(sellOffers.size()); @@ -4635,7 +4655,7 @@ void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList & } else { msg.add(static_cast(offer.price)); } - msg.addString(offer.playerName); + msg.addString(offer.playerName, "ProtocolGame::sendMarketBrowseItem - offer.playerName"); } updateCoinBalance(); @@ -4664,7 +4684,7 @@ void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx &offer) { } else { msg.add(static_cast(offer.price)); } - msg.addString(offer.playerName); + msg.addString(offer.playerName, "ProtocolGame::sendMarketAcceptOffer - offer.playerName"); msg.add(0x00); } else { msg.add(0x00); @@ -4677,7 +4697,7 @@ void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx &offer) { } else { msg.add(static_cast(offer.price)); } - msg.addString(offer.playerName); + msg.addString(offer.playerName, "ProtocolGame::sendMarketAcceptOffer - offer.playerName"); } writeToOutputBuffer(msg); @@ -5098,7 +5118,7 @@ void ProtocolGame::sendForgeHistory(uint8_t page) { auto action = magic_enum::enum_integer(history.actionType); msg.add(static_cast(history.createdAt)); msg.addByte(action); - msg.addString(history.description); + msg.addString(history.description, "ProtocolGame::sendForgeHistory - history.description"); msg.addByte((history.bonus >= 1 && history.bonus < 8) ? 0x01 : 0x00); } } @@ -5128,7 +5148,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { } if (it.armor != 0) { - msg.addString(std::to_string(it.armor)); + msg.addString(std::to_string(it.armor), "ProtocolGame::sendMarketDetail - std::to_string(it.armor)"); } else { msg.add(0x00); } @@ -5156,21 +5176,21 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { } ss << static_cast(it.shootRange) << " fields"; } - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else if (!it.isRanged() && it.attack != 0) { if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { std::ostringstream ss; ss << it.attack << " physical +" << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType); - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else { - msg.addString(std::to_string(it.attack)); + msg.addString(std::to_string(it.attack), "ProtocolGame::sendMarketDetail - std::to_string(it.attack)"); } } else { msg.add(0x00); } if (it.isContainer()) { - msg.addString(std::to_string(it.maxItems)); + msg.addString(std::to_string(it.maxItems), "ProtocolGame::sendMarketDetail - std::to_string(it.maxItems)"); } else { msg.add(0x00); } @@ -5179,9 +5199,9 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { if (it.extraDefense != 0) { std::ostringstream ss; ss << it.defense << ' ' << std::showpos << it.extraDefense << std::noshowpos; - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else { - msg.addString(std::to_string(it.defense)); + msg.addString(std::to_string(it.defense), "ProtocolGame::sendMarketDetail - std::to_string(it.defense)"); } } else { msg.add(0x00); @@ -5190,9 +5210,9 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { if (!it.description.empty()) { const std::string &descr = it.description; if (descr.back() == '.') { - msg.addString(std::string(descr, 0, descr.length() - 1)); + msg.addString(std::string(descr, 0, descr.length() - 1), "ProtocolGame::sendMarketDetail - std::string(descr, 0, descr.length() - 1)"); } else { - msg.addString(descr); + msg.addString(descr, "ProtocolGame::sendMarketDetail - descr"); } } else { msg.add(0x00); @@ -5201,7 +5221,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { if (it.decayTime != 0) { std::ostringstream ss; ss << it.decayTime << " seconds"; - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else { msg.add(0x00); } @@ -5224,25 +5244,25 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { ss << fmt::format("{} {:+}%", getCombatName(indexToCombatType(i)), it.abilities->absorbPercent[i]); } - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else { msg.add(0x00); } if (it.minReqLevel != 0) { - msg.addString(std::to_string(it.minReqLevel)); + msg.addString(std::to_string(it.minReqLevel), "ProtocolGame::sendMarketDetail - std::to_string(it.minReqLevel)"); } else { msg.add(0x00); } if (it.minReqMagicLevel != 0) { - msg.addString(std::to_string(it.minReqMagicLevel)); + msg.addString(std::to_string(it.minReqMagicLevel), "ProtocolGame::sendMarketDetail - std::to_string(it.minReqMagicLevel)"); } else { msg.add(0x00); } - msg.addString(it.vocationString); - msg.addString(it.runeSpellName); + msg.addString(it.vocationString, "ProtocolGame::sendMarketDetail - it.vocationString"); + msg.addString(it.runeSpellName, "ProtocolGame::sendMarketDetail - it.runeSpellName"); if (it.abilities) { std::ostringstream ss; @@ -5319,13 +5339,13 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { ss << fmt::format("speed {:+}", (it.abilities->speed >> 1)); } - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else { msg.add(0x00); } if (it.charges != 0) { - msg.addString(std::to_string(it.charges)); + msg.addString(std::to_string(it.charges), "ProtocolGame::sendMarketDetail - std::to_string(it.charges)"); } else { msg.add(0x00); } @@ -5340,7 +5360,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { } } - msg.addString(weaponName); + msg.addString(weaponName, "ProtocolGame::sendMarketDetail - weaponName"); if (it.weight != 0) { std::ostringstream ss; @@ -5354,7 +5374,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { ss << weightString; } ss << " oz"; - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else { msg.add(0x00); } @@ -5364,7 +5384,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { } if (it.imbuementSlot > 0) { - msg.addString(std::to_string(it.imbuementSlot)); + msg.addString(std::to_string(it.imbuementSlot), "ProtocolGame::sendMarketDetail - std::to_string(it.imbuementSlot)"); } else { msg.add(0x00); } @@ -5376,7 +5396,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { if (it.abilities->magicShieldCapacityFlat > 0) { string.clear(); string << std::showpos << it.abilities->magicShieldCapacityFlat << std::noshowpos << " and " << it.abilities->magicShieldCapacityPercent << "%"; - msg.addString(string.str()); + msg.addString(string.str(), "ProtocolGame::sendMarketDetail - string.str()"); } else { msg.add(0x00); } @@ -5384,7 +5404,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { if (it.abilities->cleavePercent > 0) { string.clear(); string << it.abilities->cleavePercent << "%"; - msg.addString(string.str()); + msg.addString(string.str(), "ProtocolGame::sendMarketDetail - string.str()"); } else { msg.add(0x00); } @@ -5392,7 +5412,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { if (it.abilities->reflectFlat[COMBAT_PHYSICALDAMAGE] > 0) { string.clear(); string << it.abilities->reflectFlat[COMBAT_PHYSICALDAMAGE]; - msg.addString(string.str()); + msg.addString(string.str(), "ProtocolGame::sendMarketDetail - string.str()"); } else { msg.add(0x00); } @@ -5400,7 +5420,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { if (it.abilities->perfectShotDamage > 0) { string.clear(); string << std::showpos << it.abilities->perfectShotDamage << std::noshowpos << " at " << it.abilities->perfectShotRange << "%"; - msg.addString(string.str()); + msg.addString(string.str(), "ProtocolGame::sendMarketDetail - string.str()"); } else { msg.add(0x00); } @@ -5418,7 +5438,7 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { // Upgrade and tier detail modifier if (it.upgradeClassification > 0 && tier > 0) { - msg.addString(std::to_string(it.upgradeClassification)); + msg.addString(std::to_string(it.upgradeClassification), "ProtocolGame::sendMarketDetail - std::to_string(it.upgradeClassification)"); std::ostringstream ss; double chance; @@ -5432,10 +5452,10 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { chance = (0.0307576 * tier * tier) + (0.440697 * tier) + 0.026; ss << fmt::format("{} ({:.2f}% Ruse)", static_cast(tier), chance); } - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendMarketDetail - ss.str()"); } else if (it.upgradeClassification > 0 && tier == 0) { - msg.addString(std::to_string(it.upgradeClassification)); - msg.addString(std::to_string(tier)); + msg.addString(std::to_string(it.upgradeClassification), "ProtocolGame::sendMarketDetail - std::to_string(it.upgradeClassification)"); + msg.addString(std::to_string(tier), "ProtocolGame::sendMarketDetail - std::to_string(tier)"); } else { msg.add(0x00); msg.add(0x00); @@ -5488,7 +5508,7 @@ void ProtocolGame::sendTradeItemRequest(const std::string &traderName, std::shar msg.addByte(0x7E); } - msg.addString(traderName); + msg.addString(traderName, "ProtocolGame::sendTradeItemRequest - traderName"); if (std::shared_ptr tradeContainer = item->getContainer()) { std::list> listContainer { tradeContainer }; @@ -5553,7 +5573,7 @@ void ProtocolGame::sendCreatureSay(std::shared_ptr creature, SpeakClas static uint32_t statementId = 0; msg.add(++statementId); - msg.addString(creature->getName()); + msg.addString(creature->getName(), "ProtocolGame::sendCreatureSay - creature->getName()"); if (!oldProtocol) { msg.addByte(0x00); // Show (Traded) @@ -5578,7 +5598,7 @@ void ProtocolGame::sendCreatureSay(std::shared_ptr creature, SpeakClas msg.addPosition(creature->getPosition()); } - msg.addString(text); + msg.addString(text, "ProtocolGame::sendCreatureSay - text"); writeToOutputBuffer(msg); } @@ -5600,7 +5620,7 @@ void ProtocolGame::sendToChannel(std::shared_ptr creature, SpeakClasse } type = TALKTYPE_CHANNEL_R1; } else { - msg.addString(creature->getName()); + msg.addString(creature->getName(), "ProtocolGame::sendToChannel - creature->getName()"); if (!oldProtocol && statementId != 0) { msg.addByte(0x00); // Show (Traded) } @@ -5620,7 +5640,7 @@ void ProtocolGame::sendToChannel(std::shared_ptr creature, SpeakClasse } msg.add(channelId); - msg.addString(text); + msg.addString(text, "ProtocolGame::sendToChannel - text"); writeToOutputBuffer(msg); } @@ -5630,7 +5650,7 @@ void ProtocolGame::sendPrivateMessage(std::shared_ptr speaker, SpeakClas static uint32_t statementId = 0; msg.add(++statementId); if (speaker) { - msg.addString(speaker->getName()); + msg.addString(speaker->getName(), "ProtocolGame::sendPrivateMessage - speaker->getName()"); if (!oldProtocol && statementId != 0) { msg.addByte(0x00); // Show (Traded) } @@ -5648,7 +5668,7 @@ void ProtocolGame::sendPrivateMessage(std::shared_ptr speaker, SpeakClas msg.addByte(type); } - msg.addString(text); + msg.addString(text, "ProtocolGame::sendPrivateMessage - text"); writeToOutputBuffer(msg); } @@ -5730,7 +5750,7 @@ void ProtocolGame::sendRestingStatus(uint8_t protection) { int32_t PlayerdailyStreak = player->getStorageValue(STORAGEVALUE_DAILYREWARD); msg.addByte(PlayerdailyStreak < 2 ? 0 : 1); if (PlayerdailyStreak < 2) { - msg.addString("Resting Area (no active bonus)"); + msg.addString("Resting Area (no active bonus)", "ProtocolGame::sendRestingStatus - Resting Area (no active bonus)"); } else { std::ostringstream ss; ss << "Active Resting Area Bonuses: "; @@ -5753,7 +5773,7 @@ void ProtocolGame::sendRestingStatus(uint8_t protection) { ss << ",\nSoul Points Regeneration"; } ss << "."; - msg.addString(ss.str()); + msg.addString(ss.str(), "ProtocolGame::sendRestingStatus - ss.str()"); } writeToOutputBuffer(msg); } @@ -5945,7 +5965,7 @@ void ProtocolGame::sendPlayerVocation(std::shared_ptr target) { void ProtocolGame::sendFYIBox(const std::string &message) { NetworkMessage msg; msg.addByte(0x15); - msg.addString(message); + msg.addString(message, "ProtocolGame::sendFYIBox - message"); writeToOutputBuffer(msg); } @@ -6108,7 +6128,7 @@ void ProtocolGame::sendAddCreature(std::shared_ptr creature, const Pos msg.addByte(0x00); // can change pvp framing option msg.addByte(0x00); // expert mode button enabled - msg.addString(g_configManager().getString(STORE_IMAGES_URL, __FUNCTION__)); + msg.addString(g_configManager().getString(STORE_IMAGES_URL, __FUNCTION__), "ProtocolGame::sendAddCreature - g_configManager().getString(STORE_IMAGES_URL)"); msg.add(static_cast(g_configManager().getNumber(STORE_COIN_PACKET, __FUNCTION__))); if (!oldProtocol) { @@ -6351,16 +6371,16 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, std::shared_ptr i if (canWrite) { msg.add(maxlen); - msg.addString(item->getAttribute(ItemAttribute_t::TEXT)); + msg.addString(item->getAttribute(ItemAttribute_t::TEXT), "ProtocolGame::sendTextWindow - item->getAttribute(ItemAttribute_t::TEXT)"); } else { const std::string &text = item->getAttribute(ItemAttribute_t::TEXT); msg.add(text.size()); - msg.addString(text); + msg.addString(text, "ProtocolGame::sendTextWindow - text"); } const std::string &writer = item->getAttribute(ItemAttribute_t::WRITER); if (!writer.empty()) { - msg.addString(writer); + msg.addString(writer, "ProtocolGame::sendTextWindow - writer"); } else { msg.add(0x00); } @@ -6371,7 +6391,7 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, std::shared_ptr i auto writtenDate = item->getAttribute(ItemAttribute_t::DATE); if (writtenDate != 0) { - msg.addString(formatDateShort(writtenDate)); + msg.addString(formatDateShort(writtenDate), "ProtocolGame::sendTextWindow - formatDateShort(writtenDate)"); } else { msg.add(0x00); } @@ -6385,7 +6405,7 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const msg.add(windowTextId); AddItem(msg, itemId, 1, 0); msg.add(text.size()); - msg.addString(text); + msg.addString(text, "ProtocolGame::sendTextWindow - text"); msg.add(0x00); if (!oldProtocol) { @@ -6401,7 +6421,7 @@ void ProtocolGame::sendHouseWindow(uint32_t windowTextId, const std::string &tex msg.addByte(0x97); msg.addByte(0x00); msg.add(windowTextId); - msg.addString(text); + msg.addString(text, "ProtocolGame::sendHouseWindow - text"); writeToOutputBuffer(msg); } @@ -6411,7 +6431,7 @@ void ProtocolGame::sendOutfitWindow() { if (oldProtocol) { Outfit_t currentOutfit = player->getDefaultOutfit(); - const auto currentMount = g_game().mounts.getMountByID(player->getCurrentMount()); + const auto currentMount = g_game().mounts.getMountByID(player->getLastMount()); if (currentMount) { currentOutfit.lookMount = currentMount->clientId; } @@ -6448,7 +6468,7 @@ void ProtocolGame::sendOutfitWindow() { msg.addByte(protocolOutfits.size()); for (const ProtocolOutfit &outfit : protocolOutfits) { msg.add(outfit.lookType); - msg.addString(outfit.name); + msg.addString(outfit.name, "ProtocolGame::sendOutfitWindow - outfit.name"); msg.addByte(outfit.addons); } @@ -6462,7 +6482,7 @@ void ProtocolGame::sendOutfitWindow() { msg.addByte(mounts.size()); for (const auto mount : mounts) { msg.add(mount->clientId); - msg.addString(mount->name); + msg.addString(mount->name, "ProtocolGame::sendOutfitWindow - mount->name"); } writeToOutputBuffer(msg); @@ -6471,7 +6491,7 @@ void ProtocolGame::sendOutfitWindow() { bool mounted = false; Outfit_t currentOutfit = player->getDefaultOutfit(); - const auto currentMount = g_game().mounts.getMountByID(player->getCurrentMount()); + const auto currentMount = g_game().mounts.getMountByID(player->getLastMount()); if (currentMount) { mounted = (currentOutfit.lookMount == currentMount->clientId); currentOutfit.lookMount = currentMount->clientId; @@ -6493,19 +6513,19 @@ void ProtocolGame::sendOutfitWindow() { if (player->isAccessPlayer()) { msg.add(75); - msg.addString("Gamemaster"); + msg.addString("Gamemaster", "ProtocolGame::sendOutfitWindow - Gamemaster"); msg.addByte(0); msg.addByte(0x00); ++outfitSize; msg.add(266); - msg.addString("Customer Support"); + msg.addString("Customer Support", "ProtocolGame::sendOutfitWindow - Customer Support"); msg.addByte(0); msg.addByte(0x00); ++outfitSize; msg.add(302); - msg.addString("Community Manager"); + msg.addString("Community Manager", "ProtocolGame::sendOutfitWindow - Community Manager"); msg.addByte(0); msg.addByte(0x00); ++outfitSize; @@ -6517,25 +6537,25 @@ void ProtocolGame::sendOutfitWindow() { uint8_t addons; if (player->getOutfitAddons(outfit, addons)) { msg.add(outfit->lookType); - msg.addString(outfit->name); + msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); msg.addByte(addons); msg.addByte(0x00); ++outfitSize; } else if (outfit->lookType == 1210 || outfit->lookType == 1211) { msg.add(outfit->lookType); - msg.addString(outfit->name); + msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); msg.addByte(3); msg.addByte(0x02); ++outfitSize; } else if (outfit->lookType == 1456 || outfit->lookType == 1457) { msg.add(outfit->lookType); - msg.addString(outfit->name); + msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); msg.addByte(3); msg.addByte(0x03); ++outfitSize; } else if (outfit->from == "store") { msg.add(outfit->lookType); - msg.addString(outfit->name); + msg.addString(outfit->name, "ProtocolGame::sendOutfitWindow - outfit->name"); msg.addByte(outfit->lookType >= 962 && outfit->lookType <= 975 ? 0 : 3); msg.addByte(0x01); msg.add(0x00); @@ -6561,12 +6581,12 @@ void ProtocolGame::sendOutfitWindow() { for (const auto mount : mounts) { if (player->hasMount(mount)) { msg.add(mount->clientId); - msg.addString(mount->name); + msg.addString(mount->name, "ProtocolGame::sendOutfitWindow - mount->name"); msg.addByte(0x00); ++mountSize; } else if (mount->type == "store") { msg.add(mount->clientId); - msg.addString(mount->name); + msg.addString(mount->name, "ProtocolGame::sendOutfitWindow - mount->name"); msg.addByte(0x01); msg.add(0x00); ++mountSize; @@ -6595,7 +6615,7 @@ void ProtocolGame::sendOutfitWindow() { } msg.add(familiar.lookType); - msg.addString(familiar.name); + msg.addString(familiar.name, "ProtocolGame::sendOutfitWindow - familiar.name"); msg.addByte(0x00); if (++familiarSize == limitFamiliars) { break; @@ -6659,7 +6679,7 @@ void ProtocolGame::sendPodiumWindow(std::shared_ptr podium, const Position } msg.add(outfit->lookType); - msg.addString(outfit->name); + msg.addString(outfit->name, "ProtocolGame::sendPodiumWindow - outfit->name"); msg.addByte(addons); msg.addByte(0x00); if (++outfitSize == limitOutfits) { @@ -6681,7 +6701,7 @@ void ProtocolGame::sendPodiumWindow(std::shared_ptr podium, const Position for (const auto mount : mounts) { if (player->hasMount(mount)) { msg.add(mount->clientId); - msg.addString(mount->name); + msg.addString(mount->name, "ProtocolGame::sendPodiumWindow - mount->name"); msg.addByte(0x00); if (++mountSize == limitMounts) { break; @@ -6731,8 +6751,8 @@ void ProtocolGame::sendVIP(uint32_t guid, const std::string &name, const std::st NetworkMessage msg; msg.addByte(0xD2); msg.add(guid); - msg.addString(name); - msg.addString(description); + msg.addString(name, "ProtocolGame::sendVIP - name"); + msg.addString(description, "ProtocolGame::sendVIP - description"); msg.add(std::min(10, icon)); msg.addByte(notify ? 0x01 : 0x00); msg.addByte(status); @@ -6829,7 +6849,7 @@ void ProtocolGame::sendPreyData(const std::unique_ptr &slot) { // Empty } else if (slot->state == PreyDataState_Active) { if (const auto mtype = g_monsters().getMonsterTypeByRaceId(slot->selectedRaceId)) { - msg.addString(mtype->name); + msg.addString(mtype->name, "ProtocolGame::sendPreyData - mtype->name"); const Outfit_t outfit = mtype->info.outfit; msg.add(outfit.lookType); if (outfit.lookType == 0) { @@ -6855,7 +6875,7 @@ void ProtocolGame::sendPreyData(const std::unique_ptr &slot) { continue; } - msg.addString(mtype->name); + msg.addString(mtype->name, "ProtocolGame::sendPreyData - mtype->name"); const Outfit_t outfit = mtype->info.outfit; msg.add(outfit.lookType); if (outfit.lookType == 0) { @@ -6880,7 +6900,7 @@ void ProtocolGame::sendPreyData(const std::unique_ptr &slot) { continue; } - msg.addString(mtype->name); + msg.addString(mtype->name, "ProtocolGame::sendPreyData - mtype->name"); const Outfit_t outfit = mtype->info.outfit; msg.add(outfit.lookType); if (outfit.lookType == 0) { @@ -6948,18 +6968,18 @@ void ProtocolGame::sendModalWindow(const ModalWindow &modalWindow) { msg.addByte(0xFA); msg.add(modalWindow.id); - msg.addString(modalWindow.title); - msg.addString(modalWindow.message); + msg.addString(modalWindow.title, "ProtocolGame::sendModalWindow - modalWindow.title"); + msg.addString(modalWindow.message, "ProtocolGame::sendModalWindow - modalWindow.message"); msg.addByte(modalWindow.buttons.size()); for (const auto &it : modalWindow.buttons) { - msg.addString(it.first); + msg.addString(it.first, "ProtocolGame::sendModalWindow - it.first"); msg.addByte(it.second); } msg.addByte(modalWindow.choices.size()); for (const auto &it : modalWindow.choices) { - msg.addString(it.first); + msg.addString(it.first, "ProtocolGame::sendModalWindow - it.first"); msg.addByte(it.second); } @@ -6997,9 +7017,9 @@ void ProtocolGame::AddCreature(NetworkMessage &msg, std::shared_ptr cr } if (!oldProtocol && creature->isHealthHidden()) { - msg.addString(""); + msg.addString("", "ProtocolGame::AddCreature - empty"); } else { - msg.addString(creature->getName()); + msg.addString(creature->getName(), "ProtocolGame::AddCreature - creature->getName()"); } } @@ -7218,9 +7238,10 @@ void ProtocolGame::addImbuementInfo(NetworkMessage &msg, uint16_t imbuementId) c const CategoryImbuement* categoryImbuement = g_imbuements().getCategoryByID(imbuement->getCategory()); msg.add(imbuementId); - msg.addString(baseImbuement->name + " " + imbuement->getName()); - msg.addString(imbuement->getDescription()); - msg.addString(categoryImbuement->name + imbuement->getSubGroup()); + msg.addString(baseImbuement->name + " " + imbuement->getName(), "ProtocolGame::addImbuementInfo - baseImbuement->name + " + " + imbuement->getName()"); + msg.addString(imbuement->getDescription(), "ProtocolGame::addImbuementInfo - imbuement->getDescription()"); + msg.addString(categoryImbuement->name + imbuement->getSubGroup(), "ProtocolGame::addImbuementInfo - categoryImbuement->name + imbuement->getSubGroup()"); msg.add(imbuement->getIconID()); msg.add(baseImbuement->duration); @@ -7233,7 +7254,7 @@ void ProtocolGame::addImbuementInfo(NetworkMessage &msg, uint16_t imbuementId) c for (const auto &itm : items) { const ItemType &it = Item::items[itm.first]; msg.add(itm.first); - msg.addString(it.name); + msg.addString(it.name, "ProtocolGame::addImbuementInfo - it.name"); msg.add(itm.second); } @@ -7305,7 +7326,7 @@ void ProtocolGame::sendMessageDialog(const std::string &message) { NetworkMessage msg; msg.addByte(0xED); msg.addByte(0x14); // Unknown type - msg.addString(message); + msg.addString(message, "ProtocolGame::sendMessageDialog - message"); writeToOutputBuffer(msg); } @@ -7313,7 +7334,7 @@ void ProtocolGame::sendImbuementResult(const std::string message) { NetworkMessage msg; msg.addByte(0xED); msg.addByte(0x01); - msg.addString(message); + msg.addString(message, "ProtocolGame::sendImbuementResult - message"); writeToOutputBuffer(msg); } @@ -7374,7 +7395,7 @@ void ProtocolGame::updatePartyTrackerAnalyzer(const std::shared_ptr party msg.addByte(static_cast(party->membersData.size())); for (const std::shared_ptr analyzer : party->membersData) { msg.add(analyzer->id); - msg.addString(analyzer->name); + msg.addString(analyzer->name, "ProtocolGame::updatePartyTrackerAnalyzer - analyzer->name"); } } @@ -7410,7 +7431,7 @@ void ProtocolGame::sendKillTrackerUpdate(std::shared_ptr corpse, cons NetworkMessage msg; msg.addByte(0xD1); - msg.addString(name); + msg.addString(name, "ProtocolGame::sendKillTrackerUpdate - name"); msg.add(creatureOutfit.lookType ? creatureOutfit.lookType : 21); msg.addByte(creatureOutfit.lookType ? creatureOutfit.lookHead : 0x00); msg.addByte(creatureOutfit.lookType ? creatureOutfit.lookBody : 0x00); @@ -7478,7 +7499,7 @@ void ProtocolGame::sendUpdateInputAnalyzer(CombatType_t type, int32_t amount, st msg.addByte(ANALYZER_DAMAGE_RECEIVED); msg.add(amount); msg.addByte(clientElement); - msg.addString(target); + msg.addString(target, "ProtocolGame::sendUpdateInputAnalyzer - target"); writeToOutputBuffer(msg); } @@ -7642,7 +7663,7 @@ void ProtocolGame::AddHiddenShopItem(NetworkMessage &msg) { // Empty bytes from AddShopItem msg.add(0); msg.addByte(0); - msg.addString(std::string()); + msg.addString(std::string(), "ProtocolGame::AddHiddenShopItem - std::string()"); msg.add(0); msg.add(0); msg.add(0); @@ -7677,9 +7698,9 @@ void ProtocolGame::AddShopItem(NetworkMessage &msg, const ShopBlock &shopBlock) // If not send "itemName" variable from the npc shop, will registered the name that is in items.xml if (shopBlock.itemName.empty()) { - msg.addString(it.name); + msg.addString(it.name, "ProtocolGame::AddShopItem - it.name"); } else { - msg.addString(shopBlock.itemName); + msg.addString(shopBlock.itemName, "ProtocolGame::AddShopItem - shopBlock.itemName"); } msg.add(it.weight); msg.add(shopBlock.itemBuyPrice == 4294967295 ? 0 : shopBlock.itemBuyPrice); @@ -7761,7 +7782,8 @@ void ProtocolGame::sendInventoryImbuements(const std::mapgetBaseID()); msg.addByte(0x01); - msg.addString(baseImbuement->name + " " + imbuement->getName()); + msg.addString(baseImbuement->name + " " + imbuement->getName(), "ProtocolGame::sendInventoryImbuements - baseImbuement->name + " + " + imbuement->getName()"); msg.add(imbuement->getIconID()); msg.add(imbuementInfo.duration); @@ -7800,18 +7822,21 @@ void ProtocolGame::sendItemsPrice() { NetworkMessage msg; msg.addByte(0xCD); - msg.add(g_game().getItemsPriceCount()); - if (g_game().getItemsPriceCount() > 0) { - for (const auto &[itemId, tierAndPriceMap] : g_game().getItemsPrice()) { - for (const auto &[tier, price] : tierAndPriceMap) { - msg.add(itemId); - if (Item::items[itemId].upgradeClassification > 0) { - msg.addByte(tier); - } - msg.add(price); + auto countBuffer = msg.getBufferPosition(); + uint16_t count = 0; + msg.skipBytes(2); + for (const auto &[itemId, tierAndPriceMap] : g_game().getItemsPrice()) { + for (const auto &[tier, price] : tierAndPriceMap) { + msg.add(itemId); + if (Item::items[itemId].upgradeClassification > 0) { + msg.addByte(tier); } + msg.add(price); + count++; } } + msg.setBufferPosition(countBuffer); + msg.add(count); writeToOutputBuffer(msg); } @@ -8354,9 +8379,9 @@ void ProtocolGame::sendPodiumDetails(NetworkMessage &msg, const std::vectorname); + msg.addString(mType->name, "ProtocolGame::sendPodiumDetails - mType->name"); } msg.add(monsterOutfit.lookType); if (isLookType) { diff --git a/src/server/network/protocol/protocollogin.cpp b/src/server/network/protocol/protocollogin.cpp index 1b8753b01..53dc9f051 100644 --- a/src/server/network/protocol/protocollogin.cpp +++ b/src/server/network/protocol/protocollogin.cpp @@ -22,7 +22,7 @@ void ProtocolLogin::disconnectClient(const std::string &message) { auto output = OutputMessagePool::getOutputMessage(); output->addByte(0x0B); - output->addString(message); + output->addString(message, "ProtocolLogin::disconnectClient - message"); send(output); disconnect(); @@ -56,12 +56,12 @@ void ProtocolLogin::getCharacterList(const std::string &accountDescriptor, const std::ostringstream ss; ss << g_game().getMotdNum() << "\n" << motd; - output->addString(ss.str()); + output->addString(ss.str(), "ProtocolLogin::getCharacterList - ss.str()"); } // Add session key output->addByte(0x28); - output->addString(accountDescriptor + "\n" + password); + output->addString(accountDescriptor + "\n" + password, "ProtocolLogin::getCharacterList - accountDescriptor + password"); // Add char list auto [players, result] = account.getAccountPlayers(); @@ -74,8 +74,8 @@ void ProtocolLogin::getCharacterList(const std::string &accountDescriptor, const output->addByte(1); // number of worlds output->addByte(0); // world id - output->addString(g_configManager().getString(SERVER_NAME, __FUNCTION__)); - output->addString(g_configManager().getString(IP, __FUNCTION__)); + output->addString(g_configManager().getString(SERVER_NAME, __FUNCTION__), "ProtocolLogin::getCharacterList - _configManager().getString(SERVER_NAME)"); + output->addString(g_configManager().getString(IP, __FUNCTION__), "ProtocolLogin::getCharacterList - g_configManager().getString(IP)"); output->add(g_configManager().getNumber(GAME_PORT, __FUNCTION__)); @@ -85,7 +85,7 @@ void ProtocolLogin::getCharacterList(const std::string &accountDescriptor, const output->addByte(size); for (const auto &[name, deletion] : players) { output->addByte(0); - output->addString(name); + output->addString(name, "ProtocolLogin::getCharacterList - name"); } // Add premium days diff --git a/src/server/network/protocol/protocolstatus.cpp b/src/server/network/protocol/protocolstatus.cpp index 8f4a6e8de..52d3c07cc 100644 --- a/src/server/network/protocol/protocolstatus.cpp +++ b/src/server/network/protocol/protocolstatus.cpp @@ -154,22 +154,22 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &charact if (requestedInfo & REQUEST_BASIC_SERVER_INFO) { output->addByte(0x10); - output->addString(g_configManager().getString(ConfigKey_t::SERVER_NAME, __FUNCTION__)); - output->addString(g_configManager().getString(IP, __FUNCTION__)); - output->addString(std::to_string(g_configManager().getNumber(LOGIN_PORT, __FUNCTION__))); + output->addString(g_configManager().getString(ConfigKey_t::SERVER_NAME, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(stringConfig_t::SERVER_NAME)"); + output->addString(g_configManager().getString(IP, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(IP)"); + output->addString(std::to_string(g_configManager().getNumber(LOGIN_PORT, __FUNCTION__)), "ProtocolStatus::sendInfo - std::to_string(g_configManager().getNumber(LOGIN_PORT))"); } if (requestedInfo & REQUEST_OWNER_SERVER_INFO) { output->addByte(0x11); - output->addString(g_configManager().getString(OWNER_NAME, __FUNCTION__)); - output->addString(g_configManager().getString(OWNER_EMAIL, __FUNCTION__)); + output->addString(g_configManager().getString(OWNER_NAME, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(OWNER_NAME)"); + output->addString(g_configManager().getString(OWNER_EMAIL, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(OWNER_EMAIL)"); } if (requestedInfo & REQUEST_MISC_SERVER_INFO) { output->addByte(0x12); - output->addString(g_configManager().getString(SERVER_MOTD, __FUNCTION__)); - output->addString(g_configManager().getString(LOCATION, __FUNCTION__)); - output->addString(g_configManager().getString(URL, __FUNCTION__)); + output->addString(g_configManager().getString(SERVER_MOTD, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(SERVER_MOTD)"); + output->addString(g_configManager().getString(LOCATION, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(LOCATION)"); + output->addString(g_configManager().getString(URL, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(URL)"); output->add((OTSYS_TIME() - ProtocolStatus::start) / 1000); } @@ -182,8 +182,8 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &charact if (requestedInfo & REQUEST_MAP_INFO) { output->addByte(0x30); - output->addString(g_configManager().getString(MAP_NAME, __FUNCTION__)); - output->addString(g_configManager().getString(MAP_AUTHOR, __FUNCTION__)); + output->addString(g_configManager().getString(MAP_NAME, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(MAP_NAME)"); + output->addString(g_configManager().getString(MAP_AUTHOR, __FUNCTION__), "ProtocolStatus::sendInfo - g_configManager().getString(MAP_AUTHOR)"); uint32_t mapWidth, mapHeight; g_game().getMapDimensions(mapWidth, mapHeight); output->add(mapWidth); @@ -196,7 +196,7 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &charact const auto players = g_game().getPlayers(); output->add(players.size()); for (const auto &it : players) { - output->addString(it.second->getName()); + output->addString(it.second->getName(), "ProtocolStatus::sendInfo - it.second->getName()"); output->add(it.second->getLevel()); } } @@ -212,9 +212,9 @@ void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string &charact if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) { output->addByte(0x23); // server software info - output->addString(ProtocolStatus::SERVER_NAME); - output->addString(ProtocolStatus::SERVER_VERSION); - output->addString(fmt::format("{}.{}", CLIENT_VERSION_UPPER, CLIENT_VERSION_LOWER)); + output->addString(ProtocolStatus::SERVER_NAME, "ProtocolStatus::sendInfo - ProtocolStatus::SERVER_NAME"); + output->addString(ProtocolStatus::SERVER_VERSION, "ProtocolStatus::sendInfo - ProtocolStatus::SERVER_VERSION)"); + output->addString(fmt::format("{}.{}", CLIENT_VERSION_UPPER, CLIENT_VERSION_LOWER), "ProtocolStatus::sendInfo - fmt::format(CLIENT_VERSION_UPPER, CLIENT_VERSION_LOWER)"); } send(output); disconnect(); diff --git a/src/server/server.cpp b/src/server/server.cpp index 00f9d79e7..9a3c784c7 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -97,7 +97,7 @@ void ServicePort::onAccept(Connection_ptr connection, const std::error_code &err if (service->is_single_socket()) { connection->accept(service->make_protocol(connection)); } else { - connection->accept(); + connection->acceptInternal(); } } else { connection->close(FORCE_CLOSE); diff --git a/src/server/server.hpp b/src/server/server.hpp index 1760fdafa..ae6f805e4 100644 --- a/src/server/server.hpp +++ b/src/server/server.hpp @@ -10,6 +10,7 @@ #pragma once #include "lib/logging/logger.hpp" +#include "lib/metrics/metrics.hpp" #include "server/network/connection/connection.hpp" #include "server/signals.hpp"