Support for World Boss encounters

This commit is contained in:
2025-11-22 23:22:42 -05:00
parent 81e5872427
commit de803cb938
10 changed files with 539 additions and 45 deletions

View File

@@ -132,3 +132,41 @@ MythicPlus.DiminishingThreshold.Ascendant = 40000
MythicPlus.ElementalMeleeReducer = 0.50
MythicPlus.NormalEnemyReducer = 0.50
MythicPlus.NonCreatureSpellReducer = 0.50
##########################################################
#
# World Boss Settings
# - These settings control Mythic+ scaling for world bosses in open-world zones
# - World bosses work differently than dungeons:
# * Multiple groups can engage the same boss
# * Boss scales to the highest difficulty of participating groups
# * Proximity-based group detection (players within range)
# * Summoned creatures (adds) are automatically scaled
#
##########################################################
# Enable Mythic+ scaling for world bosses
# Default: 0 (disabled)
MythicPlus.WorldBoss.Enabled = 0
# Comma-separated list of zone IDs where world boss scaling is enabled
# Examples: 16 (Azshara), 490 (Moonglade), 28 (Western Plaguelands), 139 (Eastern Plaguelands)
# Leave empty to allow all zones
# Default: empty (all zones allowed when enabled)
MythicPlus.WorldBoss.EnabledZones = 16,28,139,490
# Comma-separated list of specific world boss creature entries to enable
# Examples: 6109 (Azuregos), 14889 (Emeriss), 14890 (Taerar), 14887 (Ysondre), 14888 (Lethon)
# Leave empty to allow all world bosses in enabled zones
# Default: empty (all world bosses in enabled zones)
MythicPlus.WorldBoss.EnabledBosses = 6109,14889,14890,14887,14888
# Proximity range in yards for detecting groups near world bosses
# Groups within this range will be registered to the encounter
# Default: 100 yards
MythicPlus.WorldBoss.ProximityRange = 100
# Scale boss difficulty based on number of participating groups
# If enabled, boss gets additional scaling per group beyond the first
# Default: 0 (disabled - uses highest difficulty only)
MythicPlus.WorldBoss.ScaleWithGroupCount = 0

View File

@@ -1,3 +1,4 @@
#include <algorithm>
#include "CharacterDatabase.h"
#include "MpDataStore.h"
#include "Chat.h"
@@ -5,6 +6,68 @@
#include "MpLogger.h"
#include "Chat.h"
void MpWorldBossEncounter::AddGroup(ObjectGuid groupGuid, MpDifficulty groupDifficulty)
{
if (!groupGuid)
return;
// Store the highest difficulty among participating groups
auto it = participatingGroups.find(groupGuid);
if (it == participatingGroups.end() || it->second < groupDifficulty) {
participatingGroups[groupGuid] = groupDifficulty;
if (groupDifficulty > difficulty) {
difficulty = groupDifficulty;
}
}
}
void MpWorldBossEncounter::AddScaledCreature(ObjectGuid creatureGuid)
{
if (creatureGuid) {
scaledCreatures.insert(creatureGuid);
}
}
void MpDataStore::AddWorldBossEncounter(ObjectGuid bossGuid, uint32 bossEntry, uint32 zoneId)
{
if (!bossGuid || !bossEntry) {
return;
}
MpWorldBossEncounter encounter(bossGuid, bossEntry, zoneId);
encounter.isActive = true;
(*_worldBossEncounters)[bossGuid] = encounter;
}
MpWorldBossEncounter* MpDataStore::GetWorldBossEncounter(ObjectGuid bossGuid)
{
if (!_worldBossEncounters->contains(bossGuid)) {
return nullptr;
}
return &(*_worldBossEncounters)[bossGuid];
}
MpWorldBossEncounter* MpDataStore::GetWorldBossEncounterByZone(uint32 zoneId)
{
for (auto& [guid, encounter] : *_worldBossEncounters) {
if (encounter.zoneId == zoneId && encounter.isActive) {
return &encounter;
}
}
return nullptr;
}
void MpDataStore::RemoveWorldBossEncounter(ObjectGuid bossGuid)
{
if (!bossGuid) {
return;
}
_worldBossEncounters->erase(bossGuid);
}
// Adds an entry for the group difficult to memory and updats database
void MpDataStore::AddGroupData(Group *group, MpGroupData groupData) {
if(!group) {
@@ -314,6 +377,48 @@ int32 MpDataStore::LoadScaleFactors() {
return int32(_scaleFactors->size());
}
int32 MpDataStore::LoadWorldBossConfig() {
// 0 1 2 3 4 5 6 7 8
QueryResult result = WorldDatabase.Query("SELECT entry, name, zoneId, enabled, melee_bonus, spell_bonus, heal_bonus, hp_bonus, difficulty FROM mp_world_boss_config WHERE enabled = 1");
if (!result) {
MpLogger::warn("No world boss configuration found in database (table may not exist or is empty)");
return 0;
}
int32 count = 0;
do {
Field* fields = result->Fetch();
uint32 entry = fields[0].Get<uint32>();
std::string name = fields[1].Get<std::string>();
uint32 zoneId = fields[2].Get<uint32>();
bool enabled = fields[3].Get<bool>();
float meleeBonus = fields[4].Get<float>();
float spellBonus = fields[5].Get<float>();
float healBonus = fields[6].Get<float>();
float hpBonus = fields[7].Get<float>();
int32 difficulty = fields[8].Get<int32>();
// Store scale factors for this world boss using its zone ID and difficulty
MpScaleFactor scaleFactor = {
.meleeBonus = meleeBonus,
.spellBonus = spellBonus,
.healBonus = healBonus,
.healthBonus = hpBonus
};
// Use zone ID as the "map" for world bosses
_scaleFactors->emplace(GetScaleFactorKey(zoneId, difficulty), scaleFactor);
MpLogger::debug("Loaded world boss config: {} (entry: {}, zone: {}, difficulty: {})",
name, entry, zoneId, difficulty);
count++;
} while (result->NextRow());
return count;
}
void MpDataStore::LoadPlayerHealthAvg() {
_playerHealthAvg.clear();

View File

@@ -10,6 +10,7 @@
#include "ObjectGuid.h"
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <string>
#include <vector>
@@ -25,6 +26,26 @@ enum MpDifficulty
MP_DIFFICULTY_ASCENDANT = 5
};
struct MpWorldBossEncounter
{
ObjectGuid bossGuid;
uint32 bossEntry = 0;
uint32 zoneId = 0;
bool isActive = false;
MpDifficulty difficulty = MP_DIFFICULTY_MYTHIC;
MpDifficulty scaledDifficulty = MP_DIFFICULTY_NORMAL;
std::unordered_map<ObjectGuid, MpDifficulty> participatingGroups;
std::unordered_set<ObjectGuid> scaledCreatures;
MpWorldBossEncounter() = default;
MpWorldBossEncounter(ObjectGuid guid, uint32 entry, uint32 zone)
: bossGuid(guid), bossEntry(entry), zoneId(zone), isActive(true) {}
void AddGroup(ObjectGuid groupGuid, MpDifficulty groupDifficulty);
void AddScaledCreature(ObjectGuid creatureGuid);
};
class MpDataStore;
struct MpPlayerInstanceData
@@ -304,7 +325,8 @@ private:
_instanceData(std::make_unique<std::map<std::pair<uint32, uint32>, MpInstanceData>>()),
_groupData(std::make_unique<std::unordered_map<ObjectGuid, MpGroupData>>()),
_instanceCreatureData(std::make_unique<std::unordered_map<ObjectGuid, MpCreatureData>>()),
_scaleFactors(std::make_unique<std::map<std::pair<int32, int32>,MpScaleFactor>>())
_scaleFactors(std::make_unique<std::map<std::pair<int32, int32>,MpScaleFactor>>()),
_worldBossEncounters(std::make_unique<std::unordered_map<ObjectGuid, MpWorldBossEncounter>>())
{
_playerData->reserve(32);
_groupData->reserve(32);
@@ -333,6 +355,9 @@ 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}
// Active world boss encounters keyed by boss GUID
std::unique_ptr<std::unordered_map<ObjectGuid, MpWorldBossEncounter>> _worldBossEncounters;
// 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
@@ -405,6 +430,11 @@ public:
// Retrieves the average players hp pool for a player level
uint32 GetPlayerHealthAvg(uint32 level) const;
// Debug method to get map sizes
std::tuple<size_t, size_t, size_t> GetMapSizes() const {
return {_instanceData->size(), _groupData->size(), _instanceCreatureData->size()};
}
// Individual Creature Scaling Multipliers
// void AddCreatureOverride(uint32 entry, CreatureOverride* override);
// MpMultipliers* GetCreatureOverride(uint32 entry);
@@ -419,6 +449,15 @@ public:
// Used at initial server load
int32 LoadScaleFactors();
// Load world boss scaling configuration from database
int32 LoadWorldBossConfig();
// World boss encounter management
void AddWorldBossEncounter(ObjectGuid bossGuid, uint32 bossEntry, uint32 zoneId);
MpWorldBossEncounter* GetWorldBossEncounter(ObjectGuid bossGuid);
MpWorldBossEncounter* GetWorldBossEncounterByZone(uint32 zoneId);
void RemoveWorldBossEncounter(ObjectGuid bossGuid);
// Load the player health average from the database
void LoadPlayerHealthAvg();

View File

@@ -8,6 +8,9 @@
#include "WorldPacket.h"
#include "UpdateMask.h"
#include "MpScriptAI.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "CellImpl.h"
#include <algorithm>
#include <cmath>
@@ -17,17 +20,167 @@ const uint32 HEADLESS_HORSEMAN = 23682;
bool MythicPlus::IsMapEligible(Map* map)
{
if (!Enabled) {
if (!Enabled || !map) {
return false;
}
// Existing dungeon support
if (map->IsDungeon()) {
return true;
}
// World boss encounters happen on outdoor maps; allow them when the feature is enabled
if (EnableWorldBoss && !map->Instanceable()) {
return true;
}
return false;
}
bool MythicPlus::IsWorldBossZone(uint32 zoneId)
{
return std::find(enabledWorldBossZones.begin(), enabledWorldBossZones.end(), zoneId) != enabledWorldBossZones.end();
}
bool MythicPlus::IsWorldBossEnabled(uint32 creatureEntry)
{
// If no specific bosses are configured, all world bosses in enabled zones are allowed
if (enabledWorldBosses.empty()) {
return true;
}
return std::find(enabledWorldBosses.begin(), enabledWorldBosses.end(), creatureEntry) != enabledWorldBosses.end();
}
void MythicPlus::ScanForNearbyGroups(Creature* worldBoss)
{
if (!worldBoss || !worldBoss->isWorldBoss()) {
return;
}
Map* map = worldBoss->GetMap();
if (!map) {
return;
}
MpWorldBossEncounter* encounter = sMpDataStore->GetWorldBossEncounter(worldBoss->GetGUID());
if (!encounter) {
MpLogger::warn("ScanForNearbyGroups: No encounter found for world boss {}", worldBoss->GetName());
return;
}
// Get all players in range
std::list<Player*> playersInRange;
Acore::AnyPlayerInObjectRangeCheck checker(worldBoss, worldBossProximityRange);
Acore::PlayerListSearcher<Acore::AnyPlayerInObjectRangeCheck> searcher(worldBoss, playersInRange, checker);
Cell::VisitWorldObjects(worldBoss, searcher, worldBossProximityRange);
// Process each player's group
for (Player* player : playersInRange) {
Group* group = player->GetGroup();
if (!group) {
continue;
}
MpGroupData* groupData = sMpDataStore->GetGroupData(group->GetGUID());
if (!groupData) {
continue; // Group doesn't have Mythic+ difficulty set
}
// Add group to encounter
encounter->AddGroup(group->GetGUID(), groupData->difficulty);
MpLogger::debug("World boss {}: Added group {} with difficulty {}",
worldBoss->GetName(),
group->GetGUID().ToString(),
static_cast<int>(groupData->difficulty));
}
}
void MythicPlus::HandleWorldBossEncounter(Creature* worldBoss)
{
if (!worldBoss || !worldBoss->isWorldBoss()) {
return;
}
Map* map = worldBoss->GetMap();
if (!map) {
return;
}
ObjectGuid bossGuid = worldBoss->GetGUID();
uint32 bossEntry = worldBoss->GetEntry();
uint32 zoneId = worldBoss->GetZoneId();
if (!zoneId) {
zoneId = worldBoss->GetAreaId();
}
if (!zoneId) {
zoneId = map->GetId();
}
// Check if encounter already exists
MpWorldBossEncounter* encounter = sMpDataStore->GetWorldBossEncounter(bossGuid);
if (!encounter) {
// Create new encounter
sMpDataStore->AddWorldBossEncounter(bossGuid, bossEntry, zoneId);
encounter = sMpDataStore->GetWorldBossEncounter(bossGuid);
if (!encounter) {
MpLogger::error("Failed to create world boss encounter for {}", worldBoss->GetName());
return;
}
MpLogger::info("Created world boss encounter: {} in zone {}", worldBoss->GetName(), zoneId);
}
// Scan for nearby groups
ScanForNearbyGroups(worldBoss);
// If no groups are participating, don't scale
if (encounter->participatingGroups.empty()) {
MpLogger::debug("World boss {}: No groups in range with Mythic+ difficulty", worldBoss->GetName());
return;
}
// Get the highest difficulty from participating groups
MpDifficulty difficulty = encounter->difficulty;
// Get appropriate multipliers based on difficulty
MpMultipliers* multipliers = nullptr;
switch (difficulty) {
case MP_DIFFICULTY_MYTHIC:
multipliers = &mythicBossModifiers;
break;
case MP_DIFFICULTY_LEGENDARY:
multipliers = &legendaryBossModifiers;
break;
case MP_DIFFICULTY_ASCENDANT:
multipliers = &ascendantBossModifiers;
break;
default:
MpLogger::warn("World boss {}: Invalid difficulty {}", worldBoss->GetName(), static_cast<int>(difficulty));
return;
}
bool alreadyScaled = encounter->scaledCreatures.contains(bossGuid) && encounter->scaledDifficulty == difficulty;
if (alreadyScaled) {
return;
}
// Scale the world boss
ScaleCreature(multipliers->avgLevel, worldBoss, multipliers, difficulty);
// Track this creature as scaled
encounter->AddScaledCreature(bossGuid);
encounter->scaledDifficulty = difficulty;
MpLogger::info("Scaled world boss {} to difficulty {} for {} groups",
worldBoss->GetName(),
static_cast<int>(difficulty),
encounter->participatingGroups.size());
}
bool MythicPlus::IsDifficultySet(Player const* player)
{
Group const* group = player->GetGroup();
@@ -130,6 +283,18 @@ bool MythicPlus::IsCreatureEligible(Creature* creature)
return true;
}
// NEW: World boss eligibility check
if (EnableWorldBoss && creature->isWorldBoss()) {
uint32 zoneId = creature->GetZoneId();
if (!zoneId) {
zoneId = creature->GetAreaId();
}
if (zoneId && IsWorldBossZone(zoneId) && IsWorldBossEnabled(creature->GetEntry())) {
return true;
}
}
// Check if the creature is a pet or summon controlled by a player
if ((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer()) {
return false;
@@ -251,12 +416,9 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
CreatureTemplate const* cInfo = creature->GetCreatureTemplate();
uint32 mapId = creature->GetMapId();
// get the map difficulty from the map instance to see if it is a heroic or normal set instance
InstanceMap *instanceMap = creature->GetMap()->ToInstanceMap();
if (!instanceMap) {
MpLogger::error("Invalid instance map ScaleCreature()");
return;
}
// When inside an instanced map we can access additional info (heroic, 25-man, etc.)
// but world bosses run on outdoor maps, so tolerate the lack of InstanceMap here.
InstanceMap* instanceMap = creature->GetMap()->ToInstanceMap();
creature->SetLevel(level);
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(
@@ -291,13 +453,17 @@ void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* m
creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana * 3.0f);
}
MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
MpInstanceData* instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
// 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);
float meleeMultiplier = multipliers->melee;
if (instanceData) {
meleeMultiplier = sMpDataStore->GetMeleeScaleFactor(creature->GetMapId(), instanceData->difficulty);
}
// Since Heroic Scaling can get out of hand. Reduce the instance multiplier by way too much 10%
if(instanceMap->IsHeroic() || instanceMap->Is25ManRaid()) {
if(instanceMap && (instanceMap->IsHeroic() || instanceMap->Is25ManRaid())) {
// if the enemy is a boss reduce it by less
meleeMultiplier *= 0.9f;
}
@@ -394,13 +560,31 @@ int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, M
return damage;
}
MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
if (!instanceData) {
MpLogger::debug("No instance data found for spell scaling, using original damage");
return damage;
}
MpInstanceData* instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
float scaleFactor = 1.0f;
float scaleFactor = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty);
if (instanceData) {
scaleFactor = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty);
} else {
MpWorldBossEncounter* encounter = sMpDataStore->GetWorldBossEncounter(creature->GetGUID());
if (!encounter) {
uint32 zoneId = creature->GetZoneId();
if (!zoneId) {
zoneId = creature->GetAreaId();
}
if (zoneId) {
encounter = sMpDataStore->GetWorldBossEncounterByZone(zoneId);
}
}
if (!encounter) {
MpLogger::debug("No scaling context found for spell damage (map {}), leaving damage unchanged", creature->GetMapId());
return damage;
}
uint32 scaleKey = encounter->zoneId ? encounter->zoneId : creature->GetMapId();
scaleFactor = sMpDataStore->GetSpellScaleFactor(scaleKey, encounter->difficulty);
}
MpLogger::debug("DAMAGE SPELL: >> ScaleFactor: {} DamageMultiplier: {}", scaleFactor, damageMultiplier);

View File

@@ -82,6 +82,11 @@ public:
float normalEnemyReducer;
float nonCreatureSpellReducer;
// World Boss Settings
bool EnableWorldBoss;
std::vector<uint32> enabledWorldBossZones;
std::vector<uint32> enabledWorldBosses;
uint32 worldBossProximityRange;
enum MP_UNIT_EVENT_TYPE
{
@@ -95,6 +100,18 @@ public:
// Map is eligible for mythic+ scaling
bool IsMapEligible(Map* map);
// Check if a zone is enabled for world boss scaling
bool IsWorldBossZone(uint32 zoneId);
// Check if a specific world boss is enabled for scaling
bool IsWorldBossEnabled(uint32 creatureEntry);
// Scan for groups near a world boss and register them to the encounter
void ScanForNearbyGroups(Creature* worldBoss);
// Handle world boss encounter creation and scaling
void HandleWorldBossEncounter(Creature* worldBoss);
// If a player difficulty is set that is eligible for mythic+ scaling
bool IsDifficultySet(Player const* player);

View File

@@ -66,6 +66,14 @@ public:
}
m_creatureUpdateTimers[creatureGuid] = 0;
// NEW: Handle world boss encounters (non-instanced)
if (sMythicPlus->EnableWorldBoss && creature->isWorldBoss()) {
// Only scan when boss is in combat to avoid unnecessary overhead
if (creature->IsInCombat()) {
sMythicPlus->HandleWorldBossEncounter(creature);
}
return; // World bosses don't use instance data
}
auto instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
// no instance data yet means dont scale.
@@ -97,8 +105,6 @@ public:
sMythicPlus->AddScaledCreature(creature, instanceData);
}
}
}
// When a new creature is added into a mythic+ map add it to the list of creatures to scale later.
@@ -110,6 +116,46 @@ public:
}
if (!sMythicPlus->IsCreatureEligible(creature)) {
// NEW: Check if this is a summon/add for a world boss encounter
if (sMythicPlus->EnableWorldBoss && creature->IsSummon()) {
// Ignore invisible triggers / non-combat helpers
if (creature->IsTrigger() || creature->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE) || creature->IsCritter()) {
return;
}
// Check if there's an active world boss encounter in this zone
uint32 zoneId = creature->GetZoneId();
if (!zoneId) {
zoneId = creature->GetAreaId();
}
MpWorldBossEncounter* encounter = sMpDataStore->GetWorldBossEncounterByZone(zoneId);
if (encounter && encounter->isActive) {
// This is likely an add for the world boss - scale it
MpDifficulty difficulty = encounter->difficulty;
MpMultipliers* multipliers = nullptr;
switch (difficulty) {
case MP_DIFFICULTY_MYTHIC:
multipliers = &sMythicPlus->mythicDungeonModifiers;
break;
case MP_DIFFICULTY_LEGENDARY:
multipliers = &sMythicPlus->legendaryDungeonModifiers;
break;
case MP_DIFFICULTY_ASCENDANT:
multipliers = &sMythicPlus->ascendantDungeonModifiers;
break;
default:
return;
}
sMythicPlus->ScaleCreature(multipliers->avgLevel, creature, multipliers, difficulty);
encounter->AddScaledCreature(creature->GetGUID());
MpLogger::debug("Scaled world boss add: {} for encounter in zone {}",
creature->GetName(), map->GetId());
return;
}
}
return;
}
@@ -126,6 +172,18 @@ public:
// Cleanup the creature from custom data used for mythic+ mod
void OnCreatureRemoveWorld(Creature* creature) override
{
// NEW: Clean up world boss encounters
if (sMythicPlus->EnableWorldBoss && creature->isWorldBoss()) {
MpWorldBossEncounter* encounter = sMpDataStore->GetWorldBossEncounter(creature->GetGUID());
if (encounter) {
encounter->isActive = false;
MpLogger::info("World boss {} removed - encounter marked inactive", creature->GetName());
}
}
// Remove any throttling state for this creature
m_creatureUpdateTimers.erase(creature->GetGUID());
sMythicPlus->RemoveCreature(creature);
}

View File

@@ -39,6 +39,7 @@ public:
{"mp", commandTableMain},
{"mythicplus", commandTableMain},
{"mp debug", HandleDebug, SEC_PLAYER, Console::No},
{"mp debug maps", HandleDebugMaps, SEC_PLAYER, Console::No},
{"mp reload", HandleReload, SEC_GAMEMASTER, Console::No},
{"advancement", HandleAdvancement, SEC_PLAYER, Console::No}
};
@@ -105,6 +106,18 @@ public:
}
static bool HandleDebugMaps(ChatHandler* handler)
{
auto [instanceDataSize, groupDataSize, creatureDataSize] = sMpDataStore->GetMapSizes();
handler->PSendSysMessage("=== MpDataStore Map Sizes ===");
handler->PSendSysMessage("Instance Data (_instanceData): {}", instanceDataSize);
handler->PSendSysMessage("Group Data (_groupData): {}", groupDataSize);
handler->PSendSysMessage("Creature Data (_instanceCreatureData): {}", creatureDataSize);
return true;
}
// sets the difficluty for the group
static bool HandleSetDifficulty(ChatHandler* handler, const std::vector<std::string>& args)
{

View File

@@ -42,43 +42,28 @@ public:
return;
}
// get the item to scale up
// get the original item template
ItemTemplate const* origItem = sObjectMgr->GetItemTemplate(LootStoreItem->itemid);
if (!origItem) {
// 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;
}
// Original item doesn't exist in database - this shouldn't happen
MpLogger::warn("Original item template not found for itemid {} in OnBeforeDropAddItem()", LootStoreItem->itemid);
LootStoreItem->itemid = 0;
return;
}
// Calculate the scaled mythic+ item ID
uint32 newItemId = origItem->ItemId + mythicSettings->itemOffset;
ItemTemplate const* newItemTempl = sObjectMgr->GetItemTemplate(newItemId);
if(!newItemTempl) {
MpLogger::warn("New Loot Item not found for itemid {} original item: {} ({})", newItemId, origItem->Name1, origItem->ItemId);
// Scaled item doesn't exist - keep the original item
// LootStoreItem->itemid is already set to the original, so just return
return;
}
// Scaled item exists - use it
LootStoreItem->itemid = newItemId;
// Revalidate the LootStoreItem to ensure consistency

View File

@@ -152,12 +152,19 @@ private:
#endif
Creature* creatureCaster = attacker->ToCreature();
MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creatureCaster->GetGUID());
if (!creatureCaster) {
MpLogger::error("Creature caster is null in map {}", attacker ? attacker->GetMap()->GetId() : 0);
return;
}
// Only scale spells for creatures that Mythic+ tracks (instances or world-boss encounters)
Map* casterMap = creatureCaster->GetMap();
if (!casterMap || !sMythicPlus->IsMapEligible(casterMap) || !sMythicPlus->IsCreatureEligible(creatureCaster)) {
return;
}
MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creatureCaster->GetGUID());
if (!creatureData) {
MpLogger::error("Failed to get creature data for {} in map {}", creatureCaster->GetName(), attacker ? attacker->GetMap()->GetId() : 0);
return;

View File

@@ -7,6 +7,8 @@
#include "ScriptMgr.h"
#include "MpEventHandlers.cpp"
#include <sstream>
class MythicPlus_WorldScript : public WorldScript
{
public:
@@ -100,6 +102,46 @@ public:
sMythicPlus->elementalMeleeReducer = sConfigMgr->GetOption<float>("MythicPlus.ElementalMeleeReducer", 0.50f);
sMythicPlus->normalEnemyReducer = sConfigMgr->GetOption<float>("MythicPlus.NormalEnemyReducer", 0.50f);
sMythicPlus->nonCreatureSpellReducer = sConfigMgr->GetOption<float>("MythicPlus.NonCreatureSpellReducer", 0.50f);
// World Boss Settings
sMythicPlus->EnableWorldBoss = sConfigMgr->GetOption<bool>("MythicPlus.WorldBoss.Enabled", true);
sMythicPlus->worldBossProximityRange = sConfigMgr->GetOption<uint32>("MythicPlus.WorldBoss.ProximityRange", 100);
// Parse enabled zones (comma-separated list)
std::string zonesStr = sConfigMgr->GetOption<std::string>("MythicPlus.WorldBoss.EnabledZones", "");
if (!zonesStr.empty()) {
std::istringstream zoneStream(zonesStr);
std::string zone;
while (std::getline(zoneStream, zone, ',')) {
// Trim whitespace
zone.erase(0, zone.find_first_not_of(" \t"));
zone.erase(zone.find_last_not_of(" \t") + 1);
if (!zone.empty()) {
sMythicPlus->enabledWorldBossZones.push_back(std::stoul(zone));
}
}
}
// Parse enabled bosses (comma-separated list)
std::string bossesStr = sConfigMgr->GetOption<std::string>("MythicPlus.WorldBoss.EnabledBosses", "");
if (!bossesStr.empty()) {
std::istringstream bossStream(bossesStr);
std::string boss;
while (std::getline(bossStream, boss, ',')) {
// Trim whitespace
boss.erase(0, boss.find_first_not_of(" \t"));
boss.erase(boss.find_last_not_of(" \t") + 1);
if (!boss.empty()) {
sMythicPlus->enabledWorldBosses.push_back(std::stoul(boss));
}
}
}
if (sMythicPlus->EnableWorldBoss) {
MpLogger::info("World Boss scaling enabled for {} zones and {} specific bosses",
sMythicPlus->enabledWorldBossZones.size(),
sMythicPlus->enabledWorldBosses.size());
}
}
void OnStartup() override
@@ -107,6 +149,12 @@ public:
int32 size = sMpDataStore->LoadScaleFactors();
MpLogger::info("Loaded {} Mythic+ Scaling Factors from database...", size);
// Load world boss configuration if enabled
if (sMythicPlus->EnableWorldBoss) {
size = sMpDataStore->LoadWorldBossConfig();
MpLogger::info("Loaded {} World Boss configurations from database...", size);
}
size = sAdvancementMgr->LoadAdvancementRanks();
MpLogger::info("Loaded {} advancement ranks...", size);