From de803cb938614d15a17b65864f40dc1522d931e6 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Sat, 22 Nov 2025 23:22:42 -0500 Subject: [PATCH] Support for World Boss encounters --- conf/mod-mythic-plus.conf.dist | 38 ++++++ src/MpDataStore.cpp | 105 +++++++++++++++ src/MpDataStore.h | 41 +++++- src/MythicPlus.cpp | 216 +++++++++++++++++++++++++++--- src/MythicPlus.h | 17 +++ src/Scripts/AllCreatureScript.cpp | 62 ++++++++- src/Scripts/CommandScript.cpp | 13 ++ src/Scripts/GlobalScript.cpp | 35 ++--- src/Scripts/UnitScript.cpp | 9 +- src/Scripts/WorldScript.cpp | 48 +++++++ 10 files changed, 539 insertions(+), 45 deletions(-) diff --git a/conf/mod-mythic-plus.conf.dist b/conf/mod-mythic-plus.conf.dist index 55db478..dea901f 100644 --- a/conf/mod-mythic-plus.conf.dist +++ b/conf/mod-mythic-plus.conf.dist @@ -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 diff --git a/src/MpDataStore.cpp b/src/MpDataStore.cpp index 2a2459d..0054038 100644 --- a/src/MpDataStore.cpp +++ b/src/MpDataStore.cpp @@ -1,3 +1,4 @@ +#include #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(); + std::string name = fields[1].Get(); + uint32 zoneId = fields[2].Get(); + bool enabled = fields[3].Get(); + float meleeBonus = fields[4].Get(); + float spellBonus = fields[5].Get(); + float healBonus = fields[6].Get(); + float hpBonus = fields[7].Get(); + int32 difficulty = fields[8].Get(); + + // 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(); diff --git a/src/MpDataStore.h b/src/MpDataStore.h index 85ae941..de25148 100644 --- a/src/MpDataStore.h +++ b/src/MpDataStore.h @@ -10,6 +10,7 @@ #include "ObjectGuid.h" #include +#include #include #include #include @@ -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 participatingGroups; + std::unordered_set 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, MpInstanceData>>()), _groupData(std::make_unique>()), _instanceCreatureData(std::make_unique>()), - _scaleFactors(std::make_unique,MpScaleFactor>>()) + _scaleFactors(std::make_unique,MpScaleFactor>>()), + _worldBossEncounters(std::make_unique>()) { _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,MpScaleFactor>> _scaleFactors; // {mapId,difficulty} + // Active world boss encounters keyed by boss GUID + std::unique_ptr> _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 _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 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(); diff --git a/src/MythicPlus.cpp b/src/MythicPlus.cpp index 9b2e262..8f82559 100644 --- a/src/MythicPlus.cpp +++ b/src/MythicPlus.cpp @@ -8,6 +8,9 @@ #include "WorldPacket.h" #include "UpdateMask.h" #include "MpScriptAI.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "CellImpl.h" #include #include @@ -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 playersInRange; + Acore::AnyPlayerInObjectRangeCheck checker(worldBoss, worldBossProximityRange); + Acore::PlayerListSearcher 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(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(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(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); diff --git a/src/MythicPlus.h b/src/MythicPlus.h index 5184137..eebdf3f 100644 --- a/src/MythicPlus.h +++ b/src/MythicPlus.h @@ -82,6 +82,11 @@ public: float normalEnemyReducer; float nonCreatureSpellReducer; + // World Boss Settings + bool EnableWorldBoss; + std::vector enabledWorldBossZones; + std::vector 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); diff --git a/src/Scripts/AllCreatureScript.cpp b/src/Scripts/AllCreatureScript.cpp index 0c57800..2a691ea 100644 --- a/src/Scripts/AllCreatureScript.cpp +++ b/src/Scripts/AllCreatureScript.cpp @@ -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); } diff --git a/src/Scripts/CommandScript.cpp b/src/Scripts/CommandScript.cpp index 61e8ef5..7126203 100644 --- a/src/Scripts/CommandScript.cpp +++ b/src/Scripts/CommandScript.cpp @@ -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& args) { diff --git a/src/Scripts/GlobalScript.cpp b/src/Scripts/GlobalScript.cpp index 8d99ae1..d7cde24 100644 --- a/src/Scripts/GlobalScript.cpp +++ b/src/Scripts/GlobalScript.cpp @@ -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 diff --git a/src/Scripts/UnitScript.cpp b/src/Scripts/UnitScript.cpp index f6ceec6..ce2b1e8 100644 --- a/src/Scripts/UnitScript.cpp +++ b/src/Scripts/UnitScript.cpp @@ -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; diff --git a/src/Scripts/WorldScript.cpp b/src/Scripts/WorldScript.cpp index d023f2b..8eff56a 100644 --- a/src/Scripts/WorldScript.cpp +++ b/src/Scripts/WorldScript.cpp @@ -7,6 +7,8 @@ #include "ScriptMgr.h" #include "MpEventHandlers.cpp" +#include + class MythicPlus_WorldScript : public WorldScript { public: @@ -100,6 +102,46 @@ public: sMythicPlus->elementalMeleeReducer = sConfigMgr->GetOption("MythicPlus.ElementalMeleeReducer", 0.50f); sMythicPlus->normalEnemyReducer = sConfigMgr->GetOption("MythicPlus.NormalEnemyReducer", 0.50f); sMythicPlus->nonCreatureSpellReducer = sConfigMgr->GetOption("MythicPlus.NonCreatureSpellReducer", 0.50f); + + // World Boss Settings + sMythicPlus->EnableWorldBoss = sConfigMgr->GetOption("MythicPlus.WorldBoss.Enabled", true); + sMythicPlus->worldBossProximityRange = sConfigMgr->GetOption("MythicPlus.WorldBoss.ProximityRange", 100); + + // Parse enabled zones (comma-separated list) + std::string zonesStr = sConfigMgr->GetOption("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("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);