From 738194fbfa463f8ef0a9f267bfe8cb15afd7a759 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Thu, 12 Dec 2024 23:42:16 -0500 Subject: [PATCH] added many methods related to increasing a players rank --- src/AdvancementMgr.cpp | 276 ++++++++++++++++++++++++++++++++++++++++- src/AdvancementMgr.h | 76 +++++++++++- 2 files changed, 344 insertions(+), 8 deletions(-) diff --git a/src/AdvancementMgr.cpp b/src/AdvancementMgr.cpp index 2af4a97..61646e1 100644 --- a/src/AdvancementMgr.cpp +++ b/src/AdvancementMgr.cpp @@ -3,8 +3,11 @@ #include "WorldDatabase.h" #include "Player.h" #include "MpLogger.h" +#include "MythicPlus.h" +#include "MpConstants.h" #include +#include /** * Table schema for upgrade ranks populated by go script: @@ -85,7 +88,7 @@ int32 AdvancementMgr::LoadAdvencementRanks() { uint32 chanceCost2 = fields[15].Get(); uint32 chanceCost3 = fields[16].Get(); - // Should add validator... but let's do it without and trust the o-DB-wan-kenobee + // Should add validator... but let's do it without and trust in the o-DB-Wan-kenobe MpAdvancements advancement = static_cast(advancementId); // List of all ranks keyed by rank, advancementId @@ -93,7 +96,7 @@ int32 AdvancementMgr::LoadAdvencementRanks() { .rank = upgradeRank, .advancementId = advancement, .materialCost = std::unordered_map(), - .rollCost = std::make_tuple(chanceCost1, chanceCost2, chanceCost3), + .rollCost = {chanceCost1, chanceCost2, chanceCost3}, .lowRange = std::make_pair(minIncrease1, maxIncrease1), .midRange = std::make_pair(minIncrease2, maxIncrease2), .highRange = std::make_pair(minIncrease3, maxIncrease3) @@ -132,7 +135,7 @@ int32 AdvancementMgr::LoadPlayerAdvancements(Player* player) { upgradeRank, diceSpent FROM mp_player_advancements - WHERE guid = ? + WHERE guid = {} )"; QueryResult result = CharacterDatabase.Query(query, player->GetGUID().GetCounter()); @@ -151,12 +154,277 @@ int32 AdvancementMgr::LoadPlayerAdvancements(Player* player) { uint32 diceSpent = fields[4].Get(); MpAdvancements advancement = static_cast(advancementId); + MpPlayerRank playerRank = MpPlayerRank(); + playerRank.rank = upgradeRank; + playerRank.advancementId = advancement; + playerRank.diceSpent = diceSpent; + playerRank.bonus = bonus; // List of all ranks keyed by rank, advancementId - _playerAdvancements[guid][advancement] = upgradeRank; + _playerAdvancements[guid][advancement] = playerRank; } while (result->NextRow()); return result->GetRowCount(); +} + +/** + * Load Material Types from the database into memory + */ +int32 AdvancementMgr::LoadPlayerAdvancements(Player* player) { + + constexpr std::string_view query = R"( + SELECT + guid, + advancementId, + bonus, + upgradeRank, + diceSpent + FROM mp_player_advancements + WHERE guid = {} + )"; + + QueryResult result = CharacterDatabase.Query(query, player->GetGUID().GetCounter()); + + // If the player does not have any upgrades just return not a problem until they purchase one. + if(!result) { + return 0; + } + + do { + Field* fields = result->Fetch(); + uint32 guid = fields[0].Get(); + uint32 advancementId = fields[1].Get(); + float bonus = fields[2].Get(); + uint32 upgradeRank = fields[3].Get(); + uint32 diceSpent = fields[4].Get(); + + MpAdvancements advancement = static_cast(advancementId); + MpPlayerRank playerRank = MpPlayerRank(); + playerRank.rank = upgradeRank; + playerRank.advancementId = advancement; + playerRank.diceSpent = diceSpent; + playerRank.bonus = bonus; + + // List of all ranks keyed by rank, advancementId + _playerAdvancements[guid][advancement] = playerRank; + + } while (result->NextRow()); + + return result->GetRowCount(); +} + +MpAdvancementRank* AdvancementMgr::GetAdvancementRank(uint32 rank, MpAdvancements advancement) +{ + auto key = std::make_pair(rank, advancement); + if (_advancementRanks.contains(key)) + { + return &_advancementRanks.at(key); + } + else + { + MpLogger::error("Advancment Id {} and rank {} could not be found", rank, advancement); + return nullptr; + } +} + +MpPlayerRank* AdvancementMgr::GetPlayerAdvancementRank(Player* player, MpAdvancements advancement) +{ + if(!player) { + MpLogger::error("Could not retrieve player advancement for null player {}", player->GetName()); + return nullptr; + } + + if (_playerAdvancements.contains(player->GetGUID().GetCounter()) && _playerAdvancements[player->GetGUID().GetCounter()].contains(advancement)) + { + return &_playerAdvancements[player->GetGUID().GetCounter()][advancement]; + } + + return nullptr; +} + +bool AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3) +{ + // Validators to make sure inputs are correct to perform the upgrade + if(!player) { + MpLogger::error("Could not upgrade advancement for player, player was nullpointer"); + return false; + } + if(diceCostLevel < 1 || diceCostLevel > 3) { + MpLogger::error("Invalid dice cost level valid vales (1,2,3) received {} for player {}", diceCostLevel, player->GetName()); + return false; + } + if(itemEntry1 == 0) { + MpLogger::error("Material1 can not be 0 can not perform advancement upgrade for player {} Advancment {}", player->GetName(), advancement); + return false; + } + + MpPlayerRank* playerRank = GetPlayerAdvancementRank(player, advancement); + + // IF there is not create the base struct and add to the player map for this advancement + if(!playerRank) { + MpPlayerRank newPlayerRank; + newPlayerRank.advancementId = advancement; + + auto& playerAdvMap = _playerAdvancements[player->GetGUID().GetCounter()]; + playerAdvMap.emplace(advancement, newPlayerRank); + playerRank = &playerAdvMap.at(advancement); + } + + if(playerRank->rank == MP_MAX_ADVANCEMENT_RANK) { + MpLogger::error("Player {} has reached the maximum rank for advancement {}", player->GetName(), advancement); + return false; + } + + uint32 newRank = playerRank->rank + 1; + MpAdvancementRank* advancementRank = GetAdvancementRank(newRank, advancement); + if(!advancementRank->IsValid()) { + MpLogger::error("Advancement {} rank {} could not be found", advancement, newRank); + return false; + } + + // If the player has the items needed to upgrade this advancement, then remove the items from the player inventory and apply the upgrade + if(!_PlayerHasItems(player, advancementRank, diceCostLevel, itemEntry1, itemEntry2, itemEntry3)) { + MpLogger::info("Player {} does not have the required items to upgrade advancement {}", player->GetName(), advancement); + return false; + } + + // Charge the player the cost of the upgrade + _ChargeItemCost(player, advancementRank, diceCostLevel, itemEntry1, itemEntry2, itemEntry3); + + // Finally get the bonus to apply for the player + float roll = _RollAdvancement(advancementRank, diceCostLevel); + + // Update the player advancement rank in memory and database + playerRank->rank = newRank; + playerRank->diceSpent += advancementRank->rollCost[diceCostLevel]; + playerRank->bonus += roll; + + return true; +} + +bool AdvancementMgr::ResetPlayerAdvancements(Player* player) +{ + return true; +} + +/****************** + * + * Private Methods + * + ******************/ + +void AdvancementMgr::_ResetPlayerAdvancement(Player* player, MpAdvancements advancement) +{ } + +// Roll them stats DnD style. +float AdvancementMgr::_RollAdvancement(MpAdvancementRank* advancementRank, uint32 diceCostLevel) +{ + uint32 min, max; + + switch (diceCostLevel) + { + case 1: + min = advancementRank->lowRange.first; + max = advancementRank->lowRange.second; + break; + case 2: + min = advancementRank->midRange.first; + max = advancementRank->midRange.second; + break; + case 3: + min = advancementRank->highRange.first; + max = advancementRank->highRange.second; + break; + default: + MpLogger::error("Invalid dice cost level valid vales (1,2,3) received {} for rank roll", diceCostLevel, advancementRank->rank); + break; + } + + return frand(min, max); +} + +// Checks the players inventory to validate they have the required items to perform an upgrade based on the set cost for the passed in level. +bool AdvancementMgr::_PlayerHasItems(Player* player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3) +{ + // Check if player has the required dice to upgrade the advancement if not do nothing. + uint32 diceCost = advancementRank->materialCost.at(diceCostLevel); + if(!player->HasItemCount(MpConstants::ANCIENT_DICE, diceCost)) { + MpLogger::info("Player {} does not have enough dice to upgrade advancement {}", player->GetName(), advancementRank->advancementId); + return false; + } + + // Validate the passed in item for materialId 1 is valid and the player has enough to purchase. + if(itemEntry1 > 0) { + if(!advancementRank->HasMaterial(itemEntry1)) { + MpLogger::error("Material1 {} is not a valid material for advancement {}", itemEntry1, advancementRank->advancementId); + return false; + } + + if(!player->HasItemCount(itemEntry1, advancementRank->materialCost[itemEntry1])) { + MpLogger::info("Player {} does not have enough material {} to upgrade advancement {}", player->GetName(), itemEntry1, advancementRank->advancementId); + return false; + } + } + + if(itemEntry2 > 0) { + if(!advancementRank->HasMaterial(itemEntry2)) { + MpLogger::error("Material1 {} is not a valid material for advancement {}", itemEntry2, advancementRank->advancementId); + return false; + } + + if(!player->HasItemCount(itemEntry2, advancementRank->materialCost[itemEntry2])) { + MpLogger::info("Player {} does not have enough material {} to upgrade advancement {}", player->GetName(), itemEntry2, advancementRank->advancementId); + return false; + } + } + + if(itemEntry3 > 0) { + if(!advancementRank->HasMaterial(itemEntry3)) { + MpLogger::error("Material1 {} is not a valid material for advancement {}", itemEntry3, advancementRank->advancementId); + return false; + } + + if(!player->HasItemCount(itemEntry3, advancementRank->materialCost[itemEntry3])) { + MpLogger::info("Player {} does not have enough material {} to upgrade advancement {}", player->GetName(), itemEntry3, advancementRank->advancementId); + return false; + } + } + + return true; +} + +// Remove all items required for the upgrade. +void AdvancementMgr::_ChargeItemCost(Player *player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3) +{ + uint32 diceCost = advancementRank->materialCost[diceCostLevel]; + Item* item = player->GetItemByEntry(MpConstants::ANCIENT_DICE); + item->SetCount(item->GetCount() - diceCost); + item->SendUpdateToPlayer(player); + + // Remove the material from the player inventory + if(itemEntry1 > 0) { + item = player->GetItemByEntry(itemEntry1); + item->SetCount(item->GetCount() - advancementRank->materialCost[itemEntry1]); + item->SendUpdateToPlayer(player); // Update the client with the new dice count + } + + if(itemEntry2 > 0) { + item = player->GetItemByEntry(itemEntry2); + item->SetCount(item->GetCount() - advancementRank->materialCost[itemEntry2]); + item->SendUpdateToPlayer(player); // Update the client with the new dice count + } + + if(itemEntry3 > 0) { + item = player->GetItemByEntry(itemEntry3); + item->SetCount(item->GetCount() - advancementRank->materialCost[itemEntry3]); + item->SendUpdateToPlayer(player); // Update the client with the new dice count + } + + return; +} + + + diff --git a/src/AdvancementMgr.h b/src/AdvancementMgr.h index c9333f5..6145957 100644 --- a/src/AdvancementMgr.h +++ b/src/AdvancementMgr.h @@ -18,7 +18,8 @@ enum MpAdvancements { MP_ADV_RESIST_NATURE = 6, MP_ADV_RESIST_FROST = 7, MP_ADV_RESIST_SHADOW = 8, - MP_ADV_RESIST_ARCANE = 9 + MP_ADV_RESIST_ARCANE = 9, + MP_ADV_MAX = 10 }; /** @@ -31,14 +32,43 @@ struct MpAdvancementRank MpAdvancements advancementId; std::unordered_map materialCost; - std::tuple rollCost; + std::array rollCost; // 0 = low, 1 = mid, 2 = high // Range of status based on bet dice roll. std::pair lowRange; std::pair midRange; std::pair highRange; + + // Used to validate this struct is set correctly + bool IsValid() { + return (rank > 0 && advancementId >= 0 && advancementId < MP_ADV_MAX); + } + + // Check if the map has an the item entry for the passed in material + bool HasMaterial(uint32 itemEntry) { + return materialCost.contains(itemEntry); + } }; +// Struct is used for tracking player advancement bonuses for improving stats +struct MpPlayerRank +{ + uint32 rank; + MpAdvancements advancementId; + uint32 diceSpent; + float bonus; + + MpPlayerRank() : rank(0), diceSpent(0), bonus(0.0f) {} +}; + +/** + * This singleton class is responsible for managing the advancement system + * used to improve player stats enough to challenge harder difficulties on existing dungeons. + * + * Advancements are purchased by players using based on the mp_material_type table with a "bet" + * on dice roll. This enables players to increase their stats in a random way that is not + * guaranteed to be successful. (Similar to how DND stats rolls work on character creation. ) + */ class AdvancementMgr { @@ -46,7 +76,10 @@ class AdvancementMgr std::map, MpAdvancementRank> _advancementRanks; // Map of player advancements [player_guid][advancement_id] = rank -std::unordered_map> _playerAdvancements; +std::unordered_map> _playerAdvancements; + +// Map of different material types used fo advancing stats for players +std::unordered_map /* item entries */> _materialTypes; public: static AdvancementMgr* instance() { @@ -54,15 +87,50 @@ public: return &instance; } + // Loads advancement information from the database into memory when players are logged in or server starts. int32 LoadAdvencementRanks(); + int32 LoadMaterialTypes(); int32 LoadPlayerAdvancements(Player* player); + // Methods for looking up advancment rank data + MpAdvancementRank* GetAdvancementRank(uint32 rank, MpAdvancements advancement); + + // Methods for updating and setting data related to current player advancements + MpPlayerRank* AdvancementMgr::GetPlayerAdvancementRank(Player* player, MpAdvancements advancement); + + /** + * This upgrades a player Advancement on the server side, which will handle the following actions: + * 1. Validating player has enough dice and materials to upgrade the advancement + * 2. Rolling the dice to see what bonus is rewarded + * 3. Removing the dice and materials from the player inventory + * 4. Updating the player advancement rank in memory and database + * + * Since different materials can be used for each advancement, at the moment only support one material type from the list. supporting + * mixed materials is more complicated and the UI to support it is much more complex, while this is not as nice it is much simpler to implement. + * That means all materials have to be selected and passed in at the time of making this call. + */ + bool UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3); + + // Used to reset all advancements for a specific player + bool ResetPlayerAdvancements(Player* player); + private: AdvancementMgr() {} ~AdvancementMgr() {} -}; + // Will reset all the player advancements and refund the spent dice and material with a penalty for the reset. + void _ResetPlayerAdvancement(Player* player, MpAdvancements advancement); + // Rolls the dice to see how much a bonus is given based on the dice spend level + float _RollAdvancement(MpAdvancementRank* advancementRank, uint32 diceCostLevel); + + // Determines if a player has required items to upgrade + bool _PlayerHasItems(Player* player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3); + + // Removes items from player inventory based on the required advancement rank. + void _ChargeItemCost(Player *player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3); + +}; #define sAdvancementMgr AdvancementMgr::instance() #endif