From ba62a415a6e9f819b852925e005e99e04a7f63db Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 11 Dec 2024 00:38:22 -0300 Subject: [PATCH 01/11] improve: migration database update (#3071) This improves the database migration process by removing the need to manually define if more migrations are required with return values in Lua scripts. The new approach automatically processes all migration files based on their version, reducing complexity and making the update process more robust. This change aims to streamline the database update logic and eliminate manual steps previously required. --- data-canary/migrations/0.lua | 3 - data-canary/migrations/1.lua | 5 -- data-canary/migrations/README.md | 45 +++++++++++++ data-otservbr-global/migrations/0.lua | 14 ---- data-otservbr-global/migrations/1.lua | 35 +++------- data-otservbr-global/migrations/10.lua | 1 - data-otservbr-global/migrations/11.lua | 1 - data-otservbr-global/migrations/12.lua | 1 - data-otservbr-global/migrations/13.lua | 1 - data-otservbr-global/migrations/14.lua | 1 - data-otservbr-global/migrations/15.lua | 1 - data-otservbr-global/migrations/16.lua | 1 - data-otservbr-global/migrations/17.lua | 1 - data-otservbr-global/migrations/18.lua | 2 - data-otservbr-global/migrations/19.lua | 2 - data-otservbr-global/migrations/2.lua | 2 - data-otservbr-global/migrations/20.lua | 1 - data-otservbr-global/migrations/21.lua | 1 - data-otservbr-global/migrations/22.lua | 1 - data-otservbr-global/migrations/23.lua | 1 - data-otservbr-global/migrations/24.lua | 1 - data-otservbr-global/migrations/25.lua | 1 - data-otservbr-global/migrations/26.lua | 1 - data-otservbr-global/migrations/27.lua | 1 - data-otservbr-global/migrations/28.lua | 1 - data-otservbr-global/migrations/29.lua | 1 - data-otservbr-global/migrations/3.lua | 1 - data-otservbr-global/migrations/30.lua | 1 - data-otservbr-global/migrations/31.lua | 1 - data-otservbr-global/migrations/32.lua | 1 - data-otservbr-global/migrations/33.lua | 1 - data-otservbr-global/migrations/34.lua | 1 - data-otservbr-global/migrations/35.lua | 1 - data-otservbr-global/migrations/36.lua | 1 - data-otservbr-global/migrations/37.lua | 1 - data-otservbr-global/migrations/38.lua | 1 - data-otservbr-global/migrations/39.lua | 1 - data-otservbr-global/migrations/4.lua | 1 - data-otservbr-global/migrations/40.lua | 2 - data-otservbr-global/migrations/41.lua | 2 - data-otservbr-global/migrations/42.lua | 2 - data-otservbr-global/migrations/43.lua | 2 - data-otservbr-global/migrations/44.lua | 2 - data-otservbr-global/migrations/45.lua | 2 - data-otservbr-global/migrations/46.lua | 2 - data-otservbr-global/migrations/47.lua | 23 ++++++- data-otservbr-global/migrations/5.lua | 1 - data-otservbr-global/migrations/6.lua | 1 - data-otservbr-global/migrations/7.lua | 1 - data-otservbr-global/migrations/8.lua | 1 - data-otservbr-global/migrations/9.lua | 1 - data-otservbr-global/migrations/README.md | 45 +++++++++++++ src/database/databasemanager.cpp | 81 +++++++++++++++-------- 53 files changed, 177 insertions(+), 129 deletions(-) delete mode 100644 data-canary/migrations/0.lua delete mode 100644 data-canary/migrations/1.lua create mode 100644 data-canary/migrations/README.md delete mode 100644 data-otservbr-global/migrations/0.lua create mode 100644 data-otservbr-global/migrations/README.md diff --git a/data-canary/migrations/0.lua b/data-canary/migrations/0.lua deleted file mode 100644 index d0ffd9c0cb3..00000000000 --- a/data-canary/migrations/0.lua +++ /dev/null @@ -1,3 +0,0 @@ -function onUpdateDatabase() - return false -end diff --git a/data-canary/migrations/1.lua b/data-canary/migrations/1.lua deleted file mode 100644 index 332f1838723..00000000000 --- a/data-canary/migrations/1.lua +++ /dev/null @@ -1,5 +0,0 @@ --- return true = There are others migrations file --- return false = This is the last migration file -function onUpdateDatabase() - return false -end diff --git a/data-canary/migrations/README.md b/data-canary/migrations/README.md new file mode 100644 index 00000000000..b23473bf29d --- /dev/null +++ b/data-canary/migrations/README.md @@ -0,0 +1,45 @@ +# Database Migration System + +This document provides an overview of the current database migration system for the project. The migration process has been streamlined to ensure that all migration scripts are automatically applied in order, making it easier to maintain database updates. + +## How It Works + +The migration system is designed to apply updates to the database schema or data whenever a new server version is started. Migration scripts are stored in the `migrations` directory, and the system will automatically apply any scripts that have not yet been executed. + +### Steps Involved + +1. **Retrieve Current Database Version**: + - The system first retrieves the current version of the database using `getDatabaseVersion()`. + - This version is used to determine which migration scripts need to be executed. + +2. **Migration Files Directory**: + - All migration scripts are stored in the `migrations` directory. + - Each migration script is named using a numerical pattern, such as `1.lua`, `2.lua`, etc. + - The naming convention helps determine the order in which scripts should be applied. + +3. **Execute Migration Scripts**: + - The migration system iterates through the migration directory and applies each migration script that has a version greater than the current database version. + - Only scripts that have not been applied are executed. + - The Lua state (`lua_State* L`) is initialized to run each script. + +4. **Update Database Version**: + - After each migration script is successfully applied, the system updates the database version to reflect the applied change. + - This ensures that the script is not re-applied on subsequent server startups. + +## Example Migration Script + +Below is an example of what a migration script might look like. Note that no return value is required, as all migration files are applied based on the current database version. + +```lua +-- Migration script example (for documentation purposes only) +-- This migration script should include all necessary SQL commands or operations to apply a specific update to the database. + +-- Example: Adding a new column to the "players" table +local query = [[ + ALTER TABLE players ADD COLUMN new_feature_flag TINYINT(1) NOT NULL DEFAULT 0; +]] + +-- Execute the query +db.execute(query) -- This function executes the given SQL query on the database. + +-- Note: Ensure that queries are validated to avoid errors during the migration process. diff --git a/data-otservbr-global/migrations/0.lua b/data-otservbr-global/migrations/0.lua deleted file mode 100644 index b40962e7c1f..00000000000 --- a/data-otservbr-global/migrations/0.lua +++ /dev/null @@ -1,14 +0,0 @@ -function onUpdateDatabase() - logger.info("Updating database to version 1 (sample players)") - -- Rook Sample - db.query("UPDATE `players` SET `level` = 2, `vocation` = 0, `health` = 155, `healthmax` = 155, `experience` = 100, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 60, `manamax` = 60, `town_id` = 1, `cap` = 410 WHERE `id` = 1;") - -- Sorcerer Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 1, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 2;") - -- Druid Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 2, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 3;") - -- Paladin Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 3, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 4;") - -- Knight Sample - db.query("UPDATE `players` SET `level` = 8, `vocation` = 4, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 5;") - return true -end diff --git a/data-otservbr-global/migrations/1.lua b/data-otservbr-global/migrations/1.lua index 180a8b2ce90..1c5888f52d7 100644 --- a/data-otservbr-global/migrations/1.lua +++ b/data-otservbr-global/migrations/1.lua @@ -1,26 +1,13 @@ function onUpdateDatabase() - logger.info("Updating database to version 2 (hireling)") - - db.query([[ - CREATE TABLE IF NOT EXISTS `player_hirelings` ( - `id` INT NOT NULL PRIMARY KEY auto_increment, - `player_id` INT NOT NULL, - `name` varchar(255), - `active` tinyint unsigned NOT NULL DEFAULT '0', - `sex` tinyint unsigned NOT NULL DEFAULT '0', - `posx` int(11) NOT NULL DEFAULT '0', - `posy` int(11) NOT NULL DEFAULT '0', - `posz` int(11) NOT NULL DEFAULT '0', - `lookbody` int(11) NOT NULL DEFAULT '0', - `lookfeet` int(11) NOT NULL DEFAULT '0', - `lookhead` int(11) NOT NULL DEFAULT '0', - `looklegs` int(11) NOT NULL DEFAULT '0', - `looktype` int(11) NOT NULL DEFAULT '136', - - FOREIGN KEY(`player_id`) REFERENCES `players`(`id`) - ON DELETE CASCADE - ) - ]]) - - return true + logger.info("Updating database to version 1 (sample players)") + -- Rook Sample + db.query("UPDATE `players` SET `level` = 2, `vocation` = 0, `health` = 155, `healthmax` = 155, `experience` = 100, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 60, `manamax` = 60, `town_id` = 1, `cap` = 410 WHERE `id` = 1;") + -- Sorcerer Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 1, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 2;") + -- Druid Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 2, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 3;") + -- Paladin Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 3, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 4;") + -- Knight Sample + db.query("UPDATE `players` SET `level` = 8, `vocation` = 4, `health` = 185, `healthmax` = 185, `experience` = 4200, `soul` = 100, `lookbody` = 113, `lookfeet` = 115, `lookhead` = 95, `looklegs` = 39, `looktype` = 129, `mana` = 90, `manamax` = 90, `town_id` = 8, `cap` = 470 WHERE `id` = 5;") end diff --git a/data-otservbr-global/migrations/10.lua b/data-otservbr-global/migrations/10.lua index 0285bb0fee6..9dfded3813d 100644 --- a/data-otservbr-global/migrations/10.lua +++ b/data-otservbr-global/migrations/10.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 11 (Guilds Balance)") db.query("ALTER TABLE `guilds` ADD `balance` bigint(20) UNSIGNED NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/11.lua b/data-otservbr-global/migrations/11.lua index 7f448f94a9d..08d40b66381 100644 --- a/data-otservbr-global/migrations/11.lua +++ b/data-otservbr-global/migrations/11.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 12 (Player get daily reward)") db.query("ALTER TABLE `players` ADD `isreward` tinyint(1) NOT NULL DEFAULT 1") - return true end diff --git a/data-otservbr-global/migrations/12.lua b/data-otservbr-global/migrations/12.lua index 0c6e27bb275..e83ca4e51f8 100644 --- a/data-otservbr-global/migrations/12.lua +++ b/data-otservbr-global/migrations/12.lua @@ -7,5 +7,4 @@ function onUpdateDatabase() db.query("ALTER TABLE boosted_creature ADD `lookbody` int(11) NOT NULL DEFAULT 0;") db.query("ALTER TABLE boosted_creature ADD `lookaddons` int(11) NOT NULL DEFAULT 0;") db.query("ALTER TABLE boosted_creature ADD `lookmount` int(11) DEFAULT 0;") - return true end diff --git a/data-otservbr-global/migrations/13.lua b/data-otservbr-global/migrations/13.lua index 5918b6cbd58..479b28eda79 100644 --- a/data-otservbr-global/migrations/13.lua +++ b/data-otservbr-global/migrations/13.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 14 (Fixed mana spent)") db.query("ALTER TABLE `players` CHANGE `manaspent` `manaspent` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/14.lua b/data-otservbr-global/migrations/14.lua index d2a1faf273b..7c23d8053b5 100644 --- a/data-otservbr-global/migrations/14.lua +++ b/data-otservbr-global/migrations/14.lua @@ -2,5 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 15 (Magic Shield Spell)") db.query("ALTER TABLE `players` ADD `manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0' AFTER `skill_manaleech_amount`") db.query("ALTER TABLE `players` ADD `max_manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0' AFTER `manashield`") - return true end diff --git a/data-otservbr-global/migrations/15.lua b/data-otservbr-global/migrations/15.lua index 1521e96101f..73daf3c5b31 100644 --- a/data-otservbr-global/migrations/15.lua +++ b/data-otservbr-global/migrations/15.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() db.query("UPDATE `players` SET `maglevel` = 2, `manaspent` = 5936, `skill_club` = 12, `skill_club_tries` = 155, `skill_sword` = 12, `skill_sword_tries` = 155, `skill_axe` = 12, `skill_axe_tries` = 155, `skill_dist` = 12, `skill_dist_tries` = 93 WHERE `id` = 1;") -- GOD db.query("UPDATE `players` SET `health` = 155, `healthmax` = 155, `experience` = 100, `looktype` = 75, `town_id` = 8 WHERE `id` = 6;") - return true end diff --git a/data-otservbr-global/migrations/16.lua b/data-otservbr-global/migrations/16.lua index c9ca340f0b3..a5766130bc3 100644 --- a/data-otservbr-global/migrations/16.lua +++ b/data-otservbr-global/migrations/16.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() print("Updating database to version 17 (Tutorial support)") db.query("ALTER TABLE `players` ADD `istutorial` SMALLINT(1) NOT NULL DEFAULT '0'") - return true -- true = There are others migrations file | false = this is the last migration file end diff --git a/data-otservbr-global/migrations/17.lua b/data-otservbr-global/migrations/17.lua index d25e4ccd5a9..9d5f0d8d624 100644 --- a/data-otservbr-global/migrations/17.lua +++ b/data-otservbr-global/migrations/17.lua @@ -2,5 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 18 (Fix guild creation myaac)") db.query("ALTER TABLE `guilds` ADD `level` int(11) NOT NULL DEFAULT 1") db.query("ALTER TABLE `guilds` ADD `points` int(11) NOT NULL DEFAULT 0") - return true end diff --git a/data-otservbr-global/migrations/18.lua b/data-otservbr-global/migrations/18.lua index 01ef6048033..e017b86e05b 100644 --- a/data-otservbr-global/migrations/18.lua +++ b/data-otservbr-global/migrations/18.lua @@ -48,6 +48,4 @@ function onUpdateDatabase() `monster_list` BLOB NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - - return true end diff --git a/data-otservbr-global/migrations/19.lua b/data-otservbr-global/migrations/19.lua index 382273dfef1..e7d27a859ef 100644 --- a/data-otservbr-global/migrations/19.lua +++ b/data-otservbr-global/migrations/19.lua @@ -4,6 +4,4 @@ function onUpdateDatabase() db.query("ALTER TABLE `accounts` ADD `tournament_coins` int(11) NOT NULL DEFAULT 0 AFTER `coins`") db.query("ALTER TABLE `store_history` ADD `coin_type` tinyint(1) NOT NULL DEFAULT 0 AFTER `description`") db.query("ALTER TABLE `store_history` DROP COLUMN `coins`") -- Not in use anywhere. - - return true end diff --git a/data-otservbr-global/migrations/2.lua b/data-otservbr-global/migrations/2.lua index e953d579a3e..72c797b4b0e 100644 --- a/data-otservbr-global/migrations/2.lua +++ b/data-otservbr-global/migrations/2.lua @@ -109,6 +109,4 @@ function onUpdateDatabase() ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - - return true end diff --git a/data-otservbr-global/migrations/20.lua b/data-otservbr-global/migrations/20.lua index 49bc9ecd56f..daaefc6f041 100644 --- a/data-otservbr-global/migrations/20.lua +++ b/data-otservbr-global/migrations/20.lua @@ -2,5 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 21 (Fix market price size)") db.query("ALTER TABLE `market_history` CHANGE `price` `price` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';") db.query("ALTER TABLE `market_offers` CHANGE `price` `price` BIGINT(20) UNSIGNED NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/21.lua b/data-otservbr-global/migrations/21.lua index a2e945576f7..cec635ed937 100644 --- a/data-otservbr-global/migrations/21.lua +++ b/data-otservbr-global/migrations/21.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() db.query("ALTER TABLE `market_history` ADD `tier` tinyint UNSIGNED NOT NULL DEFAULT '0';") db.query("ALTER TABLE `players` ADD `forge_dusts` bigint(21) NOT NULL DEFAULT '0';") db.query("ALTER TABLE `players` ADD `forge_dust_level` bigint(21) UNSIGNED NOT NULL DEFAULT '100';") - return true end diff --git a/data-otservbr-global/migrations/22.lua b/data-otservbr-global/migrations/22.lua index ee1caf87ac3..c4c5bd385bc 100644 --- a/data-otservbr-global/migrations/22.lua +++ b/data-otservbr-global/migrations/22.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() ALTER TABLE `players` MODIFY offlinetraining_skill tinyint(2) NOT NULL DEFAULT '-1'; ]]) - return true end diff --git a/data-otservbr-global/migrations/23.lua b/data-otservbr-global/migrations/23.lua index da05835f759..8edac8cef47 100644 --- a/data-otservbr-global/migrations/23.lua +++ b/data-otservbr-global/migrations/23.lua @@ -16,5 +16,4 @@ function onUpdateDatabase() FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - return true end diff --git a/data-otservbr-global/migrations/24.lua b/data-otservbr-global/migrations/24.lua index c3cc1563b56..fed9f189085 100644 --- a/data-otservbr-global/migrations/24.lua +++ b/data-otservbr-global/migrations/24.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 25 (random mount outfit window)") db.query("ALTER TABLE `players` ADD `randomize_mount` SMALLINT(1) NOT NULL DEFAULT '0'") - return true end diff --git a/data-otservbr-global/migrations/25.lua b/data-otservbr-global/migrations/25.lua index e486b9ddf76..4d229bb58e5 100644 --- a/data-otservbr-global/migrations/25.lua +++ b/data-otservbr-global/migrations/25.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 26 (reward bag fix)") db.query("UPDATE player_rewards SET pid = 0 WHERE itemtype = 19202;") - return true end diff --git a/data-otservbr-global/migrations/26.lua b/data-otservbr-global/migrations/26.lua index bbac31b8170..ddf821ca5cd 100644 --- a/data-otservbr-global/migrations/26.lua +++ b/data-otservbr-global/migrations/26.lua @@ -11,5 +11,4 @@ function onUpdateDatabase() PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`)) ]]) - return true end diff --git a/data-otservbr-global/migrations/27.lua b/data-otservbr-global/migrations/27.lua index b16b589da87..73e7bf5c4f7 100644 --- a/data-otservbr-global/migrations/27.lua +++ b/data-otservbr-global/migrations/27.lua @@ -23,5 +23,4 @@ function onUpdateDatabase() `bossIdSlotTwo` int NOT NULL DEFAULT 0, `removeTimes` int NOT NULL DEFAULT 1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;]]) - return true end diff --git a/data-otservbr-global/migrations/28.lua b/data-otservbr-global/migrations/28.lua index 2bd44799cfc..d7575edf3c8 100644 --- a/data-otservbr-global/migrations/28.lua +++ b/data-otservbr-global/migrations/28.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 29 (transfer coins)") db.query("ALTER TABLE `accounts` ADD `coins_transferable` int unsigned NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/29.lua b/data-otservbr-global/migrations/29.lua index 1b490d324c2..0c633e46b5d 100644 --- a/data-otservbr-global/migrations/29.lua +++ b/data-otservbr-global/migrations/29.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 30 (looktypeEx)") db.query("ALTER TABLE `boosted_boss` ADD `looktypeEx` int unsigned NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/3.lua b/data-otservbr-global/migrations/3.lua index 72fc0d41cd8..ae06343be07 100644 --- a/data-otservbr-global/migrations/3.lua +++ b/data-otservbr-global/migrations/3.lua @@ -5,5 +5,4 @@ function onUpdateDatabase() ALTER TABLE `prey_slots` ADD `tick` smallint(3) NOT NULL DEFAULT '0'; ]]) - return true end diff --git a/data-otservbr-global/migrations/30.lua b/data-otservbr-global/migrations/30.lua index f724284e258..4ee1632421d 100644 --- a/data-otservbr-global/migrations/30.lua +++ b/data-otservbr-global/migrations/30.lua @@ -3,5 +3,4 @@ function onUpdateDatabase() db.query([[ ALTER TABLE `accounts` ADD COLUMN `premdays_purchased` int(11) NOT NULL DEFAULT 0; ]]) - return true end diff --git a/data-otservbr-global/migrations/31.lua b/data-otservbr-global/migrations/31.lua index 4207776b90e..5ba21bbe561 100644 --- a/data-otservbr-global/migrations/31.lua +++ b/data-otservbr-global/migrations/31.lua @@ -13,5 +13,4 @@ function onUpdateDatabase() db.query([[ ALTER TABLE `accounts` MODIFY `password` TEXT NOT NULL; ]]) - return true end diff --git a/data-otservbr-global/migrations/32.lua b/data-otservbr-global/migrations/32.lua index 102a9aafd9f..078ef407da6 100644 --- a/data-otservbr-global/migrations/32.lua +++ b/data-otservbr-global/migrations/32.lua @@ -10,5 +10,4 @@ function onUpdateDatabase() ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - return true end diff --git a/data-otservbr-global/migrations/33.lua b/data-otservbr-global/migrations/33.lua index afac0cebdfc..2c77cbb6e24 100644 --- a/data-otservbr-global/migrations/33.lua +++ b/data-otservbr-global/migrations/33.lua @@ -24,5 +24,4 @@ function onUpdateDatabase() ALTER TABLE `player_wheeldata` ADD PRIMARY KEY (`player_id`); ]]) - return true end diff --git a/data-otservbr-global/migrations/34.lua b/data-otservbr-global/migrations/34.lua index 7f0a289f656..7537f6e6582 100644 --- a/data-otservbr-global/migrations/34.lua +++ b/data-otservbr-global/migrations/34.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 35 (bosstiary tracker)") db.query("ALTER TABLE `player_bosstiary` ADD `tracker` blob NOT NULL;") - return true end diff --git a/data-otservbr-global/migrations/35.lua b/data-otservbr-global/migrations/35.lua index 10bddc47138..70c820c32fd 100644 --- a/data-otservbr-global/migrations/35.lua +++ b/data-otservbr-global/migrations/35.lua @@ -12,7 +12,6 @@ function onUpdateDatabase() until not Result.next(resultQuery) Result.free(resultQuery) end - return true end function getNewValue(premDays, lastDay) diff --git a/data-otservbr-global/migrations/36.lua b/data-otservbr-global/migrations/36.lua index 7d35e223cf9..5f912763cc8 100644 --- a/data-otservbr-global/migrations/36.lua +++ b/data-otservbr-global/migrations/36.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 37 (add coin_type to accounts)") db.query("ALTER TABLE `coins_transactions` ADD `coin_type` tinyint(1) UNSIGNED NOT NULL DEFAULT '1';") - return true end diff --git a/data-otservbr-global/migrations/37.lua b/data-otservbr-global/migrations/37.lua index 70f2ec126ce..ae3dbefbc27 100644 --- a/data-otservbr-global/migrations/37.lua +++ b/data-otservbr-global/migrations/37.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 38 (add pronoun to players)") db.query("ALTER TABLE `players` ADD `pronoun` int(11) NOT NULL DEFAULT '0';") - return true end diff --git a/data-otservbr-global/migrations/38.lua b/data-otservbr-global/migrations/38.lua index 7981d5d7063..3412e4d488f 100644 --- a/data-otservbr-global/migrations/38.lua +++ b/data-otservbr-global/migrations/38.lua @@ -8,5 +8,4 @@ function onUpdateDatabase() PRIMARY KEY (`key_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ]]) - return true end diff --git a/data-otservbr-global/migrations/39.lua b/data-otservbr-global/migrations/39.lua index 2bf3815016f..181994882db 100644 --- a/data-otservbr-global/migrations/39.lua +++ b/data-otservbr-global/migrations/39.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 40 (house transfer ownership on startup)") db.query("ALTER TABLE `houses` ADD `new_owner` int(11) NOT NULL DEFAULT '-1';") - return true end diff --git a/data-otservbr-global/migrations/4.lua b/data-otservbr-global/migrations/4.lua index fa07383aeaa..891bc20915a 100644 --- a/data-otservbr-global/migrations/4.lua +++ b/data-otservbr-global/migrations/4.lua @@ -6,5 +6,4 @@ function onUpdateDatabase() `raceid` varchar(250) NOT NULL DEFAULT '', PRIMARY KEY (`date`) ) AS SELECT 0 AS date, "default" AS boostname, 0 AS raceid]]) - return true end diff --git a/data-otservbr-global/migrations/40.lua b/data-otservbr-global/migrations/40.lua index 22bfea0da95..be8794e7b25 100644 --- a/data-otservbr-global/migrations/40.lua +++ b/data-otservbr-global/migrations/40.lua @@ -12,6 +12,4 @@ function onUpdateDatabase() ALTER TABLE `house_lists` MODIFY `version` bigint(20) NOT NULL DEFAULT '0'; ]]) - - return true end diff --git a/data-otservbr-global/migrations/41.lua b/data-otservbr-global/migrations/41.lua index 15eb1d88e99..1fa9a40e36d 100644 --- a/data-otservbr-global/migrations/41.lua +++ b/data-otservbr-global/migrations/41.lua @@ -6,6 +6,4 @@ function onUpdateDatabase() MODIFY `xpboost_stamina` smallint(5) UNSIGNED DEFAULT NULL, MODIFY `xpboost_value` tinyint(4) UNSIGNED DEFAULT NULL ]]) - - return true end diff --git a/data-otservbr-global/migrations/42.lua b/data-otservbr-global/migrations/42.lua index 4d07b663daa..4b0b97b9987 100644 --- a/data-otservbr-global/migrations/42.lua +++ b/data-otservbr-global/migrations/42.lua @@ -5,6 +5,4 @@ function onUpdateDatabase() ALTER TABLE `guildwar_kills` DROP INDEX `guildwar_kills_unique` ]]) - - return true end diff --git a/data-otservbr-global/migrations/43.lua b/data-otservbr-global/migrations/43.lua index 6d3492a815a..438ba91b713 100644 --- a/data-otservbr-global/migrations/43.lua +++ b/data-otservbr-global/migrations/43.lua @@ -7,6 +7,4 @@ function onUpdateDatabase() ADD `payment` bigint(13) UNSIGNED NOT NULL DEFAULT '0', ADD `duration_days` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' ]]) - - return true end diff --git a/data-otservbr-global/migrations/44.lua b/data-otservbr-global/migrations/44.lua index c551fc79aeb..0e2140c3183 100644 --- a/data-otservbr-global/migrations/44.lua +++ b/data-otservbr-global/migrations/44.lua @@ -6,6 +6,4 @@ function onUpdateDatabase() MODIFY COLUMN `manashield` INT UNSIGNED NOT NULL DEFAULT '0', MODIFY COLUMN `max_manashield` INT UNSIGNED NOT NULL DEFAULT '0'; ]]) - - return true end diff --git a/data-otservbr-global/migrations/45.lua b/data-otservbr-global/migrations/45.lua index 4ceb5f7e3fd..a88de886861 100644 --- a/data-otservbr-global/migrations/45.lua +++ b/data-otservbr-global/migrations/45.lua @@ -51,6 +51,4 @@ function onUpdateDatabase() INSERT INTO `account_vipgroups` (`id`, `account_id`, `name`, `customizable`) SELECT 3, id, 'Trading Partners', 0 FROM `accounts`; ]]) - - return true end diff --git a/data-otservbr-global/migrations/46.lua b/data-otservbr-global/migrations/46.lua index 506da3a132b..da4ade8cbf3 100644 --- a/data-otservbr-global/migrations/46.lua +++ b/data-otservbr-global/migrations/46.lua @@ -2,6 +2,4 @@ function onUpdateDatabase() logger.info("Updating database to version 47 (fix: creature speed and conditions)") db.query("ALTER TABLE `players` MODIFY `conditions` mediumblob NOT NULL;") - - return true end diff --git a/data-otservbr-global/migrations/47.lua b/data-otservbr-global/migrations/47.lua index 86a6d8ffec1..3c8908b5641 100644 --- a/data-otservbr-global/migrations/47.lua +++ b/data-otservbr-global/migrations/47.lua @@ -1,3 +1,24 @@ function onUpdateDatabase() - return false -- true = There are others migrations file | false = this is the last migration file + logger.info("Updating database to version 46 (hireling)") + + db.query([[ + CREATE TABLE IF NOT EXISTS `player_hirelings` ( + `id` INT NOT NULL PRIMARY KEY auto_increment, + `player_id` INT NOT NULL, + `name` varchar(255), + `active` tinyint unsigned NOT NULL DEFAULT '0', + `sex` tinyint unsigned NOT NULL DEFAULT '0', + `posx` int(11) NOT NULL DEFAULT '0', + `posy` int(11) NOT NULL DEFAULT '0', + `posz` int(11) NOT NULL DEFAULT '0', + `lookbody` int(11) NOT NULL DEFAULT '0', + `lookfeet` int(11) NOT NULL DEFAULT '0', + `lookhead` int(11) NOT NULL DEFAULT '0', + `looklegs` int(11) NOT NULL DEFAULT '0', + `looktype` int(11) NOT NULL DEFAULT '136', + + FOREIGN KEY(`player_id`) REFERENCES `players`(`id`) + ON DELETE CASCADE + ) + ]]) end diff --git a/data-otservbr-global/migrations/5.lua b/data-otservbr-global/migrations/5.lua index 50a02a7feb5..dbc324198dd 100644 --- a/data-otservbr-global/migrations/5.lua +++ b/data-otservbr-global/migrations/5.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 6 (quickloot)") db.query("ALTER TABLE `players` ADD `quickloot_fallback` TINYINT DEFAULT 0") - return true end diff --git a/data-otservbr-global/migrations/6.lua b/data-otservbr-global/migrations/6.lua index 905a02ac146..91766a68ca7 100644 --- a/data-otservbr-global/migrations/6.lua +++ b/data-otservbr-global/migrations/6.lua @@ -4,5 +4,4 @@ function onUpdateDatabase() `player_id` INT(16) NOT NULL, `item_id` INT(16) NOT NULL, `item_count` INT(32) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;]]) - return true -- true = There are others migrations file | false = this is the last migration file end diff --git a/data-otservbr-global/migrations/7.lua b/data-otservbr-global/migrations/7.lua index f5c7e6ed530..cade1faae1f 100644 --- a/data-otservbr-global/migrations/7.lua +++ b/data-otservbr-global/migrations/7.lua @@ -1,5 +1,4 @@ function onUpdateDatabase() logger.info("Updating database to version 8 (recruiter system)") db.query("ALTER TABLE `accounts` ADD `recruiter` INT(6) DEFAULT 0") - return true end diff --git a/data-otservbr-global/migrations/8.lua b/data-otservbr-global/migrations/8.lua index bab5e908756..523b705c0c7 100644 --- a/data-otservbr-global/migrations/8.lua +++ b/data-otservbr-global/migrations/8.lua @@ -26,5 +26,4 @@ function onUpdateDatabase() `UsedRunesBit` VARCHAR(250) NULL , `UnlockedRunesBit` VARCHAR(250) NULL, `tracker list` BLOB NULL ) ENGINE = InnoDB DEFAULT CHARSET=utf8;]]) - return true -- true = There are others migrations file | false = this is the last migration file end diff --git a/data-otservbr-global/migrations/9.lua b/data-otservbr-global/migrations/9.lua index 23aa28daf75..7ce8e189768 100644 --- a/data-otservbr-global/migrations/9.lua +++ b/data-otservbr-global/migrations/9.lua @@ -5,5 +5,4 @@ function onUpdateDatabase() db.query("ALTER TABLE `players` ADD `lookmounthead` tinyint(3) unsigned NOT NULL DEFAULT '0'") db.query("ALTER TABLE `players` ADD `lookmountlegs` tinyint(3) unsigned NOT NULL DEFAULT '0'") db.query("ALTER TABLE `players` ADD `lookfamiliarstype` int(11) unsigned NOT NULL DEFAULT '0'") - return true end diff --git a/data-otservbr-global/migrations/README.md b/data-otservbr-global/migrations/README.md new file mode 100644 index 00000000000..b23473bf29d --- /dev/null +++ b/data-otservbr-global/migrations/README.md @@ -0,0 +1,45 @@ +# Database Migration System + +This document provides an overview of the current database migration system for the project. The migration process has been streamlined to ensure that all migration scripts are automatically applied in order, making it easier to maintain database updates. + +## How It Works + +The migration system is designed to apply updates to the database schema or data whenever a new server version is started. Migration scripts are stored in the `migrations` directory, and the system will automatically apply any scripts that have not yet been executed. + +### Steps Involved + +1. **Retrieve Current Database Version**: + - The system first retrieves the current version of the database using `getDatabaseVersion()`. + - This version is used to determine which migration scripts need to be executed. + +2. **Migration Files Directory**: + - All migration scripts are stored in the `migrations` directory. + - Each migration script is named using a numerical pattern, such as `1.lua`, `2.lua`, etc. + - The naming convention helps determine the order in which scripts should be applied. + +3. **Execute Migration Scripts**: + - The migration system iterates through the migration directory and applies each migration script that has a version greater than the current database version. + - Only scripts that have not been applied are executed. + - The Lua state (`lua_State* L`) is initialized to run each script. + +4. **Update Database Version**: + - After each migration script is successfully applied, the system updates the database version to reflect the applied change. + - This ensures that the script is not re-applied on subsequent server startups. + +## Example Migration Script + +Below is an example of what a migration script might look like. Note that no return value is required, as all migration files are applied based on the current database version. + +```lua +-- Migration script example (for documentation purposes only) +-- This migration script should include all necessary SQL commands or operations to apply a specific update to the database. + +-- Example: Adding a new column to the "players" table +local query = [[ + ALTER TABLE players ADD COLUMN new_feature_flag TINYINT(1) NOT NULL DEFAULT 0; +]] + +-- Execute the query +db.execute(query) -- This function executes the given SQL query on the database. + +-- Note: Ensure that queries are validated to avoid errors during the migration process. diff --git a/src/database/databasemanager.cpp b/src/database/databasemanager.cpp index 2941172f35a..142a1db332a 100644 --- a/src/database/databasemanager.cpp +++ b/src/database/databasemanager.cpp @@ -13,6 +13,19 @@ #include "lua/functions/core/libs/core_libs_functions.hpp" #include "lua/scripts/luascript.hpp" +namespace InternalDBManager { + int32_t extractVersionFromFilename(const std::string &filename) { + std::regex versionRegex(R"((\d+)\.lua)"); + std::smatch match; + + if (std::regex_search(filename, match, versionRegex) && match.size() > 1) { + return std::stoi(match.str(1)); + } + + return -1; + } +} + bool DatabaseManager::optimizeTables() { Database &db = Database::getInstance(); std::ostringstream query; @@ -73,48 +86,62 @@ int32_t DatabaseManager::getDatabaseVersion() { } void DatabaseManager::updateDatabase() { + Benchmark bm; lua_State* L = luaL_newstate(); if (!L) { return; } luaL_openlibs(L); - CoreLibsFunctions::init(L); - int32_t version = getDatabaseVersion(); - do { - std::ostringstream ss; - ss << g_configManager().getString(DATA_DIRECTORY) + "/migrations/" << version << ".lua"; - if (luaL_dofile(L, ss.str().c_str()) != 0) { - g_logger().error("DatabaseManager::updateDatabase - Version: {}" - "] {}", - version, lua_tostring(L, -1)); - break; - } + int32_t currentVersion = getDatabaseVersion(); + std::string migrationDirectory = g_configManager().getString(DATA_DIRECTORY) + "/migrations/"; - if (!LuaScriptInterface::reserveScriptEnv()) { - break; - } + std::vector> migrations; - lua_getglobal(L, "onUpdateDatabase"); - if (lua_pcall(L, 0, 1, 0) != 0) { - LuaScriptInterface::resetScriptEnv(); - g_logger().warn("[DatabaseManager::updateDatabase - Version: {}] {}", version, lua_tostring(L, -1)); - break; + for (const auto &entry : std::filesystem::directory_iterator(migrationDirectory)) { + if (entry.is_regular_file()) { + std::string filename = entry.path().filename().string(); + int32_t fileVersion = InternalDBManager::extractVersionFromFilename(filename); + migrations.emplace_back(fileVersion, entry.path().string()); } + } + + std::sort(migrations.begin(), migrations.end()); + + for (const auto &[fileVersion, scriptPath] : migrations) { + if (fileVersion > currentVersion) { + if (!LuaScriptInterface::reserveScriptEnv()) { + break; + } + + if (luaL_dofile(L, scriptPath.c_str()) != 0) { + g_logger().error("DatabaseManager::updateDatabase - Version: {}] {}", fileVersion, lua_tostring(L, -1)); + continue; + } + + lua_getglobal(L, "onUpdateDatabase"); + if (lua_pcall(L, 0, 1, 0) != 0) { + LuaScriptInterface::resetScriptEnv(); + g_logger().warn("[DatabaseManager::updateDatabase - Version: {}] {}", fileVersion, lua_tostring(L, -1)); + continue; + } + + currentVersion = fileVersion; + g_logger().info("Database has been updated to version {}", currentVersion); + registerDatabaseConfig("db_version", currentVersion); - if (!LuaScriptInterface::getBoolean(L, -1, false)) { LuaScriptInterface::resetScriptEnv(); - break; } + } - version++; - g_logger().info("Database has been updated to version {}", version); - registerDatabaseConfig("db_version", version); - - LuaScriptInterface::resetScriptEnv(); - } while (true); + double duration = bm.duration(); + if (duration < 1000.0) { + g_logger().debug("Database update completed in {:.2f} ms", duration); + } else { + g_logger().debug("Database update completed in {:.2f} seconds", duration / 1000.0); + } lua_close(L); } From 60dd51440cd364454dac1ce5ef6197cca187d223 Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 13 Dec 2024 15:19:25 -0300 Subject: [PATCH 02/11] fix: remove unsupported compiler flags for MSVC (#3173) Resolves warnings issued during the Windows build process with MSVC caused by unsupported compiler flags `(-march, -mtune, -mno-avx, and -mno-sse4)`. --- CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d7ba93bafb2..c24705e51e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,9 +38,10 @@ endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) # Configure build options for compatibility with commodity CPUs -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64 -mtune=generic -mno-avx -mno-sse4") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64 -mtune=generic -mno-avx -mno-sse4") - +if(NOT MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64 -mtune=generic -mno-avx -mno-sse4") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=x86-64 -mtune=generic -mno-avx -mno-sse4") +endif() # ***************************************************************************** # Include cmake tools From 61c1fc08abb9e9fbfecf6bcfb464a09a8214e1a1 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 14 Dec 2024 01:53:28 -0300 Subject: [PATCH 03/11] fix: infinite loop in Zone:randomPosition when no valid tile exist (#3178) This commit fixes an infinite loop issue in the `Zone:randomPosition` function. When no valid positions (walkable tiles) exist in the zone, the function would previously enter an infinite loop. The updated logic now filters all positions upfront to identify walkable tiles, and if none are found, the function returns `nil` with appropriate logging. This change ensures better error handling and prevents server freezes due to infinite loops. --- data/libs/systems/zones.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/data/libs/systems/zones.lua b/data/libs/systems/zones.lua index 698a464fe87..a232a071f41 100644 --- a/data/libs/systems/zones.lua +++ b/data/libs/systems/zones.lua @@ -15,12 +15,24 @@ function Zone:randomPosition() logger.error("Zone:randomPosition() - Zone {} has no positions", self:getName()) return nil end - local destination = positions[math.random(1, #positions)] - local tile = destination:getTile() - while not tile or not tile:isWalkable(false, false, false, false, true) do - destination = positions[math.random(1, #positions)] - tile = destination:getTile() + + local validPositions = {} + for _, position in ipairs(positions) do + local tile = position:getTile() + if tile and tile:isWalkable(false, false, false, false, true) then + table.insert(validPositions, position) + else + logger.debug("Zone:randomPosition() - Position {} is invalid (Tile: {}, Walkable: {})", position, tile or "nil", tile and tile:isWalkable(false, false, false, false, true) or "false") + end end + + if #validPositions == 0 then + logger.error("Zone:randomPosition() - No valid positions in Zone {}", self:getName()) + return nil + end + + local destination = validPositions[math.random(1, #validPositions)] + logger.debug("Zone:randomPosition() - Selected valid position: {}", destination) return destination end From 03f1d1435e5460fd8ef3f277d5fe7bff91d7099c Mon Sep 17 00:00:00 2001 From: Felipe Pessoa Date: Sat, 14 Dec 2024 03:57:27 -0300 Subject: [PATCH 04/11] fix: position after try to cross bridge (#3175) The problem was that the ladder action was executed first, followed immediately by the teleportTo action, causing the Player to move above the ladder with the possibility of crossing the bridge. --- .../scripts/movements/rookgaard/rook_village.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/movements/rookgaard/rook_village.lua b/data-otservbr-global/scripts/movements/rookgaard/rook_village.lua index 3b22a4c2394..b8e9b95f3bd 100644 --- a/data-otservbr-global/scripts/movements/rookgaard/rook_village.lua +++ b/data-otservbr-global/scripts/movements/rookgaard/rook_village.lua @@ -6,7 +6,7 @@ function rookVillage.onStepIn(creature, item, position, fromPosition) return true end - player:teleportTo(Position(player:getPosition().x, player:getPosition().y - 1, player:getPosition().z)) + player:teleportTo(Position(player:getPosition().x, player:getPosition().y - 3, player:getPosition().z + 1)) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have any business there anymore.") return true From 05ff8be4219afb7a411e3418055c502184e50e29 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 14 Dec 2024 05:31:41 -0300 Subject: [PATCH 05/11] enhance: Monster::getDanceStep code duplication (#2997) Refactored the `getDanceStep` function to avoid code duplication by introducing a helper function `tryAddDirection`. This change improves code readability and maintainability by reducing repetitive logic. --- src/creatures/monsters/monster.cpp | 57 ++++++++---------------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index d30c2a33471..690ebd10b39 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -1581,73 +1581,44 @@ bool Monster::getDanceStep(const Position &creaturePos, Direction &moveDirection uint32_t centerToDist = std::max(distance_x, distance_y); // monsters not at targetDistance shouldn't dancestep - if (centerToDist < (uint32_t)targetDistance) { + if (centerToDist < static_cast(targetDistance)) { return false; } std::vector dirList; - if (!keepDistance || offset_y >= 0) { - uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() - 1) - centerPos.getY())); - if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_NORTH)) { + auto tryAddDirection = [&](Direction direction, int_fast32_t newX, int_fast32_t newY) { + uint32_t tmpDist = std::max(std::abs(newX - centerPos.getX()), std::abs(newY - centerPos.getY())); + if (tmpDist == centerToDist && canWalkTo(creaturePos, direction)) { bool result = true; if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); + result = (!canDoAttackNow || canUseAttack(Position(newX, newY, creaturePos.z), attackedCreature)); } if (result) { - dirList.push_back(DIRECTION_NORTH); + dirList.emplace_back(direction); } } + }; + + if (!keepDistance || offset_y >= 0) { + tryAddDirection(DIRECTION_NORTH, creaturePos.getX(), creaturePos.getY() - 1); } if (!keepDistance || offset_y <= 0) { - uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() + 1) - centerPos.getY())); - if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_SOUTH)) { - bool result = true; - - if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); - } - - if (result) { - dirList.push_back(DIRECTION_SOUTH); - } - } + tryAddDirection(DIRECTION_SOUTH, creaturePos.getX(), creaturePos.getY() + 1); } if (!keepDistance || offset_x <= 0) { - uint32_t tmpDist = std::max(std::abs((creaturePos.getX() + 1) - centerPos.getX()), distance_y); - if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_EAST)) { - bool result = true; - - if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); - } - - if (result) { - dirList.push_back(DIRECTION_EAST); - } - } + tryAddDirection(DIRECTION_EAST, creaturePos.getX() + 1, creaturePos.getY()); } if (!keepDistance || offset_x >= 0) { - uint32_t tmpDist = std::max(std::abs((creaturePos.getX() - 1) - centerPos.getX()), distance_y); - if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_WEST)) { - bool result = true; - - if (keepAttack) { - result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); - } - - if (result) { - dirList.push_back(DIRECTION_WEST); - } - } + tryAddDirection(DIRECTION_WEST, creaturePos.getX() - 1, creaturePos.getY()); } if (!dirList.empty()) { - std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); + std::ranges::shuffle(dirList, getRandomGenerator()); moveDirection = dirList[uniform_random(0, dirList.size() - 1)]; return true; } From f2750bb8eac8d95c26788b15619210fea8e6b3d2 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Fri, 20 Dec 2024 23:58:41 -0300 Subject: [PATCH 06/11] fix: hazard spawn initialization (#3184) Fixes bugs introduced here: https://github.com/opentibiabr/canary/pull/3076 By mistake I removed the onSpawn from the hazard and didn't realize it. --- data/libs/systems/hazard.lua | 12 ++++++------ data/scripts/lib/register_monster_type.lua | 6 +++++- src/creatures/monsters/monster.cpp | 7 ++++--- src/creatures/monsters/monster.hpp | 2 +- src/creatures/monsters/spawns/spawn_monster.cpp | 2 +- src/lua/functions/core/game/game_functions.cpp | 2 +- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/data/libs/systems/hazard.lua b/data/libs/systems/hazard.lua index a1501b2d1fd..387aef874c2 100644 --- a/data/libs/systems/hazard.lua +++ b/data/libs/systems/hazard.lua @@ -193,16 +193,16 @@ function HazardMonster.onSpawn(monster, position) if not zones then return true end + + logger.debug("Monster {} spawned in hazard zone, position {}", monster:getName(), position:toString()) for _, zone in ipairs(zones) do local hazard = Hazard.getByName(zone:getName()) if hazard then monster:hazard(true) - if hazard then - monster:hazardCrit(hazard.crit) - monster:hazardDodge(hazard.dodge) - monster:hazardDamageBoost(hazard.damageBoost) - monster:hazardDefenseBoost(hazard.defenseBoost) - end + monster:hazardCrit(hazard.crit) + monster:hazardDodge(hazard.dodge) + monster:hazardDamageBoost(hazard.damageBoost) + monster:hazardDefenseBoost(hazard.defenseBoost) end end return true diff --git a/data/scripts/lib/register_monster_type.lua b/data/scripts/lib/register_monster_type.lua index ed5de6421d9..81960f4f84d 100644 --- a/data/scripts/lib/register_monster_type.lua +++ b/data/scripts/lib/register_monster_type.lua @@ -17,6 +17,10 @@ end registerMonsterType.name = function(mtype, mask) if mask.name then mtype:name(mask.name) + -- Try register hazard monsters + mtype.onSpawn = function(monster, spawnPosition) + HazardMonster.onSpawn(monster, spawnPosition) + end end end registerMonsterType.description = function(mtype, mask) @@ -194,7 +198,7 @@ registerMonsterType.flags = function(mtype, mask) end if mask.flags.rewardBoss then mtype:isRewardBoss(mask.flags.rewardBoss) - mtype.onSpawn = function(monster) + mtype.onSpawn = function(monster, spawnPosition) monster:setRewardBoss() end end diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 690ebd10b39..fed817af239 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -501,9 +501,9 @@ void Monster::onAttackedByPlayer(const std::shared_ptr &attackerPlayer) } } -void Monster::onSpawn() { +void Monster::onSpawn(const Position &position) { if (mType->info.spawnEvent != -1) { - // onSpawn(self) + // onSpawn(self, spawnPosition) LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { g_logger().error("Monster {} creature {}] Call stack overflow. Too many lua " @@ -520,8 +520,9 @@ void Monster::onSpawn() { LuaScriptInterface::pushUserdata(L, getMonster()); LuaScriptInterface::setMetatable(L, -1, "Monster"); + LuaScriptInterface::pushPosition(L, position); - scriptInterface->callVoidFunction(1); + scriptInterface->callVoidFunction(2); } } diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 145b973fdc9..6e44d8f36bf 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -91,7 +91,7 @@ class Monster final : public Creature { void onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) override; void onCreatureSay(const std::shared_ptr &creature, SpeakClasses type, const std::string &text) override; void onAttackedByPlayer(const std::shared_ptr &attackerPlayer); - void onSpawn(); + void onSpawn(const Position &position); void drainHealth(const std::shared_ptr &attacker, int32_t damage) override; void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 3a915d737b2..b80266f56ca 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -232,7 +232,7 @@ bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, spawnBlock_t &sb, const spawnedMonsterMap[spawnMonsterId] = monster; sb.lastSpawn = OTSYS_TIME(); - monster->onSpawn(); + monster->onSpawn(sb.pos); return true; } diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index aad4b71d06c..eec876eee7c 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -524,7 +524,7 @@ int GameFunctions::luaGameCreateMonster(lua_State* L) { const bool extended = Lua::getBoolean(L, 3, false); const bool force = Lua::getBoolean(L, 4, false); if (g_game().placeCreature(monster, position, extended, force)) { - monster->onSpawn(); + monster->onSpawn(position); const auto &mtype = monster->getMonsterType(); if (mtype && mtype->info.raceid > 0 && mtype->info.bosstiaryRace == BosstiaryRarity_t::RARITY_ARCHFOE) { for (const auto &spectator : Spectators().find(monster->getPosition(), true)) { From 13ae9e55224421bbb1a01eaa1706042a0961da80 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 21 Dec 2024 00:47:32 -0300 Subject: [PATCH 07/11] fix: suppress get byte log (#3185) Resolves #3153 This simple "suppress" the "getByte" log, some locations send "0" by default, so we don't need to send log in these locations. --- src/server/network/message/networkmessage.cpp | 8 +++++--- src/server/network/message/networkmessage.hpp | 2 +- src/server/network/protocol/protocolgame.cpp | 16 ++++++++-------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/server/network/message/networkmessage.cpp b/src/server/network/message/networkmessage.cpp index 0fb8b63324f..4f203dc4551 100644 --- a/src/server/network/message/networkmessage.cpp +++ b/src/server/network/message/networkmessage.cpp @@ -45,10 +45,12 @@ int32_t NetworkMessage::decodeHeader() { } // Simply read functions for incoming message -uint8_t NetworkMessage::getByte(const std::source_location &location /*= std::source_location::current()*/) { +uint8_t NetworkMessage::getByte(bool suppresLog /*= false*/, const std::source_location &location /*= std::source_location::current()*/) { // Check if there is at least 1 byte to read if (!canRead(1)) { - g_logger().error("[{}] Not enough data to read a byte. Current position: {}, Length: {}. Called line {}:{} in {}", __FUNCTION__, info.position, info.length, location.line(), location.column(), location.function_name()); + if (!suppresLog) { + g_logger().error("[{}] Not enough data to read a byte. Current position: {}, Length: {}. Called line {}:{} in {}", __FUNCTION__, info.position, info.length, location.line(), location.column(), location.function_name()); + } return {}; } @@ -113,7 +115,7 @@ Position NetworkMessage::getPosition() { Position pos; pos.x = get(); pos.y = get(); - pos.z = getByte(); + pos.z = getByte(true); return pos; } diff --git a/src/server/network/message/networkmessage.hpp b/src/server/network/message/networkmessage.hpp index f738de8506b..6549eec8ea5 100644 --- a/src/server/network/message/networkmessage.hpp +++ b/src/server/network/message/networkmessage.hpp @@ -34,7 +34,7 @@ class NetworkMessage { } // simply read functions for incoming message - uint8_t getByte(const std::source_location &location = std::source_location::current()); + uint8_t getByte(bool suppresLog = false, const std::source_location &location = std::source_location::current()); uint8_t getPreviousByte(); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 9c09f5ead38..dc02e316f1f 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -1713,14 +1713,14 @@ void ProtocolGame::parseSetOutfit(NetworkMessage &msg) { } void ProtocolGame::parseToggleMount(NetworkMessage &msg) { - bool mount = msg.getByte() != 0; + bool mount = msg.getByte(true) != 0; g_game().playerToggleMount(player->getID(), mount); } void ProtocolGame::parseApplyImbuement(NetworkMessage &msg) { uint8_t slot = msg.getByte(); auto imbuementId = msg.get(); - bool protectionCharm = msg.getByte() != 0x00; + bool protectionCharm = msg.getByte(true) != 0x00; g_game().playerApplyImbuement(player->getID(), imbuementId, slot, protectionCharm); } @@ -1967,8 +1967,8 @@ void ProtocolGame::parsePlayerBuyOnShop(NetworkMessage &msg) { auto id = msg.get(); uint8_t count = msg.getByte(); uint16_t amount = oldProtocol ? static_cast(msg.getByte()) : msg.get(); - bool ignoreCap = msg.getByte() != 0; - bool inBackpacks = msg.getByte() != 0; + bool ignoreCap = msg.getByte(true) != 0; + bool inBackpacks = msg.getByte(true) != 0; g_game().playerBuyItem(player->getID(), id, count, amount, ignoreCap, inBackpacks); } @@ -1976,7 +1976,7 @@ void ProtocolGame::parsePlayerSellOnShop(NetworkMessage &msg) { auto id = msg.get(); uint8_t count = std::max(msg.getByte(), (uint8_t)1); uint16_t amount = oldProtocol ? static_cast(msg.getByte()) : msg.get(); - bool ignoreEquipped = msg.getByte() != 0; + bool ignoreEquipped = msg.getByte(true) != 0; g_game().playerSellItem(player->getID(), id, count, amount, ignoreEquipped); } @@ -2010,7 +2010,7 @@ void ProtocolGame::parseEditVip(NetworkMessage &msg) { auto guid = msg.get(); const std::string description = msg.getString(); uint32_t icon = std::min(10, msg.get()); // 10 is max icon in 9.63 - bool notify = msg.getByte() != 0; + bool notify = msg.getByte(true) != 0; uint8_t groupsAmount = msg.getByte(); for (uint8_t i = 0; i < groupsAmount; ++i) { uint8_t groupId = msg.getByte(); @@ -2176,7 +2176,7 @@ void ProtocolGame::parseTaskHuntingAction(NetworkMessage &msg) { uint8_t slot = msg.getByte(); uint8_t action = msg.getByte(); - bool upgrade = msg.getByte() != 0; + bool upgrade = msg.getByte(true) != 0; auto raceId = msg.get(); if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { @@ -3141,7 +3141,7 @@ void ProtocolGame::parseMarketCreateOffer(NetworkMessage &msg) { auto amount = msg.get(); uint64_t price = oldProtocol ? static_cast(msg.get()) : msg.get(); - bool anonymous = (msg.getByte() != 0); + bool anonymous = (msg.getByte(true) != 0); if (amount > 0 && price > 0) { g_game().playerCreateMarketOffer(player->getID(), type, itemId, amount, price, itemTier, anonymous); } From ba2c41f7ff952b3db971b65738b3d4fc49a2193c Mon Sep 17 00:00:00 2001 From: Renato Machado Date: Sat, 21 Dec 2024 02:14:16 -0300 Subject: [PATCH 08/11] fix: death call several times (#3186) fix #3177 --- src/creatures/creature.cpp | 4 ++++ src/creatures/creature.hpp | 6 +++++- src/creatures/monsters/monster.cpp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 586f5b8c1b2..4d02f54c364 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -711,6 +711,10 @@ std::shared_ptr Creature::getCorpse(const std::shared_ptr &, con } void Creature::changeHealth(int32_t healthChange, bool sendHealthChange /* = true*/) { + if (isLifeless()) { + return; + } + int32_t oldHealth = health; if (healthChange > 0) { diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 35f39292609..bea95313f8a 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -209,7 +209,11 @@ class Creature : virtual public Thing, public SharedObject { } bool isAlive() const { - return !isDead(); + return !isLifeless(); + } + + bool isLifeless() const { + return health <= 0; } virtual int32_t getMaxHealth() const { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index fed817af239..13b30321a21 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -1110,7 +1110,7 @@ void Monster::onThink_async() { void Monster::doAttacking(uint32_t interval) { const auto &attackedCreature = getAttackedCreature(); - if (!attackedCreature || (isSummon() && attackedCreature.get() == this)) { + if (!attackedCreature || attackedCreature->isLifeless() || (isSummon() && attackedCreature.get() == this)) { return; } From 145da70d0136e837b4d4926d079471b784e9598b Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 21 Dec 2024 18:46:57 -0300 Subject: [PATCH 09/11] fix: warning on decode lenght (#3188) Resolves #3002 --- src/security/rsa.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/security/rsa.cpp b/src/security/rsa.cpp index 18e1f6e8393..f148ae6d952 100644 --- a/src/security/rsa.cpp +++ b/src/security/rsa.cpp @@ -170,11 +170,17 @@ uint16_t RSA::decodeLength(char*&pos) const { if (length & 0x80) { uint8_t numLengthBytes = length & 0x7F; if (numLengthBytes > 4) { - g_logger().error("[RSA::loadPEM] - Invalid 'length'"); + g_logger().error("[RSA::decodeLength] - Invalid 'length'"); return 0; } - // Copy 'numLengthBytes' bytes from 'pos' into 'buffer', starting at the correct position - std::ranges::copy_n(pos, numLengthBytes, buffer.begin() + (4 - numLengthBytes)); + // Adjust the copy destination to ensure it doesn't overflow + auto destIt = buffer.begin() + (4 - numLengthBytes); + if (destIt < buffer.begin() || destIt + numLengthBytes > buffer.end()) { + g_logger().error("[RSA::decodeLength] - Invalid copy range"); + return 0; + } + // Copy 'numLengthBytes' bytes from 'pos' into 'buffer' + std::copy_n(pos, numLengthBytes, destIt); pos += numLengthBytes; // Reconstruct 'length' from 'buffer' (big-endian) uint32_t tempLength = 0; @@ -182,7 +188,7 @@ uint16_t RSA::decodeLength(char*&pos) const { tempLength = (tempLength << 8) | buffer[4 - numLengthBytes + i]; } if (tempLength > UINT16_MAX) { - g_logger().error("[RSA::loadPEM] - Length too large"); + g_logger().error("[RSA::decodeLength] - Length too large"); return 0; } length = static_cast(tempLength); From f81e5cd5faff04bbedc39c3a3dccd71ed4703787 Mon Sep 17 00:00:00 2001 From: Jean Carlo de Souza Date: Fri, 27 Dec 2024 12:25:00 -0300 Subject: [PATCH 10/11] fix: for lava tiles in entrance of The Pits of Inferno Quest (#3196) Implements an alternative solution to handle lava tiles in The Pits of Inferno Quest. The logic in register_actions.lua has been modified to transform existing lava items (ID: 21477) into the floor item (ID: 5815) using Tile:getItemById and Item:transform, instead of relying on Game.createItem. This addresses an issue where lava tiles in The Pits of Inferno Quest were not behaving as expected Resolves #3144 --- data-otservbr-global/scripts/lib/register_actions.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data-otservbr-global/scripts/lib/register_actions.lua b/data-otservbr-global/scripts/lib/register_actions.lua index a65de89e017..ec68c90e27c 100644 --- a/data-otservbr-global/scripts/lib/register_actions.lua +++ b/data-otservbr-global/scripts/lib/register_actions.lua @@ -704,7 +704,10 @@ function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) -- The Pits of Inferno Quest if toPosition == Position(32808, 32334, 11) then for i = 1, #lava do - Game.createItem(5815, 1, lava[i]) + local lavaTile = Tile(lava[i]):getItemById(21477) + if lavaTile then + lavaTile:transform(5815) + end end target:transform(3141) toPosition:sendMagicEffect(CONST_ME_SMOKE) From f8212483983c8da2e5a490822cf3650dbb4cd739 Mon Sep 17 00:00:00 2001 From: Pedro Cruz Date: Sun, 29 Dec 2024 15:21:45 -0300 Subject: [PATCH 11/11] fix: party shared experience (#3133) This fixes the party shared experience that was not providing the correct amount of experience giving the unique vocations count and party size. --- data/events/scripts/party.lua | 27 ++++++------------- src/creatures/players/grouping/party.cpp | 26 ++++++++++++++++-- src/creatures/players/grouping/party.hpp | 1 + .../creatures/player/party_functions.cpp | 12 +++++++++ .../creatures/player/party_functions.hpp | 1 + 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/data/events/scripts/party.lua b/data/events/scripts/party.lua index b27965994b8..b94613c070a 100644 --- a/data/events/scripts/party.lua +++ b/data/events/scripts/party.lua @@ -66,25 +66,14 @@ function Party:onDisband() end function Party:onShareExperience(exp) - local sharedExperienceMultiplier = 1.20 --20% - local vocationsIds = {} + local uniqueVocationsCount = self:getUniqueVocationsCount() + local partySize = self:getMemberCount() + 1 - local vocationId = self:getLeader():getVocation():getBase():getId() - if vocationId ~= VOCATION_NONE then - table.insert(vocationsIds, vocationId) - end - - for _, member in ipairs(self:getMembers()) do - vocationId = member:getVocation():getBase():getId() - if not table.contains(vocationsIds, vocationId) and vocationId ~= VOCATION_NONE then - table.insert(vocationsIds, vocationId) - end - end - - local size = #vocationsIds - if size > 1 then - sharedExperienceMultiplier = 1.0 + ((size * (5 * (size - 1) + 10)) / 100) - end + -- Formula to calculate the % based on the vocations amount + local sharedExperienceMultiplier = ((0.1 * (uniqueVocationsCount ^ 2)) - (0.2 * uniqueVocationsCount) + 1.3) + -- Since the formula its non linear, we need to subtract 0.1 if all vocations are present, + -- because on all vocations the multiplier is 2.1 and it should be 2.0 + sharedExperienceMultiplier = partySize < 4 and sharedExperienceMultiplier or sharedExperienceMultiplier - 0.1 - return math.ceil((exp * sharedExperienceMultiplier) / (#self:getMembers() + 1)) + return math.ceil((exp * sharedExperienceMultiplier) / partySize) end diff --git a/src/creatures/players/grouping/party.cpp b/src/creatures/players/grouping/party.cpp index e63ca14b629..c04f8d57909 100644 --- a/src/creatures/players/grouping/party.cpp +++ b/src/creatures/players/grouping/party.cpp @@ -12,6 +12,7 @@ #include "config/configmanager.hpp" #include "creatures/creature.hpp" #include "creatures/players/player.hpp" +#include "creatures/players/vocations/vocation.hpp" #include "game/game.hpp" #include "game/movement/position.hpp" #include "lua/callbacks/event_callback.hpp" @@ -61,6 +62,25 @@ size_t Party::getInvitationCount() const { return inviteList.size(); } +uint8_t Party::getUniqueVocationsCount() const { + std::unordered_set uniqueVocations; + + for (const auto &player : getPlayers()) { + if (uniqueVocations.size() >= 4) { + break; + } + + const auto &vocation = player->getVocation(); + if (!vocation) { + continue; + } + + uniqueVocations.insert(vocation->getBaseId()); + } + + return uniqueVocations.size(); +} + void Party::disband() { if (!g_events().eventPartyOnDisband(getParty())) { return; @@ -504,9 +524,11 @@ void Party::shareExperience(uint64_t experience, const std::shared_ptr g_callbacks().executeCallback(EventCallback_t::partyOnShareExperience, &EventCallback::partyOnShareExperience, getParty(), std::ref(shareExperience)); for (const auto &member : getMembers()) { - member->onGainSharedExperience(shareExperience, target); + const auto memberStaminaBoost = static_cast(member->getStaminaXpBoost()) / 100; + member->onGainSharedExperience(shareExperience * memberStaminaBoost, target); } - leader->onGainSharedExperience(shareExperience, target); + const auto leaderStaminaBoost = static_cast(leader->getStaminaXpBoost()) / 100; + leader->onGainSharedExperience(shareExperience * leaderStaminaBoost, target); } bool Party::canUseSharedExperience(const std::shared_ptr &player) { diff --git a/src/creatures/players/grouping/party.hpp b/src/creatures/players/grouping/party.hpp index cef450c0c77..bc1ba77d135 100644 --- a/src/creatures/players/grouping/party.hpp +++ b/src/creatures/players/grouping/party.hpp @@ -40,6 +40,7 @@ class Party final : public SharedObject { std::vector> getInvitees(); size_t getMemberCount() const; size_t getInvitationCount() const; + uint8_t getUniqueVocationsCount() const; void disband(); bool invitePlayer(const std::shared_ptr &player); diff --git a/src/lua/functions/creatures/player/party_functions.cpp b/src/lua/functions/creatures/player/party_functions.cpp index 5c43593282a..a44e0129b94 100644 --- a/src/lua/functions/creatures/player/party_functions.cpp +++ b/src/lua/functions/creatures/player/party_functions.cpp @@ -24,6 +24,7 @@ void PartyFunctions::init(lua_State* L) { Lua::registerMethod(L, "Party", "getMemberCount", PartyFunctions::luaPartyGetMemberCount); Lua::registerMethod(L, "Party", "getInvitees", PartyFunctions::luaPartyGetInvitees); Lua::registerMethod(L, "Party", "getInviteeCount", PartyFunctions::luaPartyGetInviteeCount); + Lua::registerMethod(L, "Party", "getUniqueVocationsCount", PartyFunctions::luaPartyGetUniqueVocationsCount); Lua::registerMethod(L, "Party", "addInvite", PartyFunctions::luaPartyAddInvite); Lua::registerMethod(L, "Party", "removeInvite", PartyFunctions::luaPartyRemoveInvite); Lua::registerMethod(L, "Party", "addMember", PartyFunctions::luaPartyAddMember); @@ -162,6 +163,17 @@ int PartyFunctions::luaPartyGetInviteeCount(lua_State* L) { return 1; } +int PartyFunctions::luaPartyGetUniqueVocationsCount(lua_State* L) { + // party:getUniqueVocationsCount() + const auto &party = Lua::getUserdataShared(L, 1); + if (party) { + lua_pushnumber(L, party->getUniqueVocationsCount()); + } else { + lua_pushnil(L); + } + return 1; +} + int PartyFunctions::luaPartyAddInvite(lua_State* L) { // party:addInvite(player) const auto &player = Lua::getPlayer(L, 2); diff --git a/src/lua/functions/creatures/player/party_functions.hpp b/src/lua/functions/creatures/player/party_functions.hpp index 52801e349d6..92e629f71a1 100644 --- a/src/lua/functions/creatures/player/party_functions.hpp +++ b/src/lua/functions/creatures/player/party_functions.hpp @@ -22,6 +22,7 @@ class PartyFunctions { static int luaPartyGetMemberCount(lua_State* L); static int luaPartyGetInvitees(lua_State* L); static int luaPartyGetInviteeCount(lua_State* L); + static int luaPartyGetUniqueVocationsCount(lua_State* L); static int luaPartyAddInvite(lua_State* L); static int luaPartyRemoveInvite(lua_State* L); static int luaPartyAddMember(lua_State* L);