diff --git a/conf/transmog.conf.dist b/conf/transmog.conf.dist index 1867676f..90fecdcd 100644 --- a/conf/transmog.conf.dist +++ b/conf/transmog.conf.dist @@ -18,11 +18,24 @@ # If disabled, players must have an item in their bags to use as a transmogrification appearance source. # Default: 1 # +# Transmogrification.UseVendorInterface +# Description: Enables/Disables the use of a fake vendor interface for item selection. +# There are (optional) custom items available for Hide Item and Remove Transmog if data/sql/db-world/tasm_world_VendorItems.sql is imported. +# If enabled, players can select items from a vendor menu, complete with ctrl-click previews. +# If disabled, players will use the gossip menu to select items and will not have access to previews. +# Default: 0 +# # 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.HiddenTransmogIsFree +# Description: Enables/Disables free hiding of items through the transmog system. +# If enabled, players can hide pieces of equipment for free. +# If disabled, players will be charged the standard transmog price (affected by all modifiers) to hide an item. +# 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. @@ -62,7 +75,9 @@ Transmogrification.Enable = 1 Transmogrification.UseCollectionSystem = 1 +Transmogrification.UseVendorInterface = 0 Transmogrification.AllowHiddenTransmog = 1 +Transmogrification.HiddenTransmogIsFree = 1 Transmogrification.TrackUnusableItems = 1 Transmogrification.RetroActiveAppearances = 1 Transmogrification.ResetRetroActiveAppearancesFlag = 0 diff --git a/data/sql/db-world/trasm_world_VendorItems.sql b/data/sql/db-world/trasm_world_VendorItems.sql new file mode 100644 index 00000000..7c0c9efa --- /dev/null +++ b/data/sql/db-world/trasm_world_VendorItems.sql @@ -0,0 +1,30 @@ +SET +@HideEntry = 57575, +@RemoveEntry = 57576, +@HideName = "Hide Equipped", +@RemoveName = "Clear Transmog"; + +DELETE FROM `item_template` WHERE `entry` = @HideEntry OR `entry` = @RemoveEntry; + +INSERT INTO `item_template` (`entry`, `class`, `subclass`, `name`, `displayid`, `InventoryType`, `description`) VALUES +(@HideEntry, 15, 0, @HideName, 55112, 0, "Hide the item in this slot."), +(@RemoveEntry, 15, 0, @RemoveName, 8931, 0, "Remove active transmog for this item."); + +DELETE FROM `item_template_locale` WHERE `ID` = @HideEntry OR `ID` = @RemoveEntry; +INSERT INTO `item_template_locale` (`ID`, `locale`, `Name`, `Description`) VALUES +(@HideEntry, "koKR", "장착된 아이템 숨기기", "이 슬롯의 아이템을 숨깁니다."), +(@RemoveEntry,"koKR", "변형 지우기", "이 아이템의 활성화된 변형을 제거합니다."), +(@HideEntry, "frFR", "Masquer l'équipement", "Masquer l'objet dans cet emplacement."), +(@RemoveEntry,"frFR", "Effacer transmog", "Supprimer la transmog active."), +(@HideEntry, "deDE", "Ausgerüstet verbergen", "Item in diesem Slot verbergen."), +(@RemoveEntry,"deDE", "Transmog zurücksetzen", "Aktive Transmogrifikation entfernen."), +(@HideEntry, "zhCN", "隐藏已装备", "隐藏此物品。"), +(@RemoveEntry,"zhCN", "清除幻化", "移除激活的幻化。"), +(@HideEntry, "zhTW", "隱藏已裝備", "隱藏此物品。"), +(@RemoveEntry,"zhTW", "清除幻化", "移除啟用的幻化。"), +(@HideEntry, "esES", "Ocultar equipado", "Ocultar el objeto en esta ranura."), +(@RemoveEntry,"esES", "Borrar transmog", "Eliminar la transmog activa."), +(@HideEntry, "esMX", "Ocultar equipado", "Ocultar el objeto en este espacio."), +(@RemoveEntry,"esMX", "Borrar transmog", "Eliminar la transmog activa."), +(@HideEntry, "ruRU", "Скрыть экипированное", "Скрыть предмет в слоте."), +(@RemoveEntry,"ruRU", "Очистить трансмог", "Удалить активный трансмог."); diff --git a/src/Transmogrification.cpp b/src/Transmogrification.cpp index aebc2a58..6ff973cf 100644 --- a/src/Transmogrification.cpp +++ b/src/Transmogrification.cpp @@ -521,6 +521,22 @@ TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, Item* item if (hidden_transmog) { + cost = GetSpecialPrice(itemTransmogrified->GetTemplate()); + cost *= ScaledCostModifier; + cost += CopperCost; + + if (!HiddenTransmogIsFree && cost) + { + if (cost < 0) + LOG_DEBUG("module", "Transmogrification::Transmogrify - {} ({}) transmogrification invalid cost (non negative, amount {}). Transmogrified {} with {}", + player->GetName(), player->GetGUID().ToString(), -cost, itemTransmogrified->GetEntry(), itemTransmogrifier->GetEntry()); + else + { + if (!player->HasEnoughMoney(cost)) + return LANG_ERR_TRANSMOG_NOT_ENOUGH_MONEY; + player->ModifyMoney(-cost, false); + } + } SetFakeEntry(player, HIDDEN_ITEM_ID, slot, itemTransmogrified); // newEntry return LANG_ERR_TRANSMOG_OK; } @@ -680,6 +696,8 @@ bool Transmogrification::IsSubclassMismatchAllowed(Player *player, const ItemTem { return true; } + if (sourceSub == ITEM_SUBCLASS_WEAPON_MISC) + return sourceType == targetType; } else if (targetClass == ITEM_CLASS_ARMOR) { @@ -764,7 +782,8 @@ bool Transmogrification::SuitableForTransmogrification(Player* player, ItemTempl return false; //[AZTH] Yehonal - if (proto->SubClass > 0 && player->GetSkillValue(proto->GetSkill()) == 0) + uint32 subclassSkill = proto->GetSkill(); + if (proto->SubClass > 0 && subclassSkill && player->GetSkillValue(proto->GetSkill()) == 0) { if (proto->Class == ITEM_CLASS_ARMOR && !AllowMixedArmorTypes) { @@ -1124,7 +1143,9 @@ void Transmogrification::LoadConfig(bool reload) IgnoreReqEvent = sConfigMgr->GetOption("Transmogrification.IgnoreReqEvent", false); IgnoreReqStats = sConfigMgr->GetOption("Transmogrification.IgnoreReqStats", false); UseCollectionSystem = sConfigMgr->GetOption("Transmogrification.UseCollectionSystem", true); + UseVendorInterface = sConfigMgr->GetOption("Transmogrification.UseVendorInterface", false); AllowHiddenTransmog = sConfigMgr->GetOption("Transmogrification.AllowHiddenTransmog", true); + HiddenTransmogIsFree = sConfigMgr->GetOption("Transmogrification.HiddenTransmogIsFree", true); TrackUnusableItems = sConfigMgr->GetOption("Transmogrification.TrackUnusableItems", true); RetroActiveAppearances = sConfigMgr->GetOption("Transmogrification.RetroActiveAppearances", true); ResetRetroActiveAppearances = sConfigMgr->GetOption("Transmogrification.ResetRetroActiveAppearancesFlag", false); @@ -1280,12 +1301,18 @@ bool Transmogrification::GetUseCollectionSystem() const { return UseCollectionSystem; }; - +bool Transmogrification::GetUseVendorInterface() const +{ + return UseVendorInterface; +} bool Transmogrification::GetAllowHiddenTransmog() const { return AllowHiddenTransmog; } - +bool Transmogrification::GetHiddenTransmogIsFree() const +{ + return HiddenTransmogIsFree; +} bool Transmogrification::GetAllowTradeable() const { return AllowTradeable; diff --git a/src/Transmogrification.h b/src/Transmogrification.h index 17c55922..9c3b4a72 100644 --- a/src/Transmogrification.h +++ b/src/Transmogrification.h @@ -106,10 +106,13 @@ class Transmogrification typedef std::unordered_map> collectionCacheMap; typedef std::unordered_map searchStringMap; typedef std::unordered_map> transmogPlusData; + typedef std::unordered_map selectedSlotMap; + transmogPlusData plusDataMap; transmogMap entryMap; // entryMap[pGUID][iGUID] = entry transmogData dataMap; // dataMap[iGUID] = pGUID collectionCacheMap collectionCache; + selectedSlotMap selectionCache; #ifdef PRESETS bool EnableSetInfo; @@ -184,7 +187,11 @@ class Transmogrification bool IgnoreReqStats; bool UseCollectionSystem; + bool UseVendorInterface; + bool AllowHiddenTransmog; + bool HiddenTransmogIsFree; + bool TrackUnusableItems; bool RetroActiveAppearances; bool ResetRetroActiveAppearances; @@ -241,7 +248,9 @@ class Transmogrification bool GetAllowTradeable() const; bool GetUseCollectionSystem() const; + bool GetUseVendorInterface() const; bool GetAllowHiddenTransmog() const; + bool GetHiddenTransmogIsFree() const; bool GetTrackUnusableItems() const; bool EnableRetroActiveAppearances() const; bool EnableResetRetroActiveAppearances() const; @@ -262,9 +271,7 @@ class Transmogrification bool IsTransmogPlusEnabled; [[nodiscard]] bool IsPlusFeatureEligible(ObjectGuid const& playerGuid, uint32 feature) const; uint32 getPlayerMembershipLevel(ObjectGuid const & playerGuid) const; - [[nodiscard]] bool IgnoreLevelRequirement(ObjectGuid const& playerGuid) const { return IgnoreReqLevel || IsPlusFeatureEligible(playerGuid, PLUS_FEATURE_SKIP_LEVEL_REQ); } - }; #define sTransmogrification Transmogrification::instance() diff --git a/src/transmog_scripts.cpp b/src/transmog_scripts.cpp index 7bfef347..ca82bc94 100644 --- a/src/transmog_scripts.cpp +++ b/src/transmog_scripts.cpp @@ -25,6 +25,8 @@ Cant transmogrify rediculus items // Foereaper: would be fun to stab people with #include "ScriptedCreature.h" #include "ItemTemplate.h" #include "DatabaseEnv.h" +#include "WorldPacket.h" +#include "Opcodes.h" #define sT sTransmogrification #define GTS session->GetAcoreString // dropped translation support, no one using? @@ -331,6 +333,12 @@ std::unordered_mapGetSpecialPrice(targetItem); + price *= sT->GetScaledCostModifier(); + price += sT->GetCopperCost(); + return price; +} + +bool ValidForTransmog (Player* player, Item* target, Item* source, bool hasSearch, std::string searchTerm) +{ + if (!target || !source || !player) return false; + ItemTemplate const* targetTemplate = target->GetTemplate(); + ItemTemplate const* sourceTemplate = source->GetTemplate(); + + if (!sT->CanTransmogrifyItemWithItem(player, targetTemplate, sourceTemplate)) + return false; + if (sT->GetFakeEntry(target->GetGUID()) == source->GetEntry()) + return false; + if (hasSearch && sourceTemplate->Name1.find(searchTerm) == std::string::npos) + return false; + return true; +} + +std::vector GetValidTransmogs (Player* player, Item* target, bool hasSearch, std::string searchTerm) +{ + std::vector allowedItems; + if (!target) return allowedItems; + + if (sT->GetUseCollectionSystem()) + { + uint32 accountId = player->GetSession()->GetAccountId(); + if (sT->collectionCache.find(accountId) == sT->collectionCache.end()) + return allowedItems; + + for (uint32 itemId : sT->collectionCache[accountId]) + { + if (!sObjectMgr->GetItemTemplate(itemId)) + continue; + Item* srcItem = Item::CreateItem(itemId, 1, 0); + if (ValidForTransmog(player, target, srcItem, hasSearch, searchTerm)) + allowedItems.push_back(srcItem); + } + } + else + { + for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + Item* srcItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (ValidForTransmog(player, target, srcItem, hasSearch, searchTerm)) + allowedItems.push_back(srcItem); + } + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + Bag* bag = player->GetBagByPos(i); + if (!bag) + continue; + for (uint32 j = 0; j < bag->GetBagSize(); ++j) + { + Item* srcItem = player->GetItemByPos(i, j); + if (ValidForTransmog(player, target, srcItem, hasSearch, searchTerm)) + allowedItems.push_back(srcItem); + } + } + } + return allowedItems; +} + +void PerformTransmogrification (Player* player, uint32 itemEntry, uint32 cost) +{ + uint8 slot = sT->selectionCache[player->GetGUID()]; + WorldSession* session = player->GetSession(); + if (!player->HasEnoughMoney(cost)) + { + ChatHandler(session).SendNotification(LANG_ERR_TRANSMOG_NOT_ENOUGH_MONEY); + return; + } + TransmogAcoreStrings res = sT->Transmogrify(player, itemEntry, slot); + if (res == LANG_ERR_TRANSMOG_OK) + session->SendAreaTriggerMessage("%s",GTS(LANG_ERR_TRANSMOG_OK)); + else + ChatHandler(session).SendNotification(res); +} + +void RemoveTransmogrification (Player* player) +{ + uint8 slot = sT->selectionCache[player->GetGUID()]; + WorldSession* session = player->GetSession(); + if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + if (sT->GetFakeEntry(newItem->GetGUID())) + { + sT->DeleteFakeEntry(player, slot, newItem); + session->SendAreaTriggerMessage("%s", GTS(LANG_ERR_UNTRANSMOG_OK)); + } + else + ChatHandler(session).SendNotification(LANG_ERR_UNTRANSMOG_NO_TRANSMOGS); + } +} + class npc_transmogrifier : public CreatureScript { public: @@ -412,13 +519,18 @@ class npc_transmogrifier : public CreatureScript // Next page if (sender > EQUIPMENT_SLOT_END + 10) { - ShowTransmogItems(player, creature, action, sender); + ShowTransmogItemsInGossipMenu(player, creature, action, sender); return true; } switch (sender) { case EQUIPMENT_SLOT_END: // Show items you can use - ShowTransmogItems(player, creature, action, sender); + sT->selectionCache[player->GetGUID()] = action; + + if (sT->GetUseVendorInterface()) + ShowTransmogItemsInFakeVendor(player, creature, action); + else + ShowTransmogItemsInGossipMenu(player, creature, action, sender); break; case EQUIPMENT_SLOT_END + 1: // Main menu OnGossipHello(player, creature); @@ -448,16 +560,7 @@ class npc_transmogrifier : public CreatureScript } break; case EQUIPMENT_SLOT_END + 3: // Remove Transmogrification from single item { - if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, action)) - { - if (sT->GetFakeEntry(newItem->GetGUID())) - { - sT->DeleteFakeEntry(player, action, newItem); - session->SendAreaTriggerMessage("%s", GTS(LANG_ERR_UNTRANSMOG_OK)); - } - else - ChatHandler(session).SendNotification(LANG_ERR_UNTRANSMOG_NO_TRANSMOGS); - } + RemoveTransmogrification(player); OnGossipSelect(player, creature, EQUIPMENT_SLOT_END, action); } break; #ifdef PRESETS @@ -576,25 +679,7 @@ class npc_transmogrifier : public CreatureScript OnGossipHello(player, creature); return true; } - // sender = slot, action = display - if (sT->GetUseCollectionSystem()) - { - TransmogAcoreStrings res = sT->Transmogrify(player, action, sender); - if (res == LANG_ERR_TRANSMOG_OK) - session->SendAreaTriggerMessage("%s",GTS(LANG_ERR_TRANSMOG_OK)); - else - ChatHandler(session).SendNotification(res); - } - else - { - TransmogAcoreStrings res = sT->Transmogrify(player, ObjectGuid::Create(action), sender); - if (res == LANG_ERR_TRANSMOG_OK) - session->SendAreaTriggerMessage("%s",GTS(LANG_ERR_TRANSMOG_OK)); - else - ChatHandler(session).SendNotification(res); - } - // OnGossipSelect(player, creature, EQUIPMENT_SLOT_END, sender); - // ShowTransmogItems(player, creature, sender); + PerformTransmogrification(player, action, sender); CloseGossipMenuFor(player); // Wait for SetMoney to get fixed, issue #10053 } break; } @@ -685,174 +770,206 @@ class npc_transmogrifier : public CreatureScript } #endif - void ShowTransmogItems(Player* player, Creature* creature, uint8 slot, uint16 gossipPageNumber) // Only checks bags while can use an item from anywhere in inventory + void ShowTransmogItemsInGossipMenu(Player* player, Creature* creature, uint8 slot, uint16 gossipPageNumber) // Only checks bags while can use an item from anywhere in inventory { WorldSession* session = player->GetSession(); LocaleConstant locale = session->GetSessionDbLocaleIndex(); Item* oldItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); - bool sendGossip = true; bool hasSearchString; + + uint16 pageNumber = 0; + uint32 startValue = 0; + uint32 endValue = MAX_OPTIONS - 4; + bool lastPage = true; + if (gossipPageNumber > EQUIPMENT_SLOT_END + 10) + { + pageNumber = gossipPageNumber - EQUIPMENT_SLOT_END - 10; + startValue = (pageNumber * (MAX_OPTIONS - 2)); + endValue = (pageNumber + 1) * (MAX_OPTIONS - 2) - 1; + } + if (oldItem) { - uint32 price = sT->GetSpecialPrice(oldItem->GetTemplate()); - price *= sT->GetScaledCostModifier(); - price += sT->GetCopperCost(); + uint32 price = GetTransmogPrice(oldItem->GetTemplate()); std::ostringstream ss; ss << std::endl; if (sT->GetRequireToken()) ss << std::endl << std::endl << sT->GetTokenAmount() << " x " << sT->GetItemLink(sT->GetTokenEntry(), session); std::string lineEnd = ss.str(); - if (sT->GetUseCollectionSystem()) + std::unordered_map::iterator searchStringIterator = sT->searchStringByPlayer.find(player->GetGUID().GetCounter()); + hasSearchString = !(searchStringIterator == sT->searchStringByPlayer.end()); + std::string searchDisplayValue(hasSearchString ? searchStringIterator->second : GetLocaleText(locale, "search")); + std::vector allowedItems = GetValidTransmogs(player, oldItem, hasSearchString, searchDisplayValue); + + if (allowedItems.size() > 0) { - sendGossip = false; - - uint16 pageNumber = 0; - uint32 startValue = 0; - uint32 endValue = MAX_OPTIONS - 4; - bool lastPage = false; - if (gossipPageNumber > EQUIPMENT_SLOT_END + 10) + lastPage = false; + // Offset values to add Search gossip item + if (pageNumber == 0) { - 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::unordered_map::iterator searchStringIterator = sT->searchStringByPlayer.find(player->GetGUID().GetCounter()); - hasSearchString = !(searchStringIterator == sT->searchStringByPlayer.end()); - std::string searchDisplayValue(hasSearchString ? searchStringIterator->second : GetLocaleText(locale, "search")); - // Offset values to add Search gossip item - if (pageNumber == 0) + if (hasSearchString) { - if (hasSearchString) - { - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(30620, 30, 30, -18, 0) + GetLocaleText(locale, "searching_for") + searchDisplayValue, slot + 1, 0, GetLocaleText(locale, "search_for_item"), 0, true); - } - else - { - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(30620, 30, 30, -18, 0) + GetLocaleText(locale, "search"), slot + 1, 0, GetLocaleText(locale, "search_for_item"), 0, true); - } + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(30620, 30, 30, -18, 0) + GetLocaleText(locale, "searching_for") + searchDisplayValue, slot + 1, 0, GetLocaleText(locale, "search_for_item"), 0, true); } else { - startValue--; + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(30620, 30, 30, -18, 0) + GetLocaleText(locale, "search"), slot + 1, 0, GetLocaleText(locale, "search_for_item"), 0, true); } - std::vector allowedItems; - if (sT->GetAllowHiddenTransmog()) + } + else + { + startValue--; + } + if (sT->GetAllowHiddenTransmog()) + { + // Offset the start and end values to make space for invisible item entry + endValue--; + if (pageNumber != 0) { - // Offset the start and end values to make space for invisible item entry - endValue--; - if (pageNumber != 0) - { - startValue--; - } - else - { - // Add invisible item entry - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/inv_misc_enggizmos_27:30:30:-18:0|t" + GetLocaleText(locale, "hide_slot"), slot, UINT_MAX, GetLocaleText(locale, "confirm_hide_item") + lineEnd, 0, false); - } - } - for (uint32 newItemEntryId : sT->collectionCache[accountId]) { - if (!sObjectMgr->GetItemTemplate(newItemEntryId)) - continue; - 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; - if (hasSearchString && newItem->GetTemplate()->Name1.find(searchDisplayValue) == std::string::npos) - continue; - allowedItems.push_back(newItem); + startValue--; } - for (uint32 i = startValue; i <= endValue; i++) + else { - 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(), GetLocaleText(locale, "confirm_use_item") + sT->GetItemIcon(newItem->GetEntry(), 40, 40, -15, -10) + sT->GetItemLink(newItem, session) + lineEnd, price, false); + // Add invisible item entry + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/inv_misc_enggizmos_27:30:30:-18:0|t" + GetLocaleText(locale, "hide_slot"), slot, UINT_MAX, GetLocaleText(locale, "confirm_hide_item") + lineEnd, 0, false); } } - if (gossipPageNumber == EQUIPMENT_SLOT_END + 11) + for (uint32 i = startValue; i <= endValue; i++) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GetLocaleText(locale, "previous_page"), EQUIPMENT_SLOT_END, slot); - if (!lastPage) + if (allowedItems.empty() || i > allowedItems.size() - 1) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GetLocaleText(locale, "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(), GetLocaleText(locale, "confirm_use_item") + 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, GetLocaleText(locale, "previous_page"), EQUIPMENT_SLOT_END, slot); + if (!lastPage) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GetLocaleText(locale, "previous_page"), gossipPageNumber - 1, slot); - if (!lastPage) - { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, GetLocaleText(locale, "next_page"), gossipPageNumber + 1, slot); - } + AddGossipItemFor(player, GOSSIP_ICON_CHAT, GetLocaleText(locale, "next_page"), gossipPageNumber + 1, slot); } - else if (!lastPage) + } + else if (gossipPageNumber > EQUIPMENT_SLOT_END + 11) + { + AddGossipItemFor(player, GOSSIP_ICON_CHAT, GetLocaleText(locale, "previous_page"), gossipPageNumber - 1, slot); + if (!lastPage) { - AddGossipItemFor(player, GOSSIP_ICON_CHAT, "Next Page", EQUIPMENT_SLOT_END + 11, slot); + AddGossipItemFor(player, GOSSIP_ICON_CHAT, GetLocaleText(locale, "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|t" + GetLocaleText(locale, "remove_transmog"), EQUIPMENT_SLOT_END + 3, slot, GetLocaleText(locale, "remove_transmog_slot"), 0, false); - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/PaperDollInfoFrame/UI-GearManager-Undo:30:30:-18:0|t" + GetLocaleText(locale, "update_menu"), EQUIPMENT_SLOT_END, slot); - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/Ability_Spy:30:30:-18:0|t" + GetLocaleText(locale, "back"), 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|t" + GetLocaleText(locale, "remove_transmog"), EQUIPMENT_SLOT_END + 3, slot, GetLocaleText(locale, "remove_transmog_slot"), 0, false); + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/PaperDollInfoFrame/UI-GearManager-Undo:30:30:-18:0|t" + GetLocaleText(locale, "update_menu"), EQUIPMENT_SLOT_END, slot); + } + AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/Ability_Spy:30:30:-18:0|t" + GetLocaleText(locale, "back"), EQUIPMENT_SLOT_END + 1, 0); + SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + } + + static std::vector GetSpoofedVendorItems (Item* target) + { + std::vector spoofedItems; + uint32 existingTransmog = sT->GetFakeEntry(target->GetGUID()); + if (sT->AllowHiddenTransmog && !existingTransmog) + { + ItemTemplate const* _hideSlotButton = sObjectMgr->GetItemTemplate(CUSTOM_HIDE_ITEM_VENDOR_ID); + if (_hideSlotButton) + spoofedItems.push_back(_hideSlotButton); + else + { + _hideSlotButton = sObjectMgr->GetItemTemplate(FALLBACK_HIDE_ITEM_VENDOR_ID); + spoofedItems.push_back(_hideSlotButton); } + } + if (existingTransmog) + { + ItemTemplate const* _removeTransmogButton = sObjectMgr->GetItemTemplate(CUSTOM_REMOVE_TMOG_VENDOR_ID); + if (_removeTransmogButton) + spoofedItems.push_back(_removeTransmogButton); else { - uint32 limit = 0; - for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) - { - if (limit > MAX_OPTIONS) - break; - Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); - if (!newItem) - continue; - if (!sT->CanTransmogrifyItemWithItem(player, oldItem->GetTemplate(), newItem->GetTemplate())) - continue; - if (sT->GetFakeEntry(oldItem->GetGUID()) == newItem->GetEntry()) - continue; - ++limit; - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(newItem->GetEntry(), 30, 30, -18, 0) + sT->GetItemLink(newItem, session), slot, newItem->GetGUID().GetCounter(), GetLocaleText(locale, "confirm_use_item") + sT->GetItemIcon(newItem->GetEntry(), 40, 40, -15, -10) + sT->GetItemLink(newItem, session) + lineEnd, price, false); - } - - for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) - { - Bag* bag = player->GetBagByPos(i); - if (!bag) - continue; - for (uint32 j = 0; j < bag->GetBagSize(); ++j) - { - if (limit > MAX_OPTIONS) - break; - Item* newItem = player->GetItemByPos(i, j); - if (!newItem) - continue; - if (!sT->CanTransmogrifyItemWithItem(player, oldItem->GetTemplate(), newItem->GetTemplate())) - continue; - if (sT->GetFakeEntry(oldItem->GetGUID()) == newItem->GetEntry()) - continue; - ++limit; - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, sT->GetItemIcon(newItem->GetEntry(), 30, 30, -18, 0) + sT->GetItemLink(newItem, session), slot, newItem->GetGUID().GetCounter(), GetLocaleText(locale, "confirm_use_item") + sT->GetItemIcon(newItem->GetEntry(), 40, 40, -15, -10) + sT->GetItemLink(newItem, session) + ss.str(), price, false); - } - } + _removeTransmogButton = sObjectMgr->GetItemTemplate(FALLBACK_REMOVE_TMOG_VENDOR_ID); + spoofedItems.push_back(_removeTransmogButton); } } + return spoofedItems; + } - if (sendGossip) + static uint32 GetSpoofedItemPrice (uint32 itemId, ItemTemplate const* target) + { + switch (itemId) { - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/INV_Enchant_Disenchant:30:30:-18:0|t" + GetLocaleText(locale, "remove_transmog"), EQUIPMENT_SLOT_END + 3, slot, GetLocaleText(locale, "remove_transmog_slot"), 0, false); - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/PaperDollInfoFrame/UI-GearManager-Undo:30:30:-18:0|t" + GetLocaleText(locale, "update_menu"), EQUIPMENT_SLOT_END, slot); - AddGossipItemFor(player, GOSSIP_ICON_MONEY_BAG, "|TInterface/ICONS/Ability_Spy:30:30:-18:0|t" + GetLocaleText(locale, "back"), EQUIPMENT_SLOT_END + 1, 0); - SendGossipMenuFor(player, DEFAULT_GOSSIP_MESSAGE, creature->GetGUID()); + case CUSTOM_HIDE_ITEM_VENDOR_ID: + case FALLBACK_HIDE_ITEM_VENDOR_ID: + return sT->HiddenTransmogIsFree ? 0 : sT->GetSpecialPrice(target); + default: + return 0; + } + } + + static void EncodeItemToPacket (WorldPacket& data, ItemTemplate const* proto, uint8& slot, uint32 price) + { + data << uint32(slot + 1); + data << uint32(proto->ItemId); + data << uint32(proto->DisplayInfoID); + data << int32 (-1); //Infinite Stock + data << uint32(price); + data << uint32(proto->MaxDurability); + data << uint32(1); //Buy Count of 1 + data << uint32(0); + slot++; + } + + //The actual vendor options are handled in the player script below, OnBeforeBuyItemFromVendor + static void ShowTransmogItemsInFakeVendor (Player* player, Creature* creature, uint8 slot) + { + Item* targetItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (!targetItem) + { + ChatHandler(player->GetSession()).SendNotification(LANG_ERR_TRANSMOG_MISSING_DEST_ITEM); + CloseGossipMenuFor(player); + return; + } + ItemTemplate const* targetTemplate = targetItem->GetTemplate(); + + std::vector itemList = GetValidTransmogs(player, targetItem, false, ""); + std::vector spoofedItems = GetSpoofedVendorItems(targetItem); + + uint32 itemCount = itemList.size(); + uint32 spoofCount = spoofedItems.size(); + uint32 totalItems = itemCount + spoofCount; + uint32 price = GetTransmogPrice(targetItem->GetTemplate()); + + WorldPacket data(SMSG_LIST_INVENTORY, 8 + 1 + totalItems * 8 * 4); + data << uint64(creature->GetGUID().GetRawValue()); + + uint8 count = 0; + size_t count_pos = data.wpos(); + data << uint8(count); + + for (uint32 i = 0; i < spoofCount && count < MAX_VENDOR_ITEMS; ++i) + { + EncodeItemToPacket ( + data, spoofedItems[i], count, + GetSpoofedItemPrice(spoofedItems[i]->ItemId, targetTemplate) + ); } + for (uint32 i = 0; i < itemCount && count < MAX_VENDOR_ITEMS; ++i) + { + ItemTemplate const* _proto = itemList[i]->GetTemplate(); + if (_proto) EncodeItemToPacket(data, _proto, count, price); + } + + data.put(count_pos, count); + player->GetSession()->SendPacket(&data); } }; @@ -1037,12 +1154,36 @@ class PS_Transmogrification : public PlayerScript for (Transmogrification::transmog2Data::const_iterator it = sT->entryMap[pGUID].begin(); it != sT->entryMap[pGUID].end(); ++it) sT->dataMap.erase(it->first); sT->entryMap.erase(pGUID); - + sT->selectionCache.erase(pGUID); + #ifdef PRESETS if (sT->GetEnableSets()) sT->UnloadPlayerSets(pGUID); #endif } + + void OnBeforeBuyItemFromVendor(Player* player, ObjectGuid vendorguid, uint32 /*vendorslot*/, uint32& itemEntry, uint8 /*count*/, uint8 /*bag*/, uint8 /*slot*/) override + { + Creature* vendor = player->GetMap()->GetCreature(vendorguid); + if (!vendor) return; + if (vendor->GetEntry() != TMOG_VENDOR_CREATURE_ID) return; + uint8 slot = sT->selectionCache[player->GetGUID()]; + + if (itemEntry == CUSTOM_HIDE_ITEM_VENDOR_ID || itemEntry == FALLBACK_HIDE_ITEM_VENDOR_ID) + { + PerformTransmogrification(player, UINT_MAX, 0); + } + else if (itemEntry == CUSTOM_REMOVE_TMOG_VENDOR_ID || itemEntry == FALLBACK_REMOVE_TMOG_VENDOR_ID) + { + RemoveTransmogrification(player); + } + else + { + PerformTransmogrification(player, itemEntry, 0); + } + npc_transmogrifier::ShowTransmogItemsInFakeVendor(player, vendor, slot); //Refresh menu + itemEntry = 0; //Prevents the handler from proceeding to core vendor handling + } }; class WS_Transmogrification : public WorldScript