From b6c0f58dda3dca708a5cf557208e3026af5e4d03 Mon Sep 17 00:00:00 2001 From: IntelligentQuantum Date: Mon, 9 Dec 2019 10:33:33 +0330 Subject: [PATCH] feat(Core/ItemHandler): Optional item recovery (#2442) --- .../rev_1575656087867346414.sql | 10 ++++ .../Implementation/CharacterDatabase.cpp | 4 ++ .../Implementation/CharacterDatabase.h | 3 + src/server/game/Handlers/ItemHandler.cpp | 49 ++++++++++++--- src/server/game/Server/WorldSession.h | 2 + src/server/game/World/World.cpp | 6 ++ src/server/game/World/World.h | 4 ++ src/server/worldserver/worldserver.conf.dist | 60 +++++++++++++++++-- 8 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 data/sql/updates/pending_db_characters/rev_1575656087867346414.sql diff --git a/data/sql/updates/pending_db_characters/rev_1575656087867346414.sql b/data/sql/updates/pending_db_characters/rev_1575656087867346414.sql new file mode 100644 index 000000000..7cc3f9e39 --- /dev/null +++ b/data/sql/updates/pending_db_characters/rev_1575656087867346414.sql @@ -0,0 +1,10 @@ +INSERT INTO `version_db_characters` (`sql_rev`) VALUES ('1575656087867346414'); + +CREATE TABLE IF NOT EXISTS `recovery_item` ( + `Id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `Guid` int(11) unsigned NOT NULL DEFAULT 0, + `ItemEntry` mediumint(8) unsigned NOT NULL DEFAULT 0, + `Count` int(11) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (`Id`), + KEY `idx_guid` (`Guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/src/common/Database/Implementation/CharacterDatabase.cpp b/src/common/Database/Implementation/CharacterDatabase.cpp index 2f068fcf0..002d2044c 100644 --- a/src/common/Database/Implementation/CharacterDatabase.cpp +++ b/src/common/Database/Implementation/CharacterDatabase.cpp @@ -566,4 +566,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_QUEST_TRACK_GM_COMPLETE, "UPDATE quest_tracker SET completed_by_gm = 1 WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_QUEST_TRACK_COMPLETE_TIME, "UPDATE quest_tracker SET quest_complete_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_QUEST_TRACK_ABANDON_TIME, "UPDATE quest_tracker SET quest_abandon_time = NOW() WHERE id = ? AND character_guid = ? ORDER BY quest_accept_time DESC LIMIT 1", CONNECTION_ASYNC); + + // Recovery Item + PrepareStatement(CHAR_INS_RECOVERY_ITEM, "INSERT INTO recovery_item (Guid, ItemEntry, Count) VALUES (?, ?, ?)", CONNECTION_SYNCH); + PrepareStatement(CHAR_DEL_RECOVERY_ITEM, "DELETE FROM recovery_item WHERE Guid = ? AND ItemEntry = ? ORDER BY Id DESC LIMIT 1", CONNECTION_ASYNC); } diff --git a/src/common/Database/Implementation/CharacterDatabase.h b/src/common/Database/Implementation/CharacterDatabase.h index 8496c8838..46d1c3724 100644 --- a/src/common/Database/Implementation/CharacterDatabase.h +++ b/src/common/Database/Implementation/CharacterDatabase.h @@ -500,6 +500,9 @@ enum CharacterDatabaseStatements CHAR_UPD_QUEST_TRACK_COMPLETE_TIME, CHAR_UPD_QUEST_TRACK_ABANDON_TIME, + CHAR_INS_RECOVERY_ITEM, + CHAR_DEL_RECOVERY_ITEM, + MAX_CHARACTERDATABASE_STATEMENTS }; diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index b27205534..f1c806c29 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -283,6 +283,8 @@ void WorldSession::HandleDestroyItemOpcode(WorldPacket & recvData) return; } + recoveryItem(pItem); + if (count) { uint32 i_count = count; @@ -437,7 +439,7 @@ void WorldSession::HandleItemQuerySingleOpcode(WorldPacket & recvData) { std::string Name = pProto->Name1; std::string Description = pProto->Description; - + int loc_idx = GetSessionDbLocaleIndex(); if (loc_idx >= 0) { @@ -492,7 +494,7 @@ void WorldSession::HandleItemQuerySingleOpcode(WorldPacket & recvData) queryData << pProto->Damage[i].DamageMax; queryData << pProto->Damage[i].DamageType; } - + // resistances (7) queryData << pProto->Armor; queryData << pProto->HolyRes; @@ -501,11 +503,11 @@ void WorldSession::HandleItemQuerySingleOpcode(WorldPacket & recvData) queryData << pProto->FrostRes; queryData << pProto->ShadowRes; queryData << pProto->ArcaneRes; - + queryData << pProto->Delay; queryData << pProto->AmmoType; queryData << pProto->RangedModRange; - + for (int s = 0; s < MAX_ITEM_PROTO_SPELLS; ++s) { // send DBC data for cooldowns in same way as it used in Spell::SendSpellCooldown @@ -514,11 +516,11 @@ void WorldSession::HandleItemQuerySingleOpcode(WorldPacket & recvData) if (spell) { bool db_data = pProto->Spells[s].SpellCooldown >= 0 || pProto->Spells[s].SpellCategoryCooldown >= 0; - + queryData << pProto->Spells[s].SpellId; queryData << pProto->Spells[s].SpellTrigger; queryData << uint32(-abs(pProto->Spells[s].SpellCharges)); - + if (db_data) { queryData << uint32(pProto->Spells[s].SpellCooldown); @@ -699,6 +701,9 @@ void WorldSession::HandleSellItemOpcode(WorldPacket & recvData) { if (pProto->SellPrice > 0) { + if (sWorld->getBoolConfig(CONFIG_ITEMDELETE_VENDOR)) + recoveryItem(pItem); + if (count < pItem->GetCount()) // need split items { Item* pNewItem = pItem->CloneItem(count, _player); @@ -784,6 +789,14 @@ void WorldSession::HandleBuybackItem(WorldPacket & recvData) _player->ItemAddedQuestCheck(pItem->GetEntry(), pItem->GetCount()); _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, pItem->GetEntry(), pItem->GetCount()); _player->StoreItem(dest, pItem, true); + + if (sWorld->getBoolConfig(CONFIG_ITEMDELETE_VENDOR)) + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_RECOVERY_ITEM); + stmt->setUInt32(0, _player->GetGUID()); + stmt->setUInt32(1, pItem->GetEntry()); + CharacterDatabase.Execute(stmt); + } } else _player->SendEquipError(msg, pItem, NULL); @@ -1241,7 +1254,7 @@ void WorldSession::HandleItemNameQueryOpcode(WorldPacket & recvData) if (loc_idx >= 0) if (ItemSetNameLocale const* isnl = sObjectMgr->GetItemSetNameLocale(itemid)) ObjectMgr::GetLocaleString(isnl->Name, loc_idx, Name); - + WorldPacket data(SMSG_ITEM_NAME_QUERY_RESPONSE, (4+Name.size()+1+4)); data << uint32(itemid); data << Name; @@ -1632,7 +1645,7 @@ void WorldSession::HandleItemRefund(WorldPacket &recvData) #endif return; } - + // Don't try to refund item currently being disenchanted if (_player->GetLootGUID() == guid) return; @@ -1687,3 +1700,23 @@ bool WorldSession::CanUseBank(uint64 bankerGUID) const return true; } + +bool WorldSession::recoveryItem(Item* pItem) +{ + if (sWorld->getBoolConfig(CONFIG_ITEMDELETE_METHOD) + && pItem->GetTemplate()->Quality >= sWorld->getIntConfig(CONFIG_ITEMDELETE_QUALITY) + && pItem->GetTemplate()->ItemLevel >= sWorld->getIntConfig(CONFIG_ITEMDELETE_ITEM_LEVEL)) + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_RECOVERY_ITEM); + + stmt->setUInt32(0, pItem->GetOwnerGUID()); + stmt->setUInt32(1, pItem->GetTemplate()->ItemId); + stmt->setUInt32(2, pItem->GetCount()); + + CharacterDatabase.Query(stmt); + + return true; + } + + return false; +} diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 58183c2ee..55377cd6b 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -952,6 +952,8 @@ class WorldSession bool CanUseBank(uint64 bankerGUID = 0) const; + bool recoveryItem(Item* pItem); + // EnumData helpers bool IsLegitCharacterForAccount(uint32 lowGUID) { diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 58bbe3085..1c1375fec 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1192,6 +1192,12 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_CHARDELETE_MIN_LEVEL] = sConfigMgr->GetIntDefault("CharDelete.MinLevel", 0); m_int_configs[CONFIG_CHARDELETE_KEEP_DAYS] = sConfigMgr->GetIntDefault("CharDelete.KeepDays", 30); + ///- Load the ItemDelete related config options + m_bool_configs[CONFIG_ITEMDELETE_METHOD] = sConfigMgr->GetBoolDefault("ItemDelete.Method", 0); + m_bool_configs[CONFIG_ITEMDELETE_VENDOR] = sConfigMgr->GetBoolDefault("ItemDelete.Vendor", 0); + m_int_configs[CONFIG_ITEMDELETE_QUALITY] = sConfigMgr->GetIntDefault("ItemDelete.Quality", 3); + m_int_configs[CONFIG_ITEMDELETE_ITEM_LEVEL] = sConfigMgr->GetIntDefault("ItemDelete.ItemLevel", 80); + ///- Read the "Data" directory from the config file std::string dataPath = sConfigMgr->GetStringDefault("DataDir", "./"); if (dataPath.empty() || (dataPath.at(dataPath.length()-1) != '/' && dataPath.at(dataPath.length()-1) != '\\')) diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 6f5425b7f..e4c44735b 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -171,6 +171,8 @@ enum WorldBoolConfigs CONFIG_LFG_LOCATION_ALL, // Player can join LFG anywhere CONFIG_PRELOAD_ALL_NON_INSTANCED_MAP_GRIDS, CONFIG_ALLOW_TWO_SIDE_INTERACTION_EMOTE, + CONFIG_ITEMDELETE_METHOD, + CONFIG_ITEMDELETE_VENDOR, BOOL_CONFIG_VALUE_COUNT }; @@ -344,6 +346,8 @@ enum WorldIntConfigs CONFIG_AFK_PREVENT_LOGOUT, CONFIG_ICC_BUFF_HORDE, CONFIG_ICC_BUFF_ALLIANCE, + CONFIG_ITEMDELETE_QUALITY, + CONFIG_ITEMDELETE_ITEM_LEVEL, INT_CONFIG_VALUE_COUNT }; diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 339c61edb..53a191884 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -25,6 +25,7 @@ # NETWORK CONFIG # CONSOLE AND REMOTE ACCESS # CHARACTER DELETE OPTIONS +# ITEM DELETE OPTIONS # CUSTOM SERVER OPTIONS # ################################################################################################### @@ -1456,11 +1457,11 @@ Motd = "Welcome to an AzerothCore server." # All the AzerothCore contributors, as well as its father projects (MaNGOS, TrinityCore, etc..), # have worked for free to provide you this software. Please do not remove the credits. # -# Changing or removing such hardcoded text is considered a violation of our license +# Changing or removing such hardcoded text is considered a violation of our license # and it's not allowed. We reserve the right to take legal action in case of violations. # Furthermore, any kind of support will be always denied. # -# All AzerothCore contributors and its father projects are publicly listed in +# All AzerothCore contributors and its father projects are publicly listed in # our official repository. Credits to open source contributions should always be shown. # @@ -2966,6 +2967,55 @@ CharDelete.KeepDays = 30 # ################################################################################################### +################################################################################################### +# ITEM DELETE OPTIONS +# +# ItemDelete.Method +# Description: Item deletion behavior. +# Default: 0 - (Completely remove item from the database) +# 1 - (Save Item to database) + +ItemDelete.Method = 0 + +# +# ItemDelete.Vendor +# Description: Saving items into database when the player sells items to vendor +# Default: 0 (disabled) +# 1 (enabled) +# + +ItemDelete.Vendor = 0 + +# +# ItemDelete.Quality +# Description: Saving items into database that have quality greater or equal to ItemDelete.Quality +# +# ID | Color | Quality +# 0 | Grey | Poor +# 1 | White | Common +# 2 | Green | Uncommon +# 3 | Blue | Rare +# 4 | Purple| Epic +# 5 | Orange| Legendary +# 6 | Red | Artifact +# 7 | Gold | Bind to Account +# +# Default: 3 +# + +ItemDelete.Quality = 3 + +# +# ItemDelete.ItemLevel +# Description: Saving items into database that are Item Levels greater or equal to ItemDelete.ItemLevel +# Default: 80 +# + +ItemDelete.ItemLevel = 80 + +# +################################################################################################### + ################################################################################################### # CUSTOM SERVER OPTIONS # @@ -3265,7 +3315,7 @@ Minigob.Manabonk.Enable = 1 # Description: Logs actions, e.g. account login and logout to name a few, based on IP of current session. # Default: 0 - (Disabled) # 1 - (Enabled) - + Allow.IP.Based.Action.Logging = 0 # Calculate.Creature.Zone.Area.Data @@ -3284,8 +3334,8 @@ Calculate.Gameoject.Zone.Area.Data = 0 # LFG SETTINGS # # Includes satellite to search for work elsewhere LFG -# Default: 0 - Disable -# 1 - Enable +# Default: 0 - Disable +# 1 - Enable LFG.Location.All = 0