diff --git a/conf/transmog.conf.dist b/conf/transmog.conf.dist index 2232457..171f146 100644 --- a/conf/transmog.conf.dist +++ b/conf/transmog.conf.dist @@ -18,6 +18,11 @@ # If disabled, players must have an item in their bags to use as a transmogrification appearance source. # Default: 1 # +# Transmogrification.AllowHiddenTransmog +# Description: Enables/Disables the hiding of equipment through transmog +# If enabled, players can select an "invisible" appearance for items at the transmog vendor +# Default: 1 +# # Transmogrification.TrackUnusableItems # Description: If enabled, appearances are collected even for items that are not suitable for transmogrification. # This allows these appearances to be used later if the configuration is changed. @@ -43,6 +48,7 @@ Transmogrification.Enable = 1 Transmogrification.UseCollectionSystem = 1 +Transmogrification.AllowHiddenTransmog = 1 Transmogrification.TrackUnusableItems = 1 Transmogrification.EnableTransmogInfo = 1 diff --git a/src/Transmogrification.cpp b/src/Transmogrification.cpp index 60387b6..ca2ee2b 100644 --- a/src/Transmogrification.cpp +++ b/src/Transmogrification.cpp @@ -293,9 +293,29 @@ void Transmogrification::SetFakeEntry(Player* player, uint32 newEntry, uint8 /*s UpdateItem(player, itemTransmogrified); } +bool Transmogrification::AddCollectedAppearance(uint32 accountId, uint32 itemId) +{ + if (collectionCache.find(accountId) == collectionCache.end()) + { + collectionCache.insert({accountId, {itemId}}); + return true; + } + if (std::find(collectionCache[accountId].begin(), collectionCache[accountId].end(), itemId) == collectionCache[accountId].end()) + { + collectionCache[accountId].push_back(itemId); + std::sort(collectionCache[accountId].begin(), collectionCache[accountId].end()); + return true; + } + return false; +} + TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, uint32 itemEntry, uint8 slot, /*uint32 newEntry, */bool no_cost) { + if (itemEntry == UINT_MAX) // Hidden transmog + { + return Transmogrify(player, nullptr, slot, no_cost, true); + } Item* itemTransmogrifier = Item::CreateItem(itemEntry, 1, 0); - return Transmogrify(player, itemTransmogrifier, slot, no_cost); + return Transmogrify(player, itemTransmogrifier, slot, no_cost, false); } TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, ObjectGuid itemGUID, uint8 slot, /*uint32 newEntry, */bool no_cost) { @@ -310,10 +330,10 @@ TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, ObjectGuid return LANG_ERR_TRANSMOG_MISSING_SRC_ITEM; } } - return Transmogrify(player, itemTransmogrifier, slot, no_cost); + return Transmogrify(player, itemTransmogrifier, slot, no_cost, false); } -TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, Item* itemTransmogrifier, uint8 slot, /*uint32 newEntry, */bool no_cost) +TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, Item* itemTransmogrifier, uint8 slot, /*uint32 newEntry, */bool no_cost, bool hidden_transmog) { int32 cost = 0; // slot of the transmogrified item @@ -331,6 +351,12 @@ TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, Item* item return LANG_ERR_TRANSMOG_MISSING_DEST_ITEM; } + if (hidden_transmog) + { + SetFakeEntry(player, HIDDEN_ITEM_ID, slot, itemTransmogrified); // newEntry + return LANG_ERR_TRANSMOG_OK; + } + if (!itemTransmogrifier) // reset look newEntry { // Custom @@ -673,6 +699,7 @@ void Transmogrification::LoadConfig(bool reload) IgnoreReqEvent = sConfigMgr->GetOption("Transmogrification.IgnoreReqEvent", false); IgnoreReqStats = sConfigMgr->GetOption("Transmogrification.IgnoreReqStats", false); UseCollectionSystem = sConfigMgr->GetOption("Transmogrification.UseCollectionSystem", true); + AllowHiddenTransmog = sConfigMgr->GetOption("Transmogrification.AllowHiddenTransmog", true); TrackUnusableItems = sConfigMgr->GetOption("Transmogrification.TrackUnusableItems", true); IsTransmogEnabled = sConfigMgr->GetOption("Transmogrification.Enable", true); @@ -748,6 +775,11 @@ bool Transmogrification::GetUseCollectionSystem() const return UseCollectionSystem; }; +bool Transmogrification::GetAllowHiddenTransmog() const +{ + return AllowHiddenTransmog; +} + bool Transmogrification::GetTrackUnusableItems() const { return TrackUnusableItems; diff --git a/src/Transmogrification.h b/src/Transmogrification.h index ebad395..815bc71 100644 --- a/src/Transmogrification.h +++ b/src/Transmogrification.h @@ -16,6 +16,7 @@ #include #define PRESETS // comment this line to disable preset feature totally +#define HIDDEN_ITEM_ID 1 // used for hidden transmog - do not use a valid equipment ID #define MAX_OPTIONS 25 // do not alter class Item; @@ -58,8 +59,10 @@ public: typedef std::unordered_map transmogData; typedef std::unordered_map transmog2Data; typedef std::unordered_map transmogMap; + typedef std::unordered_map> collectionCacheMap; transmogMap entryMap; // entryMap[pGUID][iGUID] = entry transmogData dataMap; // dataMap[iGUID] = pGUID + collectionCacheMap collectionCache; #ifdef PRESETS bool EnableSetInfo; @@ -126,6 +129,7 @@ public: bool IgnoreReqStats; bool UseCollectionSystem; + bool AllowHiddenTransmog; bool TrackUnusableItems; bool IsTransmogEnabled; @@ -146,10 +150,11 @@ public: void UpdateItem(Player* player, Item* item) const; void DeleteFakeEntry(Player* player, uint8 slot, Item* itemTransmogrified, CharacterDatabaseTransaction* trans = nullptr); void SetFakeEntry(Player* player, uint32 newEntry, uint8 slot, Item* itemTransmogrified); + bool AddCollectedAppearance(uint32 accountId, uint32 itemId); TransmogAcoreStrings Transmogrify(Player* player, ObjectGuid itemGUID, uint8 slot, /*uint32 newEntry, */bool no_cost = false); TransmogAcoreStrings Transmogrify(Player* player, uint32 itemEntry, uint8 slot, /*uint32 newEntry, */bool no_cost = false); - TransmogAcoreStrings Transmogrify(Player* player, Item* itemTransmogrifier, uint8 slot, /*uint32 newEntry, */bool no_cost = false); + TransmogAcoreStrings Transmogrify(Player* player, Item* itemTransmogrifier, uint8 slot, /*uint32 newEntry, */bool no_cost = false, bool hidden_transmog = false); bool CanTransmogrifyItemWithItem(Player* player, ItemTemplate const* destination, ItemTemplate const* source) const; bool SuitableForTransmogrification(Player* player, ItemTemplate const* proto) const; // bool CanBeTransmogrified(Item const* item); @@ -174,6 +179,7 @@ public: uint32 GetSetNpcText() const; bool GetUseCollectionSystem() const; + bool GetAllowHiddenTransmog() const; bool GetTrackUnusableItems() const; [[nodiscard]] bool IsEnabled() const; }; diff --git a/src/transmog_scripts.cpp b/src/transmog_scripts.cpp index d87242f..4d54c0f 100644 --- a/src/transmog_scripts.cpp +++ b/src/transmog_scripts.cpp @@ -22,6 +22,7 @@ Cant transmogrify rediculus items // Foereaper: would be fun to stab people with #include "Transmogrification.h" #include "ScriptedCreature.h" +#include "ItemTemplate.h" #define sT sTransmogrification #define GTS session->GetAcoreString // dropped translation support, no one using? @@ -358,70 +359,80 @@ public: { sendGossip = false; - std::string query = "SELECT item_template_id FROM custom_unlocked_appearances WHERE account_id = " + std::to_string(player->GetSession()->GetAccountId()) + " ORDER BY item_template_id"; - session->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(query).WithCallback([=](QueryResult result) + uint16 pageNumber = 0; + uint32 startValue = 0; + uint32 endValue = MAX_OPTIONS - 3; + bool lastPage = false; + if (gossipPageNumber > EQUIPMENT_SLOT_END + 10) { - uint16 pageNumber = 0; - uint32 startValue = 0; - uint32 endValue = MAX_OPTIONS - 3; - bool lastPage = false; - if (gossipPageNumber > EQUIPMENT_SLOT_END + 10) + pageNumber = gossipPageNumber - EQUIPMENT_SLOT_END - 10; + startValue = (pageNumber * (MAX_OPTIONS - 2)); + endValue = (pageNumber + 1) * (MAX_OPTIONS - 2) - 1; + } + uint32 accountId = player->GetSession()->GetAccountId(); + if (sT->collectionCache.find(accountId) != sT->collectionCache.end()) + { + std::vector allowedItems; + if (sT->GetAllowHiddenTransmog()) { - pageNumber = gossipPageNumber - EQUIPMENT_SLOT_END - 10; - startValue = (pageNumber * (MAX_OPTIONS - 2)); - endValue = (pageNumber + 1) * (MAX_OPTIONS - 2) - 1; - } - if (result) - { - std::vector allowedItems; - do { - uint32 newItemEntryId = (*result)[0].Get(); - Item* newItem = Item::CreateItem(newItemEntryId, 1, 0); - if (!newItem) - continue; - if (!sT->CanTransmogrifyItemWithItem(player, oldItem->GetTemplate(), newItem->GetTemplate())) - continue; - if (sT->GetFakeEntry(oldItem->GetGUID()) == newItem->GetEntry()) - continue; - allowedItems.push_back(newItem); - } while (result->NextRow()); - for (uint32 i = startValue; i <= endValue; i++) + // Offset the start and end values to make space for invisible item entry + endValue--; + if (pageNumber != 0) { - if (allowedItems.empty() || i > allowedItems.size() - 1) - { - lastPage = true; - break; - } - Item* newItem = allowedItems.at(i); - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(newItem->GetEntry(), 30, 30, -18, 0) + sT->GetItemLink(newItem, session), slot, newItem->GetEntry(), "Using this item for transmogrify will bind it to you and make it non-refundable and non-tradeable.\nDo you wish to continue?\n\n" + sT->GetItemIcon(newItem->GetEntry(), 40, 40, -15, -10) + sT->GetItemLink(newItem, session) + lineEnd, price, false); + startValue--; + } + else + { + // Add invisible item entry + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "Hide Slot", slot, UINT_MAX, "You are hiding the item in this slot.\nDo you wish to continue?\n\n" + lineEnd, 0, false); } } - if (gossipPageNumber == EQUIPMENT_SLOT_END + 11) + for (uint32 newItemEntryId : sT->collectionCache[accountId]) { + Item* newItem = Item::CreateItem(newItemEntryId, 1, 0); + if (!newItem) + continue; + if (!sT->CanTransmogrifyItemWithItem(player, oldItem->GetTemplate(), newItem->GetTemplate())) + continue; + if (sT->GetFakeEntry(oldItem->GetGUID()) == newItem->GetEntry()) + continue; + allowedItems.push_back(newItem); + } + for (uint32 i = startValue; i <= endValue; i++) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Previous Page", EQUIPMENT_SLOT_END, slot); - if (!lastPage) + if (allowedItems.empty() || i > allowedItems.size() - 1) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", gossipPageNumber + 1, slot); + lastPage = true; + break; } + Item* newItem = allowedItems.at(i); + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(newItem->GetEntry(), 30, 30, -18, 0) + sT->GetItemLink(newItem, session), slot, newItem->GetEntry(), "Using this item for transmogrify will bind it to you and make it non-refundable and non-tradeable.\nDo you wish to continue?\n\n" + sT->GetItemIcon(newItem->GetEntry(), 40, 40, -15, -10) + sT->GetItemLink(newItem, session) + lineEnd, price, false); } - else if (gossipPageNumber > EQUIPMENT_SLOT_END + 11) + } + if (gossipPageNumber == EQUIPMENT_SLOT_END + 11) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Previous Page", EQUIPMENT_SLOT_END, slot); + if (!lastPage) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Previous Page", gossipPageNumber - 1, slot); - if (!lastPage) - { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", gossipPageNumber + 1, slot); - } + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", gossipPageNumber + 1, slot); } - else if (!lastPage) + } + else if (gossipPageNumber > EQUIPMENT_SLOT_END + 11) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Previous Page", gossipPageNumber - 1, slot); + if (!lastPage) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", EQUIPMENT_SLOT_END + 11, slot); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", gossipPageNumber + 1, slot); } + } + else if (!lastPage) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", EQUIPMENT_SLOT_END + 11, slot); + } - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/INV_Enchant_Disenchant:30:30:-18:0|tRemove transmogrification", EQUIPMENT_SLOT_END + 3, slot, "Remove transmogrification from the slot?", 0, false); - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/PaperDollInfoFrame/UI-GearManager-Undo:30:30:-18:0|tUpdate menu", EQUIPMENT_SLOT_END, slot); - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/Ability_Spy:30:30:-18:0|tBack...", EQUIPMENT_SLOT_END + 1, 0); - SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); - })); + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/INV_Enchant_Disenchant:30:30:-18:0|tRemove transmogrification", EQUIPMENT_SLOT_END + 3, slot, "Remove transmogrification from the slot?", 0, false); + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/PaperDollInfoFrame/UI-GearManager-Undo:30:30:-18:0|tUpdate menu", EQUIPMENT_SLOT_END, slot); + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/Ability_Spy:30:30:-18:0|tBack...", EQUIPMENT_SLOT_END + 1, 0); + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); } else { @@ -476,6 +487,14 @@ public: class PS_Transmogrification : public PlayerScript { private: + void AddToDatabase(Player* player, Item* item) + { + if (item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE) || item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE)) + return; + ItemTemplate const* itemTemplate = item->GetTemplate(); + AddToDatabase(player, itemTemplate); + } + void AddToDatabase(Player* player, ItemTemplate const* itemTemplate) { if (!sT->GetTrackUnusableItems() && !sT->SuitableForTransmogrification(player, itemTemplate)) @@ -489,16 +508,12 @@ private: tempStream << std::hex << ItemQualityColors[itemTemplate->Quality]; std::string itemQuality = tempStream.str(); bool showChatMessage = !(player->GetPlayerSetting("mod-transmog", SETTING_HIDE_TRANSMOG).value); - std::string query = "SELECT account_id, item_template_id FROM custom_unlocked_appearances WHERE account_id = " + std::to_string(accountId) + " AND item_template_id = " + std::to_string(itemId); - player->GetSession()->GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(query).WithCallback([=](QueryResult result) + if (sT->AddCollectedAppearance(accountId, itemId)) { - if (!result) - { - if (showChatMessage) - ChatHandler(player->GetSession()).PSendSysMessage( R"(|c%s|Hitem:%u:0:0:0:0:0:0:0:0|h[%s]|h|r has been added to your appearance collection.)", itemQuality.c_str(), itemId, itemName.c_str()); - CharacterDatabase.Execute( "INSERT INTO custom_unlocked_appearances (account_id, item_template_id) VALUES ({}, {})", accountId, itemId); - } - })); + if (showChatMessage) + ChatHandler(player->GetSession()).PSendSysMessage( R"(|c%s|Hitem:%u:0:0:0:0:0:0:0:0|h[%s]|h|r has been added to your appearance collection.)", itemQuality.c_str(), itemId, itemName.c_str()); + CharacterDatabase.Execute( "INSERT INTO custom_unlocked_appearances (account_id, item_template_id) VALUES ({}, {})", accountId, itemId); + } } public: PS_Transmogrification() : PlayerScript("Player_Transmogrify") { } @@ -507,8 +522,7 @@ public: { if (!sT->GetUseCollectionSystem()) return; - ItemTemplate const* pProto = it->GetTemplate(); - AddToDatabase(player, pProto); + AddToDatabase(player, it); } void OnLootItem(Player* player, Item* item, uint32 /*count*/, ObjectGuid /*lootguid*/) override @@ -517,7 +531,7 @@ public: return; if (item->GetTemplate()->Bonding == ItemBondingType::BIND_WHEN_PICKED_UP || item->IsSoulBound()) { - AddToDatabase(player, item->GetTemplate()); + AddToDatabase(player, item); } } @@ -527,7 +541,7 @@ public: return; if (item->GetTemplate()->Bonding == ItemBondingType::BIND_WHEN_PICKED_UP || item->IsSoulBound()) { - AddToDatabase(player, item->GetTemplate()); + AddToDatabase(player, item); } } @@ -537,7 +551,7 @@ public: return; if (item->GetTemplate()->Bonding == ItemBondingType::BIND_WHEN_PICKED_UP || item->IsSoulBound()) { - AddToDatabase(player, item->GetTemplate()); + AddToDatabase(player, item); } } @@ -590,7 +604,7 @@ public: { ObjectGuid itemGUID = ObjectGuid::Create((*result)[0].Get()); uint32 fakeEntry = (*result)[1].Get(); - if (sObjectMgr->GetItemTemplate(fakeEntry)) + if (fakeEntry == HIDDEN_ITEM_ID || sObjectMgr->GetItemTemplate(fakeEntry)) { sT->dataMap[itemGUID] = playerGUID; sT->entryMap[playerGUID][itemGUID] = fakeEntry; @@ -637,6 +651,25 @@ public: void OnAfterConfigLoad(bool reload) override { sT->LoadConfig(reload); + if (sT->GetUseCollectionSystem()) + { + LOG_INFO("module", "Loading transmog appearance collection cache...."); + uint32 collectedAppearanceCount = 0; + QueryResult result = CharacterDatabase.Query("SELECT account_id, item_template_id FROM custom_unlocked_appearances"); + if (result) + { + do + { + uint32 accountId = (*result)[0].Get(); + uint32 itemId = (*result)[1].Get(); + if (sT->AddCollectedAppearance(accountId, itemId)) + { + collectedAppearanceCount++; + } + } while (result->NextRow()); + } + LOG_INFO("module", "Loaded {} collected appearances into cache", collectedAppearanceCount); + } } void OnStartup() override