Major overhauls of key systems and scripts

This commit is contained in:
2025-07-18 19:49:02 -04:00
parent 53218b487a
commit 1481fe1cce
18 changed files with 1171 additions and 837 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"math"
"os" "os"
) )
@@ -15,13 +16,80 @@ const (
) )
const ( const (
RESIST_FROST = iota RESIST_ARCANE = iota
RESIST_FIRE RESIST_FIRE
RESIST_NATURE RESIST_NATURE
RESIST_FROST
RESIST_SHADOW RESIST_SHADOW
RESIST_ARCANE
) )
// Return the itemEntry for a state item and based on quality (3 - rare, 4 - epic)
func getStatItemEntry(statID int, quality int) int {
switch statID {
case STAT_INTELLECT:
if quality == 3 {
return 911005
}
return 911006
case STAT_SPIRIT:
if quality == 3 {
return 911009
}
return 911010
case STAT_STRENGTH:
if quality == 3 {
return 911003
}
return 911004
case STAT_AGILITY:
if quality == 3 {
return 911007
}
return 911008
case STAT_STAMINA:
if quality == 3 {
return 911011
}
return 911012
default:
return 0
}
}
func getResistItemEntry(resistID int, quality int) int {
switch resistID {
case RESIST_FROST:
if quality == 3 {
return 911015
}
return 911016
case RESIST_FIRE:
if quality == 3 {
return 911017
}
return 911018
case RESIST_NATURE:
if quality == 3 {
return 911023
}
return 911024
case RESIST_SHADOW:
if quality == 3 {
return 911021
}
return 911022
case RESIST_ARCANE:
if quality == 3 {
return 911019
}
return 911020
default:
return 0
}
}
func main() { func main() {
// Output file for the SQL script // Output file for the SQL script
outputFile, err := os.Create("generate_stat_upgrades.sql") outputFile, err := os.Create("generate_stat_upgrades.sql")
@@ -40,9 +108,17 @@ func main() {
STAT_STAMINA: "Stamina", STAT_STAMINA: "Stamina",
} }
resistTypes := map[int]string{
RESIST_ARCANE: "Arcane",
RESIST_FIRE: "Fire",
RESIST_NATURE: "Nature",
RESIST_FROST: "Frost",
RESIST_SHADOW: "Shadow",
}
// Start writing the SQL script // Start writing the SQL script
fmt.Fprintln(outputFile, "-- SQL Script to Insert 50 Ranks for Each Stat") fmt.Fprintln(outputFile, "-- SQL Script to Insert 50 Ranks for Each Stat")
fmt.Fprintln(outputFile, "INSERT INTO mp_upgrade_ranks (upgradeRank, advancementId, materialId1, materialCost1, materialId2, materialCost2, materialId3, materialCost3, minIncrease1, maxIncrease1, minIncrease2, maxIncrease2, minIncrease3, maxIncrease3, chanceCost1, chanceCost2, chanceCost3) VALUES") fmt.Fprintln(outputFile, "INSERT INTO mp_upgrade_ranks (upgradeRank, advancementId, itemEntry1, itemCost1, itemEntry2, itemCost2, itemEntry3, itemCost3, minIncrease1, maxIncrease1, minIncrease2, maxIncrease2, minIncrease3, maxIncrease3, chanceCost1, chanceCost2, chanceCost3) VALUES")
// Iterate over stats // Iterate over stats
for statID := range stats { for statID := range stats {
@@ -59,6 +135,9 @@ func main() {
materialCost = 1000 + (rank-30)*18 materialCost = 1000 + (rank-30)*18
} }
// Make adjustment for new fusion core types
itemCost1 := int(math.Ceil(float64(materialCost) / 20))
// Stat growth // Stat growth
minIncrease1 := 1 + (rank-1)/10*2 minIncrease1 := 1 + (rank-1)/10*2
maxIncrease1 := 10 + (rank-1)/10*2 maxIncrease1 := 10 + (rank-1)/10*2
@@ -74,47 +153,49 @@ func main() {
chanceCost3 := 75 + (rank-1)*3 chanceCost3 := 75 + (rank-1)*3
// use material ids from the mp_material_types table material1 should be common stuff. // use material ids from the mp_material_types table material1 should be common stuff.
materialId1 := statID*2 + 1 itemEntry1 := getStatItemEntry(statID, 3)
// material2 should be rare stuff only required after rank 10 at growth rate of 5 per rank // material2 should be rare stuff only required after rank 10 at growth rate of 5 per rank
materialId2, materialCost2 := 0, 0 itemEntry2, itemCost2 := 0, 0
if rank > 10 { if rank > 10 {
materialId2 = statID*2 + 2 itemEntry2 = getStatItemEntry(statID, 4)
materialCost2 = (rank - 11) * 10 itemCost2 = (rank - 11) * 10
} }
materialId3, materialCost3 := 0, 0 // Adjust from old formula to new mythic fusion core types
itemCost2 = int(math.Ceil(float64(itemCost2) / 5))
itemEntry3, itemCost3 := 0, 0
if rank >= 30 { if rank >= 30 {
materialId3 = 20 // Group lot of raid only items itemEntry3 = 911002 // veilstones
materialCost3 = (rank - 29) * 3 itemCost3 = (rank - 29) * 3
if itemCost3 > 15 {
itemCost3 = 15
}
} }
// Write SQL insert statement for this rank // Write SQL insert statement for this rank
sql := fmt.Sprintf( sql := fmt.Sprintf(
"(%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", "(%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)",
rank, statID, materialId1, materialCost, materialId2, materialCost2, materialId3, materialCost3, rank, statID, itemEntry1, itemCost1, itemEntry2, itemCost2, itemEntry3, itemCost3,
minIncrease1, maxIncrease1, minIncrease2, maxIncrease1, minIncrease3, maxIncrease3, minIncrease1, maxIncrease1, minIncrease2, maxIncrease1, minIncrease3, maxIncrease3,
chanceCost1, chanceCost2, chanceCost3, chanceCost1, chanceCost2, chanceCost3,
) )
// Add a comma for all but the last line // Add a comma for all but the last line
sql += "," isLastStat := statID == STAT_STAMINA && rank == 50
fmt.Fprintln(outputFile, sql) isLastResist := false
isLast := isLastStat && len(resistTypes) == 0 || isLastResist
if !isLast {
sql += ","
}
fmt.Fprintln(outputFile, sql)
} }
} }
resists := map[int]string{ // Iterate over resists
RESIST_FROST: "Frost", for resistId := range resistTypes {
RESIST_FIRE: "Fire",
RESIST_NATURE: "Nature",
RESIST_SHADOW: "Shadow",
RESIST_ARCANE: "Arcane",
}
// Iterate over stats
for resistId := range resists {
resistIdbump := resistId + 5 resistIdbump := resistId + 5
for rank := 1; rank <= 50; rank++ { for rank := 1; rank <= 50; rank++ {
// Material cost increases by 50 per rank // Material cost increases by 50 per rank
@@ -129,6 +210,9 @@ func main() {
materialCost = 700 + (rank-30)*18 materialCost = 700 + (rank-30)*18
} }
// Make adjustment for new fusion core types
itemCost1 := int(math.Ceil(float64(materialCost) / 20))
// Stat growth // Stat growth
minIncrease1 := 1 + (rank-1)/5*2 minIncrease1 := 1 + (rank-1)/5*2
maxIncrease1 := 5 + (rank-1)/5*2 maxIncrease1 := 5 + (rank-1)/5*2
@@ -145,31 +229,36 @@ func main() {
chanceCost3 := 75 + (rank-1)*3 chanceCost3 := 75 + (rank-1)*3
// use material ids from the mp_material_types table material1 should be common stuff. // use material ids from the mp_material_types table material1 should be common stuff.
materialId1 := resistIdbump*2 + 1 itemEntry1 := getResistItemEntry(resistId, 3)
// material2 should be rare stuff only required after rank 10 at growth rate of 5 per rank // material2 should be rare stuff only required after rank 10 at growth rate of 5 per rank
materialId2, materialCost2 := 0, 0 itemEntry2, itemCost2 := 0, 0
if rank > 10 { if rank > 10 {
materialId2 = resistIdbump*2 + 2 itemEntry2 = getResistItemEntry(resistId, 4)
materialCost2 = (rank - 11) * 10 itemCost2 = (rank - 11) * 10
} }
materialId3, materialCost3 := 0, 0 // Adjust from old formula to new mythic fusion core types
itemCost2 = int(math.Ceil(float64(itemCost2) / 5))
itemEntry3, itemCost3 := 0, 0
if rank >= 30 { if rank >= 30 {
materialId3 = 20 // Group lot of raid only items itemEntry3 = 911002 // veilstones
materialCost3 = (rank - 29) * 3 itemCost3 = (rank - 29) * 3
} }
// Write SQL insert statement for this rank // Write SQL insert statement for this rank
sql := fmt.Sprintf( sql := fmt.Sprintf(
"(%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", "(%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)",
rank, resistIdbump, materialId1, materialCost, materialId2, materialCost2, materialId3, materialCost3, rank, resistIdbump, itemEntry1, itemCost1, itemEntry2, itemCost2, itemEntry3, itemCost3,
minIncrease1, maxIncrease1, minIncrease2, maxIncrease1, minIncrease3, maxIncrease3, minIncrease1, maxIncrease1, minIncrease2, maxIncrease1, minIncrease3, maxIncrease3,
chanceCost1, chanceCost2, chanceCost3, chanceCost1, chanceCost2, chanceCost3,
) )
// Add a comma for all but the last line // Add a comma for all but the last line
if !(resistId == RESIST_ARCANE && rank == 50) { isLast := resistId == RESIST_ARCANE && rank == 50
if !isLast {
sql += "," sql += ","
} }
fmt.Fprintln(outputFile, sql) fmt.Fprintln(outputFile, sql)

View File

@@ -34,12 +34,12 @@ std::string MpAdvancementsToString(MpAdvancements advancement)
* *
* upgradeRank INT UNSIGNED NOT NULL, * upgradeRank INT UNSIGNED NOT NULL,
* advancementId INT UNSIGNED NOT NULL, * advancementId INT UNSIGNED NOT NULL,
* materialId1 INT UNSIGNED NOT NULL, * itemEntry1 INT UNSIGNED NOT NULL,
* materialId2 INT UNSIGNED NOT NULL, * itemEntry2 INT UNSIGNED NOT NULL,
* materialId3 INT UNSIGNED NOT NULL, * itemEntry3 INT UNSIGNED NOT NULL,
* materialCost INT UNSIGNED NOT NULL, * itemCost1 INT UNSIGNED NOT NULL,
* materialCost2 INT UNSIGNED NOT NULL, * itemCost2 INT UNSIGNED NOT NULL,
* materialCost3 INT UNSIGNED NOT NULL, * itemCost3 INT UNSIGNED NOT NULL,
* minIncrease1 INT UNSIGNED NOT NULL, * minIncrease1 INT UNSIGNED NOT NULL,
* maxIncrease1 INT UNSIGNED NOT NULL, * maxIncrease1 INT UNSIGNED NOT NULL,
* minIncrease2 INT UNSIGNED NOT NULL, * minIncrease2 INT UNSIGNED NOT NULL,
@@ -64,12 +64,12 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
SELECT SELECT
upgradeRank, upgradeRank,
advancementId, advancementId,
materialId1, itemEntry1,
materialId2, itemEntry2,
materialId3, itemEntry3,
materialCost1, itemCost1,
materialCost2, itemCost2,
materialCost3, itemCost3,
minIncrease1, minIncrease1,
maxIncrease1, maxIncrease1,
minIncrease2, minIncrease2,
@@ -84,7 +84,7 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
QueryResult result = WorldDatabase.Query(query); QueryResult result = WorldDatabase.Query(query);
if (!result) { if (!result) {
MpLogger::error("Failed to load mythic scale factors from database"); MpLogger::error("Failed to load advancement ranks from database");
return 0; return 0;
} }
@@ -96,12 +96,12 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
Field* fields = result->Fetch(); Field* fields = result->Fetch();
uint32 upgradeRank = fields[0].Get<uint32>(); uint32 upgradeRank = fields[0].Get<uint32>();
uint32 advancementId = fields[1].Get<uint32>(); uint32 advancementId = fields[1].Get<uint32>();
uint32 materialId1 = fields[2].Get<uint32>(); uint32 itemEntry1 = fields[2].Get<uint32>();
uint32 materialId2 = fields[3].Get<uint32>(); uint32 itemEntry2 = fields[3].Get<uint32>();
uint32 materialId3 = fields[4].Get<uint32>(); uint32 itemEntry3 = fields[4].Get<uint32>();
uint32 materialCost1 = fields[5].Get<uint32>(); uint32 itemCost1 = fields[5].Get<uint32>();
uint32 materialCost2 = fields[6].Get<uint32>(); uint32 itemCost2 = fields[6].Get<uint32>();
uint32 materialCost3 = fields[7].Get<uint32>(); uint32 itemCost3 = fields[7].Get<uint32>();
uint32 minIncrease1 = fields[8].Get<uint32>(); uint32 minIncrease1 = fields[8].Get<uint32>();
uint32 maxIncrease1 = fields[9].Get<uint32>(); uint32 maxIncrease1 = fields[9].Get<uint32>();
uint32 minIncrease2 = fields[10].Get<uint32>(); uint32 minIncrease2 = fields[10].Get<uint32>();
@@ -124,9 +124,9 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
.lowRange = std::make_pair(minIncrease1, maxIncrease1), .lowRange = std::make_pair(minIncrease1, maxIncrease1),
.midRange = std::make_pair(minIncrease2, maxIncrease2), .midRange = std::make_pair(minIncrease2, maxIncrease2),
.highRange = std::make_pair(minIncrease3, maxIncrease3), .highRange = std::make_pair(minIncrease3, maxIncrease3),
.material1 = std::make_pair(materialId1, materialCost1), .material1 = std::make_pair(itemEntry1, itemCost1),
.material2 = std::make_pair(materialId2, materialCost2), .material2 = std::make_pair(itemEntry2, itemCost2),
.material3 = std::make_pair(materialId3, materialCost3) .material3 = std::make_pair(itemEntry3, itemCost3)
}; };
_advancementRanks.try_emplace(std::make_pair(upgradeRank, advancement), rank); _advancementRanks.try_emplace(std::make_pair(upgradeRank, advancement), rank);
@@ -172,24 +172,34 @@ void AdvancementMgr::LoadPlayerAdvancements(Player* player) {
return; return;
} }
Field* fields = result->Fetch(); uint32 count = 0;
uint32 guid = fields[0].Get<uint32>(); uint32 guid = player->GetGUID().GetCounter();
uint32 advancementId = fields[1].Get<uint32>();
float bonus = fields[2].Get<float>();
uint32 upgradeRank = fields[3].Get<uint32>();
uint32 diceSpent = fields[4].Get<uint32>();
MpAdvancements advancement = static_cast<MpAdvancements>(advancementId); // Loop through all results to load all advancements for this player
MpPlayerRank playerRank = MpPlayerRank(); do {
playerRank.rank = upgradeRank; Field* fields = result->Fetch();
playerRank.advancementId = advancement; uint32 advancementId = fields[1].Get<uint32>();
playerRank.diceSpent = diceSpent; float bonus = fields[2].Get<float>();
playerRank.bonus = bonus; uint32 upgradeRank = fields[3].Get<uint32>();
uint32 diceSpent = fields[4].Get<uint32>();
// List of all ranks keyed by rank, advancementId MpAdvancements advancement = static_cast<MpAdvancements>(advancementId);
_playerAdvancements[guid][advancement] = playerRank; MpPlayerRank playerRank = MpPlayerRank();
playerRank.rank = upgradeRank;
playerRank.advancementId = advancement;
playerRank.diceSpent = diceSpent;
playerRank.bonus = bonus;
MpLogger::info("Loaded player {} advancement {} rank {}", player->GetName(), playerRank.advancementId, upgradeRank); // List of all ranks keyed by rank, advancementId
_playerAdvancements[guid][advancement] = playerRank;
MpLogger::debug("Loaded player {} advancement {} rank {} with bonus {}",
player->GetName(), static_cast<int>(advancement), upgradeRank, bonus);
count++;
} while (result->NextRow());
MpLogger::info("Loaded {} advancements for player {}", count, player->GetName());
} }
/** /**
@@ -204,21 +214,25 @@ int32 AdvancementMgr::LoadMaterialTypes() {
FROM mp_material_types FROM mp_material_types
)"; )";
QueryResult result = WorldDatabase.Query(query); if(QueryResult result = WorldDatabase.Query(query)) {
do { do {
Field* fields = result->Fetch(); Field* fields = result->Fetch();
uint32 materialId = fields[0].Get<uint32>(); uint32 materialId = fields[0].Get<uint32>();
uint32 entry = fields[1].Get<uint32>(); uint32 entry = fields[1].Get<uint32>();
if(!_materialTypes.contains(materialId)) { if(!_materialTypes.contains(materialId)) {
_materialTypes.emplace(materialId,std::vector<uint32>()); _materialTypes.emplace(materialId,std::vector<uint32>());
} }
_materialTypes.at(materialId).push_back(entry); _materialTypes.at(materialId).push_back(entry);
} while (result->NextRow()); } while (result->NextRow());
return result->GetRowCount(); return result->GetRowCount();
} else {
MpLogger::error("Query failed to load material types from database");
return 0;
}
} }
MpAdvancementRank* AdvancementMgr::GetAdvancementRank(uint32 rank, MpAdvancements advancement) MpAdvancementRank* AdvancementMgr::GetAdvancementRank(uint32 rank, MpAdvancements advancement)
@@ -250,7 +264,7 @@ MpPlayerRank* AdvancementMgr::GetPlayerAdvancementRank(Player* player, MpAdvance
return nullptr; return nullptr;
} }
uint32 AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3) uint32 AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel)
{ {
std::lock_guard<std::mutex> lock(_playerAdvancementMutex); std::lock_guard<std::mutex> lock(_playerAdvancementMutex);
@@ -262,9 +276,6 @@ uint32 AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advance
if(diceCostLevel < 1 || diceCostLevel > 3) { if(diceCostLevel < 1 || diceCostLevel > 3) {
throw new std::runtime_error(Acore::StringFormat("Invalid dice cost level valid vales (1,2,3) received {} for player {}", diceCostLevel, player->GetName())); throw new std::runtime_error(Acore::StringFormat("Invalid dice cost level valid vales (1,2,3) received {} for player {}", diceCostLevel, player->GetName()));
} }
if(itemEntry1 == 0) {
throw new std::runtime_error(Acore::StringFormat("Material1 can not be 0 can not perform advancement upgrade for player {} Advancement {}", player->GetName(), advancement));
}
MpPlayerRank* playerRank = GetPlayerAdvancementRank(player, advancement); MpPlayerRank* playerRank = GetPlayerAdvancementRank(player, advancement);
@@ -285,10 +296,16 @@ uint32 AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advance
uint32 newRank = playerRank->rank + 1; uint32 newRank = playerRank->rank + 1;
MpAdvancementRank* advancementRank = GetAdvancementRank(newRank, advancement); MpAdvancementRank* advancementRank = GetAdvancementRank(newRank, advancement);
if(!advancementRank->IsValid()) { if(advancementRank == nullptr || !advancementRank->IsValid()) {
throw std::runtime_error("Advancement rank could not be found. Rank: " + std::to_string(newRank) + " Advancement: " + std::to_string(advancement)); MpLogger::error("Advancement rank could not be found. Rank: {} Advancement: {}", newRank, static_cast<int>(advancement));
return 0;
} }
// Get the items needed to upgrade this advancement
uint32 itemEntry1 = advancementRank->material1.first;
uint32 itemEntry2 = advancementRank->material2.first;
uint32 itemEntry3 = advancementRank->material3.first;
// If the player has the items needed to upgrade this advancement, then remove the items from the player inventory and apply the upgrade // 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)) { if(!_PlayerHasItems(player, advancementRank, diceCostLevel, itemEntry1, itemEntry2, itemEntry3)) {
MpLogger::debug("Player {} does not have the required items to upgrade advancement {}", player->GetName(), advancement); MpLogger::debug("Player {} does not have the required items to upgrade advancement {}", player->GetName(), advancement);
@@ -315,6 +332,17 @@ uint32 AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advance
// Save the advancement to the database // Save the advancement to the database
_SaveAdvancement(player, advancementRank, playerRank, advancementRank->rollCost[diceCostLevel-1], roll, itemEntry1, itemEntry2, itemEntry3); _SaveAdvancement(player, advancementRank, playerRank, advancementRank->rollCost[diceCostLevel-1], roll, itemEntry1, itemEntry2, itemEntry3);
// Remove and reapply the aura to refresh the spell with the latest bonuses
uint32 spellId = MpConstants::GetAdvancementAura(advancement);
if (spellId > 0)
{
MpLogger::info("Refreshing advancement aura {} for player {}", spellId, player->GetName());
// First remove the aura completely
player->RemoveAura(spellId);
player->AddAura(spellId, player);
}
return roll; return roll;
} }
@@ -352,7 +380,7 @@ float AdvancementMgr::_RollAdvancement(MpAdvancementRank* advancementRank, uint3
max = advancementRank->highRange.second; max = advancementRank->highRange.second;
break; break;
default: default:
MpLogger::error("Invalid dice cost level valid vales (1,2,3) received {} for rank roll", diceCostLevel, advancementRank->rank); MpLogger::error("Invalid dice cost level valid vales (1,2,3) received {} for rank roll {}", diceCostLevel, advancementRank->rank);
break; break;
} }
@@ -367,79 +395,92 @@ float AdvancementMgr::_RollAdvancement(MpAdvancementRank* advancementRank, uint3
*/ */
bool AdvancementMgr::_PlayerHasItems(Player* player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3) bool AdvancementMgr::_PlayerHasItems(Player* player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3)
{ {
MpLogger::debug("Debugging player dice {} item1 {} item2 {} item3 {}", diceCostLevel, itemEntry1, itemEntry2, itemEntry3); MpLogger::debug("Checking items for player {} dice level {} item1 {} item2 {} item3 {}",
MpLogger::debug("Advancement Rank dice info {} {} {} {}", advancementRank->materialCost[1], advancementRank->materialCost[2], advancementRank->materialCost[3]); player->GetName(), diceCostLevel, itemEntry1, itemEntry2, itemEntry3);
// Check if player has the required dice to upgrade the advancement if not do nothing. // Check if player has the required dice to upgrade the advancement
uint32 diceCost = advancementRank->rollCost[diceCostLevel-1]; uint32 diceCost = advancementRank->rollCost[diceCostLevel-1];
if(!player->HasItemCount(MpConstants::ANCIENT_DICE, diceCost)) { if(!player->HasItemCount(MpConstants::ANCIENT_DICE, diceCost)) {
MpLogger::info("Player {} does not have enough dice to upgrade advancement {}", player->GetName(), advancementRank->advancementId); MpLogger::info("Player {} does not have enough dice to upgrade advancement {}",
player->GetName(), advancementRank->advancementId);
return false; return false;
} }
// Create arrays of material data for easier iteration // Check material 1 (required)
std::pair<uint32, uint32> materials[] = { if (advancementRank->material1.first > 0) {
advancementRank->material1, if (itemEntry1 == 0) {
advancementRank->material2, throw std::runtime_error("Primary material entry is required but was not provided");
advancementRank->material3
};
uint32 itemEntries[] = {itemEntry1, itemEntry2, itemEntry3};
// Validate each material
for (size_t i = 0; i < 3; ++i) {
uint32 materialId = materials[i].first;
uint32 requiredCount = materials[i].second;
uint32 itemEntry = itemEntries[i];
// if the materialID is not set and is not the first material then return true as no material is required
if (materialId == 0 && i != 0) {
return true;
} }
if (itemEntry == 0 && materialId != 0) { uint32 requiredCount = advancementRank->material1.second;
throw std::runtime_error("The entry for materialId: " + std::to_string(materialId) + " was not passed in."); if (!player->HasItemCount(itemEntry1, requiredCount)) {
} MpLogger::info("Player {} does not have enough of item {} for advancement {}, requires: {}",
player->GetName(), itemEntry1, advancementRank->advancementId, requiredCount);
std::vector<uint32> entries = _materialTypes.at(materialId);
if (entries.empty()) {
throw std::runtime_error("MaterialId: " + std::to_string(materialId) + " could not be found in the materials list");
}
if (std::find(entries.begin(), entries.end(), itemEntry) == entries.end()) {
throw std::runtime_error("Incorrect material entry" + std::to_string(itemEntry) + " passed in passed in for Advancement id:" + std::to_string(advancementRank->advancementId));
}
if (!player->HasItemCount(itemEntry, requiredCount)) {
MpLogger::info("Player {} does not have enough material {} to upgrade advancement {} requires: {}",
player->GetName(), itemEntry, advancementRank->advancementId, requiredCount);
return false; return false;
} }
} }
// Check material 2 (optional)
if (advancementRank->material2.first > 0) {
if (itemEntry2 == 0) {
MpLogger::debug("Secondary material is required but not provided");
return false;
}
uint32 requiredCount = advancementRank->material2.second;
if (!player->HasItemCount(itemEntry2, requiredCount)) {
MpLogger::info("Player {} does not have enough of item {} for advancement {}, requires: {}",
player->GetName(), itemEntry2, advancementRank->advancementId, requiredCount);
return false;
}
}
// Check material 3 (optional)
if (advancementRank->material3.first > 0) {
if (itemEntry3 == 0) {
MpLogger::debug("Tertiary material is required but not provided");
return false;
}
uint32 requiredCount = advancementRank->material3.second;
if (!player->HasItemCount(itemEntry3, requiredCount)) {
MpLogger::info("Player {} does not have enough of item {} for advancement {}, requires: {}",
player->GetName(), itemEntry3, advancementRank->advancementId, requiredCount);
return false;
}
}
MpLogger::debug("Player {} has all required materials to upgrade advancement {}",
player->GetName(), advancementRank->advancementId);
return true; return true;
} }
// Remove all items required for the upgrade. // Remove all items required for the upgrade.
void AdvancementMgr::_ChargeItemCost(Player *player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3) void AdvancementMgr::_ChargeItemCost(Player *player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3)
{ {
MpLogger::debug("Charging player dice {} item1 {} item2 {} item3 {}", diceCostLevel, itemEntry1, itemEntry2, itemEntry3); MpLogger::debug("Charging player {} dice level {} item1 {} item2 {} item3 {}",
player->GetName(), diceCostLevel, itemEntry1, itemEntry2, itemEntry3);
// Remove the dice cost
uint32 diceCost = advancementRank->rollCost[diceCostLevel-1]; uint32 diceCost = advancementRank->rollCost[diceCostLevel-1];
player->DestroyItemCount(MpConstants::ANCIENT_DICE, diceCost, true); player->DestroyItemCount(MpConstants::ANCIENT_DICE, diceCost, true);
std::vector<std::pair<uint32, uint32>> items = { // Remove material 1 if it exists
{itemEntry1, advancementRank->material1.second}, if (itemEntry1 > 0 && advancementRank->material1.first > 0) {
{itemEntry2, advancementRank->material2.second}, player->DestroyItemCount(itemEntry1, advancementRank->material1.second, true);
{itemEntry3, advancementRank->material3.second}
};
for (const auto& item : items) {
if (item.first > 0) {
player->DestroyItemCount(item.first, item.second, true);
}
} }
return; // Remove material 2 if it exists
if (itemEntry2 > 0 && advancementRank->material2.first > 0) {
player->DestroyItemCount(itemEntry2, advancementRank->material2.second, true);
}
// Remove material 3 if it exists
if (itemEntry3 > 0 && advancementRank->material3.first > 0) {
player->DestroyItemCount(itemEntry3, advancementRank->material3.second, true);
}
MpLogger::debug("Successfully charged player {} for advancement upgrade", player->GetName());
} }
void AdvancementMgr::_SaveAdvancement(Player* player, MpAdvancementRank* advancementRank, MpPlayerRank* playerRank, uint32 diceCost, float roll, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3) void AdvancementMgr::_SaveAdvancement(Player* player, MpAdvancementRank* advancementRank, MpPlayerRank* playerRank, uint32 diceCost, float roll, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3)

View File

@@ -121,7 +121,7 @@ public:
* 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. * 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. * That means all materials have to be selected and passed in at the time of making this call.
*/ */
uint32 UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3); uint32 UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel);
// Used to reset all advancements for a specific player // Used to reset all advancements for a specific player
bool ResetPlayerAdvancements(Player* player); bool ResetPlayerAdvancements(Player* player);

View File

@@ -35,7 +35,7 @@ inline std::unordered_map<std::string_view, MpEvent> MpEventMap = {{
{"ResetAdvancement", MpEvent::ResetAdvancement}, {"ResetAdvancement", MpEvent::ResetAdvancement},
{"ResetAllAdvancements", MpEvent::ResetAllAdvancements}, {"ResetAllAdvancements", MpEvent::ResetAllAdvancements},
{"GetPlayerRank", MpEvent::GetPlayerRank}, {"GetPlayerRank", MpEvent::GetPlayerRank},
{"GetAdvancementRank", MpEvent::ResetAllAdvancements} {"GetAdvancementRank", MpEvent::GetAdvancementRank}
}}; }};
inline std::unordered_map<MpClientEvent, std::string_view> MpClientEventNames = {{ inline std::unordered_map<MpClientEvent, std::string_view> MpClientEventNames = {{

View File

@@ -63,7 +63,7 @@ bool SendEventError(Player* player, const std::string& method, MP_EVENT_CODE cod
* Message Format: * Message Format:
* p|playerGuid|UpgradeAdvancement|advancementId|diceLevel|itemEntry1|itemEntry2|itemEntry3 * p|playerGuid|UpgradeAdvancement|advancementId|diceLevel|itemEntry1|itemEntry2|itemEntry3
*/ */
class UpdateAdvancements : public MpEventInterface class UpgradeAdvancements : public MpEventInterface
{ {
public: public:
const std::string EventName() const override const std::string EventName() const override
@@ -82,40 +82,36 @@ class UpdateAdvancements : public MpEventInterface
MpLogger::info("{} Arg: {}", EventName(), arg); MpLogger::info("{} Arg: {}", EventName(), arg);
} }
// Validate the message is int he right format // Validate the message is in the right format
if(args.size() != 5) { if(args.size() != 2) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 5, found " + std::to_string(args.size())); return SendEventError(player, EventName(), MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 2, found " + std::to_string(args.size()));
} }
uint32 advancementId = std::stoi(args[0]); uint32 advancementId = std::stoi(args[0]);
if(advancementId >= MpAdvancements::MP_ADV_MAX) { if(advancementId >= MpAdvancements::MP_ADV_MAX) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid advancement id " + args[0] + " max is " + std::to_string(MpAdvancements::MP_ADV_MAX)); return SendEventError(player, EventName(), MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid advancement id " + args[0] + " max is " + std::to_string(MpAdvancements::MP_ADV_MAX));
} }
uint32 diceLevel = std::stoi(args[1]); uint32 diceLevel = std::stoi(args[1]);
if(diceLevel < 1 || diceLevel > 3) { if(diceLevel < 1 || diceLevel > 3) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid dice level " + args[1] + " valid values are 1,2,3"); return SendEventError(player, EventName(), MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid dice level " + args[1] + " valid values are 1,2,3");
} }
uint32 itemEntry1 = std::stoi(args[2]);
if(itemEntry1 == 0) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid item entry1 can not be empty " + args[2]);
}
uint32 itemEntry2 = std::stoi(args[3]);
uint32 itemEntry3 = std::stoi(args[4]);
uint32 increase; uint32 increase;
try { try {
increase = sAdvancementMgr->UpgradeAdvancement(player, static_cast<MpAdvancements>(advancementId), diceLevel, itemEntry1, itemEntry2, itemEntry3); increase = sAdvancementMgr->UpgradeAdvancement(player, static_cast<MpAdvancements>(advancementId), diceLevel);
if( increase == 0) { if(increase == 0) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to upgrade advancement invalid request see error logs for player " + player->GetName()); return SendEventError(player, EventName(), MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to upgrade advancement invalid request see error logs for player " + player->GetName());
} }
} catch(const std::exception& e) { } catch(const std::exception& e) {
return SendEventError(player, EventName(),MP_EVENT_CODE::FAILED_UPGRADE_ADV, "Failed to upgrade: " + std::string(e.what()) + " for player " + player->GetName()); return SendEventError(player, EventName(), MP_EVENT_CODE::FAILED_UPGRADE_ADV, "Failed to upgrade: " + std::string(e.what()) + " for player " + player->GetName());
} }
// Only proceed to here if no errors occurred
MpPlayerRank* playerRank = sAdvancementMgr->GetPlayerAdvancementRank(player, static_cast<MpAdvancements>(advancementId)); MpPlayerRank* playerRank = sAdvancementMgr->GetPlayerAdvancementRank(player, static_cast<MpAdvancements>(advancementId));
if (!playerRank) {
return SendEventError(player, EventName(), MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to get advancement rank for player " + player->GetName());
}
// Format the success event data for client increase|newrank|bonus // Format the success event data for client increase|newrank|bonus
eventData = { eventData = {
@@ -206,8 +202,8 @@ class GetAdvancementRank : public MpEventInterface {
bool Execute(Player* player, std::vector<std::string>& args) override bool Execute(Player* player, std::vector<std::string>& args) override
{ {
if(args.size() != 3) { if(args.size() != 2) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 3, found " + std::to_string(args.size())); return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 2, found " + std::to_string(args.size()));
} }
uint32 advancementId = std::stoi(args[0]); uint32 advancementId = std::stoi(args[0]);
@@ -251,7 +247,7 @@ class GetAdvancementRank : public MpEventInterface {
void MP_Register_EventHandlers() void MP_Register_EventHandlers()
{ {
sMpEventProcessor->RegisterHandler(MpEvent::UpgradeAdvancement, std::make_shared<UpdateAdvancements>()); sMpEventProcessor->RegisterHandler(MpEvent::UpgradeAdvancement, std::make_shared<UpgradeAdvancements>());
sMpEventProcessor->RegisterHandler(MpEvent::GetPlayerRank, std::make_shared<GetPlayerRank>()); sMpEventProcessor->RegisterHandler(MpEvent::GetPlayerRank, std::make_shared<GetPlayerRank>());
sMpEventProcessor->RegisterHandler(MpEvent::GetAdvancementRank, std::make_shared<GetAdvancementRank>()); sMpEventProcessor->RegisterHandler(MpEvent::GetAdvancementRank, std::make_shared<GetAdvancementRank>());
} }

View File

@@ -68,7 +68,7 @@ bool MpEventProcessor::Dispatch(MpEvent event, Player* player, std::vector<std::
if(!_eventHandlers.contains(event)) { if(!_eventHandlers.contains(event)) {
// Send a client message back also to the player // Send a client message back also to the player
std::vector<std::string> clientError = { std::to_string(static_cast<int>(event)), "No handler registered for event: " + std::to_string(static_cast<int>(event)) }; std::vector<std::string> clientError = { "Error", "No handler registered for event: " + std::to_string(static_cast<int>(event)) };
sMpClientDispatcher->Dispatch(MpClientEvent::Error, player, clientError); sMpClientDispatcher->Dispatch(MpClientEvent::Error, player, clientError);
MpLogger::warn("No handler registered for event: {}", event); MpLogger::warn("No handler registered for event: {}", event);
return false; return false;

View File

@@ -1,11 +1,112 @@
#ifndef MP_CONSTANTS_H #ifndef MP_CONSTANTS_H
#define MP_CONSTANTS_H #define MP_CONSTANTS_H
#include "AdvancementMgr.h"
namespace MpConstants namespace MpConstants
{ {
// Spell IDs for passive stat and resist bonuses
constexpr int TITANS_STRENGTH_AURA = 80000001; // strength
constexpr int STEEL_FORGED_AURA = 80000002; // stamina
constexpr int CELESTIAL_GRACE_AURA = 80000003; // spirit
constexpr int FORBIDDEN_KNOWLEDGE_AURA = 80000004; // intellect
constexpr int SPECTRAL_REFLEXES_AURA = 80000005; // agility
constexpr int ELDRITCH_BARRIER_AURA = 80000006; // arcane resistance
constexpr int HELLFIRE_SHIELDING_AURA = 80000007; // fire resistance
constexpr int PRIMAL_ENDURACE_AURA = 80000008; // nature resistance
constexpr int LICHS_BANE_AURA = 80000009; // shadow resistance
constexpr int GLACIAL_FORTRESS_AURA = 80000010; // frost resistance
/**
* @brief Adds a static method for looking up the corect advancement aura based on the advancement type.
*
* @param advancement
* @return int
*/
static int GetAdvancementAura(MpAdvancements advancement)
{
switch (advancement)
{
case MpAdvancements::MP_ADV_INTELLECT:
return MpConstants::FORBIDDEN_KNOWLEDGE_AURA;
case MpAdvancements::MP_ADV_SPIRIT:
return MpConstants::CELESTIAL_GRACE_AURA;
case MpAdvancements::MP_ADV_STRENGTH:
return MpConstants::TITANS_STRENGTH_AURA;
case MpAdvancements::MP_ADV_AGILITY:
return MpConstants::SPECTRAL_REFLEXES_AURA;
case MpAdvancements::MP_ADV_STAMINA:
return MpConstants::STEEL_FORGED_AURA;
case MpAdvancements::MP_ADV_RESIST_ARCANE:
return MpConstants::ELDRITCH_BARRIER_AURA;
case MpAdvancements::MP_ADV_RESIST_FIRE:
return MpConstants::HELLFIRE_SHIELDING_AURA;
case MpAdvancements::MP_ADV_RESIST_NATURE:
return MpConstants::PRIMAL_ENDURACE_AURA;
case MpAdvancements::MP_ADV_RESIST_FROST:
return MpConstants::GLACIAL_FORTRESS_AURA;
case MpAdvancements::MP_ADV_RESIST_SHADOW:
return MpConstants::LICHS_BANE_AURA;
default:
return 0;
}
}
// Spells used for learning how to make items used for advancement.
constexpr int SPELL_ORE_FUSION = 150000;
constexpr int SPELL_CLOTH_FUSION = 150001;
constexpr int SPELL_LEATHER_FUSION = 150002;
constexpr int SPELL_ALCHEMY_FUSION = 150003;
constexpr int SPELL_GEM_FUSION = 150004;
constexpr int SPELL_ESSENCE_FUSION = 150005;
constexpr int SPELL_COLD_FUSION = 150006;
constexpr int SPELL_FLAME_FUSION = 150007;
constexpr int SPELL_ARCANE_FUSION = 150008;
constexpr int SPELL_DARK_FUSION = 150009;
constexpr int SPELL_EARTH_FUSION = 150010;
constexpr int SPELL_ORE_FUSION_RANK_2 = 150011;
constexpr int SPELL_CLOTH_FUSION_RANK_2 = 150012;
constexpr int SPELL_LEATHER_FUSION_RANK_2 = 150013;
constexpr int SPELL_ALCHEMY_FUSION_RANK_2 = 150014;
constexpr int SPELL_GEM_FUSION_RANK_2 = 150015;
constexpr int SPELL_ESSENCE_FUSION_RANK_2 = 150016;
constexpr int SPELL_COLD_FUSION_RANK_2 = 150017;
constexpr int SPELL_FLAME_FUSION_RANK_2 = 150018;
constexpr int SPELL_ARCANE_FUSION_RANK_2 = 150019;
constexpr int SPELL_DARK_FUSION_RANK_2 = 150020;
constexpr int SPELL_EARTH_FUSION_RANK_2 = 150021;
// New dropping unique items for mythic plus
constexpr int ANCIENT_DICE = 911000; constexpr int ANCIENT_DICE = 911000;
constexpr int DARK_SPIKE = 911001; constexpr int DARK_SPIKE = 911001;
constexpr int VEILSTONE = 911002;
// Item IDs for fused materials used in advancement crafting
constexpr int FUSED_RARE_ORE = 911003;
constexpr int FUSED_MYTHIC_ORE = 911004;
constexpr int FUSED_RARE_CLOTH = 911005;
constexpr int FUSED_MYTHIC_CLOTH = 911006;
constexpr int FUSED_RARE_LEATHER = 911007;
constexpr int FUSED_MYTHIC_LEATHER = 911008;
constexpr int FUSED_RARE_ALCHEMY = 911009;
constexpr int FUSED_MYTHIC_ALCHEMY = 911010;
constexpr int FUSED_RARE_GEM = 911011;
constexpr int FUSED_MYTHIC_GEM = 911012;
constexpr int FUSED_RARE_ESSENCE = 911013;
constexpr int FUSED_MYTHIC_ESSENCE = 911014;
constexpr int FUSED_RARE_ICE_STONE = 911015;
constexpr int FUSED_MYTHIC_ICE_STONE = 911016;
constexpr int FUSED_RARE_INFERNAL_STONE = 911017;
constexpr int FUSED_MYTHIC_INFERNAL_STONE = 911018;
constexpr int FUSED_RARE_ARCANE_CRYSTAL = 911019;
constexpr int FUSED_MYTHIC_ARCANE_CRYSTAL = 911020;
constexpr int FUSED_RARE_DARK_CRYSTAL = 911021;
constexpr int FUSED_MYTHIC_DARK_CRYSTAL = 911022;
constexpr int FUSED_RARE_EARTH_STONE = 911023;
constexpr int FUSED_MYTHIC_EARTH_STONE = 911024;
// Shadowy Remains
constexpr int SHADOWY_REMAINS = 911100;
} }
#endif #endif

View File

@@ -135,7 +135,7 @@ void MpDataStore::RemoveGroupData(Group *group) {
MpLogger::debug("RemoveGroupData for group {}", group->GetGUID().GetCounter()); MpLogger::debug("RemoveGroupData for group {}", group->GetGUID().GetCounter());
_groupData->erase(group->GetGUID()); _groupData->erase(group->GetGUID());
CharacterDatabase.Execute("DELETE FROM group_difficulty WHERE guid = {}) ", group->GetGUID().GetCounter()); CharacterDatabase.Execute("DELETE FROM group_difficulty WHERE guid = {}", group->GetGUID().GetCounter());
} }
// Adds PlayerData related to MythicRun Status to map // Adds PlayerData related to MythicRun Status to map
@@ -220,45 +220,61 @@ MpScaleFactor MpDataStore::GetScaleFactor(int32 mapId, int32 difficulty) const {
return _scaleFactors->at(key); return _scaleFactors->at(key);
} }
// Just send back untouched bonus database will override.
return MpScaleFactor{ return MpScaleFactor{
.dmgBonus = 3, .meleeBonus = 1.0f,
.healthBonus = 2, .healthBonus = 1.0f,
.maxDamageBonus = 30 .spellBonus = 1.0f,
.healBonus = 1.0f
}; };
} }
int32 MpDataStore::GetHealthScaleFactor(int32 mapId, int32 difficulty) const { float MpDataStore::GetHealthScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).healthBonus; return GetScaleFactor(mapId, difficulty).healthBonus;
} }
int32 MpDataStore::GetDamageScaleFactor(int32 mapId, int32 difficulty) const { float MpDataStore::GetMeleeScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).dmgBonus; return GetScaleFactor(mapId, difficulty).meleeBonus;
} }
int32 MpDataStore::GetSpellScaleFactor(int32 mapId, int32 difficulty) const { float MpDataStore::GetSpellScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).spellBonus; return GetScaleFactor(mapId, difficulty).spellBonus;
} }
int32 MpDataStore::GetMaxDamageScaleFactor(int32 mapId, int32 difficulty) const { float MpDataStore::GetHealScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).maxDamageBonus; return GetScaleFactor(mapId, difficulty).healBonus;
} }
void MpDataStore::SetHealthScaleFactor(int32 mapId, int32 difficulty, int32 newValue) { uint32 MpDataStore::GetPlayerHealthAvg(uint32 level) const {
if (_playerHealthAvg.contains(level)) {
return _playerHealthAvg.at(level);
}
return 0;
}
void MpDataStore::SetHealScaleFactor(int32 mapId, int32 difficulty, float newValue) {
auto key = GetScaleFactorKey(mapId, difficulty);
if (_scaleFactors && _scaleFactors->contains(key)) {
_scaleFactors->at(key).healBonus = newValue;
}
}
void MpDataStore::SetHealthScaleFactor(int32 mapId, int32 difficulty, float newValue) {
auto key = GetScaleFactorKey(mapId, difficulty); auto key = GetScaleFactorKey(mapId, difficulty);
if (_scaleFactors && _scaleFactors->contains(key)) { if (_scaleFactors && _scaleFactors->contains(key)) {
_scaleFactors->at(key).healthBonus = newValue; _scaleFactors->at(key).healthBonus = newValue;
} }
} }
void MpDataStore::SetDamageScaleFactor(int32 mapId, int32 difficulty, int32 newValue) { void MpDataStore::SetMeleeScaleFactor(int32 mapId, int32 difficulty, float newValue) {
auto key = GetScaleFactorKey(mapId, difficulty); auto key = GetScaleFactorKey(mapId, difficulty);
if (_scaleFactors && _scaleFactors->contains(key)) { if (_scaleFactors && _scaleFactors->contains(key)) {
_scaleFactors->at(key).dmgBonus = newValue; _scaleFactors->at(key).meleeBonus = newValue;
} }
} }
void MpDataStore::SetSpellScaleFactor(int32 mapId, int32 difficulty, int32 newValue) { void MpDataStore::SetSpellScaleFactor(int32 mapId, int32 difficulty, float newValue) {
auto key = GetScaleFactorKey(mapId, difficulty); auto key = GetScaleFactorKey(mapId, difficulty);
if (_scaleFactors && _scaleFactors->contains(key)) { if (_scaleFactors && _scaleFactors->contains(key)) {
_scaleFactors->at(key).spellBonus = newValue; _scaleFactors->at(key).spellBonus = newValue;
@@ -269,7 +285,7 @@ int32 MpDataStore::LoadScaleFactors() {
_scaleFactors->clear(); _scaleFactors->clear();
// 0 1 2 3 4 5 // 0 1 2 3 4 5
QueryResult result = WorldDatabase.Query("SELECT mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max FROM mp_scale_factors"); QueryResult result = WorldDatabase.Query("SELECT mapId, melee_bonus, spell_bonus, heal_bonus, hp_bonus, difficulty FROM mp_scale_factors");
if (!result) { if (!result) {
MpLogger::error("Failed to load mythic scale factors from database"); MpLogger::error("Failed to load mythic scale factors from database");
return 0; return 0;
@@ -278,17 +294,17 @@ int32 MpDataStore::LoadScaleFactors() {
do { do {
Field* fields = result->Fetch(); Field* fields = result->Fetch();
uint32 mapId = fields[0].Get<uint32>(); uint32 mapId = fields[0].Get<uint32>();
int32 damageBonus = fields[1].Get<int32>(); float meleeBonus = fields[1].Get<float>();
int32 spellBonus = fields[2].Get<int32>(); float spellBonus = fields[2].Get<float>();
int32 healthBonus = fields[3].Get<int32>(); float healBonus = fields[3].Get<float>();
int32 difficulty = fields[4].Get<int32>(); float healthBonus = fields[4].Get<float>();
int32 maxDamageBonus = fields[5].Get<int32>(); int32 difficulty = fields[5].Get<int32>();
MpScaleFactor scaleFactor = { MpScaleFactor scaleFactor = {
.dmgBonus = damageBonus, .meleeBonus = meleeBonus,
.healthBonus = healthBonus, .healthBonus = healthBonus,
.spellBonus = spellBonus, .spellBonus = spellBonus,
.maxDamageBonus = maxDamageBonus .healBonus = healBonus
}; };
_scaleFactors->emplace(GetScaleFactorKey(mapId, difficulty), scaleFactor); _scaleFactors->emplace(GetScaleFactorKey(mapId, difficulty), scaleFactor);
@@ -298,6 +314,45 @@ int32 MpDataStore::LoadScaleFactors() {
return int32(_scaleFactors->size()); return int32(_scaleFactors->size());
} }
void MpDataStore::LoadPlayerHealthAvg() {
_playerHealthAvg.clear();
std::string_view query = R"(
SELECT
Level,
ROUND(CASE
WHEN Level BETWEEN 1 AND 30 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.0
WHEN Level BETWEEN 31 AND 50 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.3
WHEN Level BETWEEN 51 AND 59 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 1.6
WHEN Level BETWEEN 60 AND 69 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 2.0
WHEN Level BETWEEN 70 AND 79 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 2.5
WHEN Level BETWEEN 80 AND 84 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 3.0
WHEN Level >= 85 THEN ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20) * 4.0
ELSE ((AVG(Stamina) - 20) * 10 + AVG(BaseHP) + 20)
END) AS BaseHealth
FROM
player_class_stats
GROUP BY
Level
ORDER BY
Level;
)";
if (QueryResult result = WorldDatabase.Query(query.data())) {
do {
Field* fields = result->Fetch();
uint32 level = fields[0].Get<uint32>();
uint32 baseHealth = fields[1].Get<uint32>();
_playerHealthAvg[level] = baseHealth;
} while (result->NextRow());
} else {
MpLogger::error("Failed to load player health averages from database");
}
}
/** /**
* Database Calls below for storing player data. * Database Calls below for storing player data.
* @todo refactor to use prepared statements * @todo refactor to use prepared statements

View File

@@ -143,17 +143,22 @@ struct MpGroupData
}; };
/**
* @brief Struct used for internal scaling of mythic+ difficulty. Fine-tuned in
* database.
*/
struct MpScaleFactor struct MpScaleFactor
{ {
int32 dmgBonus; float meleeBonus;
int32 healthBonus; float spellBonus;
int32 spellBonus; float healBonus;
int32 maxDamageBonus; float healthBonus;
std::string ToString() const { std::string ToString() const {
return "MpScaleFactor: { dmgBonus: " + std::to_string(dmgBonus) + return "MpScaleFactor: { meleeBonus: " + std::to_string(meleeBonus) +
", healthBonus: " + std::to_string(healthBonus) + ", healthBonus: " + std::to_string(healthBonus) +
", spellBonus: " + std::to_string(spellBonus) + "}"; ", spellBonus: " + std::to_string(spellBonus) +
", healBonus: " + std::to_string(healBonus) + "}";
} }
}; };
@@ -323,6 +328,10 @@ private:
// use to mimic pattern normals scale to heroic (loaded at server start) // use to mimic pattern normals scale to heroic (loaded at server start)
std::unique_ptr<std::map<std::pair<int32,int32>,MpScaleFactor>> _scaleFactors; // {mapId,difficulty} std::unique_ptr<std::map<std::pair<int32,int32>,MpScaleFactor>> _scaleFactors; // {mapId,difficulty}
// Player mapping of level to average amount of health for that level, this is used for scaling against
// percentages to more consistently scale damage from spells and healing from creatures.
std::unordered_map<uint32, uint32> _playerHealthAvg; // level -> avg health
// Single creature multipliers used to scale creatures individually that may need tuned up or down. // Single creature multipliers used to scale creatures individually that may need tuned up or down.
// std::unique_ptr<std::unordered_map<uint32, CreatureOverride>> _creatureOverrides; // std::unique_ptr<std::unordered_map<uint32, CreatureOverride>> _creatureOverrides;
@@ -377,15 +386,19 @@ public:
std::vector<MpCreatureData*> GetUnscaledCreatures(uint32 mapId, uint32 instanceId); std::vector<MpCreatureData*> GetUnscaledCreatures(uint32 mapId, uint32 instanceId);
// Scale factors are used to determine a base bonus for enemies base on the instance difficulty // Scale factors are used to determine a base bonus for enemies base on the instance difficulty
int32 GetHealthScaleFactor(int32 mapId, int32 difficulty) const; float GetHealthScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetDamageScaleFactor(int32 mapId, int32 difficulty) const; float GetMeleeScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetMaxDamageScaleFactor(int32 mapId, int32 difficulty) const; float GetSpellScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetSpellScaleFactor(int32 mapId, int32 difficulty) const; float GetHealScaleFactor(int32 mapId, int32 difficulty) const;
MpScaleFactor GetScaleFactor(int32 mapId, int32 difficulty) const; MpScaleFactor GetScaleFactor(int32 mapId, int32 difficulty) const;
void SetDamageScaleFactor(int32 mapId, int32 difficulty, int32 value); void SetMeleeScaleFactor(int32 mapId, int32 difficulty, float value);
void SetHealthScaleFactor(int32 mapId, int32 difficulty, int32 value); void SetHealthScaleFactor(int32 mapId, int32 difficulty, float value);
void SetSpellScaleFactor(int32 mapId, int32 difficulty, int32 value); void SetSpellScaleFactor(int32 mapId, int32 difficulty, float value);
void SetHealScaleFactor(int32 mapId, int32 difficulty, float value);
// Retrieves the average players hp pool for a player level
uint32 GetPlayerHealthAvg(uint32 level) const;
// Individual Creature Scaling Multipliers // Individual Creature Scaling Multipliers
// void AddCreatureOverride(uint32 entry, CreatureOverride* override); // void AddCreatureOverride(uint32 entry, CreatureOverride* override);
@@ -401,6 +414,9 @@ public:
// Used at initial server load // Used at initial server load
int32 LoadScaleFactors(); int32 LoadScaleFactors();
// Load the player health average from the database
void LoadPlayerHealthAvg();
// Database API calls // Database API calls
void DBUpdatePlayerInstanceData(ObjectGuid playerGuid, MpDifficulty difficulty, uint32 mapId = 0, uint32 instanceId = 0, uint32 deaths = 0); void DBUpdatePlayerInstanceData(ObjectGuid playerGuid, MpDifficulty difficulty, uint32 mapId = 0, uint32 instanceId = 0, uint32 deaths = 0);

View File

@@ -146,6 +146,12 @@ bool MythicPlus::IsCreatureEligible(Creature* creature)
MpLogger::debug("Creature {} is an NPC Bot, do not scale", creature->GetName()); MpLogger::debug("Creature {} is an NPC Bot, do not scale", creature->GetName());
return false; return false;
} }
if(creature->GetBotOwner()) {
MpLogger::debug("Creature is owned by a NPC Bot {} skip scaling creature: {}", creature->GetBotOwner()->GetName(), creature->GetName());
return false;
}
#endif #endif
// Check for NPC-related flags (vendor, gossip, quest giver, trainer, etc.) // Check for NPC-related flags (vendor, gossip, quest giver, trainer, etc.)
@@ -264,6 +270,7 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
cInfo->unit_class cInfo->unit_class
); );
// Scale the creatures base health
uint32 basehp = stats->BaseHealth[EXPANSION_WRATH_OF_THE_LICH_KING]; uint32 basehp = stats->BaseHealth[EXPANSION_WRATH_OF_THE_LICH_KING];
uint32 health = CalculateNewHealth(creature, cInfo, mapId, difficulty, basehp, multipliers->health); uint32 health = CalculateNewHealth(creature, cInfo, mapId, difficulty, basehp, multipliers->health);
@@ -276,6 +283,7 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
/** /**
* @TODO: Figure out mana later for unit_types 2 and 8 * @TODO: Figure out mana later for unit_types 2 and 8
*/ */
// Scale the creatures mana pool
uint32 mana = uint32(std::ceil(stats->BaseMana * cInfo->ModMana)); uint32 mana = uint32(std::ceil(stats->BaseMana * cInfo->ModMana));
creature->SetCreateMana(mana); creature->SetCreateMana(mana);
creature->SetMaxPower(POWER_MANA, mana); creature->SetMaxPower(POWER_MANA, mana);
@@ -289,30 +297,43 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana * 3.0f); creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana * 3.0f);
} }
// Scale the creatures damage
MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId()); MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
int32 meleeDamage = sMpDataStore->GetDamageScaleFactor(creature->GetMapId(), instanceData->difficulty); // int32 meleeDamage = sMpDataStore->GetMeleeScaleFactor(creature->GetMapId(), instanceData->difficulty);
if(creature->IsDungeonBoss() || creature->GetEntry() == 23682) {
// Give the boss an increase in casting speed.
creature->SetFloatValue(UNIT_MOD_CAST_SPEED, 1.20f);
}
// Calculate the level difference // Calculate the level difference
float levelDifference = creature->GetLevel() - origLevel; // float levelDifference = creature->GetLevel() - origLevel;
// New formula with adjusted divisor for smoother scaling // New formula with adjusted divisor for smoother scaling
float scalingFactor; float scalingFactor;
uint32 ap = uint32(sMythicPlus->meleeAttackPowerStart - sMythicPlus->meleeAttackPowerDampener); // uint32 ap = uint32(sMythicPlus->meleeAttackPowerStart - sMythicPlus->meleeAttackPowerDampener);
uint32 rangeAp = irand(215, 357);
scalingFactor = CalculateScaling(levelDifference, meleeDamage); // cInfo->DifficultyEntry
ap = uint32(stats->AttackPower * scalingFactor);
rangeAp = uint32(rangeAp * scalingFactor); // Calculate base scaling first, then apply creature type modifier
// float baseScaling = CalculateScaling(levelDifference, meleeDamage);
// if (creature->IsDungeonBoss() || creature->isElite() || cInfo->rank >= CREATURE_ELITE_ELITE) {
// // Full scaling for bosses and elites
// scalingFactor = baseScaling;
// } else {
// // Reduced scaling for normal creatures to achieve ~60% of elite damage
// scalingFactor = baseScaling * 0.6f;
// }
// Handle new melee/range scaling with simple formula (for simplicity range will just be 80% of melee bonus)
float meleeMultiplier = sMpDataStore->GetMeleeScaleFactor(creature->GetMapId(), instanceData->difficulty);
// Scale up the attack power based on the instance set in the database difficulty table.
uint32 ap = std::ceil(stats->AttackPower * meleeMultiplier);
uint32 rangeAp = std::ceil(stats->RangedAttackPower * meleeMultiplier * 0.5f);
MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creature->GetGUID()); MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creature->GetGUID());
if(creatureData) { if(creatureData) {
creatureData->NewAttackPower = ap; creatureData->NewAttackPower = ap;
creatureData->AttackPowerScaleMultiplier = scalingFactor; creatureData->AttackPowerScaleMultiplier = meleeMultiplier;
} }
// Set scaled attack power // Set scaled attack power
@@ -321,7 +342,10 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
// set the base weapon damage // set the base weapon damage
creature->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING], 0); creature->SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING], 0);
creature->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING], 0); creature->SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 1.5f, 0);
creature->SetBaseWeaponDamage(RANGED_ATTACK, MINDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 0.6f, 0);
creature->SetBaseWeaponDamage(RANGED_ATTACK, MAXDAMAGE, stats->BaseDamage[EXPANSION_WRATH_OF_THE_LICH_KING] * 1.0f, 0);
// Update all stats to apply the new damage values // Update all stats to apply the new damage values
creature->UpdateAllStats(); creature->UpdateAllStats();
@@ -334,7 +358,6 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, MpCreatureData* creatureData, Creature* creature, Unit* target, float damageMultiplier) int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, MpCreatureData* creatureData, Creature* creature, Unit* target, float damageMultiplier)
{ {
if (!spellInfo) { if (!spellInfo) {
MpLogger::error("Invalid spell info ScaleDamageSpell()"); MpLogger::error("Invalid spell info ScaleDamageSpell()");
return damage; return damage;
@@ -343,12 +366,30 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M
if(!creatureData) { if(!creatureData) {
// this is probably a summoned object or totem so going to cheat here and just use the flat modifer // this is probably a summoned object or totem so going to cheat here and just use the flat modifer
// Handle totems that do some nasty things to us Slave Pens anyone // Handle totems and summons - scale based on owner's elite status
if(creature->IsTotem()) { if(creature->IsTotem() || creature->IsSummon()) {
Unit* owner = creature->GetOwner(); Unit* owner = creature->GetOwner();
if(owner) { if(owner && owner->IsCreature()) {
float lvlDmgBonus = float(85 - owner->GetLevel() / 10.0f); Creature* ownerCreature = owner->ToCreature();
return int32(damage * lvlDmgBonus * damageMultiplier); MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
int32 spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty);
// Calculate level difference (use owner's scaling)
float levelDifference = ownerCreature->GetLevel() - 80; // Assume original level 80 for simplicity
// Apply same scaling rules as owner
float baseTotemScaling = CalculateScaling(levelDifference, spellBonus);
float scalingFactor;
if (ownerCreature->IsDungeonBoss() || ownerCreature->isElite() || ownerCreature->GetCreatureTemplate()->rank >= CREATURE_ELITE_ELITE) {
// Elite owner - use elite spell scaling
scalingFactor = baseTotemScaling * 0.85f;
} else {
// Normal owner - use normal creature spell scaling
scalingFactor = baseTotemScaling * 0.5f;
}
return int32(damage * scalingFactor * damageMultiplier);
} else { } else {
return damage * damageMultiplier; return damage * damageMultiplier;
} }
@@ -367,20 +408,21 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M
MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId()); MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
int32 spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty); int32 spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty);
// if((creature->IsDungeonBoss() && creature->isElite()) || creature->GetEntry() == 23682) {
// spellBonus *= 1.15;
// }
// Calculate the level difference // Calculate the level difference
float levelDifference = creature->GetLevel() - originalLevel; float levelDifference = creature->GetLevel() - originalLevel;
// New formula with adjusted divisor for smoother scaling // Calculate base spell scaling first, then apply creature type modifier
// float scalingFactor = 1 + (std::log2(levelDifference + 1) * (float(spellBonus))); float baseSpellScaling = CalculateScaling(levelDifference, spellBonus);
float scalingFactor = CalculateScaling(levelDifference, spellBonus); float scalingFactor;
if (creature->IsDungeonBoss() || creature->isElite() || creature->GetCreatureTemplate()->rank >= CREATURE_ELITE_ELITE) {
// float scalingFactor = pow(float((creature->GetLevel() - originalLevel) / 10.0f ), float(spellBonus) / 5.0f); // Reduced scaling for elite/boss spells to prevent them from hitting too hard
// MpLogger::debug("Creature {} original level: {} New Level{} and Scaling level {}", creature->GetName(), originalLevel, creature->GetLevel(), scalingFactor); scalingFactor = baseSpellScaling * 0.85f;
} else {
// Much more reduced scaling for normal creature spells to achieve ~60% of elite damage
scalingFactor = baseSpellScaling * 0.5f;
}
int32 totalDamage = 0; int32 totalDamage = 0;
auto effects = spellInfo->GetEffects(); auto effects = spellInfo->GetEffects();
@@ -440,7 +482,6 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M
int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCreatureData* creatureData, Creature* creature, Creature* /* target */, float healMultiplier) int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCreatureData* creatureData, Creature* creature, Creature* /* target */, float healMultiplier)
{ {
if (!spellInfo) { if (!spellInfo) {
MpLogger::error("Invalid spell info ScaleHealSpell()"); MpLogger::error("Invalid spell info ScaleHealSpell()");
return 0; return 0;
@@ -467,7 +508,8 @@ int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCre
float levelDifference = creature->GetLevel() - originalLevel; float levelDifference = creature->GetLevel() - originalLevel;
float spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), creature->GetInstanceId()); float spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), creature->GetInstanceId());
float scalingFactor = CalculateScaling(levelDifference, spellBonus, 2.5f); // Reduced scaling for heals to prevent them from being too powerful
float scalingFactor = CalculateScaling(levelDifference, spellBonus * 0.4f, 1.8f); // Reduced scaling factor and constant
MpLogger::debug(" >>> Spell {} healed scaled from for spell New Heal: {} using: scaling Factor: {} and damage Multi: {}",spellInfo->SpellName[0], heal, scalingFactor, healMultiplier); MpLogger::debug(" >>> Spell {} healed scaled from for spell New Heal: {} using: scaling Factor: {} and damage Multi: {}",spellInfo->SpellName[0], heal, scalingFactor, healMultiplier);
return int32(heal * scalingFactor * healMultiplier); return int32(heal * scalingFactor * healMultiplier);
@@ -477,7 +519,6 @@ void MythicPlus::GroupReset(Group* /*group*/, Map* /* map */) {
// Stubbed out for later implementation // Stubbed out for later implementation
} }
bool MythicPlus::IsFinalBoss(Creature* creature) { bool MythicPlus::IsFinalBoss(Creature* creature) {
std::array<uint32, 128> finalBosses = { std::array<uint32, 128> finalBosses = {
// --- WoW Classic Dungeons --- // --- WoW Classic Dungeons ---
@@ -638,46 +679,74 @@ float GetTypeHealthModifier(int32 Rank)
// This takes the orignal health and scales flat based on the factor then applies the configuration modifier from the conf file // This takes the orignal health and scales flat based on the factor then applies the configuration modifier from the conf file
uint32 CalculateNewHealth(Creature* creature, CreatureTemplate const* cInfo, uint32 mapId, MpDifficulty difficulty, uint32 origHealth, float confHPMod) uint32 CalculateNewHealth(Creature* creature, CreatureTemplate const* cInfo, uint32 mapId, MpDifficulty difficulty, uint32 origHealth, float confHPMod)
{ {
//
int32 rank = 0; int32 rank = 0;
if(cInfo && cInfo->rank > 0) { if(cInfo && cInfo->rank > 0) {
rank = cInfo->rank; rank = cInfo->rank;
} }
// These Factors that increase or decrease health based on different settings applied to the creature
// Health Variation is used to create some random element to HP so not all creatures of the same level
// have the same HP for more variety.
float healthVariation; float healthVariation;
// if(creature->IsPet() || creature->IsSummon() || creature->IsTotem()) { // This is the fine grained hpScaleFactor set for the instance (and/or) creature overrides in the database.
// return origHealth;
// }
int32 hpScaleFactor = sMpDataStore->GetHealthScaleFactor(mapId, difficulty); int32 hpScaleFactor = sMpDataStore->GetHealthScaleFactor(mapId, difficulty);
// Add some variance to the healthpool so enemies are not all the same // Add some variance to the healthpool so enemies are not all the same
if(creature->IsDungeonBoss() || creature->GetEntry() == HEADLESS_HORSEMAN || creature->isWorldBoss()) { // Is a boss of some kind if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->isElite() || cInfo->rank == CREATURE_ELITE_RARE) {
healthVariation = frand(1.1f, 1.2f); healthVariation = frand(1.0f, 1.15f);
} else if(creature->isElite() || cInfo->rank == CREATURE_ELITE_RARE) { // Is Elite Mob } else { // This addresses Normals and other trash from getting to big a HP bonus
healthVariation = frand(1.0f, 1.10f);
hpScaleFactor *= 0.90;
} else if(creature->IsSummon() || creature->IsPet() || creature->IsTotem()) { // Is a pet or summon
healthVariation = frand(1.0f, 1.05f); healthVariation = frand(1.0f, 1.05f);
hpScaleFactor *= 0.65; hpScaleFactor *= 0.40;
} else { }
// Add in special overrides here as necessary:
if(creature->GetEntry() == HEADLESS_HORSEMAN) {
healthVariation = frand(1.0f, 1.1f); healthVariation = frand(1.0f, 1.1f);
hpScaleFactor *= 0.55;
} }
float unitTypeMod = GetTypeHealthModifier(rank); float unitTypeMod = GetTypeHealthModifier(rank);
uint32 basehp = uint32(std::ceil(origHealth * unitTypeMod * healthVariation)); uint32 basehp = uint32(std::ceil(origHealth * healthVariation));
/**
* @brief Calculating the final creature health encompasses all the potential modifiers
* CreatureTemplate.HealthModifier (ModHealth) - Creatures that are capable of being in a Heroic instance get a boost here
* even though they are the same. In this case we allow
*
* hpScaleFactor: allows to tweak the bonus modifier more directly at a creature or instance level, since
* you can not override it in cInfo directly as it is loaded statically from database
*
* confHPMod: is from the mythic settings directly.
*/
if(cInfo->ModHealth > 0.0f) { if(cInfo->ModHealth > 0.0f) {
return uint32(basehp * (cInfo->ModHealth + hpScaleFactor) * confHPMod); return uint32(basehp * (cInfo->ModHealth + hpScaleFactor) * confHPMod);
} else { } else {
return uint32(basehp * (hpScaleFactor) * confHPMod); return uint32(basehp * hpScaleFactor * confHPMod);
} }
} }
// Calculates a logarithmic growth curve using scaling factor of percentages increase 50 = 1.5, 100 = 2.0,... this allows for fine grain tuning per instance. // Calculates a balanced growth curve that provides good scaling across all level ranges
float CalculateScaling(int levelDifference, float scaleFactor, float constant, float growthFactor) { float CalculateScaling(int levelDifference, float scaleFactor, float constant, float growthFactor) {
float scaling = constant * std::pow(2.0f, levelDifference / growthFactor) * (1 + (scaleFactor / 100.0f)); float levelMultiplier;
if (levelDifference <= 0) {
// High-level creatures need a minimum boost
levelMultiplier = 1.5f; // Minimum 50% boost for near-level or higher creatures
} else if (levelDifference <= 10) {
// Moderate scaling for small level differences
levelMultiplier = 1.5f + (levelDifference * 0.2f); // 1.5x to 3.5x
} else if (levelDifference <= 30) {
// Higher scaling for medium level differences
levelMultiplier = 3.5f + ((levelDifference - 10) * 0.15f); // 3.5x to 6.5x
} else {
// Cap extreme scaling for very low level creatures
levelMultiplier = 6.5f + std::min((levelDifference - 30) * 0.05f, 3.5f); // Cap at 10x
}
float scaling = constant * levelMultiplier * (1 + (scaleFactor / 100.0f));
return scaling; return scaling;
} }
@@ -699,4 +768,3 @@ float GetTypeDamageModifier(int32 Rank)
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE); return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE);
} }
} }

View File

@@ -1,5 +1,6 @@
#include "MpScheduler.h" #include "MpScheduler.h"
#include "MpLogger.h" #include "MpLogger.h"
#include "Spells/AdvancmentSpells.cpp"
// Creature Overrides // Creature Overrides
enum { enum {
@@ -8,7 +9,6 @@ enum {
// This adds schedulers for use across scripts scoped to MythicPlus // This adds schedulers for use across scripts scoped to MythicPlus
void Add_MP_Schedulers() { void Add_MP_Schedulers() {
MpLogger::debug("Add_MP_Schedulers()");
new MpScheduler_WorldScript(); new MpScheduler_WorldScript();
} }
@@ -23,20 +23,25 @@ void Add_MP_UnitScripts();
void Add_MP_WorldScripts(); void Add_MP_WorldScripts();
void Add_MP_PlayerMessageEvents(); void Add_MP_PlayerMessageEvents();
// Spell Scripts
void AddSC_AdvancementSpells();
void Addmod_mythic_plusScripts() { void Addmod_mythic_plusScripts() {
Add_MP_AllCreatureScripts(); Add_MP_AllCreatureScripts();
Add_MP_AllMapScripts(); Add_MP_AllMapScripts();
Add_MP_CommandScripts(); Add_MP_CommandScripts();
Add_MP_GlobalScripts(); Add_MP_GlobalScripts();
// Add_MP_GroupScripts();
Add_MP_PlayerScripts(); Add_MP_PlayerScripts();
Add_MP_UnitScripts(); Add_MP_UnitScripts();
Add_MP_WorldScripts(); Add_MP_WorldScripts();
Add_MP_Schedulers(); Add_MP_Schedulers();
Add_MP_PlayerMessageEvents(); Add_MP_PlayerMessageEvents();
// new Ragefire_Bazzalan_Mythic(); // Spell Scripts
AddSC_AdvancementSpells();
// new Ragefire_Bazzalan_Mythic();
// Add_MP_GroupScripts();
// list of boss / creature event handlers // list of boss / creature event handlers
// new Ragefire_Bazzalan_Mythic(RAGEFIRE_BAZZALAN); // new Ragefire_Bazzalan_Mythic(RAGEFIRE_BAZZALAN);

View File

@@ -157,7 +157,7 @@ public:
group->SetDungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL); group->SetDungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL);
} }
else { else {
handler->PSendSysMessage("|cFFFF0000 Invalid difficulty level. Expected values are 'mythic', 'legendary', or 'ascendant'."); handler->PSendSysMessage("|cFFFF0000 Invalid difficulty level. Expected values are 'normal', 'heroic', 'mythic', 'legendary', or 'ascendant'.");
return true; return true;
} }
@@ -311,8 +311,8 @@ public:
auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID()); auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID());
if(groupData) { if(groupData) {
uint32 value = std::stoi(args[0]); float value = std::stof(args[0]);
sMpDataStore->SetDamageScaleFactor(player->GetMapId(), groupData->difficulty, value); sMpDataStore->SetMeleeScaleFactor(player->GetMapId(), groupData->difficulty, value);
handler->PSendSysMessage(Acore::StringFormat("Melee scale factor set to: {}", value)); handler->PSendSysMessage(Acore::StringFormat("Melee scale factor set to: {}", value));
return true; return true;
} }
@@ -339,7 +339,7 @@ public:
auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID()); auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID());
if(groupData) { if(groupData) {
uint32 value = std::stoi(args[0]); float value = std::stof(args[0]);
sMpDataStore->SetSpellScaleFactor(player->GetMapId(), groupData->difficulty, value); sMpDataStore->SetSpellScaleFactor(player->GetMapId(), groupData->difficulty, value);
handler->PSendSysMessage(Acore::StringFormat("Spell scale factor set to: {}", value)); handler->PSendSysMessage(Acore::StringFormat("Spell scale factor set to: {}", value));
return true; return true;
@@ -367,7 +367,7 @@ public:
auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID()); auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID());
if(groupData) { if(groupData) {
uint32 value = std::stoi(args[0]); float value = std::stof(args[0]);
sMpDataStore->SetHealthScaleFactor(player->GetMapId(), groupData->difficulty, value); sMpDataStore->SetHealthScaleFactor(player->GetMapId(), groupData->difficulty, value);
handler->PSendSysMessage(Acore::StringFormat("Health scale factor set to: {}", value)); handler->PSendSysMessage(Acore::StringFormat("Health scale factor set to: {}", value));
return true; return true;

View File

@@ -1,5 +1,6 @@
#include "MpLogger.h" #include "MpLogger.h"
#include "MythicPlus.h" #include "MythicPlus.h"
#include "MpConstants.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "Player.h" #include "Player.h"
#include "Map.h" #include "Map.h"
@@ -11,7 +12,7 @@ public:
MythicPlus_GlobalScript() : GlobalScript("MythicPlus_GlobalScript") { } MythicPlus_GlobalScript() : GlobalScript("MythicPlus_GlobalScript") { }
// This adds the mythic+ item scaling to the loot table for enemies // This adds the mythic+ item scaling to the loot table for enemies
void OnBeforeDropAddItem(Player const* player, Loot& /*loot*/, bool /*canRate*/, uint16 /*lootMode*/, LootStoreItem* LootStoreItem, LootStore const& store) override { void OnBeforeDropAddItem(Player const* player, Loot& loot, bool /*canRate*/, uint16 /*lootMode*/, LootStoreItem* LootStoreItem, LootStore const& store) override {
if(LootStoreItem->itemid == 0) { if(LootStoreItem->itemid == 0) {
return; return;
@@ -44,8 +45,30 @@ public:
// get the item to scale up // get the item to scale up
ItemTemplate const* origItem = sObjectMgr->GetItemTemplate(LootStoreItem->itemid); ItemTemplate const* origItem = sObjectMgr->GetItemTemplate(LootStoreItem->itemid);
if (!origItem) { if (!origItem) {
MpLogger::warn("Item not found for itemid {} in OnBeforeDropAddItem()", LootStoreItem->itemid);
return; // If there is not a scaled up item and the item is a below quality green then set an invalid item_id so it is not added to loot
ItemTemplate const* nonMythicItem = sObjectMgr->GetItemTemplate(LootStoreItem->itemid);
if (nonMythicItem->Quality < 2) {
LootStoreItem->itemid = 0;
return;
}
// otherwise roll a chance to see a shadowy remains item is provided instead only if there is not already a shadowy remains item on the corpse
bool hasShadowyRemains = false;
for (auto& item : loot.items) {
if(item.itemid == MpConstants::SHADOWY_REMAINS) {
hasShadowyRemains = true;
break;
}
}
if (!hasShadowyRemains) {
LootStoreItem->itemid = MpConstants::SHADOWY_REMAINS;
return;
} else {
LootStoreItem->itemid = 0;
return;
}
} }
uint32 newItemId = origItem->ItemId + mythicSettings->itemOffset; uint32 newItemId = origItem->ItemId + mythicSettings->itemOffset;

View File

@@ -120,6 +120,13 @@ public:
// Load the player advancement data for the player when they login // Load the player advancement data for the player when they login
sAdvancementMgr->LoadPlayerAdvancements(player); sAdvancementMgr->LoadPlayerAdvancements(player);
// Cast all unique advancement spells
for(uint32 i = 1; i <= 10; ++i) {
uint32 spellId = 80000000 + i;
MpLogger::info("Casting spell {} to player {}", spellId, player->GetName());
player->AddAura(spellId, player);
}
} }
// When a player is bound to an instance need to make sure they are saved in the data soure to retrieve later. // When a player is bound to an instance need to make sure they are saved in the data soure to retrieve later.

View File

@@ -148,7 +148,7 @@ public:
*/ */
switch (eventType) { switch (eventType) {
case MythicPlus::UNIT_EVENT_MELEE: case MythicPlus::UNIT_EVENT_MELEE:
if(creature->IsDungeonBoss() || creature->GetEntry() == 23682) { if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->GetEntry() == 23682) {
alteredDmgHeal = damageOrHeal * instanceData->boss.melee; alteredDmgHeal = damageOrHeal * instanceData->boss.melee;
} else { } else {
alteredDmgHeal = damageOrHeal * instanceData->creature.melee; alteredDmgHeal = damageOrHeal * instanceData->creature.melee;
@@ -157,25 +157,24 @@ public:
break; break;
case MythicPlus::UNIT_EVENT_DOT: case MythicPlus::UNIT_EVENT_DOT:
case MythicPlus::UNIT_EVENT_SPELL: case MythicPlus::UNIT_EVENT_SPELL:
if(creature->IsDungeonBoss() || creature->GetEntry() == 23682) { if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->GetEntry() == 23682) {
if(spellInfo) { if(spellInfo) {
MpLogger::debug("Scaling spell {} using ScaleDamageSpell() Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal);
alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->boss.spell); alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->boss.spell);
} else { } else {
alteredDmgHeal = damageOrHeal * instanceData->boss.spell; alteredDmgHeal = damageOrHeal * instanceData->boss.spell;
MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal);
} }
} else { } else {
if(spellInfo) { if(spellInfo) {
MpLogger::debug("Scaling spell {} using ScaleDamageSpell() Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal);
alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->creature.spell); alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->creature.spell);
} else { } else {
MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal);
alteredDmgHeal = damageOrHeal * instanceData->creature.spell; alteredDmgHeal = damageOrHeal * instanceData->creature.spell;
} }
} }
if(spellInfo) {
MpLogger::debug("Incoming spell New Damage: {}({}) {} hits {} spell: {} ID: {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName(), spellInfo->SpellName[0], spellInfo->Id);
} else {
MpLogger::debug("Incoming spell New Damage: {}({}) {} hits {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName());
}
break; break;
case MythicPlus::UNIT_EVENT_HEAL: case MythicPlus::UNIT_EVENT_HEAL:
case MythicPlus::UNIT_EVENT_HOT: case MythicPlus::UNIT_EVENT_HOT:

View File

@@ -104,6 +104,9 @@ public:
size = sAdvancementMgr->LoadMaterialTypes(); size = sAdvancementMgr->LoadMaterialTypes();
MpLogger::info("Loaded {} material types...", size); MpLogger::info("Loaded {} material types...", size);
sMpDataStore->LoadPlayerHealthAvg();
MpLogger::info("Loaded player health averages used for scaling calculations...");
// Registering event handlers for the Mythic+ events from client // Registering event handlers for the Mythic+ events from client
MP_Register_EventHandlers(); MP_Register_EventHandlers();
MpLogger::info("Registered Mythic+ Event Handlers..."); MpLogger::info("Registered Mythic+ Event Handlers...");

View File

@@ -1,70 +0,0 @@
// #include "CreatureScript.h"
// #include "PetDefines.h"
// #include "Player.h"
// #include "MpLogger.h"
// #include "SpellAuraEffects.h"
// #include "SpellInfo.h"
// #include "SpellMgr.h"
// #include "SpellScript.h"
// #include "SpellScriptLoader.h"
// #include "UnitAI.h"
// #include "World.h"
// class spell_mp_toughness_aura : public AuraScript
// {
// PrepareAuraScript(spell_mp_toughness_aura);
// void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
// {
// Player* player = GetTarget()->ToPlayer();
// if (!player)
// return;
// MpLogger::info("Applying Advancement Toughness to Player {}", player->GetName());
// if (Unit* caster = GetCaster())
// {
// if (caster->IsPlayer())
// {
// Player* player = caster->ToPlayer();
// // Add 500 Strength
// player->HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, 500, true);
// // Apply red glow visual effect
// caster->SendPlaySpellVisual(11674); // Visual ID 11674 for red glow
// }
// }
// }
// void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
// {
// LOG_INFO("server.worldserver", "spell_custom_red_glow_strength_aura::HandleEffectRemove");
// if (Unit* caster = GetCaster())
// {
// if (caster->IsPlayer())
// {
// Player* player = caster->ToPlayer();
// // Remove 500 Strength
// player->HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, 500, false);
// }
// }
// }
// void Register() override
// {
// LOG_INFO("server.worldserver", "spell_custom_red_glow_strength_aura::Register");
// OnEffectApply += AuraEffectApplyFn(spell_custom_red_glow_strength_aura::HandleEffectApply, EFFECT_0, SPELL_AURA_MOD_STAT, AURA_EFFECT_HANDLE_REAL);
// OnEffectRemove += AuraEffectRemoveFn(spell_custom_red_glow_strength_aura::HandleEffectRemove, EFFECT_0, SPELL_AURA_MOD_STAT, AURA_EFFECT_HANDLE_REAL);
// }
// };
// void AddSC_spell_custom_red_glow_strength_aura()
// {
// // LOG_INFO("server.loading", "Registering spell custom_red_glow_strength_aura");
// RegisterSpellScript(spell_custom_red_glow_strength_aura);
// }