From f21cc170df7284e38227ac0d789ab7425d18e947 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 19 Jan 2025 20:10:19 -0400 Subject: [PATCH] [Feature] Evolving Item Support for RoF2 (#4496) * basic evolving items framework created * Implement evolving tab in the inventory window * Implement experience and number of kills * Move zone evolving map to a evolvingitemsmanager class * rework gm commands * rework GetInventory * wip * wip loot testing * Fix Duplicate Message * reworked evolving item looting, swapping, etc * reworked const functions for evolving methods * Functioning Player Trade of evolving items test item_id is 89550 * First pass of Final Result link working * First pass of item upgrading when reaching 100% * Add strings and logic for displaying the evolving item xp transfer window in Corathus * Prototype of xp transfer window sending items * WIP for evolve xp transfer * WIP for evolve xp transfer. First tests passed * XP Transfer Cleanup * XP Transfer Cleanup * Add Rule for evolving items equip timer/ default is 30 secs * Add logging and player events Add logging and player events * Formatting * Database updates * Updates for linux build * Perl/Cleanup * Command cleanup * Lua * Added a crash condition check if final item id is blank or not found. * Review Changes Updates to resolve review comments and a rebase. * migrate to content_db for items_evolving_details migrate to content_db for items_evolving_details * Simplify, don't hit database unless evolving * Update 2025_01_19_items_evolving_details.sql * Update client.cpp * Update manifest with items_evolving_details * character_id vs char_id * Remove _Struct from structs * Remove license header in evolving.cpp * Move evolving constants from eq_constants.h to evolving.h since it is more specific * Update database_schema.h * General cleanup * Be more specific with `evolving_items` vs `evolving` --------- Co-authored-by: Kinglykrab Co-authored-by: Akkadius --- client_files/export/main.cpp | 12 +- client_files/import/main.cpp | 12 +- common/CMakeLists.txt | 6 + common/database/database_update_manifest.cpp | 484 ++++++++++++++++++ common/database_schema.h | 3 + common/emu_oplist.h | 1 + common/eq_constants.h | 1 + common/eq_packet_structs.h | 64 +++ common/eqemu_logsys.h | 4 +- common/eqemu_logsys_log_aliases.h | 10 + common/events/player_event_logs.cpp | 1 + common/events/player_events.h | 27 +- common/evolving_items.cpp | 303 +++++++++++ common/evolving_items.h | 67 +++ common/inventory_profile.cpp | 194 +++++++ common/inventory_profile.h | 9 + common/item_instance.cpp | 107 ++-- common/item_instance.h | 104 ++-- common/patches/rof2.cpp | 74 ++- common/patches/rof2_ops.h | 1 + common/patches/rof2_structs.h | 41 +- ...base_character_evolving_items_repository.h | 475 +++++++++++++++++ .../base_items_evolving_details_repository.h | 451 ++++++++++++++++ .../character_evolving_items_repository.h | 66 +++ .../items_evolving_details_repository.h | 14 + common/ruletypes.h | 7 + common/shareddb.cpp | 80 ++- common/shareddb.h | 4 +- common/version.h | 2 +- shared_memory/main.cpp | 12 +- utils/patches/patch_RoF2.conf | 3 + world/main.cpp | 2 + zone/CMakeLists.txt | 1 + zone/attack.cpp | 2 +- zone/client.cpp | 3 +- zone/client.h | 19 +- zone/client_evolving_items.cpp | 451 ++++++++++++++++ zone/client_packet.cpp | 45 +- zone/client_packet.h | 1 + zone/command.cpp | 2 + zone/command.h | 2 + zone/doors.cpp | 5 + zone/doors.h | 2 + zone/exp.cpp | 11 +- zone/gm_commands/evolving_items.cpp | 301 +++++++++++ zone/inventory.cpp | 26 +- zone/lua_iteminst.cpp | 111 +++- zone/lua_iteminst.h | 16 +- zone/main.cpp | 7 + zone/perl_questitem.cpp | 96 +++- zone/string_ids.h | 5 + zone/trading.cpp | 3 + zone/zonedb.cpp | 2 +- 53 files changed, 3569 insertions(+), 183 deletions(-) create mode 100644 common/evolving_items.cpp create mode 100644 common/evolving_items.h create mode 100644 common/repositories/base/base_character_evolving_items_repository.h create mode 100644 common/repositories/base/base_items_evolving_details_repository.h create mode 100644 common/repositories/character_evolving_items_repository.h create mode 100644 common/repositories/items_evolving_details_repository.h create mode 100644 zone/client_evolving_items.cpp create mode 100644 zone/gm_commands/evolving_items.cpp diff --git a/client_files/export/main.cpp b/client_files/export/main.cpp index e90e6c76c..a930d5404 100644 --- a/client_files/export/main.cpp +++ b/client_files/export/main.cpp @@ -36,12 +36,14 @@ #include "../../common/file.h" #include "../../common/events/player_event_logs.h" #include "../../common/skill_caps.h" +#include "../../common/evolving_items.h" -EQEmuLogSys LogSys; -WorldContentService content_service; -ZoneStore zone_store; -PathManager path; -PlayerEventLogs player_event_logs; +EQEmuLogSys LogSys; +WorldContentService content_service; +ZoneStore zone_store; +PathManager path; +PlayerEventLogs player_event_logs; +EvolvingItemsManager evolving_items_manager; void ExportSpells(SharedDatabase *db); void ExportSkillCaps(SharedDatabase *db); diff --git a/client_files/import/main.cpp b/client_files/import/main.cpp index 0d8010221..74f447a23 100644 --- a/client_files/import/main.cpp +++ b/client_files/import/main.cpp @@ -30,12 +30,14 @@ #include "../../common/repositories/base_data_repository.h" #include "../../common/file.h" #include "../../common/events/player_event_logs.h" +#include "../../common/evolving_items.h" -EQEmuLogSys LogSys; -WorldContentService content_service; -ZoneStore zone_store; -PathManager path; -PlayerEventLogs player_event_logs; +EQEmuLogSys LogSys; +WorldContentService content_service; +ZoneStore zone_store; +PathManager path; +PlayerEventLogs player_event_logs; +EvolvingItemsManager evolving_items_manager; void ImportSpells(SharedDatabase *db); void ImportSkillCaps(SharedDatabase *db); diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 0a1766fc8..ff130adb0 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -39,6 +39,7 @@ SET(common_sources event_sub.cpp events/player_event_logs.cpp events/player_event_discord_formatter.cpp + evolving_items.cpp expedition_lockout_timer.cpp extprofile.cpp discord/discord_manager.cpp @@ -172,6 +173,7 @@ SET(repositories repositories/base/base_character_currency_repository.h repositories/base/base_character_data_repository.h repositories/base/base_character_disciplines_repository.h + repositories/base/base_character_evolving_items_repository.h repositories/base/base_character_expedition_lockouts_repository.h repositories/base/base_character_exp_modifiers_repository.h repositories/base/base_character_inspect_messages_repository.h @@ -240,6 +242,7 @@ SET(repositories repositories/base/base_inventory_snapshots_repository.h repositories/base/base_ip_exemptions_repository.h repositories/base/base_items_repository.h + repositories/base/base_items_evolving_details_repository.h repositories/base/base_ldon_trap_entries_repository.h repositories/base/base_ldon_trap_templates_repository.h repositories/base/base_level_exp_mods_repository.h @@ -355,6 +358,7 @@ SET(repositories repositories/character_currency_repository.h repositories/character_data_repository.h repositories/character_disciplines_repository.h + repositories/character_evolving_items_repository.h repositories/character_expedition_lockouts_repository.h repositories/character_exp_modifiers_repository.h repositories/character_inspect_messages_repository.h @@ -423,6 +427,7 @@ SET(repositories repositories/inventory_snapshots_repository.h repositories/ip_exemptions_repository.h repositories/items_repository.h + repositories/items_evolving_details_repository.h repositories/ldon_trap_entries_repository.h repositories/ldon_trap_templates_repository.h repositories/level_exp_mods_repository.h @@ -555,6 +560,7 @@ SET(common_headers events/player_event_discord_formatter.h events/player_events.h event_sub.h + evolving_items.h expedition_lockout_timer.h extprofile.h faction.h diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index cd484122b..8e17fb951 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5803,6 +5803,490 @@ ALTER TABLE `npc_types` MODIFY COLUMN `walkspeed` float NOT NULL DEFAULT 0; .sql = R"( ALTER TABLE `zone` ADD COLUMN `shard_at_player_count` int(11) NULL DEFAULT 0 AFTER `seconds_before_idle`; +)", + .content_schema_update = true + }, + ManifestEntry{ + .version = 9289, + .description = "2025_01_19_evolving_items__character_evolving_items", + .check = "SHOW TABLES LIKE 'character_evolving_items'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `character_evolving_items` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `character_id` INT(10) UNSIGNED NULL DEFAULT '0', + `item_id` INT(10) UNSIGNED NULL DEFAULT '0', + `activated` TINYINT(1) UNSIGNED NULL DEFAULT '0', + `equipped` TINYINT(3) UNSIGNED NULL DEFAULT '0', + `current_amount` BIGINT(20) NULL DEFAULT '0', + `progression` DOUBLE(22,0) NULL DEFAULT '0', + `final_item_id` INT(10) UNSIGNED NULL DEFAULT '0', + `deleted_at` DATETIME NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1 +; +)" + }, + ManifestEntry{ + .version = 9290, + .description = "2025_01_19_evolving_items__items_evolving_details", + .check = "SHOW TABLES LIKE 'items_evolving_details'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `items_evolving_details` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `item_evo_id` int(10) unsigned DEFAULT 0 COMMENT 'items.evoid', + `item_evolve_level` int(10) unsigned DEFAULT 0 COMMENT 'items.evolvinglevel', + `item_id` int(10) unsigned DEFAULT 0 COMMENT 'items.id', + `type` int(10) unsigned DEFAULT 0, + `sub_type` int(10) unsigned DEFAULT 0, + `required_amount` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; +INSERT INTO `items_evolving_details` VALUES +(1,1000,1,86900,99,1,0), +(2,1000,2,86901,99,1,0), +(3,1000,3,86902,99,1,0), +(4,1000,4,86903,99,1,0), +(5,1000,5,86904,99,1,0), +(6,1001,1,86910,99,1,0), +(7,1001,2,86911,99,1,0), +(8,1001,3,86912,99,1,0), +(9,1001,4,86913,99,1,0), +(10,1001,5,86914,99,1,0), +(11,1001,6,86915,99,1,0), +(12,1001,7,86916,99,1,0), +(13,1002,1,86920,99,1,0), +(14,1002,2,86921,99,1,0), +(15,1002,3,86922,99,1,0), +(16,1002,4,86923,99,1,0), +(17,1002,5,86924,99,1,0), +(18,1002,6,86925,99,1,0), +(19,1002,7,86926,99,1,0), +(20,1003,1,86930,99,1,0), +(21,1003,2,86931,99,1,0), +(22,1003,3,86932,99,1,0), +(23,1003,4,86933,99,1,0), +(24,1003,5,86934,99,1,0), +(25,1003,6,86935,99,1,0), +(26,1004,1,86940,99,1,0), +(27,1004,2,86941,99,1,0), +(28,1004,3,86942,99,1,0), +(29,1004,4,86943,99,1,0), +(30,1004,5,86944,99,1,0), +(31,1004,6,86945,99,1,0), +(32,1005,1,86950,99,1,0), +(33,1005,2,86951,99,1,0), +(34,1005,3,86952,99,1,0), +(35,1005,4,86953,99,1,0), +(36,1005,5,86954,99,1,0), +(37,1005,6,86955,99,1,0), +(38,1005,7,86956,99,1,0), +(39,1006,1,86960,99,1,0), +(40,1006,2,86961,99,1,0), +(41,1006,3,86962,99,1,0), +(42,1006,4,86963,99,1,0), +(43,1006,5,86964,99,1,0), +(44,1006,6,86965,99,1,0), +(45,1006,7,86966,99,1,0), +(46,1007,1,86970,99,1,0), +(47,1007,2,86971,99,1,0), +(48,1007,3,86972,99,1,0), +(49,1007,4,86973,99,1,0), +(50,1007,5,86974,99,1,0), +(51,1007,6,86975,99,1,0), +(52,1007,7,86976,99,1,0), +(53,1008,1,86980,99,1,0), +(54,1008,2,86981,99,1,0), +(55,1008,3,86982,99,1,0), +(56,1008,4,86983,99,1,0), +(57,1008,5,86984,99,1,0), +(58,1009,1,86990,99,1,0), +(59,1009,2,86991,99,1,0), +(60,1009,3,86992,99,1,0), +(61,1009,4,86993,99,1,0), +(62,1009,5,86994,99,1,0), +(63,1009,6,86995,99,1,0), +(64,1009,7,86996,99,1,0), +(65,1009,8,86997,99,1,0), +(66,1009,9,86998,99,1,0), +(67,1009,10,86999,99,1,0), +(68,1010,1,90001,99,1,0), +(69,1010,2,90002,99,1,0), +(70,1010,3,90003,99,1,0), +(71,1010,6,90006,99,1,0), +(72,1010,7,90007,99,1,0), +(73,1010,8,90008,99,1,0), +(74,1010,10,90010,99,1,0), +(75,1050,1,89270,99,1,0), +(76,1050,2,89271,99,1,0), +(77,1050,3,89272,99,1,0), +(78,1050,4,89273,99,1,0), +(79,1050,5,89274,99,1,0), +(80,1050,6,89275,99,1,0), +(81,1051,1,89280,99,1,0), +(82,1051,2,89281,99,1,0), +(83,1051,3,89282,99,1,0), +(84,1051,4,89283,99,1,0), +(85,1051,5,89284,99,1,0), +(86,1052,1,89290,99,1,0), +(87,1052,2,89291,99,1,0), +(88,1052,3,89292,99,1,0), +(89,1052,4,89293,99,1,0), +(90,1052,5,89294,99,1,0), +(91,1052,6,89295,99,1,0), +(92,1052,7,89296,99,1,0), +(93,1053,1,89300,99,1,0), +(94,1053,2,89301,99,1,0), +(95,1053,3,89302,99,1,0), +(96,1053,4,89303,99,1,0), +(97,1053,5,89304,99,1,0), +(98,1053,6,89305,99,1,0), +(99,1053,7,89306,99,1,0), +(100,1053,8,89307,99,1,0), +(101,1053,9,89308,99,1,0), +(102,1054,1,89310,99,1,0), +(103,1054,2,89311,99,1,0), +(104,1054,3,89312,99,1,0), +(105,1054,4,89313,99,1,0), +(106,1054,5,89314,99,1,0), +(107,1054,6,89315,99,1,0), +(108,1055,1,89320,99,1,0), +(109,1055,2,89321,99,1,0), +(110,1055,3,89322,99,1,0), +(111,1055,4,89323,99,1,0), +(112,1055,5,89324,99,1,0), +(113,1055,6,89325,99,1,0), +(114,1055,7,89326,99,1,0), +(115,1056,1,89330,99,1,0), +(116,1056,2,89331,99,1,0), +(117,1056,3,89332,99,1,0), +(118,1056,4,89333,99,1,0), +(119,1056,5,89334,99,1,0), +(120,1057,1,89340,99,1,0), +(121,1057,2,89341,99,1,0), +(122,1057,3,89342,99,1,0), +(123,1057,4,89343,99,1,0), +(124,1057,5,89344,99,1,0), +(125,1057,6,89345,99,1,0), +(126,1057,7,89346,99,1,0), +(127,1057,8,89347,99,1,0), +(128,1058,1,89350,99,1,0), +(129,1058,2,89351,99,1,0), +(130,1058,3,89352,99,1,0), +(131,1058,4,89353,99,1,0), +(132,1058,5,89354,99,1,0), +(133,1058,6,89355,99,1,0), +(134,1058,7,89356,99,1,0), +(135,1059,1,89360,99,1,0), +(136,1059,2,89361,99,1,0), +(137,1059,3,89362,99,1,0), +(138,1060,1,89490,99,1,0), +(139,1060,2,89491,99,1,0), +(140,1060,3,89492,99,1,0), +(141,1061,1,89500,99,1,0), +(142,1061,2,89501,99,1,0), +(143,1061,3,89502,99,1,0), +(144,1062,1,89510,99,1,0), +(145,1062,2,89511,99,1,0), +(146,1062,3,89512,99,1,0), +(147,1063,1,89520,99,1,0), +(148,1063,2,89521,99,1,0), +(149,1063,3,89522,99,1,0), +(150,1064,1,89530,99,1,0), +(151,1064,2,89531,99,1,0), +(152,1064,3,89532,99,1,0), +(153,1065,1,89540,99,1,0), +(154,1065,2,89541,99,1,0), +(155,1065,3,89542,99,1,0), +(156,1066,1,89550,3,274,500), +(157,1066,2,89551,3,274,1000), +(158,1066,3,89552,3,274,2000), +(159,1067,1,89560,99,1,0), +(160,1067,2,89561,99,1,0), +(161,1067,3,89562,99,1,0), +(162,1069,1,85571,99,1,0), +(163,1069,2,85572,99,1,0), +(164,1069,3,85573,99,1,0), +(165,1200,1,95001,99,1,0), +(166,1200,2,95002,99,1,0), +(167,1200,3,95003,99,1,0), +(168,1200,4,95004,99,1,0), +(169,1200,5,95005,99,1,0), +(170,1200,6,95006,99,1,0), +(171,1200,7,95007,99,1,0), +(172,1201,1,95008,99,1,0), +(173,1201,2,95009,99,1,0), +(174,1201,3,95010,99,1,0), +(175,1201,4,95011,99,1,0), +(176,1201,5,95012,99,1,0), +(177,1201,6,95013,99,1,0), +(178,1201,7,95014,99,1,0), +(179,1202,1,95015,99,1,0), +(180,1202,2,95016,99,1,0), +(181,1202,3,95017,99,1,0), +(182,1202,4,95018,99,1,0), +(183,1202,5,95019,99,1,0), +(184,1202,6,95020,99,1,0), +(185,1202,7,95021,99,1,0), +(186,1203,1,95022,99,1,0), +(187,1203,2,95023,99,1,0), +(188,1203,3,95024,99,1,0), +(189,1203,4,95025,99,1,0), +(190,1203,5,95026,99,1,0), +(191,1203,6,95027,99,1,0), +(192,1203,7,95028,99,1,0), +(193,1204,1,95029,99,1,0), +(194,1204,2,95030,99,1,0), +(195,1204,3,95031,99,1,0), +(196,1204,4,95032,99,1,0), +(197,1204,5,95033,99,1,0), +(198,1204,6,95034,99,1,0), +(199,1204,7,95035,99,1,0), +(200,1205,1,95036,99,1,0), +(201,1205,2,95037,99,1,0), +(202,1205,3,95038,99,1,0), +(203,1205,4,95039,99,1,0), +(204,1205,5,95040,99,1,0), +(205,1205,6,95041,99,1,0), +(206,1205,7,95042,99,1,0), +(207,1206,1,95043,99,1,0), +(208,1206,2,95044,99,1,0), +(209,1206,3,95045,99,1,0), +(210,1206,4,95046,99,1,0), +(211,1206,5,95047,99,1,0), +(212,1206,6,95048,99,1,0), +(213,1206,7,95049,99,1,0), +(214,1207,1,95050,99,1,0), +(215,1207,2,95051,99,1,0), +(216,1207,3,95052,99,1,0), +(217,1207,4,95053,99,1,0), +(218,1207,5,95054,99,1,0), +(219,1207,6,95055,99,1,0), +(220,1207,7,95056,99,1,0), +(221,1208,1,95057,99,1,0), +(222,1208,2,95058,99,1,0), +(223,1208,3,95059,99,1,0), +(224,1208,4,95060,99,1,0), +(225,1208,5,95061,99,1,0), +(226,1208,6,95062,99,1,0), +(227,1208,7,95063,99,1,0), +(228,1209,1,95064,99,1,0), +(229,1209,2,95065,99,1,0), +(230,1209,3,95066,99,1,0), +(231,1209,4,95067,99,1,0), +(232,1209,5,95068,99,1,0), +(233,1209,6,95069,99,1,0), +(234,1209,7,95070,99,1,0), +(235,1210,1,95071,99,1,0), +(236,1210,2,95072,99,1,0), +(237,1210,3,95073,99,1,0), +(238,1210,4,95074,99,1,0), +(239,1210,5,95075,99,1,0), +(240,1210,6,95076,99,1,0), +(241,1210,7,95077,99,1,0), +(242,1211,1,85612,1,1,100000), +(243,1211,2,85613,1,1,200000), +(244,1211,3,85614,1,1,300000), +(245,1214,1,80035,99,1,0), +(246,1301,1,102700,99,1,0), +(247,1301,4,102703,99,1,0), +(248,1302,1,102704,99,1,0), +(249,1302,2,102705,99,1,0), +(250,1303,1,102706,99,1,0), +(251,1303,2,102707,99,1,0), +(252,1303,3,102708,99,1,0), +(253,1304,1,102709,99,1,0), +(254,1304,5,102713,99,1,0), +(255,1305,1,102714,99,1,0), +(256,1306,1,102716,99,1,0), +(257,1306,5,102720,99,1,0), +(258,1307,1,102721,99,1,0), +(259,1307,3,102723,99,1,0), +(260,1308,1,102724,99,1,0), +(261,1309,1,102727,99,1,0), +(262,1309,2,102728,99,1,0), +(263,1310,1,102729,99,1,0), +(264,1310,3,102731,99,1,0), +(265,1311,1,102732,99,1,0), +(266,1311,4,102735,99,1,0), +(267,1312,1,102736,99,1,0), +(268,1312,3,102738,99,1,0), +(269,1313,1,102739,99,1,0), +(270,1314,1,102743,99,1,0), +(271,1314,2,102744,99,1,0), +(272,1314,3,102745,99,1,0), +(273,1315,1,102746,99,1,0), +(274,1315,2,102747,99,1,0), +(275,1316,1,102748,99,1,0), +(276,1316,5,102752,99,1,0), +(277,1317,1,102753,99,1,0), +(278,1318,1,102756,99,1,0), +(279,1319,1,102759,99,1,0), +(280,1319,3,102761,99,1,0), +(281,1320,1,102762,99,1,0), +(282,1321,1,102765,99,1,0), +(283,1321,2,102766,99,1,0), +(284,1321,3,102767,99,1,0), +(285,1322,1,102768,99,1,0), +(286,1322,2,102769,99,1,0), +(287,1322,3,102770,99,1,0), +(288,1323,1,102771,99,1,0), +(289,1324,1,102774,99,1,0), +(290,1400,1,102800,99,1,0), +(291,1401,1,102807,99,1,0), +(292,1401,7,102813,99,1,0), +(293,1402,1,102814,99,1,0), +(294,1402,7,102820,99,1,0), +(295,1403,1,102821,99,1,0), +(296,1404,1,102828,99,1,0), +(297,1405,1,102835,99,1,0), +(298,1406,1,102842,99,1,0), +(299,1408,1,109310,99,1,0), +(300,1408,5,109314,99,1,0), +(301,1409,1,109315,99,1,0), +(302,1409,5,109319,99,1,0), +(303,1410,1,109320,99,1,0), +(304,1410,5,109324,99,1,0), +(305,1411,1,109325,99,1,0), +(306,1411,5,109329,99,1,0), +(307,1412,1,109330,99,1,0), +(308,1412,5,109334,99,1,0), +(309,1413,1,109335,99,1,0), +(310,1413,5,109339,99,1,0), +(311,1414,1,109340,99,1,0), +(312,1414,5,109344,99,1,0), +(313,1415,1,109345,99,1,0), +(314,1415,5,109349,99,1,0), +(315,1416,1,109350,99,1,0), +(316,1416,2,109351,99,1,0), +(317,1416,5,109354,99,1,0), +(318,1417,1,109355,99,1,0), +(319,1417,5,109359,99,1,0), +(320,1418,1,109360,99,1,0), +(321,1418,5,109364,99,1,0), +(322,1419,1,109365,99,1,0), +(323,1419,3,109367,99,1,0), +(324,1419,5,109369,99,1,0), +(325,1420,1,109370,99,1,0), +(326,1420,5,109374,99,1,0), +(327,1421,1,109375,99,1,0), +(328,1421,5,109379,99,1,0), +(329,1422,1,109380,99,1,0), +(330,1422,5,109384,99,1,0), +(331,1423,1,109385,99,1,0), +(332,1423,2,109386,99,1,0), +(333,1423,5,109389,99,1,0), +(334,1436,1,120378,99,1,0), +(335,1436,2,120379,99,1,0), +(336,1436,3,120380,99,1,0), +(337,1436,4,120381,99,1,0), +(338,1436,5,120382,99,1,0), +(339,1436,6,120383,99,1,0), +(340,1436,7,120384,99,1,0), +(341,1436,8,120385,99,1,0), +(342,1436,9,120386,99,1,0), +(343,1436,10,120387,99,1,0), +(344,1436,11,120388,99,1,0), +(345,1436,12,120389,99,1,0), +(346,1436,13,120390,99,1,0), +(347,1436,14,120391,99,1,0), +(348,1436,15,120392,99,1,0), +(349,1436,16,120393,99,1,0), +(350,1436,17,120394,99,1,0), +(351,1436,18,120395,99,1,0), +(352,1436,19,120396,99,1,0), +(353,1436,20,120397,99,1,0), +(354,1440,1,56992,99,1,0), +(355,1440,2,56993,99,1,0), +(356,1440,3,56994,99,1,0), +(357,1440,4,56995,99,1,0), +(358,1440,5,56996,99,1,0), +(359,1441,1,132787,99,1,0), +(360,1441,2,132788,99,1,0), +(361,1441,3,132789,99,1,0), +(362,1441,4,132790,99,1,0), +(363,1441,5,132791,99,1,0), +(364,1441,6,132792,99,1,0), +(365,1441,7,132793,99,1,0), +(366,1441,8,132794,99,1,0), +(367,1441,9,132795,99,1,0), +(368,1441,10,132796,99,1,0), +(369,1441,11,132797,99,1,0), +(370,1441,13,132799,99,1,0), +(371,1441,14,132800,99,1,0), +(372,1441,15,132801,99,1,0), +(373,1441,16,132802,99,1,0), +(374,1441,17,132803,99,1,0), +(375,1441,18,132804,99,1,0), +(376,1441,19,132805,99,1,0), +(377,1441,20,132806,99,1,0), +(378,1442,1,133137,99,1,0), +(379,1442,2,133138,99,1,0), +(380,1442,3,133139,99,1,0), +(381,1442,4,133140,99,1,0), +(382,1442,10,133146,99,1,0), +(383,1442,11,133147,99,1,0), +(384,1442,12,133148,99,1,0), +(385,1442,13,133149,99,1,0), +(386,1442,14,133150,99,1,0), +(387,1442,15,133151,99,1,0), +(388,1442,16,133152,99,1,0), +(389,1442,17,133153,99,1,0), +(390,1442,18,133154,99,1,0), +(391,1442,19,133155,99,1,0), +(392,1442,20,133156,99,1,0), +(393,1443,1,133406,99,1,0), +(394,1443,2,133407,99,1,0), +(395,1443,3,133408,99,1,0), +(396,1443,4,133409,99,1,0), +(397,1443,5,133410,99,1,0), +(398,1443,6,133411,99,1,0), +(399,1443,7,133412,99,1,0), +(400,1443,8,133413,99,1,0), +(401,1443,9,133414,99,1,0), +(402,1443,10,133415,99,1,0), +(403,1443,11,133416,99,1,0), +(404,1443,12,133417,99,1,0), +(405,1443,13,133418,99,1,0), +(406,1443,14,133419,99,1,0), +(407,1443,15,133420,99,1,0), +(408,1443,16,133421,99,1,0), +(409,1443,17,133422,99,1,0), +(410,1443,18,133423,99,1,0), +(411,1443,19,133424,99,1,0), +(412,1443,20,133425,99,1,0), +(413,1444,1,94938,99,1,0), +(414,1444,2,94939,99,1,0), +(415,1444,3,94940,99,1,0), +(416,1444,4,94941,99,1,0), +(417,1444,5,94942,99,1,0), +(418,1444,6,94943,99,1,0), +(419,1444,7,94944,99,1,0), +(420,1444,8,94945,99,1,0), +(421,1444,9,94946,99,1,0), +(422,1444,10,94947,99,1,0), +(423,1444,11,94948,99,1,0), +(424,1444,12,94949,99,1,0), +(425,1444,13,94950,99,1,0), +(426,1444,14,94951,99,1,0), +(427,1444,15,94952,99,1,0), +(428,1444,16,94953,99,1,0), +(429,1444,17,94954,99,1,0), +(430,1444,18,94955,99,1,0), +(431,1444,19,94956,99,1,0), +(432,1444,20,94957,99,1,0), +(433,1445,1,98858,99,1,0), +(434,1445,2,98859,99,1,0), +(435,1445,3,98860,99,1,0), +(436,1445,4,98861,99,1,0), +(437,1445,5,98862,99,1,0); + )", .content_schema_update = true } diff --git a/common/database_schema.h b/common/database_schema.h index e0b87b94d..b86e16f44 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -51,6 +51,7 @@ namespace DatabaseSchema { {"character_enabledtasks", "charid"}, {"character_expedition_lockouts", "character_id"}, {"character_exp_modifiers", "character_id"}, + {"character_evolving_items", "character_id"}, {"character_inspect_messages", "id"}, {"character_instance_safereturns", "character_id"}, {"character_item_recast", "id"}, @@ -124,6 +125,7 @@ namespace DatabaseSchema { "character_enabledtasks", "character_expedition_lockouts", "character_exp_modifiers", + "character_evolving_items", "character_inspect_messages", "character_instance_safereturns", "character_item_recast", @@ -212,6 +214,7 @@ namespace DatabaseSchema { "ground_spawns", "horses", "items", + "items_evolving_details", "ldon_trap_entries", "ldon_trap_templates", "lootdrop", diff --git a/common/emu_oplist.h b/common/emu_oplist.h index e41e4994b..fcfd63c63 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -162,6 +162,7 @@ N(OP_EnduranceUpdate), N(OP_EnterChat), N(OP_EnterWorld), N(OP_EnvDamage), +N(OP_EvolveItem), N(OP_ExpansionInfo), N(OP_ExpUpdate), N(OP_FaceChange), diff --git a/common/eq_constants.h b/common/eq_constants.h index fd9cd346a..e048a9321 100644 --- a/common/eq_constants.h +++ b/common/eq_constants.h @@ -1114,4 +1114,5 @@ enum ExpSource namespace DoorType { constexpr uint32 BuyerStall = 155; } + #endif /*COMMON_EQ_CONSTANTS_H*/ diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index e4e3c28f8..bfe5abf44 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -6460,6 +6460,70 @@ struct PickZone_Struct { int32 selection_id; }; +struct EvolveItemToggle { + uint32 action; + uint32 unknown_004; + uint64 unique_id; + uint32 percentage; + uint32 activated; +}; + +struct EvolveXPWindowReceive { + uint32 action; + uint32 unknown_004; + uint64 item1_unique_id; + uint64 item2_unique_id; +}; + +struct EvolveItemMessaging { + uint32 action; + char serialized_data[]; +}; + +struct EvolveXPWindowSend { + /*000*/ uint32 action; + /*004*/ uint64 item1_unique_id; + /*012*/ uint64 item2_unique_id; + /*020*/ uint32 compatibility; + /*024*/ uint32 max_transfer_level; + /*028*/ uint8 item1_present; + /*029*/ uint8 item2_present; + /*030*/ std::string serialize_item_1; + /*034*/ std::string serialize_item_2; + + template + void serialize(Archive &archive) + { + archive( + CEREAL_NVP(action), + CEREAL_NVP(item1_unique_id), + CEREAL_NVP(item2_unique_id), + CEREAL_NVP(compatibility), + CEREAL_NVP(max_transfer_level), + CEREAL_NVP(item1_present), + CEREAL_NVP(item2_present), + CEREAL_NVP(serialize_item_1), + CEREAL_NVP(serialize_item_2) + ); + } +}; + +struct EvolveTransfer { + uint32 item_from_id; + uint32 item_from_current_amount; + uint32 item_to_id; + uint32 item_to_current_amount; + uint32 compatibility; + uint32 max_transfer_level; +}; + +struct EvolveGetNextItem { + uint32 new_item_id; + uint64 new_current_amount; + uint64 from_current_amount; + uint32 max_transfer_level; +}; + // Restore structure packing to default #pragma pack() diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 8bf474f34..4d4188554 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -142,6 +142,7 @@ namespace Logs { EqTime, Corpses, XTargets, + EvolveItem, MaxCategoryID /* Don't Remove this */ }; @@ -242,7 +243,8 @@ namespace Logs { "Zoning", "EqTime", "Corpses", - "XTargets" + "XTargets", + "EvolveItem" }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index 10c9cd98a..638657f64 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -131,6 +131,16 @@ OutF(LogSys, Logs::Detail, Logs::Error, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogEvolveItem(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::General, Logs::EvolveItem))\ + OutF(LogSys, Logs::General, Logs::EvolveItem, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogEvolveItemDetail(message, ...) do {\ + if (LogSys.IsLogEnabled(Logs::Detail, Logs::EvolveItem))\ + OutF(LogSys, Logs::Detail, Logs::EvolveItem, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define LogGuilds(message, ...) do {\ if (LogSys.IsLogEnabled(Logs::General, Logs::Guilds))\ OutF(LogSys, Logs::General, Logs::Guilds, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ diff --git a/common/events/player_event_logs.cpp b/common/events/player_event_logs.cpp index 5baa3e548..5603fb7b8 100644 --- a/common/events/player_event_logs.cpp +++ b/common/events/player_event_logs.cpp @@ -707,6 +707,7 @@ void PlayerEventLogs::SetSettingsDefaults() m_settings[PlayerEvent::PARCEL_RETRIEVE].event_enabled = 1; m_settings[PlayerEvent::PARCEL_DELETE].event_enabled = 1; m_settings[PlayerEvent::BARTER_TRANSACTION].event_enabled = 1; + m_settings[PlayerEvent::EVOLVE_ITEM].event_enabled = 1; for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) { m_settings[i].retention_days = RETENTION_DAYS_DEFAULT; diff --git a/common/events/player_events.h b/common/events/player_events.h index 6384f8e0e..6a01a5cf6 100644 --- a/common/events/player_events.h +++ b/common/events/player_events.h @@ -62,6 +62,7 @@ namespace PlayerEvent { PARCEL_RETRIEVE, PARCEL_DELETE, BARTER_TRANSACTION, + EVOLVE_ITEM, MAX // dont remove }; @@ -124,7 +125,8 @@ namespace PlayerEvent { "Parcel Item Sent", "Parcel Item Retrieved", "Parcel Prune Routine", - "Barter Transaction" + "Barter Transaction", + "Evolve Item Update" }; // Generic struct used by all events @@ -1115,6 +1117,29 @@ namespace PlayerEvent { ); } }; + + struct EvolveItem { + std::string status; + uint32 item_id; + uint64 unique_id; + std::string item_name; + uint32 level; + double progression; + + // cereal + template + void serialize(Archive &ar) + { + ar( + CEREAL_NVP(status), + CEREAL_NVP(item_id), + CEREAL_NVP(unique_id), + CEREAL_NVP(item_name), + CEREAL_NVP(level), + CEREAL_NVP(progression) + ); + } + }; } #endif //EQEMU_PLAYER_EVENTS_H diff --git a/common/evolving_items.cpp b/common/evolving_items.cpp new file mode 100644 index 000000000..2b7eb38c6 --- /dev/null +++ b/common/evolving_items.cpp @@ -0,0 +1,303 @@ +#include "evolving_items.h" +#include "item_instance.h" +#include "events/player_event_logs.h" +#include "repositories/character_evolving_items_repository.h" + +EvolvingItemsManager::EvolvingItemsManager() +{ + m_db = nullptr; + m_content_db = nullptr; +} + +void EvolvingItemsManager::LoadEvolvingItems() const +{ + auto const &results = ItemsEvolvingDetailsRepository::All(*m_content_db); + + if (results.empty()) { + return; + } + + std::ranges::transform( + results.begin(), + results.end(), + std::inserter( + evolving_items_manager.GetEvolvingItemsCache(), + evolving_items_manager.GetEvolvingItemsCache().end() + ), + [](const ItemsEvolvingDetailsRepository::ItemsEvolvingDetails &x) { + return std::make_pair(x.item_id, x); + } + ); +} + +void EvolvingItemsManager::SetDatabase(Database *db) +{ + m_db = db; +} + +void EvolvingItemsManager::SetContentDatabase(Database *db) +{ + m_content_db = db; +} + +double EvolvingItemsManager::CalculateProgression(const uint64 current_amount, const uint32 item_id) +{ + if (!evolving_items_manager.GetEvolvingItemsCache().contains(item_id)) { + return 0; + } + + return evolving_items_manager.GetEvolvingItemsCache().at(item_id).required_amount > 0 + ? static_cast(current_amount) + / static_cast(evolving_items_manager.GetEvolvingItemsCache().at(item_id).required_amount) * 100 + : 0; +} + +void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const +{ + inst.SetEvolveEquipped(false); + if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) { + inst.SetEvolveEquipped(true); + } + + if (!inst.IsEvolving()) { + return; + } + + if (!inst.GetEvolveUniqueID()) { + auto e = CharacterEvolvingItemsRepository::NewEntity(); + + e.character_id = char_id; + e.item_id = inst.GetID(); + e.equipped = inst.GetEvolveEquipped(); + e.final_item_id = evolving_items_manager.GetFinalItemID(inst); + + auto r = CharacterEvolvingItemsRepository::InsertOne(*m_db, e); + e.id = r.id; + + inst.SetEvolveUniqueID(e.id); + inst.SetEvolveCharID(e.character_id); + inst.SetEvolveItemID(e.item_id); + inst.SetEvolveFinalItemID(e.final_item_id); + + return; + } + + CharacterEvolvingItemsRepository::SetEquipped(*m_db, inst.GetEvolveUniqueID(), inst.GetEvolveEquipped()); +} + +uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const +{ + const auto start_iterator = std::ranges::find_if( + evolving_items_manager.GetEvolvingItemsCache().cbegin(), + evolving_items_manager.GetEvolvingItemsCache().cend(), + [&](const std::pair &a) { + return a.second.item_evo_id == inst.GetEvolveLoreID(); + } + ); + + if (start_iterator == std::end(evolving_items_manager.GetEvolvingItemsCache())) { + return 0; + } + + const auto final_id = std::ranges::max_element( + start_iterator, + evolving_items_manager.GetEvolvingItemsCache().cend(), + [&]( + const std::pair &a, + const std::pair &b + ) { + return a.second.item_evo_id == b.second.item_evo_id && + a.second.item_evolve_level < b.second.item_evolve_level; + } + ); + + return final_id->first; +} + +uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const +{ + int8 const current_level = inst.GetEvolveLvl(); + + const auto iterator = std::ranges::find_if( + evolving_items_manager.GetEvolvingItemsCache().cbegin(), + evolving_items_manager.GetEvolvingItemsCache().cend(), + [&](const std::pair &a) { + return a.second.item_evo_id == inst.GetEvolveLoreID() && + a.second.item_evolve_level == current_level + 1; + } + ); + + if (iterator == std::end(evolving_items_manager.GetEvolvingItemsCache())) { + return 0; + } + + return iterator->first; +} + +ItemsEvolvingDetailsRepository::ItemsEvolvingDetails EvolvingItemsManager::GetEvolveItemDetails(const uint64 unique_id) +{ + if (GetEvolvingItemsCache().contains(unique_id)) { + return GetEvolvingItemsCache().at(unique_id); + } + + return ItemsEvolvingDetailsRepository::NewEntity(); +} + +std::vector EvolvingItemsManager::GetEvolveIDItems( + const uint32 evolve_id +) +{ + std::vector e{}; + + for (auto const &[key, value]: GetEvolvingItemsCache()) { + if (value.item_evo_id == evolve_id) { + e.push_back(value); + } + } + + std::ranges::sort( + e.begin(), + e.end(), + [&]( + ItemsEvolvingDetailsRepository::ItemsEvolvingDetails const &a, + ItemsEvolvingDetailsRepository::ItemsEvolvingDetails const &b + ) { + return a.item_evolve_level < b.item_evolve_level; + } + ); + + return e; +} + +uint64 EvolvingItemsManager::GetTotalEarnedXP(const EQ::ItemInstance &inst) +{ + if (!inst) { + return 0; + } + + uint64 xp = inst.GetEvolveCurrentAmount(); + auto evolve_id_item_cache = GetEvolveIDItems(inst.GetEvolveLoreID()); + auto current_level = inst.GetEvolveLvl(); + + for (auto const &i: evolve_id_item_cache) { + if (i.item_evolve_level < current_level) { + xp += i.required_amount; + } + } + + return xp; +} + +EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp) +{ + EvolveGetNextItem ets{}; + const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID()); + uint32 max_transfer_level = 0; + int64 xp = in_xp; + + for (auto const &e: evolve_items) { + if (e.item_evolve_level < inst_in.GetEvolveLvl()) { + continue; + } + + int64 have = 0; + if (e.item_evolve_level == inst_in.GetEvolveLvl()) { + have = inst_in.GetEvolveCurrentAmount(); + } + + const auto required = e.required_amount; + const int64 need = required - have; + const int64 balance = xp - need; + + if (balance <= 0) { + ets.new_current_amount = have + xp; + ets.new_item_id = e.item_id; + ets.from_current_amount = 0; + ets.max_transfer_level = max_transfer_level; + return ets; + } + + xp = balance; + max_transfer_level += 1; + + ets.new_current_amount = required; + ets.new_item_id = e.item_id; + ets.from_current_amount = balance - required; + ets.max_transfer_level = max_transfer_level; + } + + return ets; +} + +EvolveTransfer EvolvingItemsManager::DetermineTransferResults( + const EQ::ItemInstance &inst_from, + const EQ::ItemInstance &inst_to +) +{ + EvolveTransfer ets{}; + + auto evolving_details_inst_from = evolving_items_manager.GetEvolveItemDetails(inst_from.GetID()); + auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID()); + + if (!evolving_details_inst_from.id || !evolving_details_inst_to.id) { + return ets; + } + + if (evolving_details_inst_from.type == evolving_details_inst_to.type) { + uint32 compatibility = 0; + uint64 xp = 0; + if (evolving_details_inst_from.sub_type == evolving_details_inst_to.sub_type) { + compatibility = 100; + } + else { + compatibility = 30; + } + + xp = evolving_items_manager.GetTotalEarnedXP(inst_from) * compatibility / 100; + auto results = evolving_items_manager.GetNextItemByXP(inst_to, xp); + + ets.item_from_id = evolving_items_manager.GetFirstItemInLoreGroup(inst_from.GetEvolveLoreID()); + ets.item_from_current_amount = results.from_current_amount; + ets.item_to_id = results.new_item_id; + ets.item_to_current_amount = results.new_current_amount; + ets.compatibility = compatibility; + ets.max_transfer_level = results.max_transfer_level; + } + + return ets; +} + +uint32 EvolvingItemsManager::GetFirstItemInLoreGroup(const uint32 lore_id) +{ + for (auto const &[key, value]: GetEvolvingItemsCache()) { + if (value.item_evo_id == lore_id && value.item_evolve_level == 1) { + return key; + } + } + + return 0; +} + +uint32 EvolvingItemsManager::GetFirstItemInLoreGroupByItemID(const uint32 item_id) +{ + for (auto const &[key, value]: GetEvolvingItemsCache()) { + if (value.item_id == item_id) { + for (auto const &[key2, value2]: GetEvolvingItemsCache()) { + if (value2.item_evo_id == value.item_evo_id && value2.item_evolve_level == 1) { + return key; + } + } + } + } + + return 0; +} + +void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e) +{ + e.item_id = inst.GetID(); + e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string(); + e.level = inst.GetEvolveLvl(); + e.progression = inst.GetEvolveProgression(); + e.unique_id = inst.GetEvolveUniqueID(); +} diff --git a/common/evolving_items.h b/common/evolving_items.h new file mode 100644 index 000000000..463b58493 --- /dev/null +++ b/common/evolving_items.h @@ -0,0 +1,67 @@ +#ifndef EVOLVING_H +#define EVOLVING_H + +#include "shareddb.h" +#include "events/player_events.h" +#include "repositories/items_evolving_details_repository.h" + +namespace EQ { + class ItemInstance; +} + +namespace EvolvingItems { + namespace Actions { + constexpr int8 UPDATE_ITEMS = 0; + constexpr int8 TRANSFER_WINDOW_OPEN = 1; + constexpr int8 TRANSFER_WINDOW_DETAILS = 2; + constexpr int8 TRANSFER_XP = 3; + constexpr int8 FINAL_RESULT = 4; + } + + namespace Types { + constexpr int8 AMOUNT_OF_EXP = 1; + constexpr int8 NUMBER_OF_KILLS = 2; + constexpr int8 SPECIFIC_MOB_RACE = 3; + constexpr int8 SPECIFIC_ZONE_ID = 4; + } + + namespace SubTypes { + constexpr int8 ALL_EXP = 0; + constexpr int8 SOLO_EXP = 1; + constexpr int8 GROUP_EXP = 2; + constexpr int8 RAID_EXP = 3; + } +} + +class EvolvingItemsManager +{ +public: + EvolvingItemsManager(); + void SetDatabase(Database *db); + void SetContentDatabase(Database *db); + + void LoadEvolvingItems() const; + void DoLootChecks(uint32 char_id, uint16 slot_id, const EQ::ItemInstance &inst) const; + uint32 GetFinalItemID(const EQ::ItemInstance &inst) const; + uint32 GetNextEvolveItemID(const EQ::ItemInstance &inst) const; + uint32 GetFirstItemInLoreGroup(uint32 lore_id); + uint32 GetFirstItemInLoreGroupByItemID(uint32 item_id); + uint64 GetTotalEarnedXP(const EQ::ItemInstance &inst); + static double CalculateProgression(uint64 current_amount, uint32 item_id); + static void LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e); + + ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id); + EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to); + EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp); + std::map& GetEvolvingItemsCache() { return evolving_items_cache; } + std::vector GetEvolveIDItems(uint32 evolve_id); + +private: + std::map evolving_items_cache; + Database * m_db; + Database * m_content_db; +}; + +extern EvolvingItemsManager evolving_items_manager; + +#endif //EVOLVING_H diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index 595b9f025..d83c2ab16 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -344,6 +344,8 @@ bool EQ::InventoryProfile::SwapItem( fail_state = swapNotAllowed; return false; } + + source_item_instance->SetEvolveEquipped(false); if ((destination_slot >= invslot::EQUIPMENT_BEGIN && destination_slot <= invslot::EQUIPMENT_END)) { auto source_item = source_item_instance->GetItem(); if (!source_item) { @@ -362,6 +364,9 @@ bool EQ::InventoryProfile::SwapItem( fail_state = swapLevel; return false; } + if (source_item_instance->IsEvolving() > 0) { + source_item_instance->SetEvolveEquipped(true); + } } } @@ -370,6 +375,8 @@ bool EQ::InventoryProfile::SwapItem( fail_state = swapNotAllowed; return false; } + + destination_item_instance->SetEvolveEquipped(false); if ((source_slot >= invslot::EQUIPMENT_BEGIN && source_slot <= invslot::EQUIPMENT_END)) { auto destination_item = destination_item_instance->GetItem(); if (!destination_item) { @@ -388,6 +395,9 @@ bool EQ::InventoryProfile::SwapItem( fail_state = swapLevel; return false; } + if (destination_item_instance->IsEvolving()) { + destination_item_instance->SetEvolveEquipped(true); + } } } @@ -1402,6 +1412,8 @@ int16 EQ::InventoryProfile::_PutItem(int16 slot_id, ItemInstance* inst) int16 result = INVALID_INDEX; int16 parentSlot = INVALID_INDEX; + inst->SetEvolveEquipped(false); + if (slot_id == invslot::slotCursor) { // Replace current item on cursor, if exists m_cursor.pop(); // no memory delete, clients of this function know what they are doing @@ -1410,6 +1422,9 @@ int16 EQ::InventoryProfile::_PutItem(int16 slot_id, ItemInstance* inst) } else if (slot_id >= invslot::EQUIPMENT_BEGIN && slot_id <= invslot::EQUIPMENT_END) { if ((((uint64)1 << slot_id) & m_lookup->PossessionsBitmask) != 0) { + if (inst->IsEvolving()) { + inst->SetEvolveEquipped(true); + } m_worn[slot_id] = inst; result = slot_id; } @@ -1808,3 +1823,182 @@ int16 EQ::InventoryProfile::FindFirstFreeSlotThatFitsItem(const EQ::ItemData *it } return 0; } + +//This function has the same flaw as noted above +// Helper functions for evolving items +int16 EQ::InventoryProfile::HasEvolvingItem(uint64 evolve_unique_id, uint8 quantity, uint8 where) +{ + int16 slot_id = INVALID_INDEX; + + // Altered by Father Nitwit to support a specification of + // where to search, with a default value to maintain compatibility + + // Check each inventory bucket + if (where & invWhereWorn) { + slot_id = _HasEvolvingItem(m_worn, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWherePersonal) { + slot_id = _HasEvolvingItem(m_inv, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWhereBank) { + slot_id = _HasEvolvingItem(m_bank, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWhereSharedBank) { + slot_id = _HasEvolvingItem(m_shbank, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + if (where & invWhereTrading) { + slot_id = _HasEvolvingItem(m_trade, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + // Behavioral change - Limbo is no longer checked due to improper handling of return value + if (where & invWhereCursor) { + // Check cursor queue + slot_id = _HasEvolvingItem(m_cursor, evolve_unique_id, quantity); + if (slot_id != INVALID_INDEX) { + return slot_id; + } + } + + return slot_id; +} + +// Internal Method: Checks an inventory bucket for a particular evolving item unique id +int16 EQ::InventoryProfile::_HasEvolvingItem( + std::map &bucket, uint64 evolve_unique_id, uint8 quantity) +{ + uint32 quantity_found = 0; + + for (auto const &[key, value]: bucket) { + if (!value) { + continue; + } + + if (key <= EQ::invslot::POSSESSIONS_END && key >= EQ::invslot::POSSESSIONS_BEGIN) { + if (((uint64) 1 << key & m_lookup->PossessionsBitmask) == 0) { + continue; + } + } + else if (key <= EQ::invslot::BANK_END && key >= EQ::invslot::BANK_BEGIN) { + if (key - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) { + continue; + } + } + + + if (value->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += value->GetCharges() <= 0 ? 1 : value->GetCharges(); + if (quantity_found >= quantity) { + return key; + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + + if (!value->IsClassBag()) { + continue; + } + + for (auto const &[bag_key, bag_value]: *value->GetContents()) { + if (!bag_value) { + continue; + } + + if (bag_value->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += bag_value->GetCharges() <= 0 ? 1 : bag_value->GetCharges(); + if (quantity_found >= quantity) { + return CalcSlotId(key, bag_key); + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (bag_value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + } + } + + return INVALID_INDEX; +} + +// Internal Method: Checks an inventory queue type bucket for a particular item +int16 EQ::InventoryProfile::_HasEvolvingItem(ItemInstQueue &iqueue, uint64 evolve_unique_id, uint8 quantity) +{ + // The downfall of this (these) queue procedure is that callers presume that when an item is + // found, it is presented as being available on the cursor. In cases of a parity check, this + // is sufficient. However, in cases where referential criteria is considered, this can lead + // to unintended results. Funtionality should be observed when referencing the return value + // of this query + + uint32 quantity_found = 0; + + for (auto const &inst: iqueue) { + if (!inst) { + continue; + } + + if (inst->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += inst->GetCharges() <= 0 ? 1 : inst->GetCharges(); + if (quantity_found >= quantity) { + return invslot::slotCursor; + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (inst->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + + if (!inst->IsClassBag()) { + continue; + } + + for (auto const &[bag_key, bag_value]: *inst->GetContents()) { + if (!bag_value) { + continue; + } + + if (bag_value->GetEvolveUniqueID() == evolve_unique_id) { + quantity_found += bag_value->GetCharges() <= 0 ? 1 : bag_value->GetCharges(); + if (quantity_found >= quantity) { + return CalcSlotId(invslot::slotCursor, bag_key); + } + } + + for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) { + if (bag_value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) { + return invslot::SLOT_AUGMENT_GENERIC_RETURN; + } + } + } + + // We only check the visible cursor due to lack of queue processing ability (client allows duplicate in limbo) + break; + } + + return INVALID_INDEX; +} diff --git a/common/inventory_profile.h b/common/inventory_profile.h index 4761e1b97..b1d1e4850 100644 --- a/common/inventory_profile.h +++ b/common/inventory_profile.h @@ -57,6 +57,8 @@ public: inline std::list::const_iterator cbegin() { return m_list.cbegin(); } inline std::list::const_iterator cend() { return m_list.cend(); } + inline std::list::iterator begin() { return m_list.begin(); } + inline std::list::iterator end() { return m_list.end(); } inline int size() { return static_cast(m_list.size()); } // TODO: change to size_t inline bool empty() { return m_list.empty(); } @@ -211,6 +213,11 @@ namespace EQ void SetCustomItemData(uint32 character_id, int16 slot_id, const std::string &identifier, bool value); std::string GetCustomItemData(int16 slot_id, const std::string& identifier); static const int GetItemStatValue(uint32 item_id, const std::string& identifier); + + std::map& GetWorn() { return m_worn; } + std::map& GetPersonal() { return m_inv; } + int16 HasEvolvingItem(uint64 evolve_unique_id, uint8 quantity, uint8 where); + protected: /////////////////////////////// // Protected Methods @@ -233,6 +240,8 @@ namespace EQ int16 _HasItemByUse(ItemInstQueue& iqueue, uint8 use, uint8 quantity); int16 _HasItemByLoreGroup(std::map& bucket, uint32 loregroup); int16 _HasItemByLoreGroup(ItemInstQueue& iqueue, uint32 loregroup); + int16 _HasEvolvingItem(std::map& bucket, uint64 evolve_unique_id, uint8 quantity); + int16 _HasEvolvingItem(ItemInstQueue& iqueue, uint64 evolve_unique_id, uint8 quantity); // Player inventory diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 6aeb32223..1e1e550b5 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -25,6 +25,7 @@ #include "rulesys.h" #include "shareddb.h" #include "strings.h" +#include "evolving_items.h" //#include "../common/light_source.h" @@ -76,6 +77,10 @@ EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) { m_color = m_item->Color; } + if (IsEvolving()) { + SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping)); + } + m_SerialNumber = GetNextItemInstSerialNumber(); } @@ -95,6 +100,10 @@ EQ::ItemInstance::ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges m_color = 0; } + if (IsEvolving()) { + SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping)); + } + m_SerialNumber = GetNextItemInstSerialNumber(); } @@ -146,7 +155,6 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_exp = copy.m_exp; m_evolveLvl = copy.m_evolveLvl; - m_activated = copy.m_activated; if (copy.m_scaledItem) { m_scaledItem = new ItemData(*copy.m_scaledItem); @@ -154,12 +162,7 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_scaledItem = nullptr; } - if (copy.m_evolveInfo) { - m_evolveInfo = new EvolveInfo(*copy.m_evolveInfo); - } else { - m_evolveInfo = nullptr; - } - + m_evolving_details = copy.m_evolving_details; m_scaling = copy.m_scaling; m_ornamenticon = copy.m_ornamenticon; m_ornamentidfile = copy.m_ornamentidfile; @@ -174,7 +177,6 @@ EQ::ItemInstance::~ItemInstance() Clear(); safe_delete(m_item); safe_delete(m_scaledItem); - safe_delete(m_evolveInfo); } // Query item type @@ -1030,29 +1032,6 @@ void EQ::ItemInstance::ScaleItem() { m_scaledItem->CharmFileID = 0; // this stops the client from trying to scale the item itself. } -bool EQ::ItemInstance::EvolveOnAllKills() const { - return (m_evolveInfo && m_evolveInfo->AllKills); -} - -int8 EQ::ItemInstance::GetMaxEvolveLvl() const { - if (m_evolveInfo) - return m_evolveInfo->MaxLvl; - else - return 0; -} - -uint32 EQ::ItemInstance::GetKillsNeeded(uint8 currentlevel) { - uint32 kills = -1; // default to -1 (max uint32 value) because this value is usually divided by, so we don't want to ever return zero. - if (m_evolveInfo) - if (currentlevel != m_evolveInfo->MaxLvl) - kills = m_evolveInfo->LvlKills[currentlevel - 1]; - - if (kills == 0) - kills = -1; - - return kills; -} - void EQ::ItemInstance::SetTimer(std::string name, uint32 time) { Timer t(time); t.Start(time, false); @@ -1951,28 +1930,54 @@ void EQ::ItemInstance::ClearGUIDMap() { guids.clear(); } -// -// class EvolveInfo -// -EvolveInfo::EvolveInfo() { - // nothing here yet + +bool EQ::ItemInstance::TransferOwnership(Database &db, const uint32 to_char_id) const +{ + if (!to_char_id || !IsEvolving()) { + return false; + } + + SetEvolveCharID(to_char_id); + CharacterEvolvingItemsRepository::UpdateCharID(db, GetEvolveUniqueID(), to_char_id); + return true; } -EvolveInfo::EvolveInfo(uint32 first, uint8 max, bool allkills, uint32 L2, uint32 L3, uint32 L4, uint32 L5, uint32 L6, uint32 L7, uint32 L8, uint32 L9, uint32 L10) { - FirstItem = first; - MaxLvl = max; - AllKills = allkills; - LvlKills[0] = L2; - LvlKills[1] = L3; - LvlKills[2] = L4; - LvlKills[3] = L5; - LvlKills[4] = L6; - LvlKills[5] = L7; - LvlKills[6] = L8; - LvlKills[7] = L9; - LvlKills[8] = L10; +uint32 EQ::ItemInstance::GetAugmentEvolveUniqueID(uint8 augment_index) const +{ + if (!m_item || !m_item->IsClassCommon()) { + return 0; + } + + const auto item = GetItem(augment_index); + if (item) { + return item->GetEvolveUniqueID(); + } + + return 0; } -EvolveInfo::~EvolveInfo() { - +void EQ::ItemInstance::SetTimer(std::string name, uint32 time) const{ + Timer t(time); + t.Start(time, false); + m_timers[name] = t; +} + +void EQ::ItemInstance::SetEvolveEquipped(const bool in) const +{ + if (!IsEvolving()) { + return; + } + + m_evolving_details.equipped = in; + if (in && !GetTimers().contains("evolve")) { + SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping)); + return; + } + + if (in) { + GetTimers().at("evolve").SetTimer(RuleI(EvolvingItems, DelayUponEquipping)); + return; + } + + GetTimers().at("evolve").Disable(); } diff --git a/common/item_instance.h b/common/item_instance.h index 928a6aabc..4005a70c2 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -23,6 +23,7 @@ #ifndef COMMON_ITEM_INSTANCE_H #define COMMON_ITEM_INSTANCE_H +#include "evolving_items.h" class ItemParse; // Parses item packets @@ -34,6 +35,7 @@ class EvolveInfo; // Stores information about an evolving item family #include "../common/bodytypes.h" #include "../common/deity.h" #include "../common/memory_buffer.h" +#include "../common/repositories/character_evolving_items_repository.h" #include @@ -205,13 +207,9 @@ namespace EQ bool IsDroppable(bool recurse = true) const; bool IsScaling() const { return m_scaling; } - bool IsEvolving() const { return (m_evolveLvl >= 1); } uint32 GetExp() const { return m_exp; } void SetExp(uint32 exp) { m_exp = exp; } void AddExp(uint32 exp) { m_exp += exp; } - bool IsActivated() { return m_activated; } - void SetActivated(bool activated) { m_activated = activated; } - int8 GetEvolveLvl() const { return m_evolveLvl; } void SetScaling(bool v) { m_scaling = v; } uint32 GetOrnamentationIcon() const { return m_ornamenticon; } void SetOrnamentIcon(uint32 ornament_icon) { m_ornamenticon = ornament_icon; } @@ -226,9 +224,6 @@ namespace EQ void Initialize(SharedDatabase *db = nullptr); void ScaleItem(); - bool EvolveOnAllKills() const; - int8 GetMaxEvolveLvl() const; - uint32 GetKillsNeeded(uint8 currentlevel); std::string Serialize(int16 slot_id) const { InternalSerializedItem_Struct s; s.slot_id = slot_id; s.inst = (const void*)this; std::string ser; ser.assign((char*)&s, sizeof(InternalSerializedItem_Struct)); return ser; } void Serialize(OutBuffer& ob, int16 slot_id) const { InternalSerializedItem_Struct isi; isi.slot_id = slot_id; isi.inst = (const void*)this; ob.write((const char*)&isi, sizeof(isi)); } @@ -236,8 +231,9 @@ namespace EQ inline int32 GetSerialNumber() const { return m_SerialNumber; } inline void SetSerialNumber(int32 id) { m_SerialNumber = id; } - std::map& GetTimers() { return m_timers; } + std::map& GetTimers() const { return m_timers; } void SetTimer(std::string name, uint32 time); + void SetTimer(std::string name, uint32 time) const; void StopTimer(std::string name); void ClearTimers(); @@ -312,6 +308,34 @@ namespace EQ static void AddGUIDToMap(uint64 existing_serial_number); static void ClearGUIDMap(); + // evolving items stuff + CharacterEvolvingItemsRepository::CharacterEvolvingItems &GetEvolvingDetails() const { return m_evolving_details; } + + int8 GetEvolveLvl() const { if (GetItem()) { return GetItem()->EvolvingLevel; } return false; } + bool IsEvolving() const { if (GetItem()) { return GetItem()->EvolvingItem; } return false; } + int8 GetMaxEvolveLvl() const { if (GetItem()) { return GetItem()->EvolvingMax; } return false; } + bool GetEvolveActivated() const { return m_evolving_details.activated ? true : false; } + bool GetEvolveEquipped() const { return m_evolving_details.equipped ? true : false; } + double GetEvolveProgression() const { return m_evolving_details.progression; } + uint64 GetEvolveUniqueID() const { return m_evolving_details.id; } + uint32 GetEvolveCharID() const { return m_evolving_details.character_id; } + uint32 GetEvolveItemID() const { return m_evolving_details.item_id; } + uint32 GetEvolveLoreID() const { if (GetItem()) { return GetItem()->EvolvingID; } return false; } + uint64 GetEvolveCurrentAmount() const { return m_evolving_details.current_amount; } + uint32 GetEvolveFinalItemID() const { return m_evolving_details.final_item_id; } + uint32 GetAugmentEvolveUniqueID(uint8 augment_index) const; + void SetEvolveEquipped(const bool in) const; + void SetEvolveActivated(const bool in) const { m_evolving_details.activated = in; } + void SetEvolveProgression(const double in) const { m_evolving_details.progression = in; } + void SetEvolveUniqueID(const uint64 in) const { m_evolving_details.id = in; } + void SetEvolveCharID(const uint32 in) const { m_evolving_details.character_id = in; } + void SetEvolveItemID(const uint32 in) const { m_evolving_details.item_id = in; } + void SetEvolveCurrentAmount(const uint64 in) const { m_evolving_details.current_amount = in; } + void SetEvolveAddToCurrentAmount(const uint64 in) const { m_evolving_details.current_amount += in; } + void SetEvolveFinalItemID(const uint32 in) const { m_evolving_details.final_item_id = in; } + bool TransferOwnership(Database& db, const uint32 to_char_id) const; + void CalculateEvolveProgression() const { m_evolving_details.progression = evolving_items_manager.CalculateProgression(GetEvolveCurrentAmount(), GetID()); } + protected: ////////////////////////// // Protected Members @@ -323,48 +347,32 @@ namespace EQ void _PutItem(uint8 index, ItemInstance* inst) { m_contents[index] = inst; } - ItemInstTypes m_use_type {ItemInstNormal}; // Usage type for item - const ItemData* m_item {nullptr}; // Ptr to item data - int16 m_charges {0}; // # of charges for chargeable items - uint32 m_price {0}; // Bazaar /trader price - uint32 m_color {0}; - uint32 m_merchantslot {0}; - int16 m_currentslot {0}; - bool m_attuned {false}; - int32 m_merchantcount {1}; //number avaliable on the merchant, -1=unlimited - int32 m_SerialNumber {0}; // Unique identifier for this instance of an item. Needed for Bazaar. - uint32 m_exp {0}; - int8 m_evolveLvl {0}; - bool m_activated {false}; - ItemData* m_scaledItem {nullptr}; - ::EvolveInfo* m_evolveInfo {nullptr}; - bool m_scaling {false}; - uint32 m_ornamenticon {0}; - uint32 m_ornamentidfile {0}; - uint32 m_new_id_file {0}; - uint32 m_ornament_hero_model {0}; - uint32 m_recast_timestamp {0}; - int m_task_delivered_count {0}; + ItemInstTypes m_use_type{ItemInstNormal};// Usage type for item + const ItemData * m_item{nullptr}; // Ptr to item data + int16 m_charges{0}; // # of charges for chargeable items + uint32 m_price{0}; // Bazaar /trader price + uint32 m_color{0}; + uint32 m_merchantslot{0}; + int16 m_currentslot{0}; + bool m_attuned{false}; + int32 m_merchantcount{1};//number avaliable on the merchant, -1=unlimited + int32 m_SerialNumber{0}; // Unique identifier for this instance of an item. Needed for Bazaar. + uint32 m_exp{0}; + int8 m_evolveLvl{0}; + ItemData * m_scaledItem{nullptr}; + bool m_scaling{false}; + uint32 m_ornamenticon{0}; + uint32 m_ornamentidfile{0}; + uint32 m_new_id_file{0}; + uint32 m_ornament_hero_model{0}; + uint32 m_recast_timestamp{0}; + int m_task_delivered_count{0}; + mutable CharacterEvolvingItemsRepository::CharacterEvolvingItems m_evolving_details{}; // Items inside of this item (augs or contents) {}; - std::map m_contents {}; // Zero-based index: min=0, max=9 - std::map m_custom_data {}; - std::map m_timers {}; + std::map m_contents {}; // Zero-based index: min=0, max=9 + std::map m_custom_data {}; + mutable std::map m_timers {}; }; } - -class EvolveInfo { -public: - friend class EQ::ItemInstance; - //temporary - uint16 LvlKills[9]; - uint32 FirstItem; - uint8 MaxLvl; - bool AllKills; - - EvolveInfo(); - EvolveInfo(uint32 first, uint8 max, bool allkills, uint32 L2, uint32 L3, uint32 L4, uint32 L5, uint32 L6, uint32 L7, uint32 L8, uint32 L9, uint32 L10); - ~EvolveInfo(); -}; - #endif /*COMMON_ITEM_INSTANCE_H*/ diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 783a9d77f..15f3be60e 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1350,6 +1350,58 @@ namespace RoF2 dest->FastQueuePacket(&in, ack_req); } + ENCODE(OP_EvolveItem) + { + EQApplicationPacket *in = *p; + *p = nullptr; + + auto action = *reinterpret_cast(in->pBuffer); + + switch (action) { + case EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS: { + auto emu = reinterpret_cast(in->pBuffer); + + EvolveXPWindowSend e{}; + EQ::Util::MemoryStreamReader ss(emu->serialized_data, in->size - sizeof(emu->action)); + cereal::BinaryInputArchive ar(ss); + ar(e); + + auto item_1 = static_cast(reinterpret_cast(e.serialize_item_1.data())->inst); + auto item_2 = static_cast(reinterpret_cast(e.serialize_item_2.data())->inst); + + EQ::OutBuffer ob; + + SerializeItem(ob, item_1, 0, 0, ItemPacketMerchant); + SerializeItem(ob, item_2, 0, 0, ItemPacketMerchant); + + auto out = std::make_unique( + OP_EvolveItem, + sizeof(EvolveXPWindowSendDetails_Struct) + ob.size() + ); + auto data = reinterpret_cast(out->pBuffer); + + data->action = e.action; + data->compatibility = e.compatibility; + data->max_transfer_level = e.max_transfer_level; + data->item1_unique_id = e.item1_unique_id; + data->item2_unique_id = e.item2_unique_id; + data->item1_present = e.item1_present; + data->item2_present = e.item2_present; + + memcpy(data->serialize_data, ob.str().data(), ob.size()); + dest->QueuePacket(out.get()); + safe_delete(in); + break; + } + default: { + dest->FastQueuePacket(&in); + break; + } + } + } + ENCODE(OP_ExpansionInfo) { ENCODE_LENGTH_EXACT(ExpansionInfo_Struct); @@ -6405,6 +6457,11 @@ namespace RoF2 hdr.scaled_value = (inst->IsScaling() ? (inst->GetExp() / 100) : 0); hdr.instance_id = (inst->GetMerchantSlot() ? inst->GetMerchantSlot() : inst->GetSerialNumber()); hdr.parcel_item_id = packet_type == ItemPacketParcel ? inst->GetID() : 0; + if (item->EvolvingItem) { + hdr.instance_id = inst->GetEvolveUniqueID() & 0xFFFFFFFF; //lower dword + hdr.parcel_item_id = inst->GetEvolveUniqueID() >> 32; //upper dword + } + hdr.last_cast_time = inst->GetRecastTimestamp(); hdr.charges = (inst->IsStackable() ? (item->MaxCharges ? 1 : 0) : ((inst->GetCharges() > 254) ? 0xFFFFFFFF @@ -6418,18 +6475,15 @@ namespace RoF2 ob.write((const char*)&hdr, sizeof(RoF2::structs::ItemSerializationHeader)); if (item->EvolvingItem > 0) { - RoF2::structs::EvolvingItem evotop; + RoF2::structs::EvolvingItem_Struct evotop; - evotop.unknown001 = 0; - evotop.unknown002 = 0; - evotop.unknown003 = 0; - evotop.unknown004 = 0; - evotop.evoLevel = item->EvolvingLevel; - evotop.progress = 0; - evotop.Activated = 1; - evotop.evomaxlevel = item->EvolvingMax; + evotop.final_item_id = inst->GetEvolveFinalItemID(); + evotop.evolve_level = item->EvolvingLevel; + evotop.progress = inst->GetEvolveProgression(); + evotop.activated = inst->GetEvolveActivated(); + evotop.evolve_max_level = item->EvolvingMax; - ob.write((const char*)&evotop, sizeof(RoF2::structs::EvolvingItem)); + ob.write((const char*)&evotop, sizeof(RoF2::structs::EvolvingItem_Struct)); } /** diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 08d8b9985..0330287e4 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -70,6 +70,7 @@ E(OP_DzMemberListName) E(OP_DzMemberListStatus) E(OP_DzSetLeaderName) E(OP_Emote) +E(OP_EvolveItem) E(OP_ExpansionInfo) E(OP_FormattedMessage) E(OP_GMLastName) diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 1bbf7c26c..4a8d277eb 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -4746,16 +4746,13 @@ struct ItemSerializationHeader uint8 isEvolving; }; -struct EvolvingItem { - uint8 unknown001; - uint8 unknown002; - uint8 unknown003; - uint8 unknown004; - int32 evoLevel; +struct EvolvingItem_Struct { + uint32 final_item_id; + int32 evolve_level; double progress; - uint8 Activated; - int32 evomaxlevel; - uint8 unknown005[4]; + uint8 activated; + int32 evolve_max_level; + uint8 unknown005[4]; }; struct ItemSerializationHeaderFinish @@ -5428,6 +5425,32 @@ struct Parcel_Struct }; }; /*structs*/ +struct EvolveItemToggle_Struct { + uint32 action; + uint32 unknown_004; + uint64 unique_id; + uint32 percentage; + uint32 activated; +}; + +struct EvolveXPWindowReceive_Struct { + uint32 action; + uint32 unknown_004; + uint64 item1_unique_id; + uint64 item2_unique_id; +}; + +struct EvolveXPWindowSendDetails_Struct { + /*000*/ uint32 action; + /*004*/ uint64 item1_unique_id; + /*012*/ uint64 item2_unique_id; + /*020*/ uint32 compatibility; + /*024*/ uint32 max_transfer_level; + /*028*/ uint8 item1_present; + /*029*/ uint8 item2_present; + /*030*/ char serialize_data[]; +}; + }; /*RoF2*/ #endif /*COMMON_ROF2_STRUCTS_H*/ diff --git a/common/repositories/base/base_character_evolving_items_repository.h b/common/repositories/base/base_character_evolving_items_repository.h new file mode 100644 index 000000000..9aa6c1773 --- /dev/null +++ b/common/repositories/base/base_character_evolving_items_repository.h @@ -0,0 +1,475 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseCharacterEvolvingItemsRepository { +public: + struct CharacterEvolvingItems { + uint64_t id; + uint32_t character_id; + uint32_t item_id; + uint8_t activated; + uint8_t equipped; + int64_t current_amount; + double progression; + uint32_t final_item_id; + time_t deleted_at; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "character_id", + "item_id", + "activated", + "equipped", + "current_amount", + "progression", + "final_item_id", + "deleted_at", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "character_id", + "item_id", + "activated", + "equipped", + "current_amount", + "progression", + "final_item_id", + "UNIX_TIMESTAMP(deleted_at)", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("character_evolving_items"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterEvolvingItems NewEntity() + { + CharacterEvolvingItems e{}; + + e.id = 0; + e.character_id = 0; + e.item_id = 0; + e.activated = 0; + e.equipped = 0; + e.current_amount = 0; + e.progression = 0; + e.final_item_id = 0; + e.deleted_at = 0; + + return e; + } + + static CharacterEvolvingItems GetCharacterEvolvingItems( + const std::vector &character_evolving_itemss, + int character_evolving_items_id + ) + { + for (auto &character_evolving_items : character_evolving_itemss) { + if (character_evolving_items.id == character_evolving_items_id) { + return character_evolving_items; + } + } + + return NewEntity(); + } + + static CharacterEvolvingItems FindOne( + Database& db, + int character_evolving_items_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + character_evolving_items_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterEvolvingItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.activated = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.equipped = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.current_amount = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.progression = row[6] ? strtod(row[6], nullptr) : 0; + e.final_item_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[8] ? row[8] : "-1", nullptr, 10); + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_evolving_items_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_evolving_items_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const CharacterEvolvingItems &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.character_id)); + v.push_back(columns[2] + " = " + std::to_string(e.item_id)); + v.push_back(columns[3] + " = " + std::to_string(e.activated)); + v.push_back(columns[4] + " = " + std::to_string(e.equipped)); + v.push_back(columns[5] + " = " + std::to_string(e.current_amount)); + v.push_back(columns[6] + " = " + std::to_string(e.progression)); + v.push_back(columns[7] + " = " + std::to_string(e.final_item_id)); + v.push_back(columns[8] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterEvolvingItems InsertOne( + Database& db, + CharacterEvolvingItems e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterEvolvingItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.activated = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.equipped = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.current_amount = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.progression = row[6] ? strtod(row[6], nullptr) : 0; + e.final_item_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[8] ? row[8] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterEvolvingItems e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.activated = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.equipped = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.current_amount = row[5] ? strtoll(row[5], nullptr, 10) : 0; + e.progression = row[6] ? strtod(row[6], nullptr) : 0; + e.final_item_id = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.deleted_at = strtoll(row[8] ? row[8] : "-1", nullptr, 10); + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterEvolvingItems &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.activated)); + v.push_back(std::to_string(e.equipped)); + v.push_back(std::to_string(e.current_amount)); + v.push_back(std::to_string(e.progression)); + v.push_back(std::to_string(e.final_item_id)); + v.push_back("FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H diff --git a/common/repositories/base/base_items_evolving_details_repository.h b/common/repositories/base/base_items_evolving_details_repository.h new file mode 100644 index 000000000..0f932bfb8 --- /dev/null +++ b/common/repositories/base/base_items_evolving_details_repository.h @@ -0,0 +1,451 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_ITEMS_EVOLVING_DETAILS_REPOSITORY_H +#define EQEMU_BASE_ITEMS_EVOLVING_DETAILS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseItemsEvolvingDetailsRepository { +public: + struct ItemsEvolvingDetails { + uint32_t id; + uint32_t item_evo_id; + uint32_t item_evolve_level; + uint32_t item_id; + uint32_t type; + uint32_t sub_type; + int64_t required_amount; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "item_evo_id", + "item_evolve_level", + "item_id", + "type", + "sub_type", + "required_amount", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "item_evo_id", + "item_evolve_level", + "item_id", + "type", + "sub_type", + "required_amount", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("items_evolving_details"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static ItemsEvolvingDetails NewEntity() + { + ItemsEvolvingDetails e{}; + + e.id = 0; + e.item_evo_id = 0; + e.item_evolve_level = 0; + e.item_id = 0; + e.type = 0; + e.sub_type = 0; + e.required_amount = 0; + + return e; + } + + static ItemsEvolvingDetails GetItemsEvolvingDetails( + const std::vector &items_evolving_detailss, + int items_evolving_details_id + ) + { + for (auto &items_evolving_details : items_evolving_detailss) { + if (items_evolving_details.id == items_evolving_details_id) { + return items_evolving_details; + } + } + + return NewEntity(); + } + + static ItemsEvolvingDetails FindOne( + Database& db, + int items_evolving_details_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + items_evolving_details_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + ItemsEvolvingDetails e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.item_evo_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_evolve_level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.sub_type = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int items_evolving_details_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + items_evolving_details_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const ItemsEvolvingDetails &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.item_evo_id)); + v.push_back(columns[2] + " = " + std::to_string(e.item_evolve_level)); + v.push_back(columns[3] + " = " + std::to_string(e.item_id)); + v.push_back(columns[4] + " = " + std::to_string(e.type)); + v.push_back(columns[5] + " = " + std::to_string(e.sub_type)); + v.push_back(columns[6] + " = " + std::to_string(e.required_amount)); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static ItemsEvolvingDetails InsertOne( + Database& db, + ItemsEvolvingDetails e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.sub_type)); + v.push_back(std::to_string(e.required_amount)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.sub_type)); + v.push_back(std::to_string(e.required_amount)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ItemsEvolvingDetails e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.item_evo_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_evolve_level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.sub_type = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + ItemsEvolvingDetails e{}; + + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.item_evo_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_evolve_level = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.type = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.sub_type = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.required_amount = row[6] ? strtoll(row[6], nullptr, 10) : 0; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const ItemsEvolvingDetails &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.sub_type)); + v.push_back(std::to_string(e.required_amount)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.item_evo_id)); + v.push_back(std::to_string(e.item_evolve_level)); + v.push_back(std::to_string(e.item_id)); + v.push_back(std::to_string(e.type)); + v.push_back(std::to_string(e.sub_type)); + v.push_back(std::to_string(e.required_amount)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_ITEMS_EVOLVING_DETAILS_REPOSITORY_H diff --git a/common/repositories/character_evolving_items_repository.h b/common/repositories/character_evolving_items_repository.h new file mode 100644 index 000000000..8bc379505 --- /dev/null +++ b/common/repositories/character_evolving_items_repository.h @@ -0,0 +1,66 @@ +#ifndef EQEMU_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H +#define EQEMU_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_character_evolving_items_repository.h" + +#include + +class CharacterEvolvingItemsRepository: public BaseCharacterEvolvingItemsRepository { +public: + // Custom extended repository methods here + + static CharacterEvolvingItems SetCurrentAmountAndProgression(Database& db, const uint64 id, const uint64 amount, const double progression) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return NewEntity(); + } + + e.current_amount = amount; + e.progression = progression; + e.deleted_at = 0; + UpdateOne(db, e); + return e; + } + + static CharacterEvolvingItems SetEquipped(Database& db, const uint64 id, const bool equipped) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return NewEntity(); + } + + e.equipped = equipped; + e.deleted_at = 0; + UpdateOne(db, e); + return e; + } + + static CharacterEvolvingItems SoftDelete(Database& db, const uint64 id) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return NewEntity(); + } + + e.deleted_at = time(nullptr); + UpdateOne(db, e); + return e; + } + + static bool UpdateCharID(Database &db, const uint64 id, const uint32 to_char_id) + { + auto e = FindOne(db, id); + if (e.id == 0) { + return false; + } + + e.character_id = to_char_id; + e.deleted_at = 0; + return UpdateOne(db, e); + } +}; + +#endif //EQEMU_CHARACTER_EVOLVING_ITEMS_REPOSITORY_H diff --git a/common/repositories/items_evolving_details_repository.h b/common/repositories/items_evolving_details_repository.h new file mode 100644 index 000000000..906bb6095 --- /dev/null +++ b/common/repositories/items_evolving_details_repository.h @@ -0,0 +1,14 @@ +#ifndef EQEMU_ITEMS_EVOLVING_DETAILS_REPOSITORY_H +#define EQEMU_ITEMS_EVOLVING_DETAILS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_items_evolving_details_repository.h" + +class ItemsEvolvingDetailsRepository: public BaseItemsEvolvingDetailsRepository { +public: + // Custom extended repository methods here + +}; + +#endif //EQEMU_ITEMS_EVOLVING_DETAILS_REPOSITORY_H diff --git a/common/ruletypes.h b/common/ruletypes.h index c0682979b..e27ed3f3c 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -1038,6 +1038,13 @@ RULE_INT(Parcel, ParcelMaxItems, 50, "The maximum number of parcels a player is RULE_INT(Parcel, ParcelPruneDelay, 30, "The number of days after which a parcel is deleted. Items are lost!") RULE_CATEGORY_END() +RULE_CATEGORY(EvolvingItems) +RULE_REAL(EvolvingItems, PercentOfSoloExperience, 0.1, "Percentage of solo experience allocated to evolving items that require experience.") +RULE_REAL(EvolvingItems, PercentOfGroupExperience, 0.1, "Percentage of group experience allocated to evolving items that require experience.") +RULE_REAL(EvolvingItems, PercentOfRaidExperience, 0.1, "Percentage of solo experience allocated to evolving items that require experience.") +RULE_INT(EvolvingItems, DelayUponEquipping, 30000, "Delay in ms before an evolving item will earn rewards after equipping. Default is 30000ms or 30s.") +RULE_CATEGORY_END() + #undef RULE_CATEGORY #undef RULE_INT #undef RULE_REAL diff --git a/common/shareddb.cpp b/common/shareddb.cpp index ac7a3a173..c51b79885 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -37,11 +37,13 @@ #include "strings.h" #include "eqemu_config.h" #include "data_verification.h" +#include "evolving_items.h" #include "repositories/criteria/content_filter_criteria.h" #include "repositories/account_repository.h" #include "repositories/faction_association_repository.h" #include "repositories/starting_items_repository.h" #include "path_manager.h" +#include "../zone/client.h" #include "repositories/loottable_repository.h" #include "repositories/character_item_recast_repository.h" #include "repositories/character_corpses_repository.h" @@ -645,15 +647,23 @@ bool SharedDatabase::GetSharedBank(uint32 id, EQ::InventoryProfile *inv, bool is return true; } - // Overloaded: Retrieve character inventory based on character id (zone entry) -bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) +//bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) +bool SharedDatabase::GetInventory(Client *c) { - if (!char_id || !inv) + if (!c) { return false; + } + + uint32 char_id = c->CharacterID(); + EQ::InventoryProfile &inv = c->GetInv(); // Retrieve character inventory - auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`;", char_id)); + auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`", char_id)); + auto e_results = CharacterEvolvingItemsRepository::GetWhere( + *this, fmt::format("`char_id` = '{}' AND `deleted_at` IS NULL", char_id) + ); + if (results.empty()) { LogError("Error loading inventory for char_id {} from the database.", char_id); return false; @@ -667,8 +677,8 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) const auto timestamps = GetItemRecastTimestamps(char_id); auto cv_conflict = false; - const auto pmask = inv->GetLookup()->PossessionsBitmask; - const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank; + const auto pmask = inv.GetLookup()->PossessionsBitmask; + const auto bank_size = inv.GetLookup()->InventoryTypeSize.Bank; std::vector queue{}; for (auto &row: results) { @@ -785,9 +795,55 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) } } + if (item->EvolvingItem) { + if (slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <= EQ::invslot::EQUIPMENT_END) { + inst->SetEvolveEquipped(true); + } + + auto t = std::ranges::find_if( + e_results.cbegin(), + e_results.cend(), + [&](const CharacterEvolvingItemsRepository::CharacterEvolvingItems &x) { + return x.item_id == item_id; + } + ); + + if (t == std::end(e_results)) { + auto e = CharacterEvolvingItemsRepository::NewEntity(); + + e.character_id = char_id; + e.item_id = item_id; + e.equipped = inst->GetEvolveEquipped(); + e.final_item_id = evolving_items_manager.GetFinalItemID(*inst); + + auto r = CharacterEvolvingItemsRepository::InsertOne(*this, e); + e.id = r.id; + e_results.push_back(e); + + inst->SetEvolveUniqueID(e.id); + inst->SetEvolveCharID(e.character_id); + inst->SetEvolveItemID(e.item_id); + inst->SetEvolveActivated(e.activated); + inst->SetEvolveEquipped(e.equipped); + inst->SetEvolveCurrentAmount(e.current_amount); + inst->CalculateEvolveProgression(); + inst->SetEvolveFinalItemID(e.final_item_id); + } + else { + inst->SetEvolveUniqueID(t->id); + inst->SetEvolveCharID(t->character_id); + inst->SetEvolveItemID(t->item_id); + inst->SetEvolveActivated(t->activated); + inst->SetEvolveEquipped(t->equipped); + inst->SetEvolveCurrentAmount(t->current_amount); + inst->CalculateEvolveProgression(); + inst->SetEvolveFinalItemID(t->final_item_id); + } + } + int16 put_slot_id; if (slot_id >= 8000 && slot_id <= 8999) { - put_slot_id = inv->PushCursor(*inst); + put_slot_id = inv.PushCursor(*inst); } else if (slot_id >= 3111 && slot_id <= 3179) { // Admins: please report any occurrences of this error @@ -797,10 +853,10 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) item_id, slot_id ); - put_slot_id = inv->PushCursor(*inst); + put_slot_id = inv.PushCursor(*inst); } else { - put_slot_id = inv->PutItem(slot_id, *inst); + put_slot_id = inv.PutItem(slot_id, *inst); } row.guid = inst->GetSerialNumber(); @@ -825,8 +881,8 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) "ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])", char_name, char_id, - EQ::versions::MobVersionName(inv->InventoryVersion()), - (inv->GMInventory() ? "true" : "false") + EQ::versions::MobVersionName(inv.InventoryVersion()), + (inv.GMInventory() ? "true" : "false") ); } @@ -837,7 +893,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) EQ::ItemInstance::ClearGUIDMap(); // Retrieve shared inventory - return GetSharedBank(char_id, inv, true); + return GetSharedBank(char_id, &inv, true); } // Overloaded: Retrieve character inventory based on account_id and character name (char select) diff --git a/common/shareddb.h b/common/shareddb.h index 67c684206..d36132fd3 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -28,6 +28,8 @@ #include "fixed_memory_variable_hash_set.h" #include "say_link.h" #include "repositories/command_subsettings_repository.h" +#include "repositories/items_evolving_details_repository.h" +#include "../common/repositories/character_evolving_items_repository.h" #include #include @@ -103,7 +105,7 @@ public: bool GetSharedBank(uint32 id, EQ::InventoryProfile *inv, bool is_charid); int32 GetSharedPlatinum(uint32 account_id); bool SetSharedPlatinum(uint32 account_id, int32 amount_to_add); - bool GetInventory(uint32 char_id, EQ::InventoryProfile *inv); + bool GetInventory(Client* c); bool GetInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv); // deprecated std::map GetItemRecastTimestamps(uint32 char_id); uint32 GetItemRecastTimestamp(uint32 char_id, uint32 recast_type); diff --git a/common/version.h b/common/version.h index 9ca78821e..ba2bb092b 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9288 +#define CURRENT_BINARY_DATABASE_VERSION 9290 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045 #endif diff --git a/shared_memory/main.cpp b/shared_memory/main.cpp index c598b39f5..f395867ce 100644 --- a/shared_memory/main.cpp +++ b/shared_memory/main.cpp @@ -33,12 +33,14 @@ #include "../common/zone_store.h" #include "../common/path_manager.h" #include "../common/events/player_event_logs.h" +#include "../common/evolving_items.h" -EQEmuLogSys LogSys; -WorldContentService content_service; -ZoneStore zone_store; -PathManager path; -PlayerEventLogs player_event_logs; +EQEmuLogSys LogSys; +WorldContentService content_service; +ZoneStore zone_store; +PathManager path; +PlayerEventLogs player_event_logs; +EvolvingItemsManager evolving_items_manager; #ifdef _WINDOWS #include diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index a63916911..3866cfe89 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -731,3 +731,6 @@ OP_RemoveTrap=0x71da OP_PickZoneWindow=0x72d8 OP_PickZone=0xaaba + +#evolve item related +OP_EvolveItem=0x7cfb diff --git a/world/main.cpp b/world/main.cpp index aff459fb9..71109bb95 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -46,6 +46,7 @@ #include "client.h" #include "worlddb.h" #include "wguild_mgr.h" +#include "../common/evolving_items.h" #ifdef _WINDOWS #include @@ -111,6 +112,7 @@ WorldContentService content_service; WebInterfaceList web_interface; PathManager path; PlayerEventLogs player_event_logs; +EvolvingItemsManager evolving_items_manager; void CatchSignal(int sig_num); diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 1ead62e1b..798114399 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -17,6 +17,7 @@ SET(zone_sources botspellsai.cpp cheat_manager.cpp client.cpp + client_evolving_items.cpp client_bot.cpp client_mods.cpp client_packet.cpp diff --git a/zone/attack.cpp b/zone/attack.cpp index e2d7cdf17..3de17a13c 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2815,7 +2815,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy if (con_level != ConsiderColor::Gray) { if (!GetOwner() || (GetOwner() && !GetOwner()->IsClient())) { - give_exp_client->AddEXP(ExpSource::Kill, final_exp, con_level); + give_exp_client->AddEXP(ExpSource::Kill, final_exp, con_level, false, this); if ( killer_mob && diff --git a/zone/client.cpp b/zone/client.cpp index 6b8fb3144..f6286f47c 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -13113,7 +13113,8 @@ void Client::SetAAEXPPercentage(uint8 percentage) if (before_percentage > 0 && percentage == 0) { MessageString(Chat::White, AA_OFF); - } else if (before_percentage == 0 && percentage > 0) { + } + else if (before_percentage == 0 && percentage > 0) { MessageString(Chat::White, AA_ON); } diff --git a/zone/client.h b/zone/client.h index 75c48a494..adba50bac 100644 --- a/zone/client.h +++ b/zone/client.h @@ -72,6 +72,7 @@ namespace EQ #include "../common/repositories/trader_repository.h" #include "../common/guild_base.h" #include "../common/repositories/buyer_buy_lines_repository.h" +#include "../common/repositories/character_evolving_items_repository.h" #ifdef _WINDOWS // since windows defines these within windef.h (which windows.h include) @@ -710,13 +711,13 @@ public: void SendCrystalCounts(); uint64 GetExperienceForKill(Mob *against); - void AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel = 0xFF, bool resexp = false); + void AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel = 0xFF, bool resexp = false, NPC* npc = nullptr); uint64 CalcEXP(uint8 conlevel = 0xFF, bool ignore_mods = false); void CalculateNormalizedAAExp(uint64 &add_aaxp, uint8 conlevel, bool resexp); void CalculateStandardAAExp(uint64 &add_aaxp, uint8 conlevel, bool resexp); void CalculateLeadershipExp(uint64 &add_exp, uint8 conlevel); void CalculateExp(uint64 in_add_exp, uint64 &add_exp, uint64 &add_aaxp, uint8 conlevel, bool resexp); - void SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool resexp = false); + void SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool resexp = false, NPC* npc = nullptr); void AddLevelBasedExp(ExpSource exp_source, uint8 exp_percentage, uint8 max_level = 0, bool ignore_mods = false); void SetLeadershipEXP(uint64 group_exp, uint64 raid_exp); void AddLeadershipEXP(uint64 group_exp, uint64 raid_exp); @@ -1816,6 +1817,18 @@ public: void RecordKilledNPCEvent(NPC *n); uint32 GetEXPForLevel(uint16 check_level); + + // Evolving Item Info + void ProcessEvolvingItem(const uint64 exp, const Mob* mob); + void SendEvolvingPacket(int8 action, const CharacterEvolvingItemsRepository::CharacterEvolvingItems &item); + void DoEvolveItemToggle(const EQApplicationPacket* app); + void DoEvolveItemDisplayFinalResult(const EQApplicationPacket* app); + bool DoEvolveCheckProgression(const EQ::ItemInstance &inst); + void SendEvolveXPWindowDetails(const EQApplicationPacket* app); + void DoEvolveTransferXP(const EQApplicationPacket* app); + void SendEvolveXPTransferWindow(); + void SendEvolveTransferResults(const EQ::ItemInstance &inst_from, const EQ::ItemInstance &inst_to, const EQ::ItemInstance &inst_from_new, const EQ::ItemInstance &inst_to_new, const uint32 compatibility, const uint32 max_transfer_level); + protected: friend class Mob; void CalcEdibleBonuses(StatBonuses* newbon); @@ -1975,6 +1988,8 @@ private: uint16 m_door_tool_entity_id; uint16 m_object_tool_entity_id; + + public: uint16 GetDoorToolEntityId() const; void SetDoorToolEntityId(uint16 door_tool_entity_id); diff --git a/zone/client_evolving_items.cpp b/zone/client_evolving_items.cpp new file mode 100644 index 000000000..4fb2238b0 --- /dev/null +++ b/zone/client_evolving_items.cpp @@ -0,0 +1,451 @@ +#include "../common/evolving_items.h" + +#include "../common/events/player_event_logs.h" +#include "../common/global_define.h" + +#include "client.h" +#include "string_ids.h" +#include "worldserver.h" + +extern WorldServer worldserver; + +void Client::DoEvolveItemToggle(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + auto item = CharacterEvolvingItemsRepository::FindOne(database, in->unique_id); + + LogEvolveItemDetail( + "Character ID [{}] requested to set evolve item with unique id [{}] to status [{}]", + CharacterID(), + in->unique_id, + in->activated + ); + + if (!item.id) { + LogEvolveItemDetail( + "Character ID [{}] toggle evolve item unique id [{}] failed", CharacterID(), in->unique_id); + return; + } + + item.activated = in->activated; + const auto inst = GetInv().GetItem(GetInv().HasItem(item.item_id)); + inst->SetEvolveActivated(item.activated ? true : false); + + CharacterEvolvingItemsRepository::ReplaceOne(database, item); + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, item); +} + +void Client::SendEvolvingPacket(const int8 action, const CharacterEvolvingItemsRepository::CharacterEvolvingItems &item) +{ + auto out = std::make_unique(OP_EvolveItem, sizeof(EvolveItemToggle)); + const auto data = reinterpret_cast(out->pBuffer); + + LogEvolveItemDetail( + "Character ID [{}] requested info for evolving item with unique id [{}] status [{}] " + "percentage [{}]", + CharacterID(), + item.id, + item.activated, + item.progression + ); + + data->action = action; + data->unique_id = item.id; + data->percentage = item.progression; + data->activated = item.activated; + + QueuePacket(out.get()); + + LogEvolveItem( + "Sent evolve item with unique id [{}] status [{}] percentage [{}] to Character ID " + "[{}]", + data->unique_id, + data->activated, + data->percentage, + CharacterID() + ); +} + +void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob) +{ + std::vector queue{}; + + for (auto &[key, inst]: GetInv().GetWorn()) { + LogEvolveItemDetail( + "CharacterID [{}] found equipped item ID [{}]", CharacterID(), inst->GetID()); + if (!inst->IsEvolving() || !inst->GetEvolveActivated()) { + LogEvolveItemDetail( + "CharacterID [{}], item ID [{}] not an evolving item.", CharacterID(), inst->GetID()); + continue; + } + + if (inst->GetTimers().contains("evolve") && !inst->GetTimers().at("evolve").Check()) { + LogEvolveItemDetail( + "CharacterID [{}], item ID [{}] timer not yet expired. [{}] secs remaining.", + CharacterID(), + inst->GetID(), + inst->GetTimers().at("evolve").GetRemainingTime() / 1000); + continue; + } + + auto const type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).type; + auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type; + + LogEvolveItemDetail( + "CharacterID [{}] item id [{}] type {} sub_type {} is Evolving. Continue processing...", + CharacterID(), + inst->GetID(), + type, + sub_type); + + switch (type) { + case EvolvingItems::Types::AMOUNT_OF_EXP: { + LogEvolveItemDetail("Type [{}] Processing sub_type", type); + if (sub_type == EvolvingItems::SubTypes::ALL_EXP || + (sub_type == EvolvingItems::SubTypes::GROUP_EXP && IsGrouped())) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(exp * RuleR(EvolvingItems, PercentOfGroupExperience) / 100); + } + else if ( + sub_type == EvolvingItems::SubTypes::ALL_EXP || + (sub_type == EvolvingItems::SubTypes::RAID_EXP && IsRaidGrouped())) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(exp * RuleR(EvolvingItems, PercentOfRaidExperience) / 100); + } + else if ( + sub_type == EvolvingItems::SubTypes::ALL_EXP || sub_type == EvolvingItems::SubTypes::SOLO_EXP) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(exp * RuleR(EvolvingItems, PercentOfSoloExperience) / 100); + } + + inst->CalculateEvolveProgression(); + + auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( + database, inst->GetEvolveUniqueID(), inst->GetEvolveCurrentAmount(), inst->GetEvolveProgression()); + if (!e.id) { + break; + } + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); + + LogEvolveItem( + "Processing Complete for item id [{1}] Type 1 Amount of EXP - SubType [{0}] - " + "Assigned [{2}] of exp to [{1}]", + sub_type, + inst->GetID(), + exp * 0.001 + ); + + if (inst->GetEvolveProgression() >= 100) { + queue.push_back(inst); + } + + break; + } + case EvolvingItems::Types::SPECIFIC_MOB_RACE: { + LogEvolveItemDetail("Type [{}] Processing sub type", type); + if (mob && mob->GetRace() == sub_type) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(1); + inst->CalculateEvolveProgression(); + + auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( + database, + inst->GetEvolveUniqueID(), + inst->GetEvolveCurrentAmount(), + inst->GetEvolveProgression()); + if (!e.id) { + break; + } + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); + + LogEvolveItem( + "Processing Complete for item id [{1}] Type 3 Specific Mob Race - SubType [{0}] " + "- Increased count by 1 for [{1}]", + sub_type, + inst->GetID() + ); + } + + if (inst->GetEvolveProgression() >= 100) { + queue.push_back(inst); + } + + break; + } + case EvolvingItems::Types::SPECIFIC_ZONE_ID: { + LogEvolveItemDetail("Type [{}] Processing sub type", type); + if (mob && mob->GetZoneID() == sub_type) { + LogEvolveItemDetail("Sub_Type [{}] Processing Item", sub_type); + inst->SetEvolveAddToCurrentAmount(1); + inst->CalculateEvolveProgression(); + + auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression( + database, + inst->GetEvolveUniqueID(), + inst->GetEvolveCurrentAmount(), + inst->GetEvolveProgression()); + if (!e.id) { + break; + } + + SendEvolvingPacket(EvolvingItems::Actions::UPDATE_ITEMS, e); + + LogEvolveItem( + "Processing Complete for item id [{1}] Type 4 Specific Zone ID - SubType [{0}] " + "- Increased count by 1 for [{1}]", + sub_type, + inst->GetID() + ); + } + + if (inst->GetEvolveProgression() >= 100) { + queue.push_back(inst); + } + + break; + } + default: { + } + } + } + + if (!queue.empty()) { + for (auto const &i: queue) { + DoEvolveCheckProgression(*i); + } + } +} + +void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + + const uint32 item_id = static_cast(in->unique_id & 0xFFFFFFFF); + if (item_id == 0) { + LogEvolveItem("Error - Item ID of final evolve item is blank."); + return; + } + + std::unique_ptr const inst(database.CreateItem(item_id)); + + LogEvolveItemDetail( + "Character ID [{}] requested to view final evolve item id [{}] for evolve item id [{}]", + CharacterID(), + item_id, + evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id)); + + inst->SetEvolveProgression(100); + + if (inst) { + LogEvolveItemDetail( + "Sending final result for item id [{}] to Character ID [{}]", item_id, CharacterID()); + SendItemPacket(0, inst.get(), ItemPacketViewLink); + } +} + +bool Client::DoEvolveCheckProgression(const EQ::ItemInstance &inst) +{ + if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) { + return false; + } + + const auto new_item_id = evolving_items_manager.GetNextEvolveItemID(inst); + if (!new_item_id) { + return false; + } + + std::unique_ptr const new_inst(database.CreateItem(new_item_id)); + + PlayerEvent::EvolveItem e{}; + + RemoveItemBySerialNumber(inst.GetSerialNumber()); + evolving_items_manager.LoadPlayerEvent(inst, e); + e.status = "Evolved Item due to obtaining progression - Old Evolve Item removed from inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + PushItemOnCursor(*new_inst, true); + evolving_items_manager.LoadPlayerEvent(*new_inst, e); + e.status = "Evolved Item due to obtaining progression - New Evolve Item placed in inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + MessageString(Chat::Yellow, EVOLVE_ITEM_EVOLVED, inst.GetItem()->Name); + + LogEvolveItem( + "Evolved item id [{}] into item id [{}] for Character ID [{}]", + inst.GetID(), + new_inst->GetID(), + CharacterID()); + + return true; +} + +void Client::SendEvolveXPTransferWindow() +{ + auto out = std::make_unique(OP_EvolveItem, sizeof(EvolveItemToggle)); + const auto data = reinterpret_cast(out->pBuffer); + + data->action = 1; + + QueuePacket(out.get()); +} + +void Client::SendEvolveXPWindowDetails(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + + const auto item_1_slot = + GetInv().HasEvolvingItem(in->item1_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + const auto item_2_slot = + GetInv().HasEvolvingItem(in->item2_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + + if (item_1_slot == INVALID_INDEX || item_2_slot == INVALID_INDEX) { + return; + } + + const auto inst_from = GetInv().GetItem(item_1_slot); + const auto inst_to = GetInv().GetItem(item_2_slot); + + if (!inst_from || !inst_to) { + return; + } + + const auto results = evolving_items_manager.DetermineTransferResults(*inst_from, *inst_to); + + if (!results.item_from_id || !results.item_to_id) { + SendEvolveTransferResults(*inst_from, *inst_to, *inst_from, *inst_to, 0, 0); + return; + } + + std::unique_ptr const inst_from_new(database.CreateItem(results.item_from_id)); + std::unique_ptr const inst_to_new(database.CreateItem(results.item_to_id)); + if (!inst_from_new || !inst_to_new) { + SendEvolveTransferResults(*inst_from, *inst_to, *inst_from, *inst_to, 0, 0); + return; + } + + inst_from_new->SetEvolveCurrentAmount(results.item_from_current_amount); + inst_from_new->CalculateEvolveProgression(); + inst_to_new->SetEvolveCurrentAmount(results.item_to_current_amount); + inst_to_new->CalculateEvolveProgression(); + + SendEvolveTransferResults( + *inst_from, *inst_to, *inst_from_new, *inst_to_new, results.compatibility, results.max_transfer_level); +} + +void Client::DoEvolveTransferXP(const EQApplicationPacket *app) +{ + const auto in = reinterpret_cast(app->pBuffer); + + const auto item_1_slot = + GetInv().HasEvolvingItem(in->item1_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + const auto item_2_slot = + GetInv().HasEvolvingItem(in->item2_unique_id, 1, invWherePersonal | invWhereWorn | invWhereCursor); + + if (item_1_slot == INVALID_INDEX || item_2_slot == INVALID_INDEX) { + return; + } + + const auto inst_from = GetInv().GetItem(item_1_slot); + const auto inst_to = GetInv().GetItem(item_2_slot); + + if (!inst_from || !inst_to) { + Message(Chat::Red, "Transfer Failed. Incompatible Items."); + LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); + return; + } + + const auto results = evolving_items_manager.DetermineTransferResults(*inst_from, *inst_to); + + if (!results.item_from_id || !results.item_to_id) { + Message(Chat::Red, "Transfer Failed. Incompatible Items."); + LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); + return; + } + + std::unique_ptr const inst_from_new(database.CreateItem(results.item_from_id)); + std::unique_ptr const inst_to_new(database.CreateItem(results.item_to_id)); + + if (!inst_from_new || !inst_to_new) { + Message(Chat::Red, "Transfer Failed. Incompatible Items."); + LogEvolveItem("Transfer Failed for Character ID [{}]", CharacterID()); + return; + } + + inst_from_new->SetEvolveCurrentAmount(results.item_from_current_amount); + inst_from_new->CalculateEvolveProgression(); + inst_to_new->SetEvolveCurrentAmount(results.item_to_current_amount); + inst_to_new->CalculateEvolveProgression(); + + PlayerEvent::EvolveItem e{}; + + RemoveItemBySerialNumber(inst_from->GetSerialNumber()); + evolving_items_manager.LoadPlayerEvent(*inst_from, e); + e.status = "Transfer XP - Original FROM Evolve Item removed from inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + PushItemOnCursor(*inst_from_new, true); + evolving_items_manager.LoadPlayerEvent(*inst_from_new, e); + e.status = "Transfer XP - Updated FROM item placed in inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + RemoveItemBySerialNumber(inst_to->GetSerialNumber()); + evolving_items_manager.LoadPlayerEvent(*inst_to, e); + e.status = "Transfer XP - Original TO Evolve Item removed from inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + PushItemOnCursor(*inst_to_new, true); + evolving_items_manager.LoadPlayerEvent(*inst_to_new, e); + e.status = "Transfer XP - Updated TO Evolve item placed in inventory."; + RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); + + LogEvolveItem( + "Evolve Transfer XP resulted in evolved item id [{}] into item id [{}] for Character ID " + "[{}]", + inst_to->GetID(), + inst_to_new->GetID(), + CharacterID() + ); +} + +void Client::SendEvolveTransferResults( + const EQ::ItemInstance &inst_from, + const EQ::ItemInstance &inst_to, + const EQ::ItemInstance &inst_from_new, + const EQ::ItemInstance &inst_to_new, + const uint32 compatibility, + const uint32 max_transfer_level) +{ + std::stringstream ss; + cereal::BinaryOutputArchive ar(ss); + + EvolveXPWindowSend e{}; + e.action = EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS; + e.compatibility = compatibility; + e.item1_unique_id = inst_from.GetEvolveUniqueID(); + e.item2_unique_id = inst_to.GetEvolveUniqueID(); + e.max_transfer_level = max_transfer_level; + e.item1_present = 1; + e.item2_present = 1; + e.serialize_item_1 = inst_from_new.Serialize(0); + e.serialize_item_2 = inst_to_new.Serialize(0); + + { + ar(e); + } + + uint32 packet_size = sizeof(EvolveItemMessaging) + ss.str().length(); + + std::unique_ptr out(new EQApplicationPacket(OP_EvolveItem, packet_size)); + const auto data = reinterpret_cast(out->pBuffer); + + data->action = EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS; + memcpy(data->serialized_data, ss.str().data(), ss.str().length()); + + QueuePacket(out.get()); + + ss.str(""); + ss.clear(); +} diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index ef671ef12..26da7ebce 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -80,7 +80,6 @@ extern PetitionList petition_list; extern EntityList entity_list; typedef void (Client::*ClientPacketProc)(const EQApplicationPacket *app); - //Use a map for connecting opcodes since it dosent get used a lot and is sparse std::map ConnectingOpcodes; //Use a static array for connected, for speed @@ -208,6 +207,7 @@ void MapOpcodes() ConnectedOpcodes[OP_Emote] = &Client::Handle_OP_Emote; ConnectedOpcodes[OP_EndLootRequest] = &Client::Handle_OP_EndLootRequest; ConnectedOpcodes[OP_EnvDamage] = &Client::Handle_OP_EnvDamage; + ConnectedOpcodes[OP_EvolveItem] = &Client::Handle_OP_EvolveItem; ConnectedOpcodes[OP_FaceChange] = &Client::Handle_OP_FaceChange; ConnectedOpcodes[OP_FeignDeath] = &Client::Handle_OP_FeignDeath; ConnectedOpcodes[OP_FindPersonRequest] = &Client::Handle_OP_FindPersonRequest; @@ -1309,7 +1309,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) // set to full support in case they're a gm with items in disabled expansion slots...but, have their gm flag off... // item loss will occur when they use the 'empty' slots, if this is not done m_inv.SetGMInventory(true); - loaditems = database.GetInventory(cid, &m_inv); /* Load Character Inventory */ + loaditems = database.GetInventory(this); /* Load Character Inventory */ database.LoadCharacterBandolier(cid, &m_pp); /* Load Character Bandolier */ database.LoadCharacterBindPoint(cid, &m_pp); /* Load Character Bind */ database.LoadCharacterMaterialColor(cid, &m_pp); /* Load Character Material */ @@ -10748,7 +10748,12 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) InterrogateInventory(this, true, false, true, error); } - return; + for (int slot : {mi->to_slot, mi->from_slot}) { + auto item = GetInv().GetItem(slot); + if (item && item->IsEvolving()) { + CharacterEvolvingItemsRepository::UpdateOne(database, item->GetEvolvingDetails()); + } + } } void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) @@ -17165,3 +17170,37 @@ void Client::Handle_OP_ShopRetrieveParcel(const EQApplicationPacket *app) auto parcel_in = (ParcelRetrieve_Struct *)app->pBuffer; DoParcelRetrieve(*parcel_in); } + +void Client::Handle_OP_EvolveItem(const EQApplicationPacket *app) +{ + if (app->size != sizeof(EvolveItemToggle)) { + LogError( + "Received Handle_OP_EvolveItem packet. Expected size {}, received size {}.", + sizeof(EvolveItemToggle), + app->size + ); + return; + } + + auto in = reinterpret_cast(app->pBuffer); + + switch (in->action) { + case EvolvingItems::Actions::UPDATE_ITEMS: { + DoEvolveItemToggle(app); + break; + } + case EvolvingItems::Actions::FINAL_RESULT: { + DoEvolveItemDisplayFinalResult(app); + break; + } + case EvolvingItems::Actions::TRANSFER_XP: { + DoEvolveTransferXP(app); + break; + } + case EvolvingItems::Actions::TRANSFER_WINDOW_DETAILS: { + SendEvolveXPWindowDetails(app); + } + default: { + } + } +} diff --git a/zone/client_packet.h b/zone/client_packet.h index 4ef62bdf9..2970f91dc 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -114,6 +114,7 @@ void Handle_OP_Emote(const EQApplicationPacket *app); void Handle_OP_EndLootRequest(const EQApplicationPacket *app); void Handle_OP_EnvDamage(const EQApplicationPacket *app); + void Handle_OP_EvolveItem(const EQApplicationPacket *app); void Handle_OP_FaceChange(const EQApplicationPacket *app); void Handle_OP_FeignDeath(const EQApplicationPacket *app); void Handle_OP_FindPersonRequest(const EQApplicationPacket *app); diff --git a/zone/command.cpp b/zone/command.cpp index 1007e09d3..35b920e53 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -126,6 +126,7 @@ int command_init(void) command_add("enablerecipe", "[Recipe ID] - Enables a Recipe", AccountStatus::QuestTroupe, command_enablerecipe) || command_add("entityvariable", "[clear|delete|set|view] - Modify entity variables for yourself or your target", AccountStatus::GMAdmin, command_entityvariable) || command_add("exptoggle", "[Toggle] - Toggle your or your target's experience gain.", AccountStatus::QuestTroupe, command_exptoggle) || + command_add("evolve", "Evolving Item manipulation commands. Use argument help for more info.", AccountStatus::Steward, command_evolvingitems) || command_add("faction", "[Find (criteria | all ) | Review (criteria | all) | Reset (id)] - Resets Player's Faction", AccountStatus::QuestTroupe, command_faction) || command_add("factionassociation", "[factionid] [amount] - triggers a faction hits via association", AccountStatus::GMLeadAdmin, command_faction_association) || command_add("feature", "Change your or your target's feature's temporarily", AccountStatus::QuestTroupe, command_feature) || @@ -826,6 +827,7 @@ void command_bot(Client *c, const Seperator *sep) #include "gm_commands/entityvariable.cpp" #include "gm_commands/exptoggle.cpp" #include "gm_commands/faction.cpp" +#include "gm_commands/evolving_items.cpp" #include "gm_commands/feature.cpp" #include "gm_commands/find.cpp" #include "gm_commands/fish.cpp" diff --git a/zone/command.h b/zone/command.h index 189909516..09164e6bf 100644 --- a/zone/command.h +++ b/zone/command.h @@ -38,6 +38,7 @@ void SendShowInventorySubCommands(Client *c); void SendFixMobSubCommands(Client *c); void SendDataBucketsSubCommands(Client *c); void SendParcelsSubCommands(Client *c); +void SendEvolvingItemsSubCommands(Client *c); // Commands void command_acceptrules(Client *c, const Seperator *sep); @@ -80,6 +81,7 @@ void command_emptyinventory(Client *c, const Seperator *sep); void command_enablerecipe(Client *c, const Seperator *sep); void command_entityvariable(Client *c, const Seperator *sep); void command_exptoggle(Client *c, const Seperator *sep); +void command_evolvingitems(Client *c, const Seperator *sep); void command_faction(Client *c, const Seperator *sep); void command_faction_association(Client *c, const Seperator *sep); void command_feature(Client *c, const Seperator *sep); diff --git a/zone/doors.cpp b/zone/doors.cpp index 3ec9c86af..036f38928 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -30,6 +30,7 @@ #include "string_ids.h" #include "worldserver.h" #include "zonedb.h" +#include "../common/evolving_items.h" #include "../common/repositories/criteria/content_filter_criteria.h" #include @@ -610,6 +611,10 @@ void Doors::HandleClick(Client *sender, uint8 trigger) } } } + + if (GetOpenType() == 40 && GetZone(GetDoorZone(),0)) { + sender->SendEvolveXPTransferWindow(); + } } void Doors::Open(Mob *sender, bool alt_mode) diff --git a/zone/doors.h b/zone/doors.h index 3c1ec3704..b93aed657 100644 --- a/zone/doors.h +++ b/zone/doors.h @@ -77,6 +77,8 @@ public: bool IsDoorBlacklisted(); + const char* GetDoorZone() const { return m_zone_name; } + private: bool GetIsDoorBlacklisted(); diff --git a/zone/exp.cpp b/zone/exp.cpp index be6b8af2b..a1f8c8adf 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -497,7 +497,7 @@ void Client::CalculateExp(uint64 in_add_exp, uint64 &add_exp, uint64 &add_aaxp, add_exp = GetEXP() + add_exp; } -void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, bool resexp) { +void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, bool resexp, NPC* npc) { if (!IsEXPEnabled()) { return; } @@ -574,10 +574,10 @@ void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, boo } // Now update our character's normal and AA xp - SetEXP(exp_source, exp, aaexp, resexp); + SetEXP(exp_source, exp, aaexp, resexp, npc); } -void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool isrezzexp) { +void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool isrezzexp, NPC* npc) { uint64 current_exp = GetEXP(); uint64 current_aa_exp = GetAAXP(); uint64 total_current_exp = current_exp + current_aa_exp; @@ -677,6 +677,7 @@ void Client::SetEXP(ExpSource exp_source, uint64 set_exp, uint64 set_aaxp, bool } } } + ProcessEvolvingItem(exp_gained, npc); } else if(total_add_exp < total_current_exp) { //only loss message if you lose exp, no message if you gained/lost nothing. uint64 exp_lost = current_exp - set_exp; float exp_percent = (float)((float)exp_lost / (float)(GetEXPForLevel(GetLevel() + 1) - GetEXPForLevel(GetLevel())))*(float)100; @@ -1205,7 +1206,7 @@ void Group::SplitExp(ExpSource exp_source, const uint64 exp, Mob* other) { if (diff >= max_diff) { const uint64 tmp = (m->GetLevel() + 3) * (m->GetLevel() + 3) * 75 * 35 / 10; const uint64 tmp2 = group_experience / member_count; - m->CastToClient()->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level); + m->CastToClient()->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level, false, other->CastToNPC()); } } } @@ -1256,7 +1257,7 @@ void Raid::SplitExp(ExpSource exp_source, const uint64 exp, Mob* other) { if (diff >= max_diff) { const uint64 tmp = (m.member->GetLevel() + 3) * (m.member->GetLevel() + 3) * 75 * 35 / 10; const uint64 tmp2 = (raid_experience / member_modifier) + 1; - m.member->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level); + m.member->AddEXP(exp_source, tmp < tmp2 ? tmp : tmp2, consider_level, false, other->CastToNPC()); } } } diff --git a/zone/gm_commands/evolving_items.cpp b/zone/gm_commands/evolving_items.cpp new file mode 100644 index 000000000..3971c0021 --- /dev/null +++ b/zone/gm_commands/evolving_items.cpp @@ -0,0 +1,301 @@ +#include "../client.h" +#include "../command.h" +#include "../../common/evolving_items.h" + +void command_evolvingitems(Client *c, const Seperator *sep) +{ + const uint16 arguments = sep->argnum; + if (!arguments) { + SendEvolvingItemsSubCommands(c); + return; + } + + Client* t = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + t = c->GetTarget()->CastToClient(); + } + + const bool is_item = !strcasecmp(sep->arg[1], "item"); + const bool is_target = !strcasecmp(sep->arg[1], "target"); + + if (!is_item && !is_target) { + SendEvolvingItemsSubCommands(c); + return; + } + + if (is_target) { + if (arguments > 1) { + c->Message(Chat::White, "Usage: #evolve target"); + } else { + c->Message(Chat::Red, "Worn Items"); + for (auto const &[key, value]: t->GetInv().GetWorn()) { + if (!value->IsEvolving()) { + continue; + } + + auto item = evolving_items_manager.GetEvolvingItemsCache().at(value->GetID()); + c->Message( + Chat::Yellow, + fmt::format( + "Evolving Items | {:0d}", + value->GetID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Unique ID | {:0d}", + value->GetEvolveUniqueID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Final Item ID | {:0d}", + value->GetEvolveFinalItemID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Activated | {}", + value->GetEvolveActivated() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Equipped | {}", + value->GetEvolveEquipped() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Current Amount | {:0d}", + value->GetEvolveCurrentAmount() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Required Amount | {:0d}", + item.required_amount + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Progression (%) | {:.2f}", + value->GetEvolveProgression() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Type | {}", + item.type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Subtype | {}", + item.sub_type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer | {}", + value->GetTimers().at("evolve").Enabled() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer Remaining | {}", + value->GetTimers().at("evolve").GetRemainingTime() + ).c_str() + ); + } + + c->Message(Chat::Red, "Personal Items"); + for (auto const &[key, value]: t->GetInv().GetPersonal()) { + if (!value->IsEvolving()) { + continue; + } + + auto item = evolving_items_manager.GetEvolvingItemsCache().at(value->GetID()); + c->Message( + Chat::Yellow, + fmt::format( + "Evolving Items | {:0d}", + value->GetID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Unique ID | {:0d}", + value->GetEvolveUniqueID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Final Item ID | {:0d}", + value->GetEvolveFinalItemID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Activated | {}", + value->GetEvolveActivated() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Equipped | {}", + value->GetEvolveEquipped() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Current Amount | {:0d}", + value->GetEvolveCurrentAmount() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Required Amount | {:0d}", + item.required_amount + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Progression (%) | {:.2f}", + value->GetEvolveProgression() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Type | {}", + item.type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Subtype | {}", + item.sub_type + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer | {}", + value->GetTimers().at("evolve").Enabled() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer Remaining | {}", + value->GetTimers().at("evolve").GetRemainingTime() + ).c_str() + ); + } + } + } else if (is_item) { + if (arguments > 2) { + c->Message(Chat::White, "Usage: #evolve item item_id"); + } else if (sep->IsNumber(2)) { + auto item_id = Strings::ToUnsignedInt(sep->arg[2]); + auto item = c->GetInv().GetItem(c->GetInv().HasItem(item_id)); + if (item) { + c->Message( + Chat::Yellow, + fmt::format( + "Evolving Items | {:0d}", + item->GetID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Slot | {:0d}", + c->GetInv().HasItem(item_id) + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Activated | {}", + item->GetEvolveActivated() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Equipped | {}", + item->GetEvolveEquipped() ? "Yes" : "No" + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Unique ID | {:0d}", + item->GetEvolveUniqueID() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Progression (%) | {:.2f}", + item->GetEvolveProgression() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer | {}", + item->GetTimers().at("evolve").Enabled() + ).c_str() + ); + c->Message( + Chat::Yellow, + fmt::format( + "Timer Remaining | {}", + item->GetTimers().at("evolve").GetRemainingTime() + ).c_str() + ); + } else { + c->Message( + Chat::Red, + fmt::format( + "Item {} could not be found in your inventory.", + item_id + ).c_str() + ); + } + } else { + SendEvolvingItemsSubCommands(c); + } + } +} + +void SendEvolvingItemsSubCommands(Client *c) +{ + c->Message(Chat::White, "#evolve item item_id (Shows evolve values within the iteminstance)"); + c->Message( + Chat::White, + "#evolve target (Shows evolve values within the target's cache - Must have a player target selected.)" + ); +} diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 30f4c31da..f89c1005c 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -25,6 +25,7 @@ #include "zonedb.h" #include "../common/events/player_event_logs.h" #include "bot.h" +#include "../common/evolving_items.h" #include "../common/repositories/character_corpse_items_repository.h" extern WorldServer worldserver; @@ -1091,7 +1092,11 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up } // end QS code - bool isDeleted = m_inv.DeleteItem(slot_id, quantity); + uint64 evolve_id = m_inv[slot_id]->GetEvolveUniqueID(); + bool isDeleted = m_inv.DeleteItem(slot_id, quantity); + if (isDeleted && evolve_id && (slot_id > EQ::invslot::TRADE_END || slot_id < EQ::invslot::TRADE_BEGIN)) { + CharacterEvolvingItemsRepository::SoftDelete(database, evolve_id); + } const EQ::ItemInstance* inst = nullptr; if (slot_id == EQ::invslot::slotCursor) { @@ -1143,6 +1148,8 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update) { LogInventory("Putting item [{}] ([{}]) on the cursor", inst.GetItem()->Name, inst.GetItem()->ID); + + evolving_items_manager.DoLootChecks(CharacterID(), EQ::invslot::slotCursor, inst); m_inv.PushCursor(inst); if (client_update) { @@ -1163,9 +1170,9 @@ bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, boo if (slot_id == EQ::invslot::slotCursor) { // don't trust macros before conditional statements... return PushItemOnCursor(inst, client_update); } - else { - m_inv.PutItem(slot_id, inst); - } + + evolving_items_manager.DoLootChecks(CharacterID(), slot_id, inst); + m_inv.PutItem(slot_id, inst); if (client_update) { @@ -1173,15 +1180,16 @@ bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, boo //SendWearChange(EQ::InventoryProfile::CalcMaterialFromSlot(slot_id)); } + CalcBonuses(); + if (slot_id == EQ::invslot::slotCursor) { auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend(); return database.SaveCursor(CharacterID(), s, e); } - else { - return database.SaveInventory(CharacterID(), &inst, slot_id); - } - CalcBonuses(); + return database.SaveInventory(CharacterID(), &inst, slot_id); + + //CalcBonuses(); // this never fires?? // a lot of wasted checks and calls coded above... } @@ -1191,6 +1199,8 @@ void Client::PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, Loo bool cursor_empty = m_inv.CursorEmpty(); + evolving_items_manager.DoLootChecks(CharacterID(), slot_id, inst); + if (slot_id == EQ::invslot::slotCursor) { m_inv.PushCursor(inst); auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend(); diff --git a/zone/lua_iteminst.cpp b/zone/lua_iteminst.cpp index ad9ed1405..f81cfc88b 100644 --- a/zone/lua_iteminst.cpp +++ b/zone/lua_iteminst.cpp @@ -255,11 +255,6 @@ int8 Lua_ItemInst::GetMaxEvolveLvl() { return self->GetMaxEvolveLvl(); } -uint32 Lua_ItemInst::GetKillsNeeded(uint8 current_level) { - Lua_Safe_Call_Int(); - return self->GetKillsNeeded(current_level); -} - Lua_ItemInst Lua_ItemInst::Clone() { Lua_Safe_Call_Class(Lua_ItemInst); return Lua_ItemInst(self->Clone(), true); @@ -348,6 +343,96 @@ std::string Lua_ItemInst::GetItemLink() return linker.GenerateLink(); } +void Lua_ItemInst::AddEvolveAmount(uint64 amount) +{ + Lua_Safe_Call_Void(); + self->SetEvolveAddToCurrentAmount(amount); +} + +uint32 Lua_ItemInst::GetAugmentEvolveUniqueID(uint8 slot_id) +{ + Lua_Safe_Call_Int(); + return self->GetAugmentEvolveUniqueID(slot_id); +} + +bool Lua_ItemInst::GetEvolveActivated() +{ + Lua_Safe_Call_Bool(); + return self->GetEvolveActivated(); +} + +uint64 Lua_ItemInst::GetEvolveAmount() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveCurrentAmount(); +} + +uint32 Lua_ItemInst::GetEvolveCharacterID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveCharID(); +} + +bool Lua_ItemInst::GetEvolveEquipped() +{ + Lua_Safe_Call_Bool(); + return self->GetEvolveEquipped(); +} + +uint32 Lua_ItemInst::GetEvolveFinalItemID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveFinalItemID(); +} + +uint32 Lua_ItemInst::GetEvolveItemID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveItemID(); +} + +int8 Lua_ItemInst::GetEvolveLevel() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveLvl(); +} + +uint32 Lua_ItemInst::GetEvolveLoreID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveLoreID(); +} + +float Lua_ItemInst::GetEvolveProgression() +{ + Lua_Safe_Call_Real(); + return self->GetEvolveProgression(); +} + +uint64 Lua_ItemInst::GetEvolveUniqueID() +{ + Lua_Safe_Call_Int(); + return self->GetEvolveUniqueID(); +} + +bool Lua_ItemInst::IsEvolving() +{ + Lua_Safe_Call_Bool(); + return self->IsEvolving(); +} + +void Lua_ItemInst::SetEvolveAmount(uint64 amount) +{ + Lua_Safe_Call_Void(); + self->SetEvolveCurrentAmount(amount); +} + +void Lua_ItemInst::SetEvolveProgression(float amount) +{ + Lua_Safe_Call_Void(); + self->SetEvolveProgression(amount); +} + luabind::scope lua_register_iteminst() { return luabind::class_("ItemInst") .def(luabind::constructor<>()) @@ -356,12 +441,14 @@ luabind::scope lua_register_iteminst() { .property("null", &Lua_ItemInst::Null) .property("valid", &Lua_ItemInst::Valid) .def("AddExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::AddExp) + .def("AddEvolveAmount", (void(Lua_ItemInst::*)(uint64))&Lua_ItemInst::AddEvolveAmount) .def("ClearTimers", (void(Lua_ItemInst::*)(void))&Lua_ItemInst::ClearTimers) .def("Clone", (Lua_ItemInst(Lua_ItemInst::*)(void))&Lua_ItemInst::Clone) .def("ContainsAugmentByID", (bool(Lua_ItemInst::*)(uint32))&Lua_ItemInst::ContainsAugmentByID) .def("CountAugmentByID", (int(Lua_ItemInst::*)(uint32))&Lua_ItemInst::CountAugmentByID) .def("DeleteCustomData", (void(Lua_ItemInst::*)(const std::string &))&Lua_ItemInst::DeleteCustomData) .def("GetAugment", (Lua_ItemInst(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugment) + .def("GetAugmentEvolveUniqueID", (uint32(Lua_ItemInst::*)(uint8))&Lua_ItemInst::GetAugmentEvolveUniqueID) .def("GetAugmentIDs", (luabind::object(Lua_ItemInst::*)(lua_State*))&Lua_ItemInst::GetAugmentIDs) .def("GetAugmentItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetAugmentItemID) .def("GetAugmentType", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetAugmentType) @@ -370,13 +457,22 @@ luabind::scope lua_register_iteminst() { .def("GetCustomData", (std::string(Lua_ItemInst::*)(const std::string &))&Lua_ItemInst::GetCustomData) .def("GetCustomDataString", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetCustomDataString) .def("GetExp", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetExp) + .def("GetEvolveActivated", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveActivated) + .def("GetEvolveAmount", (uint64(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveAmount) + .def("GetEvolveCharacterID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveCharacterID) + .def("GetEvolveEquipped", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveEquipped) + .def("GetEvolveFinalItemID", (uint64(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveFinalItemID) + .def("GetEvolveItemID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveItemID) + .def("GetEvolveLevel", (int8(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveLevel) + .def("GetEvolveLoreID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveLoreID) + .def("GetEvolveProgression", (float(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveProgression) + .def("GetEvolveUniqueID", (uint64(Lua_ItemInst::*)(void))&Lua_ItemInst::GetEvolveUniqueID) .def("GetID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetID) .def("GetItem", (Lua_Item(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItem) .def("GetItem", (Lua_ItemInst(Lua_ItemInst::*)(uint8))&Lua_ItemInst::GetItem) .def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID) .def("GetItemLink", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemLink) .def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID) - .def("GetKillsNeeded", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetKillsNeeded) .def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl) .def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName) .def("GetPrice", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetPrice) @@ -389,6 +485,7 @@ luabind::scope lua_register_iteminst() { .def("IsAugmented", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsAugmented) .def("IsEquipable", (bool(Lua_ItemInst::*)(int16))&Lua_ItemInst::IsEquipable) .def("IsEquipable", (bool(Lua_ItemInst::*)(uint16,uint16))&Lua_ItemInst::IsEquipable) + .def("IsEvolving", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsEvolving) .def("IsExpendable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsExpendable) .def("IsInstNoDrop", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsInstNoDrop) .def("IsStackable", (bool(Lua_ItemInst::*)(void))&Lua_ItemInst::IsStackable) @@ -404,6 +501,8 @@ luabind::scope lua_register_iteminst() { .def("SetCustomData", (void(Lua_ItemInst::*)(const std::string&,float))&Lua_ItemInst::SetCustomData) .def("SetCustomData", (void(Lua_ItemInst::*)(const std::string&,int))&Lua_ItemInst::SetCustomData) .def("SetCustomData", (void(Lua_ItemInst::*)(const std::string&,const std::string&))&Lua_ItemInst::SetCustomData) + .def("SetEvolveAmount", (void(Lua_ItemInst::*)(uint64))&Lua_ItemInst::SetEvolveAmount) + .def("SetEvolveProgression", (void(Lua_ItemInst::*)(float))&Lua_ItemInst::SetEvolveProgression) .def("SetExp", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetExp) .def("SetInstNoDrop", (void(Lua_ItemInst::*)(bool))&Lua_ItemInst::SetInstNoDrop) .def("SetPrice", (void(Lua_ItemInst::*)(uint32))&Lua_ItemInst::SetPrice) diff --git a/zone/lua_iteminst.h b/zone/lua_iteminst.h index 2ff99165c..a4c7529e4 100644 --- a/zone/lua_iteminst.h +++ b/zone/lua_iteminst.h @@ -77,7 +77,6 @@ public: void SetExp(uint32 exp); void AddExp(uint32 exp); int8 GetMaxEvolveLvl(); - uint32 GetKillsNeeded(uint8 current_level); Lua_ItemInst Clone(); void SetTimer(std::string name, uint32 time); void StopTimer(std::string name); @@ -91,6 +90,21 @@ public: void ItemSay(const char* text, uint8 language_id); luabind::object GetAugmentIDs(lua_State* L); std::string GetItemLink(); + void AddEvolveAmount(uint64 amount); + uint32 GetAugmentEvolveUniqueID(uint8 slot_id); + bool GetEvolveActivated(); + uint64 GetEvolveAmount(); + uint32 GetEvolveCharacterID(); + bool GetEvolveEquipped(); + uint32 GetEvolveFinalItemID(); + uint32 GetEvolveItemID(); + int8 GetEvolveLevel(); + uint32 GetEvolveLoreID(); + float GetEvolveProgression(); + uint64 GetEvolveUniqueID(); + bool IsEvolving(); + void SetEvolveAmount(uint64 amount); + void SetEvolveProgression(float amount); private: bool cloned_; diff --git a/zone/main.cpp b/zone/main.cpp index a05af774a..5d463fa9b 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -52,6 +52,7 @@ #include "task_manager.h" #include "quest_parser_collection.h" #include "embparser.h" +#include "../common/evolving_items.h" #include "lua_parser.h" #include "questmgr.h" #include "npc_scale_manager.h" @@ -110,6 +111,7 @@ PathManager path; PlayerEventLogs player_event_logs; DatabaseUpdate database_update; SkillCaps skill_caps; +EvolvingItemsManager evolving_items_manager; const SPDat_Spell_Struct* spells; int32 SPDAT_RECORDS = -1; @@ -387,6 +389,11 @@ int main(int argc, char **argv) title_manager.LoadTitles(); content_db.LoadTributes(); + // Load evolving item data + evolving_items_manager.SetDatabase(&database); + evolving_items_manager.SetContentDatabase(&content_db); + evolving_items_manager.LoadEvolvingItems(); + database.GetDecayTimes(npcCorpseDecayTimes); if (!EQ::ProfanityManager::LoadProfanityList(&database)) { diff --git a/zone/perl_questitem.cpp b/zone/perl_questitem.cpp index fea1bf061..53e906de7 100644 --- a/zone/perl_questitem.cpp +++ b/zone/perl_questitem.cpp @@ -152,11 +152,6 @@ uint32 Perl_QuestItem_GetItemScriptID(EQ::ItemInstance* self) return self->GetItemScriptID(); } -uint32 Perl_QuestItem_GetKillsNeeded(EQ::ItemInstance* self, uint8 current_level) -{ - return self->GetKillsNeeded(current_level); -} - int8 Perl_QuestItem_GetMaxEvolveLevel(EQ::ItemInstance* self) { return self->GetMaxEvolveLvl(); @@ -297,12 +292,88 @@ std::string Perl_QuestItem_GetItemLink(EQ::ItemInstance* self) return linker.GenerateLink(); } +void Perl_QuestItem_AddEvolveAmount(EQ::ItemInstance* self, uint64 amount) +{ + self->SetEvolveAddToCurrentAmount(amount); +} + +uint32 Perl_QuestItem_GetAugmentEvolveUniqueID(EQ::ItemInstance* self, uint8 slot_id) +{ + return self->GetAugmentEvolveUniqueID(slot_id); +} + +bool Perl_QuestItem_GetEvolveActivated(EQ::ItemInstance* self) +{ + return self->GetEvolveActivated(); +} + +uint64 Perl_QuestItem_GetEvolveAmount(EQ::ItemInstance* self) +{ + return self->GetEvolveCurrentAmount(); +} + +uint32 Perl_QuestItem_GetEvolveCharacterID(EQ::ItemInstance* self) +{ + return self->GetEvolveCharID(); +} + +bool Perl_QuestItem_GetEvolveEquipped(EQ::ItemInstance* self) +{ + return self->GetEvolveEquipped(); +} + +uint32 Perl_QuestItem_GetEvolveItemID(EQ::ItemInstance* self) +{ + return self->GetEvolveItemID(); +} + +uint32 Perl_QuestItem_GetEvolveFinalItemID(EQ::ItemInstance* self) +{ + return self->GetEvolveFinalItemID(); +} + +int8 Perl_QuestItem_GetEvolveLevel(EQ::ItemInstance* self) +{ + return self->GetEvolveLvl(); +} + +uint32 Perl_QuestItem_GetEvolveLoreID(EQ::ItemInstance* self) +{ + return self->GetEvolveLoreID(); +} + +double Perl_QuestItem_GetEvolveProgression(EQ::ItemInstance* self) +{ + return self->GetEvolveProgression(); +} + +uint64 Perl_QuestItem_GetEvolveUniqueID(EQ::ItemInstance* self) +{ + return self->GetEvolveUniqueID(); +} + +bool Perl_QuestItem_IsEvolving(EQ::ItemInstance* self) +{ + return self->IsEvolving(); +} + +void Perl_QuestItem_SetEvolveProgression(EQ::ItemInstance* self, float amount) +{ + self->SetEvolveProgression(amount); +} + +void Perl_QuestItem_SetEvolveAmount(EQ::ItemInstance* self, uint64 amount) +{ + self->SetEvolveCurrentAmount(amount); +} + void perl_register_questitem() { perl::interpreter perl(PERL_GET_THX); auto package = perl.new_class("QuestItem"); + package.add("AddEvolveAmount", &Perl_QuestItem_AddEvolveAmount); package.add("AddEXP", &Perl_QuestItem_AddEXP); package.add("ClearTimers", &Perl_QuestItem_ClearTimers); package.add("Clone", &Perl_QuestItem_Clone); @@ -310,6 +381,7 @@ void perl_register_questitem() package.add("CountAugmentByID", &Perl_QuestItem_CountAugmentByID); package.add("DeleteCustomData", &Perl_QuestItem_DeleteCustomData); package.add("GetAugment", &Perl_QuestItem_GetAugment); + package.add("GetAugmentEvolveUniqueID", &Perl_QuestItem_GetAugmentEvolveUniqueID); package.add("GetAugmentIDs", &Perl_QuestItem_GetAugmentIDs); package.add("GetAugmentItemID", &Perl_QuestItem_GetAugmentItemID); package.add("GetAugmentType", &Perl_QuestItem_GetAugmentType); @@ -317,6 +389,16 @@ void perl_register_questitem() package.add("GetColor", &Perl_QuestItem_GetColor); package.add("GetCustomData", &Perl_QuestItem_GetCustomData); package.add("GetCustomDataString", &Perl_QuestItem_GetCustomDataString); + package.add("GetEvolveActivated", &Perl_QuestItem_GetEvolveActivated); + package.add("GetEvolveAmount", &Perl_QuestItem_GetEvolveAmount); + package.add("GetEvolveCharacterID", &Perl_QuestItem_GetEvolveCharacterID); + package.add("GetEvolveEquipped", &Perl_QuestItem_GetEvolveEquipped); + package.add("GetEvolveFinalItemID", &Perl_QuestItem_GetEvolveFinalItemID); + package.add("GetEvolveItemID", &Perl_QuestItem_GetEvolveItemID); + package.add("GetEvolveLevel", &Perl_QuestItem_GetEvolveLevel); + package.add("GetEvolveLoreID", &Perl_QuestItem_GetEvolveLoreID); + package.add("GetEvolveProgression", &Perl_QuestItem_GetEvolveProgression); + package.add("GetEvolveUniqueID", &Perl_QuestItem_GetEvolveUniqueID); package.add("GetEXP", &Perl_QuestItem_GetEXP); package.add("GetID", &Perl_QuestItem_GetID); package.add("GetItem", (EQ::ItemData*(*)(EQ::ItemInstance*))&Perl_QuestItem_GetItem); @@ -324,7 +406,6 @@ void perl_register_questitem() package.add("GetItemID", &Perl_QuestItem_GetItemID); package.add("GetItemLink", &Perl_QuestItem_GetItemLink); package.add("GetItemScriptID", &Perl_QuestItem_GetItemScriptID); - package.add("GetKillsNeeded", &Perl_QuestItem_GetKillsNeeded); package.add("GetMaxEvolveLevel", &Perl_QuestItem_GetMaxEvolveLevel); package.add("GetName", &Perl_QuestItem_GetName); package.add("GetPrice", &Perl_QuestItem_GetPrice); @@ -337,6 +418,7 @@ void perl_register_questitem() package.add("IsAugmented", &Perl_QuestItem_IsAugmented); package.add("IsEquipable", (bool(*)(EQ::ItemInstance*, int16))&Perl_QuestItem_IsEquipable); package.add("IsEquipable", (bool(*)(EQ::ItemInstance*, uint16, uint16))&Perl_QuestItem_IsEquipable); + package.add("IsEvolving", &Perl_QuestItem_IsEvolving); package.add("IsExpendable", &Perl_QuestItem_IsExpendable); package.add("IsInstanceNoDrop", &Perl_QuestItem_IsInstanceNoDrop); package.add("IsStackable", &Perl_QuestItem_IsStackable); @@ -352,6 +434,8 @@ void perl_register_questitem() package.add("SetCustomData", (void(*)(EQ::ItemInstance*, std::string, float))&Perl_QuestItem_SetCustomData); package.add("SetCustomData", (void(*)(EQ::ItemInstance*, std::string, int))&Perl_QuestItem_SetCustomData); package.add("SetCustomData", (void(*)(EQ::ItemInstance*, std::string, std::string))&Perl_QuestItem_SetCustomData); + package.add("SetEvolveAmount", &Perl_QuestItem_SetEvolveAmount); + package.add("SetEvolveProgression", &Perl_QuestItem_SetEvolveProgression); package.add("SetEXP", &Perl_QuestItem_SetEXP); package.add("SetInstanceNoDrop", &Perl_QuestItem_SetInstanceNoDrop); package.add("SetPrice", &Perl_QuestItem_SetPrice); diff --git a/zone/string_ids.h b/zone/string_ids.h index b778170b6..0da8fc03e 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -419,6 +419,11 @@ #define GUILD_BANK_FULL 6098 // There is no more room in the Guild Bank. #define GUILD_BANK_TRANSFERRED 6100 // '%1' transferred to Guild Bank from Deposits. #define GUILD_BANK_EMPTY_HANDS 6108 // You must empty your hands to withdraw from the Guild Bank. +#define EVOLVE_ITEM_EVOLVED 6145 //Your %1 has evolved! +#define EVOLVE_DETAILS 6146 //Evolving: Level %1/%2 %3%% %4 +#define EVOLVE_LEVEL_LIMIT 6147 //Your %1 can not evolve until you reach level %2. +#define EVOLVE_XP_TXFR_CONFIRM 6148 //Are you sure you want to transfer experience between these two items? +#define EVOLVE_XP_TXFRD 6149 //Your item's experience has been transferred! #define TRADESKILL_COMBINE_LORE 6199 // Combine would result in a LORE item (%1) you already possess. #define TRANSFORM_FAILED 6326 //This mold cannot be applied to your %1. #define TRANSFORM_COMPLETE 6327 //You have successfully transformed your %1. diff --git a/zone/trading.cpp b/zone/trading.cpp index a64dc33e9..d9fb3499b 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -371,6 +371,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (free_slot != INVALID_INDEX) { if (other->PutItemInInventory(free_slot, *inst, true)) { + inst->TransferOwnership(database, other->CharacterID()); LogTrading("Container [{}] ([{}]) successfully transferred, deleting from trade slot", inst->GetItem()->Name, inst->GetItem()->ID); if (qs_log) { auto detail = new PlayerLogTradeItemsEntry_Struct; @@ -482,6 +483,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (other->PutItemInInventory(partial_slot, *partial_inst, true)) { LogTrading("Partial stack [{}] ([{}]) successfully transferred, deleting [{}] charges from trade slot", inst->GetItem()->Name, inst->GetItem()->ID, (old_charges - inst->GetCharges())); + inst->TransferOwnership(database, other->CharacterID()); if (qs_log) { auto detail = new PlayerLogTradeItemsEntry_Struct; @@ -589,6 +591,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (free_slot != INVALID_INDEX) { if (other->PutItemInInventory(free_slot, *inst, true)) { + inst->TransferOwnership(database, other->CharacterID()); LogTrading("Item [{}] ([{}]) successfully transferred, deleting from trade slot", inst->GetItem()->Name, inst->GetItem()->ID); if (qs_log) { auto detail = new PlayerLogTradeItemsEntry_Struct; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index f346fd57c..4000397a3 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -52,7 +52,7 @@ #include "../common/repositories/zone_repository.h" #include "../common/repositories/trader_repository.h" - +#include "../common/repositories/character_evolving_items_repository.h" #include #include