From 8f73d0cdf80a7ffb63a2dccf722575240adc5c66 Mon Sep 17 00:00:00 2001 From: luis Date: Sun, 15 Mar 2026 22:17:39 -0300 Subject: [PATCH] fix trading post --- .../game/PerkProgram/PerkProgramData.cpp | 236 +++++++++++++++++- src/server/game/PerkProgram/PerkProgramData.h | 10 +- .../game/PerkProgram/PerkProgramMgr.cpp | 60 +++-- 3 files changed, 285 insertions(+), 21 deletions(-) diff --git a/src/server/game/PerkProgram/PerkProgramData.cpp b/src/server/game/PerkProgram/PerkProgramData.cpp index 4fa087df0..26e2155af 100644 --- a/src/server/game/PerkProgram/PerkProgramData.cpp +++ b/src/server/game/PerkProgram/PerkProgramData.cpp @@ -10,8 +10,12 @@ #include "Containers.h" #include "DatabaseEnv.h" #include "ObjectMgr.h" +#include "DB2Stores.h" +#include "ItemTemplate.h" #include #include "GameTime.h" +#include +#include PerkProgramDataStoreMgr::PerkProgramDataStoreMgr() {}; @@ -33,6 +37,9 @@ void PerkProgramDataStoreMgr::Initialize() { LoadPerkVendorItem(); LoadActivities(); + + // Auto-populate missing items from DB2 + PopulateFromDB2(); } void _LoadTemplates(Field* fields) @@ -92,7 +99,7 @@ void PerkProgramDataStoreMgr::LoadPerkVendorItem() TC_LOG_INFO("server.loading", ">> Loaded {} PerkPrograms Vendor Items Templates in {} ms", uint64(_vendorTemplates.size()), GetMSTimeDiffToNow(oldMsTime)); } -std::unordered_map PerkProgramDataStoreMgr::GetVendorItems() +std::unordered_map PerkProgramDataStoreMgr::GetVendorItems() const { return _vendorTemplates; } @@ -193,3 +200,230 @@ void PerkProgramDataStoreMgr::GenerateRandomActivities(bool clearOld) CharacterDatabase.Execute(stmt); } } + +void PerkProgramDataStoreMgr::PopulateFromDB2() +{ + TC_LOG_INFO("server.loading", "Auto-populating PerkProgram items from DB2 PerksVendorItem store..."); + + uint32 addedCount = 0; + const uint32 MAX_ITEMS = 12; // Limit to 12 items maximum + + // Pre-reserve space for better performance + if (_vendorTemplates.size() < MAX_ITEMS) + _vendorTemplates.reserve(MAX_ITEMS); + + // Create a vector of all DB2 items for random selection + std::vector availableItems; + availableItems.reserve(sPerksVendorItemStore.GetNumRows()); + + for (PerksVendorItemEntry const* db2Item : sPerksVendorItemStore) + { + if (!db2Item) + continue; + + // Skip if item already exists from database loading + if (_vendorTemplates.find(db2Item->ID) != _vendorTemplates.end()) + continue; + + // Validate DB2 item data + if (db2Item->Cost > 1000000) // Reasonable maximum cost + { + TC_LOG_WARN("server.loading", ">> DB2 PerksVendorItem {} has excessive cost {}, skipping", db2Item->ID, db2Item->Cost); + continue; + } + + availableItems.push_back(db2Item); + } + + // Randomly select up to MAX_ITEMS items + std::random_device rd; + std::mt19937 gen(rd()); + std::shuffle(availableItems.begin(), availableItems.end(), gen); + + uint32 itemsToAdd = std::min(MAX_ITEMS, static_cast(availableItems.size())); + + for (uint32 i = 0; i < itemsToAdd; ++i) + { + PerksVendorItemEntry const* db2Item = availableItems[i]; + + // Create vendor item from DB2 data + PerkProgramData::PerkVendorItem vendorItem; + vendorItem.Id = db2Item->ID; + vendorItem.ItemID = db2Item->ItemID; + vendorItem.MountSpellID = db2Item->Unknown010001; + vendorItem.PetSpellID = db2Item->Unknown020001; + vendorItem.TransmogSet = 0; // Not available in DB2 + vendorItem.ItemModifiedAppearanceID = 0; // Not available in DB2 + vendorItem.ToyItem = 0; // Not available in DB2 + vendorItem.AvailableUntil = 0; // No expiration by default + vendorItem.Cost = db2Item->Cost; + vendorItem.CategoryId = db2Item->PerksVendorCategoryID; + + // Auto-determine item type based on ItemID (simplified for speed) + if (db2Item->ItemID != 0) + { + if (ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(db2Item->ItemID)) + { + // Quick mount detection - only check for mount subclass + if (itemProto->GetClass() == ITEM_CLASS_MISCELLANEOUS && + itemProto->GetSubClass() == ITEM_SUBCLASS_MISCELLANEOUS_MOUNT && + !itemProto->Effects.empty()) + { + // Just take the first spell effect for speed + if (itemProto->Effects[0] && itemProto->Effects[0]->SpellID != 0) + vendorItem.MountSpellID = itemProto->Effects[0]->SpellID; + + // Set correct category for mounts + vendorItem.CategoryId = PerkPrograms::PERKS_VENDOR_CATEGORY_MOUNTS; + } + else + { + // Default to consumables for regular items + vendorItem.CategoryId = PerkPrograms::PERKS_VENDOR_CATEGORY_CONSUMABLES; + } + } + else + { + // If no item template found, default to consumables + vendorItem.CategoryId = PerkPrograms::PERKS_VENDOR_CATEGORY_CONSUMABLES; + } + } + else + { + // For spells/mounts without ItemID, use mount category + if (vendorItem.MountSpellID != 0) + vendorItem.CategoryId = PerkPrograms::PERKS_VENDOR_CATEGORY_MOUNTS; + else if (vendorItem.PetSpellID != 0) + vendorItem.CategoryId = PerkPrograms::PERKS_VENDOR_CATEGORY_PETS; + else + vendorItem.CategoryId = PerkPrograms::PERKS_VENDOR_CATEGORY_CONSUMABLES; + } + + _vendorTemplates.insert(std::make_pair(db2Item->ID, vendorItem)); + addedCount++; + + TC_LOG_DEBUG("server.loading", ">> Added DB2 PerkVendorItem {}: ItemID={}, Cost={}, Category={}", + db2Item->ID, db2Item->ItemID, db2Item->Cost, db2Item->PerksVendorCategoryID); + } + + if (addedCount > 0) + { + TC_LOG_INFO("server.loading", ">> Auto-populated {} additional PerkProgram items from DB2 store (limited to {} max)", addedCount, MAX_ITEMS); + TC_LOG_INFO("server.loading", ">> Total PerkProgram items available: {}", uint32(_vendorTemplates.size())); + } + else + { + TC_LOG_INFO("server.loading", ">> No additional items needed from DB2 store (all items already loaded)"); + } +} + +void PerkProgramDataStoreMgr::RefreshFromDB2() +{ + TC_LOG_INFO("server.loading", "Force refreshing PerkProgram items from DB2 PerksVendorItem store..."); + + uint32 addedCount = 0; + uint32 updatedCount = 0; + + for (PerksVendorItemEntry const* db2Item : sPerksVendorItemStore) + { + if (!db2Item) + continue; + + auto existingIt = _vendorTemplates.find(db2Item->ID); + + // Validate DB2 item data + if (db2Item->Cost > 1000000) // Reasonable maximum cost + { + TC_LOG_WARN("server.loading", ">> DB2 PerksVendorItem {} has excessive cost {}, skipping", db2Item->ID, db2Item->Cost); + continue; + } + + // Create vendor item from DB2 data + PerkProgramData::PerkVendorItem vendorItem; + vendorItem.Id = db2Item->ID; + vendorItem.ItemID = db2Item->ItemID; + vendorItem.MountSpellID = db2Item->Unknown010001; + vendorItem.PetSpellID = db2Item->Unknown020001; + vendorItem.TransmogSet = 0; // Not available in DB2 + vendorItem.ItemModifiedAppearanceID = 0; // Not available in DB2 + vendorItem.ToyItem = 0; // Not available in DB2 + vendorItem.AvailableUntil = 0; // No expiration by default + vendorItem.Cost = db2Item->Cost; + vendorItem.CategoryId = db2Item->PerksVendorCategoryID; + + // Auto-determine item type based on ItemID + if (db2Item->ItemID != 0) + { + if (ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(db2Item->ItemID)) + { + switch (itemProto->GetClass()) + { + case ITEM_CLASS_MISCELLANEOUS: + if (itemProto->GetSubClass() == ITEM_SUBCLASS_MISCELLANEOUS_MOUNT) + { + // Try to find mount spell from item effects + for (ItemEffectEntry const* effect : itemProto->Effects) + { + if (effect && effect->SpellID != 0) + { + vendorItem.MountSpellID = effect->SpellID; + break; + } + } + } + // For now, we'll skip toy detection without DB2Manager + // Toys would typically be identified via DB2Manager.IsToyItem() + break; + default: + // Regular item + break; + } + } + } + + if (existingIt != _vendorTemplates.end()) + { + // Update existing item + existingIt->second = vendorItem; + updatedCount++; + } + else + { + // Add new item + _vendorTemplates.insert(std::make_pair(db2Item->ID, vendorItem)); + addedCount++; + } + } + + TC_LOG_INFO("server.loading", ">> DB2 refresh completed: {} added, {} updated", addedCount, updatedCount); + TC_LOG_INFO("server.loading", ">> Total PerkProgram items available: {}", uint32(_vendorTemplates.size())); +} + +std::vector PerkProgramDataStoreMgr::GetRandomVendorItems(uint32 maxCount) +{ + std::vector randomItems; + + if (_vendorTemplates.empty()) + return randomItems; + + // Convert map to vector for random selection + std::vector allItems; + allItems.reserve(_vendorTemplates.size()); + + for (auto const& pair : _vendorTemplates) + allItems.push_back(pair.second); + + // Random shuffle + std::random_device rd; + std::mt19937 gen(rd()); + std::shuffle(allItems.begin(), allItems.end(), gen); + + // Take up to maxCount items + uint32 itemsToTake = std::min(maxCount, static_cast(allItems.size())); + randomItems.reserve(itemsToTake); + + for (uint32 i = 0; i < itemsToTake; ++i) + randomItems.push_back(allItems[i]); + + return randomItems; +} diff --git a/src/server/game/PerkProgram/PerkProgramData.h b/src/server/game/PerkProgram/PerkProgramData.h index 0e7839d71..ad90a8c59 100644 --- a/src/server/game/PerkProgram/PerkProgramData.h +++ b/src/server/game/PerkProgram/PerkProgramData.h @@ -53,19 +53,27 @@ public: void Initialize(); - std::unordered_map GetVendorItems(); + std::unordered_map GetVendorItems() const; PerkProgramData::PerkVendorItem const* GetVendorItem(uint32 entry); PerkProgramData::PerkVendorItem const* GetRandomVendorItem(); std::vector GetActivities(); uint32 GetActivityCriteriatree(uint32 activityId); uint32 GetActivityCurrencyAmount(uint32 activityId); + + // DB2 auto-population functions + void RefreshFromDB2(); // Force refresh all items from DB2 + uint32 GetTotalItemCount() const { return GetVendorItems().size(); } + + // Get limited random items for vendor display + std::vector GetRandomVendorItems(uint32 maxCount = 12); private: void LoadPerkVendorItem(); void LoadActivities(); void GenerateRandomActivities(bool clearOld); + void PopulateFromDB2(); // Auto-populate from DB2 store time_t m_activityTime; }; diff --git a/src/server/game/PerkProgram/PerkProgramMgr.cpp b/src/server/game/PerkProgram/PerkProgramMgr.cpp index dc7ca1b9a..3afbfeba0 100644 --- a/src/server/game/PerkProgram/PerkProgramMgr.cpp +++ b/src/server/game/PerkProgram/PerkProgramMgr.cpp @@ -89,22 +89,25 @@ void PerkProgramManager::SendVendorItems(Creature* vendor, std::vector p } } - for (auto items : sPerkProgramDataStore->GetVendorItems()) + // Use limited random items for better performance and UI + auto randomItems = sPerkProgramDataStore->GetRandomVendorItems(12); + + for (auto const& items : randomItems) { WorldPackets::PerksProgram::PerksVendorItem vendorItem; - vendorItem.VendorItemID = items.second.Id; - vendorItem.MountID = items.second.MountSpellID; - vendorItem.BattlePetSpeciesID = items.second.PetSpellID; - vendorItem.ItemModifiedAppearanceID = items.second.ItemModifiedAppearanceID; - vendorItem.ToyID = items.second.ToyItem; - vendorItem.TransmogSetID = items.second.TransmogSet; - vendorItem.Price = items.second.Cost; - vendorItem.OriginalPrice = items.second.CategoryId; + vendorItem.VendorItemID = items.Id; + vendorItem.MountID = items.MountSpellID; + vendorItem.BattlePetSpeciesID = items.PetSpellID; + vendorItem.ItemModifiedAppearanceID = items.ItemModifiedAppearanceID; + vendorItem.ToyID = items.ToyItem; + vendorItem.TransmogSetID = items.TransmogSet; + vendorItem.Price = items.Cost; + vendorItem.OriginalPrice = items.CategoryId; vendorItem.Disabled = false; - vendorItem.AvailableUntil = items.second.AvailableUntil; + vendorItem.AvailableUntil = items.AvailableUntil; // Skip items that are no longer available - if (items.second.AvailableUntil > 0 && GameTime::GetGameTime() > items.second.AvailableUntil) + if (items.AvailableUntil > 0 && GameTime::GetGameTime() > items.AvailableUntil) continue; perks.Params.VendorItem.FrozenPerksVendorItems.emplace_back(vendorItem); @@ -165,36 +168,55 @@ void PerkProgramManager::GrantItem(PerkProgramData::PerkVendorItem const* vendor { case PerkPrograms::PERKS_VENDOR_CATEGORY_TOYS: { - m_player->GetSession()->GetCollectionMgr()->AddToy(vendorItem->ToyItem, false, false); + if (vendorItem->ToyItem != 0) + m_player->GetSession()->GetCollectionMgr()->AddToy(vendorItem->ToyItem, false, false); + else if (vendorItem->ItemID != 0) + m_player->GetSession()->GetCollectionMgr()->AddToy(vendorItem->ItemID, false, false); break; } case PerkPrograms::PERKS_VENDOR_CATEGORY_MOUNTS: { - m_player->LearnSpell(vendorItem->MountSpellID, false); + if (vendorItem->MountSpellID != 0) + m_player->LearnSpell(vendorItem->MountSpellID, false); + else if (vendorItem->ItemID != 0) + { + // Try to learn mount spell from item + if (ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(vendorItem->ItemID)) + { + if (!itemProto->Effects.empty() && itemProto->Effects[0] && itemProto->Effects[0]->SpellID != 0) + m_player->LearnSpell(itemProto->Effects[0]->SpellID, false); + } + } break; } case PerkPrograms::PERKS_VENDOR_CATEGORY_TRANSMOG_SETS: { - m_player->GetSession()->GetCollectionMgr()->AddTransmogSet(vendorItem->TransmogSet); + if (vendorItem->TransmogSet != 0) + m_player->GetSession()->GetCollectionMgr()->AddTransmogSet(vendorItem->TransmogSet); break; } case PerkPrograms::PERKS_VENDOR_CATEGORY_TRANSMOG: { - m_player->GetSession()->GetCollectionMgr()->AddItemAppearance(vendorItem->ItemModifiedAppearanceID); + if (vendorItem->ItemModifiedAppearanceID != 0) + m_player->GetSession()->GetCollectionMgr()->AddItemAppearance(vendorItem->ItemModifiedAppearanceID); break; } case PerkPrograms::PERKS_VENDOR_CATEGORY_PETS: { - // m_player->AddBattlePet(vendorItem->PetSpellID); + if (vendorItem->PetSpellID != 0) + { + // Add battle pet (implementation depends on your pet system) + // m_player->AddBattlePet(vendorItem->PetSpellID); + } break; } case PerkPrograms::PERKS_VENDOR_CATEGORY_CONSUMABLES: + default: { - m_player->AddItem(vendorItem->ItemID, 1); + if (vendorItem->ItemID != 0) + m_player->AddItem(vendorItem->ItemID, 1); break; } - default: - break; } }