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 (
"fmt"
"math"
"os"
)
@@ -15,13 +16,80 @@ const (
)
const (
RESIST_FROST = iota
RESIST_ARCANE = iota
RESIST_FIRE
RESIST_NATURE
RESIST_FROST
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() {
// Output file for the SQL script
outputFile, err := os.Create("generate_stat_upgrades.sql")
@@ -40,9 +108,17 @@ func main() {
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
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
for statID := range stats {
@@ -59,6 +135,9 @@ func main() {
materialCost = 1000 + (rank-30)*18
}
// Make adjustment for new fusion core types
itemCost1 := int(math.Ceil(float64(materialCost) / 20))
// Stat growth
minIncrease1 := 1 + (rank-1)/10*2
maxIncrease1 := 10 + (rank-1)/10*2
@@ -74,47 +153,49 @@ func main() {
chanceCost3 := 75 + (rank-1)*3
// 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
materialId2, materialCost2 := 0, 0
itemEntry2, itemCost2 := 0, 0
if rank > 10 {
materialId2 = statID*2 + 2
materialCost2 = (rank - 11) * 10
itemEntry2 = getStatItemEntry(statID, 4)
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 {
materialId3 = 20 // Group lot of raid only items
materialCost3 = (rank - 29) * 3
itemEntry3 = 911002 // veilstones
itemCost3 = (rank - 29) * 3
if itemCost3 > 15 {
itemCost3 = 15
}
}
// Write SQL insert statement for this rank
sql := fmt.Sprintf(
"(%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,
chanceCost1, chanceCost2, chanceCost3,
)
// Add a comma for all but the last line
isLastStat := statID == STAT_STAMINA && rank == 50
isLastResist := false
isLast := isLastStat && len(resistTypes) == 0 || isLastResist
if !isLast {
sql += ","
}
fmt.Fprintln(outputFile, sql)
}
}
resists := map[int]string{
RESIST_FROST: "Frost",
RESIST_FIRE: "Fire",
RESIST_NATURE: "Nature",
RESIST_SHADOW: "Shadow",
RESIST_ARCANE: "Arcane",
}
// Iterate over stats
for resistId := range resists {
// Iterate over resists
for resistId := range resistTypes {
resistIdbump := resistId + 5
for rank := 1; rank <= 50; rank++ {
// Material cost increases by 50 per rank
@@ -129,6 +210,9 @@ func main() {
materialCost = 700 + (rank-30)*18
}
// Make adjustment for new fusion core types
itemCost1 := int(math.Ceil(float64(materialCost) / 20))
// Stat growth
minIncrease1 := 1 + (rank-1)/5*2
maxIncrease1 := 5 + (rank-1)/5*2
@@ -145,31 +229,36 @@ func main() {
chanceCost3 := 75 + (rank-1)*3
// 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
materialId2, materialCost2 := 0, 0
itemEntry2, itemCost2 := 0, 0
if rank > 10 {
materialId2 = resistIdbump*2 + 2
materialCost2 = (rank - 11) * 10
itemEntry2 = getResistItemEntry(resistId, 4)
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 {
materialId3 = 20 // Group lot of raid only items
materialCost3 = (rank - 29) * 3
itemEntry3 = 911002 // veilstones
itemCost3 = (rank - 29) * 3
}
// Write SQL insert statement for this rank
sql := fmt.Sprintf(
"(%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,
chanceCost1, chanceCost2, chanceCost3,
)
// Add a comma for all but the last line
if !(resistId == RESIST_ARCANE && rank == 50) {
isLast := resistId == RESIST_ARCANE && rank == 50
if !isLast {
sql += ","
}
fmt.Fprintln(outputFile, sql)

View File

@@ -34,12 +34,12 @@ std::string MpAdvancementsToString(MpAdvancements advancement)
*
* upgradeRank INT UNSIGNED NOT NULL,
* advancementId INT UNSIGNED NOT NULL,
* materialId1 INT UNSIGNED NOT NULL,
* materialId2 INT UNSIGNED NOT NULL,
* materialId3 INT UNSIGNED NOT NULL,
* materialCost INT UNSIGNED NOT NULL,
* materialCost2 INT UNSIGNED NOT NULL,
* materialCost3 INT UNSIGNED NOT NULL,
* itemEntry1 INT UNSIGNED NOT NULL,
* itemEntry2 INT UNSIGNED NOT NULL,
* itemEntry3 INT UNSIGNED NOT NULL,
* itemCost1 INT UNSIGNED NOT NULL,
* itemCost2 INT UNSIGNED NOT NULL,
* itemCost3 INT UNSIGNED NOT NULL,
* minIncrease1 INT UNSIGNED NOT NULL,
* maxIncrease1 INT UNSIGNED NOT NULL,
* minIncrease2 INT UNSIGNED NOT NULL,
@@ -64,12 +64,12 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
SELECT
upgradeRank,
advancementId,
materialId1,
materialId2,
materialId3,
materialCost1,
materialCost2,
materialCost3,
itemEntry1,
itemEntry2,
itemEntry3,
itemCost1,
itemCost2,
itemCost3,
minIncrease1,
maxIncrease1,
minIncrease2,
@@ -84,7 +84,7 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
QueryResult result = WorldDatabase.Query(query);
if (!result) {
MpLogger::error("Failed to load mythic scale factors from database");
MpLogger::error("Failed to load advancement ranks from database");
return 0;
}
@@ -96,12 +96,12 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
Field* fields = result->Fetch();
uint32 upgradeRank = fields[0].Get<uint32>();
uint32 advancementId = fields[1].Get<uint32>();
uint32 materialId1 = fields[2].Get<uint32>();
uint32 materialId2 = fields[3].Get<uint32>();
uint32 materialId3 = fields[4].Get<uint32>();
uint32 materialCost1 = fields[5].Get<uint32>();
uint32 materialCost2 = fields[6].Get<uint32>();
uint32 materialCost3 = fields[7].Get<uint32>();
uint32 itemEntry1 = fields[2].Get<uint32>();
uint32 itemEntry2 = fields[3].Get<uint32>();
uint32 itemEntry3 = fields[4].Get<uint32>();
uint32 itemCost1 = fields[5].Get<uint32>();
uint32 itemCost2 = fields[6].Get<uint32>();
uint32 itemCost3 = fields[7].Get<uint32>();
uint32 minIncrease1 = fields[8].Get<uint32>();
uint32 maxIncrease1 = fields[9].Get<uint32>();
uint32 minIncrease2 = fields[10].Get<uint32>();
@@ -124,9 +124,9 @@ int32 AdvancementMgr::LoadAdvancementRanks() {
.lowRange = std::make_pair(minIncrease1, maxIncrease1),
.midRange = std::make_pair(minIncrease2, maxIncrease2),
.highRange = std::make_pair(minIncrease3, maxIncrease3),
.material1 = std::make_pair(materialId1, materialCost1),
.material2 = std::make_pair(materialId2, materialCost2),
.material3 = std::make_pair(materialId3, materialCost3)
.material1 = std::make_pair(itemEntry1, itemCost1),
.material2 = std::make_pair(itemEntry2, itemCost2),
.material3 = std::make_pair(itemEntry3, itemCost3)
};
_advancementRanks.try_emplace(std::make_pair(upgradeRank, advancement), rank);
@@ -172,8 +172,12 @@ void AdvancementMgr::LoadPlayerAdvancements(Player* player) {
return;
}
uint32 count = 0;
uint32 guid = player->GetGUID().GetCounter();
// Loop through all results to load all advancements for this player
do {
Field* fields = result->Fetch();
uint32 guid = fields[0].Get<uint32>();
uint32 advancementId = fields[1].Get<uint32>();
float bonus = fields[2].Get<float>();
uint32 upgradeRank = fields[3].Get<uint32>();
@@ -189,7 +193,13 @@ void AdvancementMgr::LoadPlayerAdvancements(Player* player) {
// List of all ranks keyed by rank, advancementId
_playerAdvancements[guid][advancement] = playerRank;
MpLogger::info("Loaded player {} advancement {} rank {}", player->GetName(), playerRank.advancementId, upgradeRank);
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,7 +214,7 @@ int32 AdvancementMgr::LoadMaterialTypes() {
FROM mp_material_types
)";
QueryResult result = WorldDatabase.Query(query);
if(QueryResult result = WorldDatabase.Query(query)) {
do {
Field* fields = result->Fetch();
@@ -219,6 +229,10 @@ int32 AdvancementMgr::LoadMaterialTypes() {
} while (result->NextRow());
return result->GetRowCount();
} else {
MpLogger::error("Query failed to load material types from database");
return 0;
}
}
MpAdvancementRank* AdvancementMgr::GetAdvancementRank(uint32 rank, MpAdvancements advancement)
@@ -250,7 +264,7 @@ MpPlayerRank* AdvancementMgr::GetPlayerAdvancementRank(Player* player, MpAdvance
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);
@@ -262,9 +276,6 @@ uint32 AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advance
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()));
}
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);
@@ -285,10 +296,16 @@ uint32 AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advance
uint32 newRank = playerRank->rank + 1;
MpAdvancementRank* advancementRank = GetAdvancementRank(newRank, advancement);
if(!advancementRank->IsValid()) {
throw std::runtime_error("Advancement rank could not be found. Rank: " + std::to_string(newRank) + " Advancement: " + std::to_string(advancement));
if(advancementRank == nullptr || !advancementRank->IsValid()) {
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(!_PlayerHasItems(player, advancementRank, diceCostLevel, itemEntry1, itemEntry2, itemEntry3)) {
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
_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;
}
@@ -352,7 +380,7 @@ float AdvancementMgr::_RollAdvancement(MpAdvancementRank* advancementRank, uint3
max = advancementRank->highRange.second;
break;
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;
}
@@ -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)
{
MpLogger::debug("Debugging player dice {} item1 {} item2 {} item3 {}", diceCostLevel, itemEntry1, itemEntry2, itemEntry3);
MpLogger::debug("Advancement Rank dice info {} {} {} {}", advancementRank->materialCost[1], advancementRank->materialCost[2], advancementRank->materialCost[3]);
MpLogger::debug("Checking items for player {} dice level {} item1 {} item2 {} item3 {}",
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];
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;
}
// Create arrays of material data for easier iteration
std::pair<uint32, uint32> materials[] = {
advancementRank->material1,
advancementRank->material2,
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;
// Check material 1 (required)
if (advancementRank->material1.first > 0) {
if (itemEntry1 == 0) {
throw std::runtime_error("Primary material entry is required but was not provided");
}
if (itemEntry == 0 && materialId != 0) {
throw std::runtime_error("The entry for materialId: " + std::to_string(materialId) + " was not passed in.");
}
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);
uint32 requiredCount = advancementRank->material1.second;
if (!player->HasItemCount(itemEntry1, requiredCount)) {
MpLogger::info("Player {} does not have enough of item {} for advancement {}, requires: {}",
player->GetName(), itemEntry1, advancementRank->advancementId, requiredCount);
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;
}
// Remove all items required for the upgrade.
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];
player->DestroyItemCount(MpConstants::ANCIENT_DICE, diceCost, true);
std::vector<std::pair<uint32, uint32>> items = {
{itemEntry1, advancementRank->material1.second},
{itemEntry2, advancementRank->material2.second},
{itemEntry3, advancementRank->material3.second}
};
for (const auto& item : items) {
if (item.first > 0) {
player->DestroyItemCount(item.first, item.second, true);
}
// Remove material 1 if it exists
if (itemEntry1 > 0 && advancementRank->material1.first > 0) {
player->DestroyItemCount(itemEntry1, advancementRank->material1.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)

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.
* 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
bool ResetPlayerAdvancements(Player* player);

View File

@@ -35,7 +35,7 @@ inline std::unordered_map<std::string_view, MpEvent> MpEventMap = {{
{"ResetAdvancement", MpEvent::ResetAdvancement},
{"ResetAllAdvancements", MpEvent::ResetAllAdvancements},
{"GetPlayerRank", MpEvent::GetPlayerRank},
{"GetAdvancementRank", MpEvent::ResetAllAdvancements}
{"GetAdvancementRank", MpEvent::GetAdvancementRank}
}};
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:
* p|playerGuid|UpgradeAdvancement|advancementId|diceLevel|itemEntry1|itemEntry2|itemEntry3
*/
class UpdateAdvancements : public MpEventInterface
class UpgradeAdvancements : public MpEventInterface
{
public:
const std::string EventName() const override
@@ -83,8 +83,8 @@ class UpdateAdvancements : public MpEventInterface
}
// Validate the message is in the right format
if(args.size() != 5) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 5, found " + std::to_string(args.size()));
if(args.size() != 2) {
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]);
@@ -97,17 +97,9 @@ class UpdateAdvancements : public MpEventInterface
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;
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) {
return SendEventError(player, EventName(), MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to upgrade advancement invalid request see error logs for player " + player->GetName());
}
@@ -115,7 +107,11 @@ class UpdateAdvancements : public MpEventInterface
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));
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
eventData = {
@@ -206,8 +202,8 @@ class GetAdvancementRank : public MpEventInterface {
bool Execute(Player* player, std::vector<std::string>& args) override
{
if(args.size() != 3) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 3, found " + std::to_string(args.size()));
if(args.size() != 2) {
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]);
@@ -251,7 +247,7 @@ class GetAdvancementRank : public MpEventInterface {
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::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)) {
// 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);
MpLogger::warn("No handler registered for event: {}", event);
return false;

View File

@@ -1,11 +1,112 @@
#ifndef MP_CONSTANTS_H
#define MP_CONSTANTS_H
#include "AdvancementMgr.h"
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 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

View File

@@ -135,7 +135,7 @@ void MpDataStore::RemoveGroupData(Group *group) {
MpLogger::debug("RemoveGroupData for group {}", group->GetGUID().GetCounter());
_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
@@ -220,45 +220,61 @@ MpScaleFactor MpDataStore::GetScaleFactor(int32 mapId, int32 difficulty) const {
return _scaleFactors->at(key);
}
// Just send back untouched bonus database will override.
return MpScaleFactor{
.dmgBonus = 3,
.healthBonus = 2,
.maxDamageBonus = 30
.meleeBonus = 1.0f,
.healthBonus = 1.0f,
.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;
}
int32 MpDataStore::GetDamageScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).dmgBonus;
float MpDataStore::GetMeleeScaleFactor(int32 mapId, int32 difficulty) const {
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;
}
int32 MpDataStore::GetMaxDamageScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).maxDamageBonus;
float MpDataStore::GetHealScaleFactor(int32 mapId, int32 difficulty) const {
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);
if (_scaleFactors && _scaleFactors->contains(key)) {
_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);
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);
if (_scaleFactors && _scaleFactors->contains(key)) {
_scaleFactors->at(key).spellBonus = newValue;
@@ -269,7 +285,7 @@ int32 MpDataStore::LoadScaleFactors() {
_scaleFactors->clear();
// 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) {
MpLogger::error("Failed to load mythic scale factors from database");
return 0;
@@ -278,17 +294,17 @@ int32 MpDataStore::LoadScaleFactors() {
do {
Field* fields = result->Fetch();
uint32 mapId = fields[0].Get<uint32>();
int32 damageBonus = fields[1].Get<int32>();
int32 spellBonus = fields[2].Get<int32>();
int32 healthBonus = fields[3].Get<int32>();
int32 difficulty = fields[4].Get<int32>();
int32 maxDamageBonus = fields[5].Get<int32>();
float meleeBonus = fields[1].Get<float>();
float spellBonus = fields[2].Get<float>();
float healBonus = fields[3].Get<float>();
float healthBonus = fields[4].Get<float>();
int32 difficulty = fields[5].Get<int32>();
MpScaleFactor scaleFactor = {
.dmgBonus = damageBonus,
.meleeBonus = meleeBonus,
.healthBonus = healthBonus,
.spellBonus = spellBonus,
.maxDamageBonus = maxDamageBonus
.healBonus = healBonus
};
_scaleFactors->emplace(GetScaleFactorKey(mapId, difficulty), scaleFactor);
@@ -298,6 +314,45 @@ int32 MpDataStore::LoadScaleFactors() {
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.
* @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
{
int32 dmgBonus;
int32 healthBonus;
int32 spellBonus;
int32 maxDamageBonus;
float meleeBonus;
float spellBonus;
float healBonus;
float healthBonus;
std::string ToString() const {
return "MpScaleFactor: { dmgBonus: " + std::to_string(dmgBonus) +
return "MpScaleFactor: { meleeBonus: " + std::to_string(meleeBonus) +
", 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)
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.
// std::unique_ptr<std::unordered_map<uint32, CreatureOverride>> _creatureOverrides;
@@ -377,15 +386,19 @@ public:
std::vector<MpCreatureData*> GetUnscaledCreatures(uint32 mapId, uint32 instanceId);
// Scale factors are used to determine a base bonus for enemies base on the instance difficulty
int32 GetHealthScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetDamageScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetMaxDamageScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetSpellScaleFactor(int32 mapId, int32 difficulty) const;
float GetHealthScaleFactor(int32 mapId, int32 difficulty) const;
float GetMeleeScaleFactor(int32 mapId, int32 difficulty) const;
float GetSpellScaleFactor(int32 mapId, int32 difficulty) const;
float GetHealScaleFactor(int32 mapId, int32 difficulty) const;
MpScaleFactor GetScaleFactor(int32 mapId, int32 difficulty) const;
void SetDamageScaleFactor(int32 mapId, int32 difficulty, int32 value);
void SetHealthScaleFactor(int32 mapId, int32 difficulty, int32 value);
void SetSpellScaleFactor(int32 mapId, int32 difficulty, int32 value);
void SetMeleeScaleFactor(int32 mapId, int32 difficulty, float value);
void SetHealthScaleFactor(int32 mapId, int32 difficulty, float 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
// void AddCreatureOverride(uint32 entry, CreatureOverride* override);
@@ -401,6 +414,9 @@ public:
// Used at initial server load
int32 LoadScaleFactors();
// Load the player health average from the database
void LoadPlayerHealthAvg();
// Database API calls
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());
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
// 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
);
// Scale the creatures base health
uint32 basehp = stats->BaseHealth[EXPANSION_WRATH_OF_THE_LICH_KING];
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
*/
// Scale the creatures mana pool
uint32 mana = uint32(std::ceil(stats->BaseMana * cInfo->ModMana));
creature->SetCreateMana(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);
}
// Scale the creatures damage
MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
int32 meleeDamage = sMpDataStore->GetDamageScaleFactor(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);
}
// int32 meleeDamage = sMpDataStore->GetMeleeScaleFactor(creature->GetMapId(), instanceData->difficulty);
// Calculate the level difference
float levelDifference = creature->GetLevel() - origLevel;
// float levelDifference = creature->GetLevel() - origLevel;
// New formula with adjusted divisor for smoother scaling
float scalingFactor;
uint32 ap = uint32(sMythicPlus->meleeAttackPowerStart - sMythicPlus->meleeAttackPowerDampener);
uint32 rangeAp = irand(215, 357);
// uint32 ap = uint32(sMythicPlus->meleeAttackPowerStart - sMythicPlus->meleeAttackPowerDampener);
scalingFactor = CalculateScaling(levelDifference, meleeDamage);
ap = uint32(stats->AttackPower * scalingFactor);
rangeAp = uint32(rangeAp * scalingFactor);
// cInfo->DifficultyEntry
// 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());
if(creatureData) {
creatureData->NewAttackPower = ap;
creatureData->AttackPowerScaleMultiplier = scalingFactor;
creatureData->AttackPowerScaleMultiplier = meleeMultiplier;
}
// Set scaled attack power
@@ -321,7 +342,10 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
// set the base weapon damage
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
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)
{
if (!spellInfo) {
MpLogger::error("Invalid spell info ScaleDamageSpell()");
return damage;
@@ -343,12 +366,30 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M
if(!creatureData) {
// 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
if(creature->IsTotem()) {
// Handle totems and summons - scale based on owner's elite status
if(creature->IsTotem() || creature->IsSummon()) {
Unit* owner = creature->GetOwner();
if(owner) {
float lvlDmgBonus = float(85 - owner->GetLevel() / 10.0f);
return int32(damage * lvlDmgBonus * damageMultiplier);
if(owner && owner->IsCreature()) {
Creature* ownerCreature = owner->ToCreature();
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 {
return damage * damageMultiplier;
}
@@ -367,20 +408,21 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M
MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
int32 spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty);
// if((creature->IsDungeonBoss() && creature->isElite()) || creature->GetEntry() == 23682) {
// spellBonus *= 1.15;
// }
// Calculate the level difference
float levelDifference = creature->GetLevel() - originalLevel;
// New formula with adjusted divisor for smoother scaling
// float scalingFactor = 1 + (std::log2(levelDifference + 1) * (float(spellBonus)));
// Calculate base spell scaling first, then apply creature type modifier
float baseSpellScaling = CalculateScaling(levelDifference, spellBonus);
float scalingFactor = CalculateScaling(levelDifference, spellBonus);
// float scalingFactor = pow(float((creature->GetLevel() - originalLevel) / 10.0f ), float(spellBonus) / 5.0f);
// MpLogger::debug("Creature {} original level: {} New Level{} and Scaling level {}", creature->GetName(), originalLevel, creature->GetLevel(), scalingFactor);
float scalingFactor;
if (creature->IsDungeonBoss() || creature->isElite() || creature->GetCreatureTemplate()->rank >= CREATURE_ELITE_ELITE) {
// Reduced scaling for elite/boss spells to prevent them from hitting too hard
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;
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)
{
if (!spellInfo) {
MpLogger::error("Invalid spell info ScaleHealSpell()");
return 0;
@@ -467,7 +508,8 @@ int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCre
float levelDifference = creature->GetLevel() - originalLevel;
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);
return int32(heal * scalingFactor * healMultiplier);
@@ -477,7 +519,6 @@ void MythicPlus::GroupReset(Group* /*group*/, Map* /* map */) {
// Stubbed out for later implementation
}
bool MythicPlus::IsFinalBoss(Creature* creature) {
std::array<uint32, 128> finalBosses = {
// --- 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
uint32 CalculateNewHealth(Creature* creature, CreatureTemplate const* cInfo, uint32 mapId, MpDifficulty difficulty, uint32 origHealth, float confHPMod)
{
//
int32 rank = 0;
if(cInfo && cInfo->rank > 0) {
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;
// if(creature->IsPet() || creature->IsSummon() || creature->IsTotem()) {
// return origHealth;
// }
// This is the fine grained hpScaleFactor set for the instance (and/or) creature overrides in the database.
int32 hpScaleFactor = sMpDataStore->GetHealthScaleFactor(mapId, difficulty);
// 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
healthVariation = frand(1.1f, 1.2f);
} else if(creature->isElite() || cInfo->rank == CREATURE_ELITE_RARE) { // Is Elite Mob
healthVariation = frand(1.0f, 1.10f);
hpScaleFactor *= 0.90;
} else if(creature->IsSummon() || creature->IsPet() || creature->IsTotem()) { // Is a pet or summon
if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->isElite() || cInfo->rank == CREATURE_ELITE_RARE) {
healthVariation = frand(1.0f, 1.15f);
} else { // This addresses Normals and other trash from getting to big a HP bonus
healthVariation = frand(1.0f, 1.05f);
hpScaleFactor *= 0.65;
} else {
hpScaleFactor *= 0.40;
}
// Add in special overrides here as necessary:
if(creature->GetEntry() == HEADLESS_HORSEMAN) {
healthVariation = frand(1.0f, 1.1f);
hpScaleFactor *= 0.55;
}
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) {
return uint32(basehp * (cInfo->ModHealth + hpScaleFactor) * confHPMod);
} 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 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;
}
@@ -699,4 +768,3 @@ float GetTypeDamageModifier(int32 Rank)
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE);
}
}

View File

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

View File

@@ -157,7 +157,7 @@ public:
group->SetDungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL);
}
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;
}
@@ -311,8 +311,8 @@ public:
auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID());
if(groupData) {
uint32 value = std::stoi(args[0]);
sMpDataStore->SetDamageScaleFactor(player->GetMapId(), groupData->difficulty, value);
float value = std::stof(args[0]);
sMpDataStore->SetMeleeScaleFactor(player->GetMapId(), groupData->difficulty, value);
handler->PSendSysMessage(Acore::StringFormat("Melee scale factor set to: {}", value));
return true;
}
@@ -339,7 +339,7 @@ public:
auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID());
if(groupData) {
uint32 value = std::stoi(args[0]);
float value = std::stof(args[0]);
sMpDataStore->SetSpellScaleFactor(player->GetMapId(), groupData->difficulty, value);
handler->PSendSysMessage(Acore::StringFormat("Spell scale factor set to: {}", value));
return true;
@@ -367,7 +367,7 @@ public:
auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID());
if(groupData) {
uint32 value = std::stoi(args[0]);
float value = std::stof(args[0]);
sMpDataStore->SetHealthScaleFactor(player->GetMapId(), groupData->difficulty, value);
handler->PSendSysMessage(Acore::StringFormat("Health scale factor set to: {}", value));
return true;

View File

@@ -1,5 +1,6 @@
#include "MpLogger.h"
#include "MythicPlus.h"
#include "MpConstants.h"
#include "ScriptMgr.h"
#include "Player.h"
#include "Map.h"
@@ -11,7 +12,7 @@ public:
MythicPlus_GlobalScript() : GlobalScript("MythicPlus_GlobalScript") { }
// 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) {
return;
@@ -44,10 +45,32 @@ public:
// get the item to scale up
ItemTemplate const* origItem = sObjectMgr->GetItemTemplate(LootStoreItem->itemid);
if (!origItem) {
MpLogger::warn("Item not found for itemid {} in OnBeforeDropAddItem()", LootStoreItem->itemid);
// 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;
ItemTemplate const* newItemTempl = sObjectMgr->GetItemTemplate(newItemId);

View File

@@ -120,6 +120,13 @@ public:
// Load the player advancement data for the player when they login
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.

View File

@@ -148,7 +148,7 @@ public:
*/
switch (eventType) {
case MythicPlus::UNIT_EVENT_MELEE:
if(creature->IsDungeonBoss() || creature->GetEntry() == 23682) {
if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->GetEntry() == 23682) {
alteredDmgHeal = damageOrHeal * instanceData->boss.melee;
} else {
alteredDmgHeal = damageOrHeal * instanceData->creature.melee;
@@ -157,25 +157,24 @@ public:
break;
case MythicPlus::UNIT_EVENT_DOT:
case MythicPlus::UNIT_EVENT_SPELL:
if(creature->IsDungeonBoss() || creature->GetEntry() == 23682) {
if(creature->IsDungeonBoss() || creature->isWorldBoss() || creature->GetEntry() == 23682) {
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);
} else {
alteredDmgHeal = damageOrHeal * instanceData->boss.spell;
MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal);
}
} else {
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);
} else {
MpLogger::debug("Scaling spell {} using flat modifier Original Damage: {} New Damage: {}", spellInfo->SpellName[0], damageOrHeal, alteredDmgHeal);
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;
case MythicPlus::UNIT_EVENT_HEAL:
case MythicPlus::UNIT_EVENT_HOT:

View File

@@ -104,6 +104,9 @@ public:
size = sAdvancementMgr->LoadMaterialTypes();
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
MP_Register_EventHandlers();
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);
// }