From 77ed1b79a606fb14ab7151ecf93912dea892e4c3 Mon Sep 17 00:00:00 2001 From: Skjalf <47818697+Nyeriah@users.noreply.github.com> Date: Mon, 23 Sep 2024 19:46:20 -0300 Subject: [PATCH] feat: Implement vendor interface --- conf/mod-zone-difficulty.conf.dist | 9 ++ src/ZoneDifficulty.h | 15 ++- src/mod_zone_difficulty_handler.cpp | 47 ++++++++ src/mod_zone_difficulty_scripts.cpp | 180 ++++++++++++++++++---------- 4 files changed, 186 insertions(+), 65 deletions(-) diff --git a/conf/mod-zone-difficulty.conf.dist b/conf/mod-zone-difficulty.conf.dist index c161f27d..fc107447 100644 --- a/conf/mod-zone-difficulty.conf.dist +++ b/conf/mod-zone-difficulty.conf.dist @@ -25,6 +25,15 @@ ModZoneDifficulty.Enable = 1 ModZoneDifficulty.DebugInfo = 0 +# +# ModZoneDifficulty.UseVendorInterface +# Description: Displays a vendor interface to claim rewards instead of gossips. +# Default: 0 - Disabled +# 1 - Enabled +# + +ModZoneDifficulty.UseVendorInterface = 0 + # # ModZoneDifficulty.SpellBuff.OnlyBosses # Description: Spell damage buffs will only affect bosses. diff --git a/src/ZoneDifficulty.h b/src/ZoneDifficulty.h index 57e94012..089717d9 100644 --- a/src/ZoneDifficulty.h +++ b/src/ZoneDifficulty.h @@ -58,6 +58,12 @@ struct ZoneDifficultyHAI bool TriggeredCast; }; +struct VendorSelectionData +{ + uint8 category; + uint8 slot; +}; + int32 const DUEL_INDEX = 0x7FFFFFFF; int32 const DUEL_AREA = 2402; // Forbidding Sea (Wetlands) @@ -121,13 +127,16 @@ enum ZoneDifficultySettings TYPE_RAID_T9 = 15, TYPE_RAID_T10 = 16, + TYPE_MAX_TIERS, + // Completed tiers settings SETTING_BLACK_TEMPLE = 0 }; enum Misc { - NPC_ILLIDAN_STORMRAGE = 22917 + NPC_ILLIDAN_STORMRAGE = 22917, + NPC_REWARD_CHROMIE = 1128002, }; class ZoneDifficulty @@ -157,12 +166,14 @@ class ZoneDifficulty [[nodiscard]] bool ShouldNerfInDuels(Unit* target); [[nodiscard]] bool ShouldNerfMap(uint32 mapId) { return NerfInfo.find(mapId) != NerfInfo.end(); }; [[nodiscard]] int32 GetLowestMatchingPhase(uint32 mapId, uint32 phaseMask); + void RewardItem(Player* player, uint8 category, uint8 itemType, uint8 counter, Creature* creature, uint32 itemEntry); bool IsEnabled{ false }; bool IsDebugInfoEnabled{ false }; float MythicmodeHpModifier{ 2.0 }; bool MythicmodeEnable{ false }; bool MythicmodeInNormalDungeons{ false }; + bool UseVendorInterface{ false }; std::vector DailyHeroicQuests; std::map HeroicTBCQuestMapList; std::map EncounterCounter; @@ -190,6 +201,8 @@ class ZoneDifficulty ZoneDifficultyHAIMap MythicmodeAI; typedef std::map > > ZoneDifficultyEncounterLogMap; ZoneDifficultyEncounterLogMap Logs; + typedef std::unordered_map ZoneDifficultyVendorSelectionMap; + ZoneDifficultyVendorSelectionMap SelectionCache; }; #define sZoneDifficulty ZoneDifficulty::instance() diff --git a/src/mod_zone_difficulty_handler.cpp b/src/mod_zone_difficulty_handler.cpp index d4abf12a..de3fa8df 100644 --- a/src/mod_zone_difficulty_handler.cpp +++ b/src/mod_zone_difficulty_handler.cpp @@ -1014,3 +1014,50 @@ bool ZoneDifficulty::HasCompletedFullTier(uint32 category, uint32 playerGuid) } return true; } + +void ZoneDifficulty::RewardItem(Player* player, uint8 category, uint8 itemType, uint8 counter, Creature* creature, uint32 itemEntry) +{ + // Check (again) if the player has enough score in the respective category. + uint32 availableScore = player->GetPlayerSetting(ModZoneDifficultyString + "score", category).value; + + auto reward = sZoneDifficulty->Rewards[category][itemType][counter]; + + if (itemEntry) + { + for (auto const& item : sZoneDifficulty->Rewards[category][itemType]) + { + if (item.Entry == itemEntry) + reward = item; + } + } + + if (availableScore < reward.Price) + { + if (player->GetSession()) + player->GetSession()->SendAreaTriggerMessage("Not enough points."); + + return; + } + + // Check if the player has the neccesary achievement + if (reward.Achievement) + { + if (!player->HasAchieved(reward.Achievement)) + { + std::string gossip = "You do not have the required achievement with ID "; + gossip.append(std::to_string(reward.Achievement)); + gossip.append(" to receive this item. Before i can give it to you, you need to complete the whole dungeon where it can be obtained."); + creature->Whisper(gossip, LANG_UNIVERSAL, player); + CloseGossipMenuFor(player); + return; + } + } + + //LOG_INFO("module", "MOD-ZONE-DIFFICULTY: Sending item with category {}, itemType {}, counter {}", category, itemType, counter); + sZoneDifficulty->DeductMythicmodeScore(player, category, reward.Price); + sZoneDifficulty->SendItem(player, category, itemType, counter); + + if (player->GetSession()) + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(reward.Entry)) + player->GetSession()->SendAreaTriggerMessage("You were rewarded %s for %u points.", proto->Name1.c_str(), reward.Price); +}; diff --git a/src/mod_zone_difficulty_scripts.cpp b/src/mod_zone_difficulty_scripts.cpp index 8c699036..4439a152 100644 --- a/src/mod_zone_difficulty_scripts.cpp +++ b/src/mod_zone_difficulty_scripts.cpp @@ -397,46 +397,6 @@ class mod_zone_difficulty_unitscript : public UnitScript } }; -class mod_zone_difficulty_playerscript : public PlayerScript -{ -public: - mod_zone_difficulty_playerscript() : PlayerScript("mod_zone_difficulty_playerscript") { } - - void OnMapChanged(Player* player) override - { - uint32 mapId = player->GetMapId(); - if (sZoneDifficulty->DisallowedBuffs.find(mapId) != sZoneDifficulty->DisallowedBuffs.end()) - { - for (auto aura : sZoneDifficulty->DisallowedBuffs[mapId]) - { - player->RemoveAura(aura); - } - } - } - - void OnLogin(Player* player) override - { - if (sZoneDifficulty->MythicmodeScore.empty()) - return; - - if (sZoneDifficulty->MythicmodeScore.find(player->GetGUID().GetCounter()) != sZoneDifficulty->MythicmodeScore.end()) - { - for (int i = 1; i <= 16; ++i) - { - uint32 availableScore = 0; - - if (sZoneDifficulty->MythicmodeScore[player->GetGUID().GetCounter()].find(i) != sZoneDifficulty->MythicmodeScore[player->GetGUID().GetCounter()].end()) - availableScore = sZoneDifficulty->MythicmodeScore[player->GetGUID().GetCounter()][i]; - - player->UpdatePlayerSetting(ModZoneDifficultyString + "score", i, availableScore); - } - - sZoneDifficulty->MythicmodeScore.erase(player->GetGUID().GetCounter()); - CharacterDatabase.Execute("DELETE FROM zone_difficulty_mythicmode_score WHERE GUID = {}", player->GetGUID().GetCounter()); - } - } -}; - class mod_zone_difficulty_petscript : public PetScript { public: @@ -470,6 +430,7 @@ class mod_zone_difficulty_worldscript : public WorldScript sZoneDifficulty->MythicmodeHpModifier = sConfigMgr->GetOption("ModZoneDifficulty.Mythicmode.HpModifier", 2); sZoneDifficulty->MythicmodeEnable = sConfigMgr->GetOption("ModZoneDifficulty.Mythicmode.Enable", false); sZoneDifficulty->MythicmodeInNormalDungeons = sConfigMgr->GetOption("ModZoneDifficulty.Mythicmode.InNormalDungeons", false); + sZoneDifficulty->UseVendorInterface = sConfigMgr->GetOption("ModZoneDifficulty.UseVendorInterface", false); sZoneDifficulty->LoadMapDifficultySettings(); } @@ -749,6 +710,12 @@ class mod_zone_difficulty_rewardnpc : public CreatureScript } //LOG_INFO("module", "MOD-ZONE-DIFFICULTY: Building gossip with category {} and counter {}", category, counter); + if (sZoneDifficulty->UseVendorInterface) + { + ShowItemsInFakeVendor(player, creature, category, counter); + return true; + } + for (size_t i = 0; i < sZoneDifficulty->Rewards[category][counter].size(); ++i) { //LOG_INFO("module", "MOD-ZONE-DIFFICULTY: Adding gossip option for entry {}", sZoneDifficulty->Rewards[category][counter][i].Entry); @@ -831,29 +798,7 @@ class mod_zone_difficulty_rewardnpc : public CreatureScript counter = counter - 100; } - // Check (again) if the player has enough score in the respective category. - uint32 availableScore = player->GetPlayerSetting(ModZoneDifficultyString + "score", category).value; - - if (availableScore < sZoneDifficulty->Rewards[category][itemType][counter].Price) - return true; - - // Check if the player has the neccesary achievement - if (sZoneDifficulty->Rewards[category][itemType][counter].Achievement) - { - if (!player->HasAchieved(sZoneDifficulty->Rewards[category][itemType][counter].Achievement)) - { - std::string gossip = "You do not have the required achievement with ID "; - gossip.append(std::to_string(sZoneDifficulty->Rewards[category][itemType][counter].Achievement)); - gossip.append(" to receive this item. Before i can give it to you, you need to complete the whole dungeon where it can be obtained."); - creature->Whisper(gossip, LANG_UNIVERSAL, player); - CloseGossipMenuFor(player); - return true; - } - } - - //LOG_INFO("module", "MOD-ZONE-DIFFICULTY: Sending item with category {}, itemType {}, counter {}", category, itemType, counter); - sZoneDifficulty->DeductMythicmodeScore(player, category, sZoneDifficulty->Rewards[category][itemType][counter].Price); - sZoneDifficulty->SendItem(player, category, itemType, counter); + sZoneDifficulty->RewardItem(player, category, itemType, counter, creature, 0); } SendGossipMenuFor(player, npcText, creature); @@ -878,6 +823,53 @@ class mod_zone_difficulty_rewardnpc : public CreatureScript SendGossipMenuFor(player, npcText, creature); return true; } + + static void ShowItemsInFakeVendor(Player* player, Creature* creature, uint8 category, uint8 slot) + { + auto const& itemList = sZoneDifficulty->Rewards[category][slot]; + + uint32 itemCount = itemList.size(); + + WorldPacket data(SMSG_LIST_INVENTORY, 8 + 1 + itemCount * 8 * 4); + data << uint64(creature->GetGUID().GetRawValue()); + + uint8 count = 0; + size_t count_pos = data.wpos(); + data << uint8(count); + + for (uint32 i = 0; i < itemCount && count < MAX_VENDOR_ITEMS; ++i) + {; + EncodeItemToPacket( + data, sObjectMgr->GetItemTemplate(itemList[i].Entry), count, + itemList[i].Price); + } + + for (uint32 i = 0; i < itemCount && count < MAX_VENDOR_ITEMS; ++i) + { + if (ItemTemplate const* _proto = sObjectMgr->GetItemTemplate(itemList[i].Entry)) + EncodeItemToPacket(data, _proto, count, itemList[i].Price); + } + + data.put(count_pos, count); + player->GetSession()->SendPacket(&data); + VendorSelectionData vendorData; + vendorData.category = category; + vendorData.slot = slot; + sZoneDifficulty->SelectionCache[player->GetGUID()] = vendorData; + } + + 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++; + } }; class mod_zone_difficulty_dungeonmaster : public CreatureScript @@ -1152,15 +1144,75 @@ class mod_zone_difficulty_allcreaturescript : public AllCreatureScript } }; +class mod_zone_difficulty_playerscript : public PlayerScript +{ +public: + mod_zone_difficulty_playerscript() : PlayerScript("mod_zone_difficulty_playerscript") { } + + void OnMapChanged(Player* player) override + { + uint32 mapId = player->GetMapId(); + if (sZoneDifficulty->DisallowedBuffs.find(mapId) != sZoneDifficulty->DisallowedBuffs.end()) + { + for (auto aura : sZoneDifficulty->DisallowedBuffs[mapId]) + { + player->RemoveAura(aura); + } + } + } + + void OnLogin(Player* player) override + { + if (sZoneDifficulty->MythicmodeScore.empty()) + return; + + if (sZoneDifficulty->MythicmodeScore.find(player->GetGUID().GetCounter()) != sZoneDifficulty->MythicmodeScore.end()) + { + for (int i = 1; i <= 16; ++i) + { + uint32 availableScore = 0; + + if (sZoneDifficulty->MythicmodeScore[player->GetGUID().GetCounter()].find(i) != sZoneDifficulty->MythicmodeScore[player->GetGUID().GetCounter()].end()) + availableScore = sZoneDifficulty->MythicmodeScore[player->GetGUID().GetCounter()][i]; + + player->UpdatePlayerSetting(ModZoneDifficultyString + "score", i, availableScore); + } + + sZoneDifficulty->MythicmodeScore.erase(player->GetGUID().GetCounter()); + CharacterDatabase.Execute("DELETE FROM zone_difficulty_mythicmode_score WHERE GUID = {}", player->GetGUID().GetCounter()); + } + } + + void OnLogout(Player* player) override + { + sZoneDifficulty->SelectionCache.erase(player->GetGUID()); + } + + 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() != NPC_REWARD_CHROMIE) + return; + + auto const& data = sZoneDifficulty->SelectionCache[player->GetGUID()]; + + sZoneDifficulty->RewardItem(player, data.category, data.slot, 0, vendor, itemEntry); + itemEntry = 0; //Prevents the handler from proceeding to core vendor handling + } +}; + // Add all scripts in one void AddModZoneDifficultyScripts() { new mod_zone_difficulty_unitscript(); - new mod_zone_difficulty_playerscript(); new mod_zone_difficulty_petscript(); new mod_zone_difficulty_worldscript(); new mod_zone_difficulty_globalscript(); new mod_zone_difficulty_rewardnpc(); new mod_zone_difficulty_dungeonmaster(); new mod_zone_difficulty_allcreaturescript(); + new mod_zone_difficulty_playerscript(); }