From a3afcbd2fe95ea58f5072df80021c24a5721f8cd Mon Sep 17 00:00:00 2001 From: sindoring Date: Sat, 30 Aug 2025 19:38:04 +0400 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20mythic+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lua_scripts/Battlepass/Battlepass_Server.lua | 0 lua_scripts/MythicPlus/Mythic_Client.lua | 1527 +++++++++++ lua_scripts/MythicPlus/Mythic_Locale.lua | 948 +++++++ lua_scripts/MythicPlus/Mythic_Server.lua | 2354 +++++++++++++++++ .../characters/character_mythic_history.sql | 45 + .../characters/character_mythic_keys.sql | 32 + .../characters/character_mythic_rating.sql | 52 + .../characters/character_mythic_vault.sql | 41 + .../character_mythic_weekly_affixes.sql | 33 + sql/Mythic+/world/creature_and_keystones.sql | 1304 +++++++++ sql/Mythic+/world/world_mythic_loot.sql | 67 + sql/Mythic+/world/world_vault_loot.sql | 34 + 12 files changed, 6437 insertions(+) create mode 100644 lua_scripts/Battlepass/Battlepass_Server.lua create mode 100644 lua_scripts/MythicPlus/Mythic_Client.lua create mode 100644 lua_scripts/MythicPlus/Mythic_Locale.lua create mode 100644 lua_scripts/MythicPlus/Mythic_Server.lua create mode 100644 sql/Mythic+/characters/character_mythic_history.sql create mode 100644 sql/Mythic+/characters/character_mythic_keys.sql create mode 100644 sql/Mythic+/characters/character_mythic_rating.sql create mode 100644 sql/Mythic+/characters/character_mythic_vault.sql create mode 100644 sql/Mythic+/characters/character_mythic_weekly_affixes.sql create mode 100644 sql/Mythic+/world/creature_and_keystones.sql create mode 100644 sql/Mythic+/world/world_mythic_loot.sql create mode 100644 sql/Mythic+/world/world_vault_loot.sql diff --git a/lua_scripts/Battlepass/Battlepass_Server.lua b/lua_scripts/Battlepass/Battlepass_Server.lua new file mode 100644 index 0000000..e69de29 diff --git a/lua_scripts/MythicPlus/Mythic_Client.lua b/lua_scripts/MythicPlus/Mythic_Client.lua new file mode 100644 index 0000000..0ce9d15 --- /dev/null +++ b/lua_scripts/MythicPlus/Mythic_Client.lua @@ -0,0 +1,1527 @@ +local activeAffixes = {} +local AIO = AIO or require("AIO") +local L = { + Text = function(self, category, key, localeIndex) + if not self[category] or not self[category][key] then + return key + end + + if not self[category][key][localeIndex] then + return self[category][key][0] + end + + return self[category][key][localeIndex] + end, + Items = {}, + Dungeons = {}, + UI = {} +} +if AIO.AddAddon() then + return +end + +MythicPlusCharRunState = { + active = false, + mapId = nil, + tier = nil, + duration = nil, + elapsed = nil, + bossNames = {}, + killedBosses = {}, + potentialGain = 0, + penalty = 0, + bonus = 0, + deaths = 0, + maxDeaths = 0, + enemyForces = { + current = 0, + required = 0, + percentage = 0, + completed = false + }, + saveTime = nil +} + +AIO.AddSavedVarChar("MythicPlusCharRunState") + +local MythicHandlers = AIO.AddHandlers("AIO_Mythic", {}) +local lastKeystoneLink = nil +local lastMapName = nil +local lastTierLevel = nil +local fmt, floor = string.format, math.floor +local localeDataReceived = false + +function MythicHandlers.ReceiveLocaleData(_, category, entries) + if type(entries) == "table" then + L[category] = entries + if L.Items and L.Dungeons and L.UI then + localeDataReceived = true + if MythicPlusFrame then + UpdateLocalizedElements() + end + end + end +end + +function MythicGetText(category, key) + local localeIndex = GetLocale() and MythicGetLocaleIndex() or 0 + return L:Text(category, key, localeIndex) +end + +function MythicGetLocaleIndex() + local locale = GetLocale() + local localeMap = { + ["enUS"] = 0, ["enGB"] = 0, + ["koKR"] = 1, + ["frFR"] = 2, + ["deDE"] = 3, + ["zhCN"] = 4, + ["zhTW"] = 5, + ["esES"] = 6, + ["esMX"] = 7, + ["ruRU"] = 8 + } + return localeMap[locale] or 0 +end + +function UpdateLocalizedElements() + if DUNGEONS then + for mapId, dungeonData in pairs(DUNGEONS) do + dungeonData.name = MythicGetText("Dungeons", dungeonData.originalName) + end + end + + if MythicPlusFrame then + if tabs then + for i, tab in ipairs(tabs) do + if i == 1 then + tab:SetText(MythicGetText("UI", "Overview")) + elseif i == 2 then + tab:SetText(MythicGetText("UI", "Score")) + elseif i == 3 then + tab:SetText(MythicGetText("UI", "Leaderboard")) + end + end + end + + if MythicPlusFrame.overviewTitle then + MythicPlusFrame.overviewTitle:SetText(MythicGetText("UI", "Overview")) + end + if MythicPlusFrame.scoreTitle then + MythicPlusFrame.scoreTitle:SetText(MythicGetText("UI", "Score")) + end + if MythicPlusFrame.leaderboardTitle then + MythicPlusFrame.leaderboardTitle:SetText(MythicGetText("UI", "Leaderboard")) + end + + if frame and frame.affixButtons then + for _, button in ipairs(frame.affixButtons) do + if button.affixName then + local isActive = frame.currentAffixes and + (button.affixName == frame.currentAffixes[1] or + button.affixName == frame.currentAffixes[2] or + button.affixName == frame.currentAffixes[3]) + + button.label:SetText((isActive and "|cff00ff00" or "") .. MythicGetText("UI", button.affixName)) + button:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + local color = AFFIXES[button.affixName].color or "|cffffffff" + GameTooltip:SetText(color .. MythicGetText("UI", button.affixName) .. "|r") + GameTooltip:AddLine(AFFIXES[button.affixName].description or "", 1, 1, 1, true) + GameTooltip:Show() + end) + end + end + end + if frame and frame.scoreButtons then + for i, button in ipairs(frame.scoreButtons) do + local mapId = DUNGEON_ORDER[i] + if mapId and DUNGEONS[mapId] then + button.nameLabel:SetText(DUNGEONS[mapId].name) + end + end + end + if frame and frame.leaderboardButtons then + for i, button in ipairs(frame.leaderboardButtons) do + local mapId = DUNGEON_ORDER[i] + if mapId then + button:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:SetText(MythicGetText("Dungeons", DUNGEONS[mapId].originalName)) + GameTooltip:Show() + end) + end + end + end + if MythicPlusFrame:IsVisible() and MythicPlusFrame.currentTab then + SetActiveTab(MythicPlusFrame.currentTab) + end + end +end + +local DUNGEONS = { + [574] = { originalName = "Utgarde Keep", name = "Utgarde Keep", icon = "Interface\\Icons\\achievement_boss_svalasorrowgrave" }, + [575] = { originalName = "Utgarde Pinnacle", name = "Utgarde Pinnacle", icon = "Interface\\Icons\\achievement_boss_kingymiron" }, + [576] = { originalName = "The Nexus", name = "The Nexus", icon = "Interface\\Icons\\spell_frost_frozencore" }, + [578] = { originalName = "The Oculus", name = "The Oculus", icon = "Interface\\Icons\\achievement_boss_eregos" }, + [595] = { originalName = "The Culling of Stratholme", name = "The Culling of Stratholme", icon = "Interface\\Icons\\achievement_dungeon_cotstratholme_normal" }, + [599] = { originalName = "Halls of Stone", name = "Halls of Stone", icon = "Interface\\Icons\\achievement_boss_sjonnir" }, + [600] = { originalName = "Drak'Tharon Keep", name = "Drak'Tharon Keep", icon = "Interface\\Icons\\inv_bone_skull_04" }, + [601] = { originalName = "Azjol-Nerub", name = "Azjol-Nerub", icon = "Interface\\Icons\\inv_misc_head_nerubian_01" }, + [602] = { originalName = "Halls of Lightning", name = "Halls of Lightning", icon = "Interface\\Icons\\achievement_boss_archaedas" }, + [604] = { originalName = "Gundrak", name = "Gundrak", icon = "Interface\\Icons\\achievement_boss_galdarah" }, + [608] = { originalName = "The Violet Hold", name = "The Violet Hold", icon = "Interface\\Icons\\achievement_reputation_kirintor" }, + [619] = { originalName = "Ahn'kahet: The Old Kingdom", name = "Ahn'kahet: The Old Kingdom", icon = "Interface\\Icons\\achievement_boss_yoggsaron_01" }, + [632] = { originalName = "The Forge of Souls", name = "The Forge of Souls", icon = "Interface\\Icons\\achievement_boss_devourerofsouls" }, + [650] = { originalName = "Trial of the Champion", name = "Trial of the Champion", icon = "Interface\\Icons\\achievement_reputation_argentcrusader" }, + [658] = { originalName = "Pit of Saron", name = "Pit of Saron", icon = "Interface\\Icons\\achievement_boss_scourgelordtyrannus" }, + [668] = { originalName = "Halls of Reflection", name = "Halls of Reflection", icon = "Interface\\Icons\\achievement_dungeon_icecrown_frostmourne" }, +} + +AIO.Handle("AIO_Mythic", "RequestLocaleData", "Items") +AIO.Handle("AIO_Mythic", "RequestLocaleData", "Dungeons") +AIO.Handle("AIO_Mythic", "RequestLocaleData", "UI") +AIO.Handle("AIO_Mythic", "RequestRunState") + +local DUNGEON_ORDER = { + 574, 575, 576, 578, + 595, 599, 600, 601, + 602, 604, 608, 619, + 632, 650, 658, 668 +} + +local AFFIXES = { + ["Enrage"] = { icon = "Interface\\Icons\\spell_nature_shamanrage", color = "|cffff0000", description = function() return MythicGetText("UI", "Enrage Description") end }, + ["Rejuvenating"] = { icon = "Interface\\Icons\\ability_druid_empoweredrejuvination", color = "|cff00ff00", description = function() return MythicGetText("UI", "Rejuvenating Description") end }, + ["Turtling"] = { icon = "Interface\\Icons\\ability_warrior_shieldmastery", color = "|cffffff00", description = function() return MythicGetText("UI", "Turtling Description") end }, + ["Shamanism"] = { icon = "Interface\\Icons\\spell_fire_totemofwrath", color = "|cffa335ee", description = function() return MythicGetText("UI", "Shamanism Description") end }, + ["Magus"] = { icon = "Interface\\Icons\\ability_mage_hotstreak", color = "|cff3399ff", description = function() return MythicGetText("UI", "Magus Description") end }, + ["Priest Empowered"] = { icon = "Interface\\Icons\\spell_holy_searinglightpriest", color = "|cffcccccc", description = function() return MythicGetText("UI", "Priest Empowered Description") end }, + ["Demonism"] = { icon = "Interface\\Icons\\ability_warlock_demonicpower", color = "|cff8b0000", description = function() return MythicGetText("UI", "Demonism Description") end }, + ["Falling Stars"] = { icon = "Interface\\Icons\\ability_druid_starfall", color = "|cff66ccff", description = function() return MythicGetText("UI", "Falling Stars Description") end }, +} + +function MythicHandlers.ReceiveMapNameAndTier(_, mapName, tier) + local originalMapName = mapName + for id, dungeon in pairs(DUNGEONS) do + if dungeon.originalName == mapName then + mapName = MythicGetText("Dungeons", dungeon.originalName) + break + end + end + + lastMapName = mapName + lastTierLevel = tier + + if GameTooltip:IsShown() then + local name, link = GameTooltip:GetItem() + if link and name and (string.find(name, "Mythic Keystone") or string.find(name, MythicGetText("Items", "Mythic Keystone"))) then + GameTooltip:Hide() + GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR") + GameTooltip:SetHyperlink(link) + end + end +end + +local lineAdded = false +function OnTooltipSetItem(tooltip) + local name, link = tooltip:GetItem() + local englishName = "Mythic Keystone" + local localizedName = MythicGetText("Items", "Mythic Keystone") + + if not name or (not string.find(name, englishName) and not string.find(name, localizedName)) then + return + end + + if link ~= lastKeystoneLink then + lastKeystoneLink = link + lastMapName = "Loading..." + lastTierLevel = "Loading..." + AIO.Handle("AIO_Mythic", "RequestMapNameAndTier") + end + local line = _G[tooltip:GetName() .. "TextLeft2"] + if line then + local tierText = lastTierLevel and lastTierLevel ~= "Loading..." and ("+" .. lastTierLevel) or "" + line:SetText("|cffa335ee" .. MythicGetText("UI", "Mythic") .. tierText .. " |r" .. (lastMapName or "Loading...")) + line:Show() + tooltip:Show() + end +end + +local function OnTooltipCleared(tooltip) + lineAdded = false +end + +GameTooltip:HookScript("OnTooltipSetItem", OnTooltipSetItem) +GameTooltip:HookScript("OnTooltipCleared", OnTooltipCleared) + +local mythicMiniButton = CreateFrame("Button", "MythicPlusMiniButton", Minimap) +mythicMiniButton:SetSize(24, 24) +mythicMiniButton:SetFrameStrata("MEDIUM") +mythicMiniButton:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") + +mythicMiniButton:SetNormalTexture("Interface\\AddOns\\Blizzard_AchievementUI\\UI-Achievement-MinimapButton") +mythicMiniButton:SetPushedTexture("Interface\\AddOns\\Blizzard_AchievementUI\\UI-Achievement-MinimapButton-Down") + +local icon = mythicMiniButton:CreateTexture(nil, "ARTWORK") +icon:SetTexture("Interface\\Icons\\achievement_bg_wineos_underxminutes") +icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) +icon:SetAllPoints(mythicMiniButton) + +local border = mythicMiniButton:CreateTexture(nil, "OVERLAY") +border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") +border:SetSize(64, 64) +border:SetPoint("CENTER", mythicMiniButton, "CENTER", 12, -12) + +mythicMiniButton:SetPoint("TOPLEFT", Minimap, "BOTTOMLEFT", -20, 70) + +mythicMiniButton:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_LEFT") + GameTooltip:SetText(MythicGetText("UI", "Mythic+")) + if self.hasVaultLoot then + GameTooltip:AddLine(MythicGetText("UI", "There is loot in your Vault in Dalaran City"), 1, 1, 0) + end + GameTooltip:Show() +end) +mythicMiniButton:SetScript("OnLeave", GameTooltip_Hide) + +mythicMiniButton:SetScript("OnClick", function() + if MythicPlusFrame:IsShown() then + PlaySound("igCharacterInfoClose") + MythicPlusFrame:Hide() + else + PlaySound("igCharacterInfoOpen") + MythicPlusFrame:Show() + end +end) + +function MythicHandlers.ReceiveWeeklyAffixes(_, affix1, affix2, affix3) + local function colorize(name) + return (AFFIXES[name].color or "|cffffffff") .. MythicGetText("UI", name) .. "|r" + end + + local text = MythicGetText("UI", "This week's affixes:") .. " " .. + colorize(affix1) .. ", " .. + colorize(affix2) .. ", " .. + colorize(affix3) + + MythicPlusFrame.affixText:SetText(text) + + MythicPlusFrame.currentAffixes = {affix1, affix2, affix3} + + for _, button in ipairs(MythicPlusFrame.affixButtons) do + local name = button.affixName + local label = button.label + local isActive = name == affix1 or name == affix2 or name == affix3 + label:SetText((isActive and "|cff00ff00" or "") .. MythicGetText("UI", name)) + end +end + +MythicPlusFrame = CreateFrame("Frame", "MythicPlusFrame", UIParent) +local frame = MythicPlusFrame + +if localeDataReceived then + UpdateLocalizedElements() +end + +frame:SetSize(720, 480) +frame:SetPoint("CENTER", UIParent, "CENTER", 270, 270) +frame:SetToplevel(true) +frame:SetClampedToScreen(true) +frame:SetMovable(true) +frame:EnableMouse(true) +frame:RegisterForDrag("LeftButton") +frame:SetScript("OnDragStart", frame.StartMoving) +frame:SetScript("OnHide", function(self)PlaySound("igCharacterInfoClose") self:StopMovingOrSizing() end) +frame:SetScript("OnDragStop", frame.StopMovingOrSizing) + +for _, region in ipairs({frame:GetRegions()}) do + if region:GetObjectType() == "Texture" then + region:Hide() + end +end + +local paperBG = frame:CreateTexture(nil, "BACKGROUND") +paperBG:SetTexture("Interface\\MythicPlus\\textures\\Paper") +paperBG:SetAllPoints(frame) +paperBG:SetTexCoord(0, 1, 75/1024, (1024-75)/1024) + +local borderSize = 167 +local borderCornerTexCoordW = 167/256 +local borderCornerTexCoordH = 168/256 +local borderExpansion = math.floor(borderSize * 0.1) + +local topLeftCorner = frame:CreateTexture(nil, "ARTWORK") +topLeftCorner:SetTexture("Interface\\MythicPlus\\textures\\Border") +topLeftCorner:SetSize(borderSize, borderSize) +topLeftCorner:SetPoint("TOPLEFT", frame, "TOPLEFT", -borderExpansion, borderExpansion) +topLeftCorner:SetTexCoord(0, borderCornerTexCoordW, 0, borderCornerTexCoordH) + +local topRightCorner = frame:CreateTexture(nil, "ARTWORK") +topRightCorner:SetTexture("Interface\\MythicPlus\\textures\\Border") +topRightCorner:SetSize(borderSize, borderSize) +topRightCorner:SetPoint("TOPRIGHT", frame, "TOPRIGHT", borderExpansion, borderExpansion) +topRightCorner:SetTexCoord(borderCornerTexCoordW, 0, 0, borderCornerTexCoordH) + +local bottomLeftCorner = frame:CreateTexture(nil, "ARTWORK") +bottomLeftCorner:SetTexture("Interface\\MythicPlus\\textures\\Border") +bottomLeftCorner:SetSize(borderSize, borderSize) +bottomLeftCorner:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", -borderExpansion, -borderExpansion) +bottomLeftCorner:SetTexCoord(0, borderCornerTexCoordW, borderCornerTexCoordH, 0) + +local bottomRightCorner = frame:CreateTexture(nil, "ARTWORK") +bottomRightCorner:SetTexture("Interface\\MythicPlus\\textures\\Border") +bottomRightCorner:SetSize(borderSize, borderSize) +bottomRightCorner:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", borderExpansion, -borderExpansion) +bottomRightCorner:SetTexCoord(borderCornerTexCoordW, 0, borderCornerTexCoordH, 0) + +local topBorder = frame:CreateTexture(nil, "ARTWORK") +topBorder:SetTexture("Interface\\MythicPlus\\textures\\Border_Top") +topBorder:SetHeight(32) +topBorder:SetPoint("TOPLEFT", topLeftCorner, "TOPRIGHT", 0, 0) +topBorder:SetPoint("TOPRIGHT", topRightCorner, "TOPLEFT", 0, 0) +local topBorderWidth = 720 + (borderExpansion * 2) - (borderSize * 2) +local topTexCoordRight = math.min(1.0, topBorderWidth / 1024) +topBorder:SetTexCoord(0, topTexCoordRight, 0, 1) + +local bottomBorder = frame:CreateTexture(nil, "ARTWORK") +bottomBorder:SetTexture("Interface\\MythicPlus\\textures\\Border_Bottom") +bottomBorder:SetHeight(32) +bottomBorder:SetPoint("BOTTOMLEFT", bottomLeftCorner, "BOTTOMRIGHT", 0, 0) +bottomBorder:SetPoint("BOTTOMRIGHT", bottomRightCorner, "BOTTOMLEFT", 0, 0) +local bottomTexCoordRight = math.min(1.0, topBorderWidth / 1024) +bottomBorder:SetTexCoord(0, bottomTexCoordRight, 0, 1) + +local leftBorder = frame:CreateTexture(nil, "ARTWORK") +leftBorder:SetTexture("Interface\\MythicPlus\\textures\\Border_Left") +leftBorder:SetWidth(32) +leftBorder:SetPoint("TOPLEFT", topLeftCorner, "BOTTOMLEFT", -1, 0) +leftBorder:SetPoint("BOTTOMLEFT", bottomLeftCorner, "TOPLEFT", -1, 0) +local leftBorderHeight = 480 + (borderExpansion * 2) - (borderSize * 2) +local leftTexCoordBottom = math.min(1.0, leftBorderHeight / 1024) +leftBorder:SetTexCoord(0, 1, 0, leftTexCoordBottom) + +local rightBorder = frame:CreateTexture(nil, "ARTWORK") +rightBorder:SetTexture("Interface\\MythicPlus\\textures\\Border_Right") +rightBorder:SetWidth(32) +rightBorder:SetPoint("TOPRIGHT", topRightCorner, "BOTTOMRIGHT", 1, 0) +rightBorder:SetPoint("BOTTOMRIGHT", bottomRightCorner, "TOPRIGHT", 1, 0) +local rightTexCoordBottom = math.min(1.0, leftBorderHeight / 1024) +rightBorder:SetTexCoord(0, 1, 0, rightTexCoordBottom) + +table.insert(UISpecialFrames, "MythicPlusFrame") +frame:Hide() + +local tabBackground = CreateFrame("Frame", nil, frame) +tabBackground:SetSize(120, 400) +tabBackground:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -20) + +local function CreateStyledTabButton(parent, text, index, onClick) + local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate") + button:SetSize(100, 32) + button:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -20 - ((index - 1) * 35)) + button:SetText(text) + button:SetNormalFontObject("GameFontNormal") + button:GetNormalTexture():SetVertexColor(0.8, 0.2, 0.2, 1) + button:GetHighlightTexture():SetVertexColor(1, 0.3, 0.3, 1) + button:GetPushedTexture():SetVertexColor(0.6, 0.1, 0.1, 1) + button.isSelected = false + button:SetScript("OnClick", function() + onClick(index) + end) + + return button +end + +local function CreateBannerTitle(parent, text, anchorPoint) + local banner = parent:CreateTexture(nil, "OVERLAY") + banner:SetTexture("Interface\\MythicPlus\\textures\\Banner") + banner:SetSize(256, 64) + banner:SetPoint("TOP", parent, "TOP", 40, anchorPoint) + + local title = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") + title:SetPoint("CENTER", banner, "CENTER", 0, 0) + title:SetText(text) + title:SetFont("Fonts\\FRIZQT__.TTF", 20, "OUTLINE") + title:SetTextColor(1, 1, 1) + + return banner, title +end + +local overviewBanner, overviewTitle = CreateBannerTitle(frame, MythicGetText("UI", "Overview"), 0) +frame.overviewTitle = overviewTitle +frame.overviewBanner = overviewBanner + +local scoreBanner, scoreTitle = CreateBannerTitle(frame, MythicGetText("UI", "Score"), 0) +frame.scoreTitle = scoreTitle +frame.scoreBanner = scoreBanner +scoreBanner:Hide() +scoreTitle:Hide() + +local leaderboardBanner, leaderboardTitle = CreateBannerTitle(frame, MythicGetText("UI", "Leaderboard"), 0) +frame.leaderboardTitle = leaderboardTitle +frame.leaderboardBanner = leaderboardBanner +leaderboardBanner:Hide() +leaderboardTitle:Hide() + +local affixText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal") +affixText:SetPoint("TOP", overviewBanner, "BOTTOM", 0, -10) +affixText:SetJustifyH("CENTER") +affixText:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE") +affixText:SetText(MythicGetText("UI", "This week's affixes:") .. " Loading...") +frame.affixText = affixText + +local scoreText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") +scoreText:SetPoint("TOP", scoreBanner, "BOTTOM", 0, -10) +scoreText:SetText("Score: Loading...") +frame.scoreText = scoreText +scoreText:Hide() + +local affixNames = { + "Enrage", "Rejuvenating", "Turtling", "Shamanism", + "Magus", "Priest Empowered", "Demonism", "Falling Stars" +} + +frame.affixButtons = {} +local buttonSize = 80 +local buttonSpacing = 30 +local columns = 4 +local totalWidth = columns * buttonSize + (columns - 1) * buttonSpacing +local startX = -(totalWidth / 2) + 40 + +for i, name in ipairs(affixNames) do + local button = CreateFrame("Button", nil, frame) + button:SetSize(buttonSize, buttonSize) + + local row = math.floor((i - 1) / columns) + local col = (i - 1) % columns + button:SetPoint( + "TOP", + affixText, + "BOTTOM", + startX + col * (buttonSize + buttonSpacing), + -40 - row * (buttonSize + buttonSpacing) + ) + + local icon = button:CreateTexture(nil, "BACKGROUND") + icon:SetAllPoints() + icon:SetTexture(AFFIXES[name].icon or "Interface\\Icons\\spell_nature_polymorph") + + local label = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + label:SetPoint("TOP", button, "BOTTOM", 0, -2) + label:SetFont("Fonts\\FRIZQT__.TTF", 12, "") + label:SetText(name) + + button.affixName = name + button.label = label + + button:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + local color = AFFIXES[name].color or "|cffffffff" + GameTooltip:SetText(color .. MythicGetText("UI", name) .. "|r") + local description = AFFIXES[name].description + if type(description) == "function" then + description = description() + end + GameTooltip:AddLine(description or "", 1, 1, 1, true) + GameTooltip:Show() + end) + + button:SetScript("OnLeave", function() GameTooltip:Hide() end) + + frame.affixButtons[i] = button +end + +frame.scoreButtons = {} +local scoreButtonSize = 50 +local scoreCols = 4 +local scoreButtonSpacingX = 60 +local scoreButtonSpacingY = 26 +local scoreStartX = -(scoreCols * scoreButtonSize + (scoreCols - 1) * scoreButtonSpacingX) / 2 + 25 + +for i, mapId in ipairs(DUNGEON_ORDER) do + local name = DUNGEONS[mapId].name or ("Map " .. mapId) + local icon = DUNGEONS[mapId].icon or "Interface\\Icons\\inv_misc_questionmark" + + local button = CreateFrame("Button", nil, frame) + button:SetSize(scoreButtonSize, scoreButtonSize) + + local row = math.floor((i - 1) / scoreCols) + local col = (i - 1) % scoreCols + button:SetPoint( + "TOP", + scoreText, + "BOTTOM", + scoreStartX + col * (scoreButtonSize + scoreButtonSpacingX), + -20 - row * (scoreButtonSize + scoreButtonSpacingY) + ) + + local tex = button:CreateTexture(nil, "BACKGROUND") + tex:SetAllPoints() + tex:SetTexture(icon) + local scoreFontSize = 16 + local scoreLabel = button:CreateFontString(nil, "OVERLAY") + scoreLabel:SetPoint("CENTER", button, "CENTER", 0, -15) + scoreLabel:SetFont("Fonts\\FRIZQT__.TTF", scoreFontSize, "OUTLINE, THICK") + scoreLabel:SetText("0") + button.scoreLabel = scoreLabel + + local nameLabel = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + nameLabel:SetPoint("TOP", button, "BOTTOM", 0, 0) + nameLabel:SetWidth(scoreButtonSize + 40) + nameLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE") + nameLabel:SetWordWrap(true) + nameLabel:SetText(DUNGEONS[mapId].name or ("Map " .. mapId)) + nameLabel:SetJustifyH("CENTER") + nameLabel:SetJustifyV("TOP") + button.nameLabel = nameLabel + + button:Hide() + frame.scoreButtons[i] = button +end + +frame.podiums = {} +local podiumSpecs = { + { height = 60, color = {0.8, 0.8, 0.8,}, x = -140 }, + { height = 90, color = {0.95, 0.84, 0.0}, x = 0 }, + { height = 40, color = {0.72, 0.45, 0.2}, x = 140 }, +} + +for i, spec in ipairs(podiumSpecs) do + local podium = CreateFrame("Frame", nil, frame) + podium:SetSize(135, spec.height) + podium:SetPoint("BOTTOM", leaderboardBanner, "TOP", spec.x + 0, -200) + + local bg = podium:CreateTexture(nil, "BACKGROUND") + bg:SetAllPoints() + bg:SetTexture(spec.color[1], spec.color[2], spec.color[3], 1) + + local name = podium:CreateFontString(nil, "OVERLAY", "GameFontNormal") + name:SetPoint("BOTTOM", podium, "TOP", 0, 4) + name:SetText("—") + podium.name = name + + local score = podium:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") + score:SetPoint("CENTER", podium, "CENTER") + score:SetText("0") + podium.score = score + + podium:Hide() + frame.podiums[i] = podium +end + +frame.leaderboardButtons = {} +local lbButtonSize = 53 +local lbSpacingX = 20 +local lbSpacingY = 15 +local lbCols = 6 +local lbStartX = -(lbCols * lbButtonSize + (lbCols - 1) * lbSpacingX) / 2 + 166 + +for i, mapId in ipairs(DUNGEON_ORDER) do + local button = CreateFrame("Button", nil, frame) + button:SetSize(lbButtonSize, lbButtonSize) + + local row = math.floor((i - 1) / lbCols) + local col = (i - 1) % lbCols + button:SetPoint( + "TOP", + frame.podiums[1], + "BOTTOM", + lbStartX + col * (lbButtonSize + lbSpacingX), + -20 - row * (lbButtonSize + lbSpacingY) + ) + + local icon = button:CreateTexture(nil, "BACKGROUND") + icon:SetAllPoints() + icon:SetTexture(DUNGEONS[mapId].icon or "Interface\\Icons\\inv_misc_questionmark") + + button:Hide() + frame.leaderboardButtons[i] = button +end + +local tabs = {} +local function SetActiveTab(index) + PlaySound("igMainMenuOptionCheckBoxOn") + frame.currentTab = index + + for i, tab in ipairs(tabs) do + if i == index then + tab.isSelected = true + tab:GetNormalTexture():SetVertexColor(1, 0.4, 0.4, 1) + tab:GetHighlightTexture():SetVertexColor(1, 0.5, 0.5, 1) + else + tab.isSelected = false + tab:GetNormalTexture():SetVertexColor(0.8, 0.2, 0.2, 1) + tab:GetHighlightTexture():SetVertexColor(1, 0.3, 0.3, 1) + end + end + + local showOverview = index == 1 + local showScore = index == 2 + local showLeaderboard = index == 3 + if showOverview then + frame.overviewBanner:Show() + frame.overviewTitle:Show() + frame.affixText:Show() + for _, button in ipairs(frame.affixButtons) do + button:Show() + end + else + frame.overviewBanner:Hide() + frame.overviewTitle:Hide() + frame.affixText:Hide() + for _, button in ipairs(frame.affixButtons) do + button:Hide() + end + end + if showScore then + frame.scoreBanner:Show() + frame.scoreTitle:Show() + frame.scoreText:Show() + for _, btn in ipairs(frame.scoreButtons) do + btn:Show() + end + AIO.Handle("AIO_Mythic", "RequestTotalPoints") + else + frame.scoreBanner:Hide() + frame.scoreTitle:Hide() + frame.scoreText:Hide() + for _, btn in ipairs(frame.scoreButtons) do + btn:Hide() + end + end + if showLeaderboard then + frame.leaderboardBanner:Show() + frame.leaderboardTitle:Show() + for _, podium in ipairs(frame.podiums) do podium:Show() end + for _, btn in ipairs(frame.leaderboardButtons) do btn:Show() end + AIO.Handle("AIO_Mythic", "RequestLeaderboard") + else + frame.leaderboardBanner:Hide() + frame.leaderboardTitle:Hide() + for _, podium in ipairs(frame.podiums) do podium:Hide() end + for _, btn in ipairs(frame.leaderboardButtons) do btn:Hide() end + end + + if showOverview then + AIO.Handle("AIO_Mythic", "RequestWeeklyAffixes") + end +end + +tabs[1] = CreateStyledTabButton(tabBackground, MythicGetText("UI", "Overview"), 1, SetActiveTab) +tabs[2] = CreateStyledTabButton(tabBackground, MythicGetText("UI", "Score"), 2, SetActiveTab) +tabs[3] = CreateStyledTabButton(tabBackground, MythicGetText("UI", "Leaderboard"), 3, SetActiveTab) + +SetActiveTab(1) + +function MythicHandlers.ReceiveLeaderboard(_, topThree, dungeonTop) + local visualToRank = {2, 1, 3} + for i = 1, 3 do + local rank = visualToRank[i] + local entry = topThree[rank] + local podium = frame.podiums[i] + + if entry then + podium.name:SetText(entry.name) + podium.score:SetText(string.format("%.2f", entry.points)) + podium.name:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE, THICK") + podium.score:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE") + else + podium.name:SetText("—") + podium.score:SetText("0") + podium.name:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE, THICK") + podium.score:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE") + end + end + + for i, mapId in ipairs(DUNGEON_ORDER) do + local top = dungeonTop[tostring(mapId)] + local button = frame.leaderboardButtons[i] + if button then + button:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:SetText(MythicGetText("Dungeons", DUNGEONS[mapId].originalName)) + + if top then + GameTooltip:AddLine(MythicGetText("UI", "Highest Score:"), 1, 1, 0) + GameTooltip:AddLine(" " .. top.name .. ": " .. top.score, 1, 1, 1) + if top.highestKey and top.highestKey > 0 then + local highestKeyText = MythicGetText("UI", "Highest Key +%d by:"):format(top.highestKey) + GameTooltip:AddLine(highestKeyText, 1, 1, 0) + if top.keyHolderNames and #top.keyHolderNames > 0 then + for _, memberName in ipairs(top.keyHolderNames) do + GameTooltip:AddLine(" " .. memberName, 0.8, 1, 0.8) + end + else + GameTooltip:AddLine(" " .. MythicGetText("UI", "Unknown"), 0.8, 1, 0.8) + end + else + GameTooltip:AddLine(MythicGetText("UI", "Highest Key: None completed in time"), 0.7, 0.7, 0.7) + end + else + GameTooltip:AddLine(MythicGetText("UI", "No records available"), 0.7, 0.7, 0.7) + end + GameTooltip:Show() + end) + button:SetScript("OnLeave", GameTooltip_Hide) + end + end +end + +local affixNames = { + "Enrage", "Rejuvenating", "Turtling", "Shamanism", + "Magus", "Priest Empowered", "Demonism", "Falling Stars" +} + +local tabs = {} +local function SetActiveTab(index) + PlaySound("igMainMenuOptionCheckBoxOn") + for i, tab in ipairs(tabs) do + tab.bg:SetTexture(i == index and 0.8 or 0.3, i == index and 0.8 or 0.3, i == index and 0.2 or 0.3, 0.5) + end + + local showOverview = index == 1 + local showScore = index == 2 + local showLeaderboard = index == 3 + + if showOverview then + frame.overviewTitle:Show() + frame.affixText:Show() + for _, button in ipairs(frame.affixButtons) do + button:Show() + end + else + frame.overviewTitle:Hide() + frame.affixText:Hide() + for _, button in ipairs(frame.affixButtons) do + button:Hide() + end + end + + if showScore then + frame.scoreTitle:Show() + frame.scoreText:Show() + for _, btn in ipairs(frame.scoreButtons) do + btn:Show() + end + AIO.Handle("AIO_Mythic", "RequestTotalPoints") + else + frame.scoreTitle:Hide() + frame.scoreText:Hide() + for _, btn in ipairs(frame.scoreButtons) do + btn:Hide() + end + end + + if showLeaderboard then + frame.leaderboardTitle:Show() + for _, podium in ipairs(frame.podiums) do podium:Show() end + for _, btn in ipairs(frame.leaderboardButtons) do btn:Show() end + AIO.Handle("AIO_Mythic", "RequestLeaderboard") + else + frame.leaderboardTitle:Hide() + for _, podium in ipairs(frame.podiums) do podium:Hide() end + for _, btn in ipairs(frame.leaderboardButtons) do btn:Hide() end + end + + if showOverview then + AIO.Handle("AIO_Mythic", "RequestWeeklyAffixes") + end +end + +SetActiveTab(1) + +function MythicHandlers.StartMythicTimerGUI(_, mapId, tier, duration, bossNames, potentialGain, enemiesRequired) + potentialGain = tonumber(potentialGain) or 0 + enemiesRequired = tonumber(enemiesRequired) or 50 + if type(bossNames) ~= "table" then bossNames = {} end + local maxDeaths = (tier == 1) and 6 or 4 + WatchFrame:Hide(); WATCHFRAME_COLLAPSED = true + + local baseHeight = 140 + #bossNames * 18 + local frameHeight = (enemiesRequired > 0) and (baseHeight + 28) or baseHeight + + local timerFrame = CreateFrame("Frame", nil, UIParent) + timerFrame:SetSize(320, frameHeight) + timerFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -20, -120) + timerFrame:SetMovable(true) + timerFrame:EnableMouse(true) + timerFrame:RegisterForDrag("LeftButton") + timerFrame:SetScript("OnDragStart", function(self) self:StartMoving() end) + timerFrame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end) + + local progressBar = timerFrame:CreateTexture(nil, "BACKGROUND") + progressBar:SetTexture("Interface\\MythicPlus\\textures\\MythicBar.blp") + progressBar:SetSize(0, 128) + progressBar:SetPoint("LEFT", timerFrame, "LEFT", 0, -10) + progressBar:SetTexCoord(0, 0, 0, 1) + progressBar:Hide() + + local function updateProgress(killedBosses, totalBosses) + if totalBosses == 0 then return end + local progress = math.min(killedBosses / totalBosses, 1.0) + local maxWidth = 256 + local currentWidth = maxWidth * progress + if progress > 0 then + progressBar:Show() + progressBar:SetWidth(currentWidth) + progressBar:SetTexCoord(0, progress, 0, 1) + else + progressBar:Hide() + end + end + + local goldenFrame = timerFrame:CreateTexture(nil, "ARTWORK") + goldenFrame:SetTexture("Interface\\MythicPlus\\textures\\MythicFrame.blp") + goldenFrame:SetSize(256, 128) + goldenFrame:SetPoint("LEFT", timerFrame, "LEFT", 0, -10) + goldenFrame:SetTexCoord(0, 1, 0, 1) + + local dungeonText = timerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + dungeonText:SetPoint("TOP", goldenFrame, "TOP", 0, -12) + local dungeonName = (DUNGEONS[mapId] and DUNGEONS[mapId].name) or ("Map "..mapId) + dungeonText:SetFont("Fonts\\FRIZQT__.TTF", 16, "OUTLINE") + dungeonText:SetText(fmt("|cffFFD700%s|r", dungeonName)) + + local tierText = timerFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + tierText:SetPoint("TOP", goldenFrame, "LEFT", 50, 26) + tierText:SetFont("Fonts\\FRIZQT__.TTF", 16, "") + tierText:SetText(fmt("|cffFFD700Level %d|r", tier)) + + local timerText = timerFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") + timerText:SetPoint("CENTER", goldenFrame, "LEFT", 48, -6) + timerText:SetFont("Fonts\\FRIZQT__.TTF", 20, "OUTLINE") + timerText:SetText(fmt("%02d:%02d", floor(duration/60), duration%60)) + + local deaths, penalty, bonus = 0, 0, 0 + local scoreLabel = timerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + scoreLabel:SetPoint("BOTTOM", goldenFrame, "BOTTOM", 60, 50) + + local gold, red, green, reset = "|cffFFD700", "|cffff0000", "|cff33ff33", "|r" + local scoreStr = gold..potentialGain..reset.." "..gold.."("..reset..red.."-"..penalty..reset..gold.."/"..reset..green.."+"..bonus..reset..gold..")"..reset.." "..gold..deaths.."/"..maxDeaths..reset + scoreLabel:SetText(scoreStr) + + local affixContainer = CreateFrame("Frame", nil, timerFrame) + affixContainer:SetSize(200, 30) + affixContainer:SetPoint("BOTTOM", scoreLabel, "TOP", 40, 11) + + local affixIcons = {} + local currentAffixes = {} + + if MythicPlusFrame and MythicPlusFrame.currentAffixes then + currentAffixes = MythicPlusFrame.currentAffixes + end + + local numAffixes = math.min(tier, 4) + local iconSize = 20 + local iconSpacing = 4 + local totalWidth = (numAffixes * iconSize) + ((numAffixes - 1) * iconSpacing) + local startX = -totalWidth / 2 + + for i = 1, numAffixes do + local affixName = currentAffixes[i] + if affixName and AFFIXES[affixName] then + local icon = CreateFrame("Button", nil, affixContainer) + icon:SetSize(iconSize, iconSize) + icon:SetPoint("LEFT", affixContainer, "LEFT", startX + (i-1) * (iconSize + iconSpacing) + 30, -5) + + local texture = icon:CreateTexture(nil, "ARTWORK") + texture:SetAllPoints() + texture:SetTexture(AFFIXES[affixName].icon) + + icon:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_TOP") + GameTooltip:SetText(affixName) + GameTooltip:Show() + end) + + icon:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + + affixIcons[i] = icon + end + end + + local bossLabels = {} + for i, name in ipairs(bossNames) do + local lbl = timerFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + lbl:SetPoint("TOPLEFT", goldenFrame, "BOTTOMLEFT", 20, 10 - (i-1)*16) + lbl:SetText(fmt("%d/%d %s", 0, 1, name)) + lbl:SetTextColor(1, 0.82, 0) + lbl.bossName = name + bossLabels[i] = lbl + end + local enemyLabel, enemyProgressFrame, enemyPercentText + if enemiesRequired > 0 then + enemyLabel = timerFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + enemyLabel:SetPoint("TOPLEFT", goldenFrame, "BOTTOMLEFT", 20, 10 - #bossNames*16) + enemyLabel:SetText("0/1 " .. MythicGetText("UI", "Enemy Forces")) + enemyLabel:SetTextColor(1, 0.82, 0) + + enemyProgressFrame = CreateFrame("StatusBar", nil, timerFrame) + enemyProgressFrame:SetSize(200, 12) + enemyProgressFrame:SetPoint("TOPLEFT", enemyLabel, "BOTTOMLEFT", 0, -10) + enemyProgressFrame:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar") + enemyProgressFrame:SetStatusBarColor(0.2, 0.8, 0.2) + enemyProgressFrame:SetMinMaxValues(0, 100) + enemyProgressFrame:SetValue(0) + + local enemyProgressBorder = CreateFrame("Frame", nil, timerFrame, BackdropTemplateMixin and "BackdropTemplate") + enemyProgressBorder:SetAllPoints(enemyProgressFrame) + enemyProgressBorder:SetBackdrop({ + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + edgeSize = 8, + insets = { left = 1, right = 1, top = 1, bottom = 1 } + }) + enemyProgressBorder:SetBackdropBorderColor(0.5, 0.5, 0.5, 1) + + enemyPercentText = enemyProgressFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + enemyPercentText:SetPoint("CENTER", enemyProgressFrame, "CENTER") + enemyPercentText:SetText("0%") + end + + local elapsed = 0 + timerFrame:SetScript("OnUpdate", function(self, dt) + if self.stopped then return end + elapsed = elapsed + dt + local left = duration - elapsed + if left < 0 then left = 0 end + local m,s = floor(left/60), floor(left%60) + local ratio = left/duration + local color = "|cff00ff00" + if ratio < 0.3 then color = "|cffff0000" + elseif ratio < 0.6 then color = "|cffffff00" end + timerText:SetText(fmt("%s%02d:%02d|r", color, m, s)) + end) + + timerFrame:Show() + + MythicBossTimerUI = { + frame = timerFrame, + timerText = timerText, + scoreLabel = scoreLabel, + labels = bossLabels, + progressBar = progressBar, + updateProgress = updateProgress, + totalBosses = #bossNames, + killedBosses = 0, + potentialGain = potentialGain, + penalty = 0, + bonus = 0, + deaths = 0, + maxDeaths = maxDeaths, + enemyLabel = enemyLabel, + enemyProgressFrame = enemyProgressFrame, + enemyPercentText = enemyPercentText, + enemiesRequired = enemiesRequired, + enemiesCurrent = 0 + } +end + +function MythicHandlers.UpdateMythicScore(_, newPenalty, newDeaths) + local ui = MythicBossTimerUI + if not ui or not ui.scoreLabel then return end + ui.penalty = tonumber(newPenalty) or ui.penalty + ui.deaths = tonumber(newDeaths) or ui.deaths + local gold = "|cffcc9933" + local red = "|cffff0000" + local green = "|cff33ff33" + local reset = "|r" + local scoreStr = + gold..ui.potentialGain..reset.." ".. + gold.."("..reset.. + red.."-"..ui.penalty..reset.. + gold.."/"..reset.. + green.."+"..ui.bonus..reset.. + gold..")"..reset.." ".. + gold..ui.deaths.."/"..ui.maxDeaths..reset + ui.scoreLabel:SetText(scoreStr) +end + +function MythicHandlers.FinalizeMythicScore(_, finalPenalty, finalDeaths, finalBonus) + local ui = MythicBossTimerUI + if not ui or not ui.scoreLabel then return end + ui.penalty = tonumber(finalPenalty) or ui.penalty + ui.deaths = tonumber(finalDeaths) or ui.deaths + ui.bonus = tonumber(finalBonus) or ui.bonus + local gold = "|cffcc9933" + local red = "|cffff0000" + local green = "|cff33ff33" + local reset = "|r" + local scoreStr = + gold..ui.potentialGain..reset.." ".. + gold.."("..reset.. + red.."-"..ui.penalty..reset.. + gold.."/"..reset.. + green.."+"..ui.bonus..reset.. + gold..")"..reset.." ".. + gold..ui.deaths.."/"..ui.maxDeaths..reset + ui.scoreLabel:SetText(scoreStr) +end + +function MythicHandlers.MarkBossKilled(_, mapId, bossIndex) + local ui = MythicBossTimerUI + if not ui or not ui.labels then return end + local lbl = ui.labels[bossIndex] + if lbl and lbl.bossName then + lbl:SetText(fmt("|cff26c426%d/%d %s|r", 1, 1, lbl.bossName)) + ui.killedBosses = ui.killedBosses + 1 + if ui.updateProgress then + ui.updateProgress(ui.killedBosses, ui.totalBosses) + end + end +end + +function MythicHandlers.StopMythicTimerGUI(_, remaining) + local ui = MythicBossTimerUI + if ui and ui.timerText then + if type(remaining) == "number" then + local m, s = floor(remaining/60), floor(remaining%60) + ui.timerText:SetText(fmt("|cffffff00%02d:%02d|r", m, s)) + end + ui.frame.stopped = true + end +end + +function MythicHandlers.KillMythicTimerGUI() + WatchFrame:Show(); WATCHFRAME_COLLAPSED = nil + if MythicBossTimerUI and MythicBossTimerUI.frame then + MythicBossTimerUI.frame.stopped = true + MythicBossTimerUI.frame:Hide() + MythicBossTimerUI.frame:SetScript("OnUpdate", nil) + MythicBossTimerUI.frame:SetParent(nil) + MythicBossTimerUI.frame = nil + end + MythicBossTimerUI = nil +end + +function MythicHandlers.StartCountdown(_, seconds) + seconds = tonumber(seconds) or 10 + if CountdownFrame then + CountdownFrame:Hide() + CountdownFrame:SetScript("OnUpdate", nil) + end + if not CountdownFrame then + local frame = CreateFrame("Frame", "CountdownFrame", UIParent) + frame:SetSize(512, 256) + frame:SetPoint("CENTER", UIParent, "CENTER", 0, 250) + frame:SetFrameStrata("FULLSCREEN_DIALOG") + frame:Hide() + + frame.digit1 = frame:CreateTexture(nil, "ARTWORK") + frame.digit1:SetSize(256, 170) + frame.digit1:SetPoint("CENTER", frame, "CENTER", -70, 0) + frame.digit1:SetTexture("Interface\\MythicPlus\\textures\\BigTimerNumbers") + + frame.digit2 = frame:CreateTexture(nil, "ARTWORK") + frame.digit2:SetSize(256, 170) + frame.digit2:SetPoint("CENTER", frame, "CENTER", 70, 0) + frame.digit2:SetTexture("Interface\\MythicPlus\\textures\\BigTimerNumbers") + + CountdownFrame = frame + end + + local frame = CountdownFrame + frame:Show() + local function setDigits(num) + local texW, texH = 1024, 512 + local digitW, digitH = 256, 170 + local columns = 4 + + local n1 = math.floor(num / 10) + local n2 = num % 10 + + local function setDigit(tex, digit) + local col = digit % columns + local row = math.floor(digit / columns) + local l = (col * digitW) / texW + local r = ((col + 1) * digitW) / texW + local t = (row * digitH) / texH + local b = ((row + 1) * digitH) / texH + tex:SetTexCoord(l, r, t, b) + tex:Show() + end + if n1 > 0 then + setDigit(frame.digit1, n1) + frame.digit1:Show() + frame.digit2:SetPoint("CENTER", frame, "CENTER", 70, 0) + else + frame.digit1:Hide() + frame.digit2:SetPoint("CENTER", frame, "CENTER", 0, 0) + end + setDigit(frame.digit2, n2) + end + + setDigits(seconds) + PlaySoundFile("Interface\\MythicPlus\\sounds\\UI_BattlegroundCountdown_Timer.ogg", "master") + + local remaining = seconds + frame:SetScript("OnUpdate", function(self, elapsed) + if not self.lastUpdate then self.lastUpdate = 0 end + self.lastUpdate = self.lastUpdate + elapsed + if self.lastUpdate >= 1 then + self.lastUpdate = self.lastUpdate - 1 + remaining = remaining - 1 + if remaining > 0 then + setDigits(remaining) + PlaySoundFile("Interface\\MythicPlus\\sounds\\UI_BattlegroundCountdown_Timer.ogg", "master") + else + PlaySoundFile("Interface\\MythicPlus\\sounds\\UI_BattlegroundCountdown_End.ogg", "master") + self:Hide() + self:SetScript("OnUpdate", nil) + end + end + end) +end + +function MythicHandlers.UpdateVaultStatus(_, hasLoot) + if hasLoot then + mythicMiniButton.hasVaultLoot = true + if not mythicMiniButton.glowTexture then + mythicMiniButton.glowTexture = mythicMiniButton:CreateTexture(nil, "OVERLAY") + mythicMiniButton.glowTexture:SetTexture("Interface\\SpellActivationOverlay\\IconAlert") + mythicMiniButton.glowTexture:SetSize(48, 48) + mythicMiniButton.glowTexture:SetPoint("CENTER") + mythicMiniButton.glowTexture:SetBlendMode("ADD") + end + mythicMiniButton.glowTexture:Show() + else + mythicMiniButton.hasVaultLoot = false + if mythicMiniButton.glowTexture then + mythicMiniButton.glowTexture:Hide() + end + end +end + +function MythicHandlers.QueryItemData(_, item1, item2, item3) + if not MythicHiddenTooltip then + MythicHiddenTooltip = CreateFrame("GameTooltip", "MythicHiddenTooltip", nil, "GameTooltipTemplate") + MythicHiddenTooltip:SetOwner(UIParent, "ANCHOR_NONE") + end + + local function QueryItem(itemId) + if itemId and itemId > 0 then + MythicHiddenTooltip:SetHyperlink("item:"..itemId..":0:0:0:0:0:0:0") + MythicHiddenTooltip:Show() + end + end + + if item1 and item1 > 0 then + QueryItem(item1) + end + + if item2 and item2 > 0 then + local item2Timer = CreateFrame("Frame") + item2Timer.elapsed = 0 + item2Timer:SetScript("OnUpdate", function(self, elapsed) + self.elapsed = self.elapsed + elapsed + if self.elapsed >= 0.1 then + QueryItem(item2) + self:SetScript("OnUpdate", nil) + end + end) + end + + if item3 and item3 > 0 then + local item3Timer = CreateFrame("Frame") + item3Timer.elapsed = 0 + item3Timer:SetScript("OnUpdate", function(self, elapsed) + self.elapsed = self.elapsed + elapsed + if self.elapsed >= 0.2 then + QueryItem(item3) + self:SetScript("OnUpdate", nil) + end + end) + end +end + +function MythicHandlers.ShowVaultGUI(_, item1, item2, item3, tier1, tier2, tier3) + AIO.Handle("AIO_Mythic", "QueryItemData", item1, item2, item3) + if VaultFrame then + VaultFrame:Hide() + end + + VaultFrame = CreateFrame("Frame", "MythicVaultFrame", UIParent) + VaultFrame:SetSize(600, 450) + VaultFrame:SetPoint("CENTER") + VaultFrame:SetToplevel(true) + + local vaultBG = VaultFrame:CreateTexture(nil, "BACKGROUND") + vaultBG:SetTexture("Interface\\MythicPlus\\textures\\VaultFrame") + vaultBG:SetAllPoints(VaultFrame) + vaultBG:SetTexCoord(0, 1, 177/1024, (1024-177)/1024) + + local title = VaultFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") + title:SetPoint("TOP", VaultFrame, "TOP", 0, -40) + title:SetText(MythicGetText("UI", "Mythic+ Vault")) + title:SetFont("Fonts\\FRIZQT__.TTF", 20, "OUTLINE") + title:SetTextColor(1, 0.82, 0) + + local subtitle = VaultFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + subtitle:SetPoint("TOP", title, "BOTTOM", 0, -2) + subtitle:SetText(MythicGetText("UI", "Choose item reward")) + subtitle:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE") + subtitle:SetTextColor(0.9, 0.9, 0.9) + + local items = {item1, item2, item3} + local tiers = {tier1, tier2, tier3} + local selectedIndex = nil + local itemButtons = {} + + for i = 1, 3 do + if items[i] and items[i] > 0 then + local button = CreateFrame("Button", nil, VaultFrame) + button:SetSize(72, 72) + button:SetPoint("TOP", subtitle, "BOTTOM", -190 + (i-1)*190, -80) + + local icon = button:CreateTexture(nil, "ARTWORK") + icon:SetAllPoints(button) + button.icon = icon + + local itemTexture = GetItemIcon(items[i]) + if itemTexture then + icon:SetTexture(itemTexture) + else + icon:SetTexture("Interface\\Icons\\inv_misc_questionmark") + end + + local tierLabel = button:CreateFontString(nil, "OVERLAY", "GameFontNormal") + tierLabel:SetPoint("TOP", button, "BOTTOM", 0, -5) + tierLabel:SetText(MythicGetText("UI", "Tier") .. " " .. (tiers[i] or "?")) + tierLabel:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE") + tierLabel:SetTextColor(1, 0.82, 0) + button.tierLabel = tierLabel + + button:SetScript("OnClick", function(self) + selectedIndex = i + + for j, btn in ipairs(itemButtons) do + if j == i then + btn.icon:SetDesaturated(false) + btn.tierLabel:SetTextColor(1, 0.82, 0) + else + btn.icon:SetDesaturated(true) + btn.tierLabel:SetTextColor(0.5, 0.5, 0.5) + end + end + + if VaultFrame.confirmButton then + VaultFrame.confirmButton:Enable() + VaultFrame.confirmButton:SetText("Confirm Selection") + end + + if VaultFrame.selectionText then + local itemName, itemLink, itemRarity = GetItemInfo(items[i]) + if itemLink then + VaultFrame.selectionText:SetText("Selected: " .. itemLink) + else + VaultFrame.selectionText:SetText("Selected: |cffffffff[Unknown Item]|r") + end + VaultFrame.selectionText:SetTextColor(1, 0.82, 0) + end + end) + + button:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + + local itemLink = select(2, GetItemInfo(items[i])) + if itemLink then + GameTooltip:SetHyperlink(tostring(itemLink)) + else + print("Item link not found for item ID:", items[i]) + local tooltipTimer = CreateFrame("Frame") + tooltipTimer.elapsed = 0 + tooltipTimer:SetScript("OnUpdate", function(frame, elapsed) + frame.elapsed = frame.elapsed + elapsed + if frame.elapsed >= 0.1 then + local link = select(2, GetItemInfo(items[i])) + if link and GameTooltip:IsOwned(self) then + GameTooltip:SetHyperlink(link) + end + frame:SetScript("OnUpdate", nil) + end + end) + end + GameTooltip:Show() + + if selectedIndex ~= i then + if not self.highlightTexture then + self.highlightTexture = self:CreateTexture(nil, "HIGHLIGHT") + self.highlightTexture:SetTexture("Interface\\Buttons\\ButtonHilight-Square") + self.highlightTexture:SetAllPoints(self) + self.highlightTexture:SetBlendMode("ADD") + self.highlightTexture:SetVertexColor(1, 1, 1, 0.3) + end + self.highlightTexture:Show() + end + end) + button:SetScript("OnLeave", function(self) + GameTooltip_Hide() + if self.highlightTexture then + self.highlightTexture:Hide() + end + end) + button:SetScript("OnMouseDown", function(self) + icon:SetPoint("TOPLEFT", 1, -1) + icon:SetPoint("BOTTOMRIGHT", 1, -1) + end) + button:SetScript("OnMouseUp", function(self) + icon:SetAllPoints(self) + end) + itemButtons[i] = button + end + end + + local selectionText = VaultFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + selectionText:SetPoint("TOP", VaultFrame, "TOP", 0, -300) + selectionText:SetText(MythicGetText("UI", "No item selected")) + selectionText:SetTextColor(1, 0.82, 0) + selectionText:SetFont("Fonts\\FRIZQT__.TTF", 14, "") + VaultFrame.selectionText = selectionText + + local confirmButton = CreateFrame("Button", nil, VaultFrame, "UIPanelButtonTemplate") + confirmButton:SetSize(140, 30) + confirmButton:SetPoint("BOTTOM", VaultFrame, "BOTTOM", 0, 40) + confirmButton:SetText(MythicGetText("UI", "Select an Item")) + confirmButton:Disable() + VaultFrame.confirmButton = confirmButton + confirmButton:SetScript("OnClick", function(self) + if selectedIndex then + AIO.Handle("AIO_Mythic", "SelectVaultItem", selectedIndex) + VaultFrame:Hide() + end + end) + + local cancelButton = CreateFrame("Button", nil, VaultFrame, "UIPanelButtonTemplate") + cancelButton:SetSize(100, 30) + cancelButton:SetPoint("BOTTOMLEFT", VaultFrame, "BOTTOMLEFT", 20, 40) + cancelButton:SetText(MythicGetText("UI", "Cancel")) + cancelButton:SetScript("OnClick", function(self) + VaultFrame:Hide() + end) + VaultFrame:SetScript("OnHide", function(self) + selectedIndex = nil + end) + VaultFrame:Show() +end + +function MythicHandlers.CloseVaultGUI() + if VaultFrame then + VaultFrame:Hide() + end +end + +function MythicHandlers.UpdateEnemyForces(_, current, required, percentage, completed) + local ui = MythicBossTimerUI + if not ui or not ui.enemyLabel then return end + ui.enemiesCurrent = current + ui.enemiesRequired = required + local completedText = completed and "1" or "0" + ui.enemyLabel:SetText(fmt("%s/1 %s", completedText, MythicGetText("UI", "Enemy Forces"))) + ui.enemyProgressFrame:SetValue(percentage) + ui.enemyPercentText:SetText(fmt("%.0f%%", percentage)) + if completed then + ui.enemyLabel:SetTextColor(0.15, 0.76, 0.15) + else + ui.enemyLabel:SetTextColor(1, 0.82, 0) + end +end + +function MythicHandlers.ReceiveTotalPoints(_, totalPoints, dungeonScores) + if not frame.scoreText then return end + frame.scoreText:SetText(MythicGetText("UI", "Total Score:") .. " " .. string.format("%.2f", totalPoints or 0)) + for i, mapId in ipairs(DUNGEON_ORDER) do + local button = frame.scoreButtons[i] + if button and button.scoreLabel then + local score = dungeonScores and dungeonScores[tostring(mapId)] or 0 + button.scoreLabel:SetText(tostring(score)) + if button.nameLabel and DUNGEONS[mapId] then + button.nameLabel:SetText(MythicGetText("Dungeons", DUNGEONS[mapId].originalName)) + end + end + end +end + +function MythicHandlers.RestoreRunState(_, runState) + if not runState or not runState.active then + MythicPlusCharRunState = { + active = false, + mapId = nil, + tier = nil, + duration = nil, + elapsed = nil, + bossNames = {}, + killedBosses = {}, + potentialGain = 0, + penalty = 0, + bonus = 0, + deaths = 0, + maxDeaths = 0, + enemyForces = { + current = 0, + required = 0, + percentage = 0, + completed = false + }, + saveTime = nil + } + return + end + + MythicPlusCharRunState = runState + MythicPlusCharRunState.saveTime = GetTime() + + MythicHandlers.StartMythicTimerGUI(nil, runState.mapId, runState.tier, runState.duration - runState.elapsed, runState.bossNames, runState.potentialGain, runState.enemyForces.required) + + if MythicBossTimerUI then + MythicBossTimerUI.deaths = runState.deaths + MythicBossTimerUI.penalty = runState.penalty + MythicBossTimerUI.maxDeaths = runState.maxDeaths + + MythicHandlers.UpdateMythicScore(nil, runState.penalty, runState.deaths) + + for _, bossIndex in ipairs(runState.killedBosses) do + MythicHandlers.MarkBossKilled(nil, runState.mapId, bossIndex) + end + + if runState.enemyForces then + MythicHandlers.UpdateEnemyForces(nil, runState.enemyForces.current, runState.enemyForces.required, runState.enemyForces.percentage, runState.enemyForces.completed) + end + + if runState.overtime then + MythicHandlers.StartOvertimeMode() + end + end +end + +function MythicHandlers.ClearRunState() + MythicPlusCharRunState = { + active = false, + mapId = nil, + tier = nil, + duration = nil, + elapsed = nil, + bossNames = {}, + killedBosses = {}, + potentialGain = 0, + penalty = 0, + bonus = 0, + deaths = 0, + maxDeaths = 0, + enemyForces = { + current = 0, + required = 0, + percentage = 0, + completed = false + }, + saveTime = nil + } +end + +function MythicHandlers.StartOvertimeMode() + if MythicBossTimerUI and MythicBossTimerUI.timerText then + MythicBossTimerUI.timerText:SetText("|cffff0000OVERTIME|r") + MythicBossTimerUI.frame.stopped = true + end +end \ No newline at end of file diff --git a/lua_scripts/MythicPlus/Mythic_Locale.lua b/lua_scripts/MythicPlus/Mythic_Locale.lua new file mode 100644 index 0000000..3826cfe --- /dev/null +++ b/lua_scripts/MythicPlus/Mythic_Locale.lua @@ -0,0 +1,948 @@ +local L = {} + +function L:Text(category, key, localeIndex) + if not self[category] or not self[category][key] then + return key + end + + if not self[category][key][localeIndex] then + return self[category][key][0] + end + + return self[category][key][localeIndex] +end + +L.Items = { + ["Mythic Keystone"] = { + [0] = "Mythic Keystone", + [1] = "신화 쐐기돌", + [2] = "Clé mythique", + [3] = "Mythischer Schlüsselstein", + [4] = "[Mythic Keystone]", + [5] = "傳奇鑰石", + [6] = "Piedra angular mítica", + [7] = "Piedra angular mítica", + [8] = "Эпохальный ключ", + } +} + +L.Dungeons = { + ["Utgarde Keep"] = { + [0] = "Utgarde Keep", + [1] = "우트가드 성채", + [2] = "Donjon d'Utgarde", + [3] = "Burg Utgarde", + [4] = "乌特加德城堡", + [5] = "俄特加德要塞", + [6] = "Fortaleza de Utgarde", + [7] = "Fortaleza de Utgarde", + [8] = "Крепость Утгард", + }, + ["Utgarde Pinnacle"] = { + [0] = "Utgarde Pinnacle", + [1] = "우트가드 첨탑", + [2] = "Cime d'Utgarde", + [3] = "Turm Utgarde", + [4] = "乌特加德之巅", + [5] = "俄特加德之巔", + [6] = "Pináculo de Utgarde", + [7] = "Pináculo de Utgarde", + [8] = "Вершина Утгард", + }, + ["The Nexus"] = { + [0] = "The Nexus", + [1] = "마력의 탑", + [2] = "Le Nexus", + [3] = "Der Nexus", + [4] = "魔枢", + [5] = "奧核之心", + [6] = "El Nexo", + [7] = "El Nexo", + [8] = "Нексус", + }, + ["The Oculus"] = { + [0] = "The Oculus", + [1] = "마력의 눈", + [2] = "L'Oculus", + [3] = "Das Oculus", + [4] = "魔环", + [5] = "奧核之眼", + [6] = "El Oculus", + [7] = "El Oculus", + [8] = "Окулус", + }, + ["The Culling of Stratholme"] = { + [0] = "The Culling of Stratholme", + [1] = "옛 스트라솔름", + [2] = "L'épuration de Stratholme", + [3] = "Das Ausmerzen von Stratholme", + [4] = "净化斯坦索姆", + [5] = "斯坦索姆的抉擇", + [6] = "La Matanza de Stratholme", + [7] = "La Matanza de Stratholme", + [8] = "Очищение Стратхольма", + }, + ["Halls of Stone"] = { + [0] = "Halls of Stone", + [1] = "돌의 전당", + [2] = "Les salles de Pierre", + [3] = "Hallen des Steins", + [4] = "岩石大厅", + [5] = "石之大廳", + [6] = "Cámaras de Piedra", + [7] = "Cámaras de Piedra", + [8] = "Чертоги Камня", + }, + ["Drak'Tharon Keep"] = { + [0] = "Drak'Tharon Keep", + [1] = "드락타론 성채", + [2] = "Donjon de Drak'Tharon", + [3] = "Feste Drak'Tharon", + [4] = "达克萨隆要塞", + [5] = "德拉克薩隆要塞", + [6] = "Fortaleza de Drak'Tharon", + [7] = "Fortaleza de Drak'Tharon", + [8] = "Крепость Драк'Тарон", + }, + ["Azjol-Nerub"] = { + [0] = "Azjol-Nerub", + [1] = "아졸네룹", + [2] = "Azjol-Nérub", + [3] = "Azjol-Nerub", + [4] = "艾卓-尼鲁布", + [5] = "阿茲歐-奈幽", + [6] = "Azjol-Nerub", + [7] = "Azjol-Nerub", + [8] = "Азжол-Неруб", + }, + ["Halls of Lightning"] = { + [0] = "Halls of Lightning", + [1] = "번개의 전당", + [2] = "Les salles de Foudre", + [3] = "Hallen der Blitze", + [4] = "闪电大厅", + [5] = "雷光大廳", + [6] = "Cámaras de Relámpagos", + [7] = "Cámaras de Relámpagos", + [8] = "Чертоги Молний", + }, + ["Gundrak"] = { + [0] = "Gundrak", + [1] = "군드락", + [2] = "Gundrak", + [3] = "Gundrak", + [4] = "古达克", + [5] = "剛德拉克", + [6] = "Gundrak", + [7] = "Gundrak", + [8] = "Гундрак", + }, + ["The Violet Hold"] = { + [0] = "The Violet Hold", + [1] = "보랏빛 요새", + [2] = "Le fort Pourpre", + [3] = "Die Violette Festung", + [4] = "紫罗兰监狱", + [5] = "紫羅蘭堡", + [6] = "El Bastión Violeta", + [7] = "El Bastión Violeta", + [8] = "Аметистовая крепость", + }, + ["Ahn'kahet: The Old Kingdom"] = { + [0] = "Ahn'kahet: The Old Kingdom", + [1] = "안카헤트: 고대 왕국", + [2] = "Ahn'kahet : l'Ancien royaume", + [3] = "Ahn'kahet: Das Alte Königreich", + [4] = "安卡赫特:古代王国", + [5] = "安卡罕特:古王國", + [6] = "Ahn'kahet: El Antiguo Reino", + [7] = "Ahn'kahet: El Antiguo Reino", + [8] = "Ан'кахет: Старое Королевство", + }, + ["Trial of the Champion"] = { + [0] = "Trial of the Champion", + [1] = "용사의 시험장", + [2] = "L'épreuve des champions", + [3] = "Prüfung des Champions", + [4] = "冠军的试炼", + [5] = "勇士試煉", + [6] = "Prueba del Campeón", + [7] = "Prueba del Campeón", + [8] = "Испытание чемпиона", + }, + ["The Forge of Souls"] = { + [0] = "The Forge of Souls", + [1] = "영혼의 제련소", + [2] = "La Forge des Âmes", + [3] = "Die Seelenschmiede", + [4] = "灵魂洪炉", + [5] = "眾魂熔爐", + [6] = "La Forja de Almas", + [7] = "La Forja de Almas", + [8] = "Кузня Душ", + }, + ["Pit of Saron"] = { + [0] = "Pit of Saron", + [1] = "사론의 구덩이", + [2] = "La fosse de Saron", + [3] = "Die Grube von Saron", + [4] = "萨隆矿坑", + [5] = "薩倫之淵", + [6] = "Foso de Saron", + [7] = "Foso de Saron", + [8] = "Яма Сарона", + }, + ["Halls of Reflection"] = { + [0] = "Halls of Reflection", + [1] = "투영의 전당", + [2] = "Les salles des Reflets", + [3] = "Die Hallen der Reflexion", + [4] = "映像大厅", + [5] = "倒影大廳", + [6] = "Cámaras de Reflexión", + [7] = "Cámaras de Reflexión", + [8] = "Залы Отражений", + } +} + +L.UI = { + ["Enrage Description"] = { + [0] = "Boosts physical damage and attack speed, making attacks more relentless.", + [1] = "물리적 피해와 공격 속도를 증가시켜 공격을 더 무자비하게 만듭니다.", + [2] = "Augmente les dégâts physiques et la vitesse d'attaque, rendant les attaques plus implacables.", + [3] = "Erhöht physischen Schaden und Angriffsgeschwindigkeit, macht Angriffe unerbittlicher.", + [4] = "提高物理伤害和攻击速度,使攻击更加凶猛。", + [5] = "提高物理傷害和攻擊速度,使攻擊更加兇猛。", + [6] = "Aumenta el daño físico y la velocidad de ataque, haciendo que los ataques sean más implacables.", + [7] = "Aumenta el daño físico y la velocidad de ataque, haciendo que los ataques sean más implacables.", + [8] = "Увеличивает физический урон и скорость атаки, делая атаки более безжалостными." + }, + ["Rejuvenating Description"] = { + [0] = "Gradually restores health over time, keeping the creature sustained in battle.", + [1] = "시간이 지남에 따라 체력을 서서히 회복하여 전투 중에도 생존력을 유지합니다.", + [2] = "Restaure progressivement la santé au fil du temps, maintenant la créature en vie durant le combat.", + [3] = "Stellt Gesundheit langsam im Laufe der Zeit wieder her und hält die Kreatur im Kampf am Leben.", + [4] = "随着时间的推移逐渐恢复生命值,使生物在战斗中保持生存能力。", + [5] = "隨著時間的推移逐漸恢復生命值,使生物在戰鬥中保持生存能力。", + [6] = "Restaura gradualmente la salud con el tiempo, manteniendo a la criatura viva durante la batalla.", + [7] = "Restaura gradualmente la salud con el tiempo, manteniendo a la criatura viva durante la batalla.", + [8] = "Постепенно восстанавливает здоровье со временем, поддерживая существо в бою." + }, + ["Turtling Description"] = { + [0] = "Significantly reduces damage taken, increasing survivability.", + [1] = "받는 피해를 크게 줄여 생존력을 높입니다.", + [2] = "Réduit considérablement les dégâts subis, augmentant la survie.", + [3] = "Reduziert erlittenen Schaden erheblich und erhöht die Überlebensfähigkeit.", + [4] = "显著减少受到的伤害,提高生存能力。", + [5] = "顯著減少受到的傷害,提高生存能力。", + [6] = "Reduce significativamente el daño recibido, aumentando la supervivencia.", + [7] = "Reduce significativamente el daño recibido, aumentando la supervivencia.", + [8] = "Значительно уменьшает получаемый урон, увеличивая выживаемость." + }, + ["Shamanism Description"] = { + [0] = "Enhances spell power, Strength, Agility, and melee speed. Also improves Fire resistance and critical strike chance.", + [1] = "주문력, 힘, 민첩성 및 근접 속도를 강화합니다. 화염 저항력과 치명타 확률도 향상됩니다.", + [2] = "Améliore la puissance des sorts, la Force, l'Agilité et la vitesse de mêlée. Améliore également la résistance au Feu et les chances de coup critique.", + [3] = "Verbessert Zaubermacht, Stärke, Beweglichkeit und Nahkampfgeschwindigkeit. Verbessert auch Feuerresistenz und kritische Trefferchance.", + [4] = "增强法术强度、力量、敏捷和近战速度。还提高火焰抗性和暴击几率。", + [5] = "增強法術強度、力量、敏捷和近戰速度。還提高火焰抗性和暴擊幾率。", + [6] = "Mejora el poder de hechizos, la Fuerza, la Agilidad y la velocidad cuerpo a cuerpo. También mejora la resistencia al Fuego y la probabilidad de golpe crítico.", + [7] = "Mejora el poder de hechizos, la Fuerza, la Agilidad y la velocidad cuerpo a cuerpo. También mejora la resistencia al Fuego y la probabilidad de golpe crítico.", + [8] = "Увеличивает силу заклинаний, Силу, Ловкость и скорость ближнего боя. Также повышает сопротивление огню и шанс критического удара." + }, + ["Magus Description"] = { + [0] = "Decreases magic damage received, strengthens armor and Frost resistance, and may slow attackers. Retaliates with Fire damage, increases critical strike effectiveness, and allows swift casting for certain Mage spells.", + [1] = "받는 마법 피해를 줄이고, 방어력과 냉기 저항력을 강화하며, 공격자의 속도를 늦출 수 있습니다. 화염 피해로 반격하고, 치명타 효과를 증가시키며, 특정 마법사 주문의 빠른 시전을 가능하게 합니다.", + [2] = "Diminue les dégâts magiques reçus, renforce l'armure et la résistance au Givre, et peut ralentir les attaquants. Riposte avec des dégâts de Feu, augmente l'efficacité des coups critiques et permet une incantation rapide de certains sorts de Mage.", + [3] = "Verringert erhaltenen magischen Schaden, stärkt Rüstung und Frostwiderstand und verlangsamt möglicherweise Angreifer. Vergilt mit Feuerschaden, erhöht die Wirksamkeit kritischer Treffer und ermöglicht schnelles Wirken bestimmter Magierzauber.", + [4] = "减少受到的魔法伤害,增强护甲和冰霜抗性,并可能减慢攻击者的速度。用火焰伤害反击,增加暴击效果,并允许快速施放某些法师法术。", + [5] = "減少受到的魔法傷害,增強護甲和冰霜抗性,並可能減慢攻擊者的速度。用火焰傷害反擊,增加暴擊效果,並允許快速施放某些法師法術。", + [6] = "Disminuye el daño mágico recibido, fortalece la armadura y la resistencia a la Escarcha, y puede ralentizar a los atacantes. Contraataca con daño de Fuego, aumenta la efectividad del golpe crítico y permite lanzamientos rápidos de ciertos hechizos de Mago.", + [7] = "Disminuye el daño mágico recibido, fortalece la armadura y la resistencia a la Escarcha, y puede ralentizar a los atacantes. Contraataca con daño de Fuego, aumenta la efectividad del golpe crítico y permite lanzamientos rápidos de ciertos hechizos de Mago.", + [8] = "Уменьшает получаемый магический урон, усиливает броню и сопротивление льду, может замедлять атакующих. Наносит ответный урон от огня, увеличивает эффективность критических ударов и позволяет быстро произносить определенные заклинания мага." + }, + ["Priest Empowered Description"] = { + [0] = "Fortifies stamina, absorbs incoming damage, and grants protection against Fear effects. Strengthens armor and spell power, with shadow magic restoring health for the caster and nearby allies.", + [1] = "체력을 강화하고, 들어오는 피해를 흡수하며, 공포 효과에 대한 보호를 제공합니다. 방어력과 주문력을 강화하고, 암흑 마법으로 시전자와 주변 동맹의 체력을 회복시킵니다.", + [2] = "Fortifie l'endurance, absorbe les dégâts entrants et offre une protection contre les effets de Peur. Renforce l'armure et la puissance des sorts, avec la magie de l'ombre qui restaure la santé du lanceur et des alliés proches.", + [3] = "Stärkt Ausdauer, absorbiert eingehenden Schaden und gewährt Schutz vor Furchteffekten. Stärkt Rüstung und Zaubermacht, wobei Schattenmagie die Gesundheit des Zauberwirkers und nahegelegener Verbündeter wiederherstellt.", + [4] = "增强耐力,吸收传入的伤害,并提供对恐惧效果的保护。增强护甲和法术能力,暗影魔法为施法者和附近的盟友恢复健康。", + [5] = "增強耐力,吸收傳入的傷害,並提供對恐懼效果的保護。增強護甲和法術能力,暗影魔法為施法者和附近的盟友恢復健康。", + [6] = "Fortalece la resistencia, absorbe el daño entrante y otorga protección contra efectos de Miedo. Fortalece la armadura y el poder de hechizos, con magia de sombra que restaura la salud del lanzador y aliados cercanos.", + [7] = "Fortalece la resistencia, absorbe el daño entrante y otorga protección contra efectos de Miedo. Fortalece la armadura y el poder de hechizos, con magia de sombra que restaura la salud del lanzador y aliados cercanos.", + [8] = "Укрепляет выносливость, поглощает входящий урон и предоставляет защиту от эффектов страха. Усиливает броню и силу заклинаний, а теневая магия восстанавливает здоровье заклинателю и ближайшим союзникам." + }, + ["Demonism Description"] = { + [0] = "Amplifies spell power, gains additional benefits from Spirit, regenerates health periodically, and harms nearby foes.", + [1] = "주문력을 증폭시키고, 정신력에서 추가 혜택을 얻으며, 주기적으로 체력을 재생하고, 근처의 적들에게 피해를 줍니다.", + [2] = "Amplifie la puissance des sorts, obtient des avantages supplémentaires de l'Esprit, régénère la santé périodiquement et nuit aux ennemis à proximité.", + [3] = "Verstärkt die Zaubermacht, erhält zusätzliche Vorteile durch Willenskraft, regeneriert regelmäßig Gesundheit und schadet Feinden in der Nähe.", + [4] = "增强法术能力,从精神中获得额外的好处,定期再生健康,并伤害附近的敌人。", + [5] = "增強法術能力,從精神中獲得額外的好處,定期再生健康,並傷害附近的敵人。", + [6] = "Amplifica el poder de hechizos, obtiene beneficios adicionales del Espíritu, regenera la salud periódicamente y daña a los enemigos cercanos.", + [7] = "Amplifica el poder de hechizos, obtiene beneficios adicionales del Espíritu, regenera la salud periódicamente y daña a los enemigos cercanos.", + [8] = "Усиливает силу заклинаний, получает дополнительные преимущества от духа, периодически восстанавливает здоровье и наносит урон ближайшим врагам." + }, + ["Falling Stars Description"] = { + [0] = "Calls down celestial forces, bombarding enemies from above.", + [1] = "천체의 힘을 불러내려 위에서 적들을 폭격합니다.", + [2] = "Invoque des forces célestes, bombardant les ennemis du ciel.", + [3] = "Ruft himmlische Kräfte herbei, die Feinde von oben bombardieren.", + [4] = "召唤天体力量,从上方轰炸敌人。", + [5] = "召喚天體力量,從上方轟炸敵人。", + [6] = "Invoca fuerzas celestiales, bombardeando a los enemigos desde arriba.", + [7] = "Invoca fuerzas celestiales, bombardeando a los enemigos desde arriba.", + [8] = "Призывает небесные силы, бомбардирующие врагов сверху." + }, + ["You do not have a Mythic Keystone."] = { + [0] = "You do not have a Mythic Keystone.", + [1] = "신화 쐐기돌이 없습니다.", + [2] = "Vous n'avez pas de Clé mythique.", + [3] = "Du hast keinen mythischen Schlüsselstein.", + [4] = "你没有神话钥石。", + [5] = "你沒有傳奇鑰石。", + [6] = "No tienes una Piedra angular mítica.", + [7] = "No tienes una Piedra angular mítica.", + [8] = "У вас нет эпохального ключа." + }, + ["Your keystone does not appear to fit."] = { + [0] = "Your keystone does not appear to fit.", + [1] = "쐐기돌이 맞지 않는 것 같습니다.", + [2] = "Votre clé ne semble pas s'adapter.", + [3] = "Dein Schlüsselstein scheint nicht zu passen.", + [4] = "你的钥石似乎不合适。", + [5] = "你的鑰石似乎不合適。", + [6] = "Tu piedra angular no parece encajar.", + [7] = "Tu piedra angular no parece encajar.", + [8] = "Ваш ключ, похоже, не подходит." + }, + ["This keystone is not for this dungeon."] = { + [0] = "This keystone is not for this dungeon.", + [1] = "이 쐐기돌은 이 던전용이 아닙니다.", + [2] = "Cette clé n'est pas pour ce donjon.", + [3] = "Dieser Schlüsselstein ist nicht für diesen Dungeon.", + [4] = "这个钥石不是给这个地下城的。", + [5] = "這個鑰石不是給這個地下城的。", + [6] = "Esta piedra angular no es para esta mazmorra.", + [7] = "Esta piedra angular no es para esta mazmorra.", + [8] = "Этот ключ не для этого подземелья." + }, + ["Keystones cannot be used in Normal mode dungeons."] = { + [0] = "Keystones cannot be used in Normal mode dungeons.", + [1] = "일반 모드 던전에서는 쐐기돌을 사용할 수 없습니다.", + [2] = "Les clés ne peuvent pas être utilisées dans les donjons en mode normal.", + [3] = "Schlüsselsteine können nicht in Dungeons im normalen Modus verwendet werden.", + [4] = "钥石不能在普通模式地下城中使用。", + [5] = "鑰石不能在普通模式地下城中使用。", + [6] = "Las piedras angulares no se pueden usar en mazmorras en modo normal.", + [7] = "Las piedras angulares no se pueden usar en mazmorras en modo normal.", + [8] = "Ключи нельзя использовать в подземельях в обычном режиме." + }, + ["Your keystone has been downgraded to Tier %d."] = { + [0] = "Your keystone has been downgraded to Tier %d.", + [1] = "당신의 쐐기돌이 %d단계로 강등되었습니다.", + [2] = "Votre clé a été rétrogradée au niveau %d.", + [3] = "Dein Schlüsselstein wurde auf Stufe %d herabgestuft.", + [4] = "你的钥石已降级至%d级。", + [5] = "你的鑰石已降級至%d階。", + [6] = "Tu piedra angular ha sido degradada al nivel %d.", + [7] = "Tu piedra angular ha sido degradada al nivel %d.", + [8] = "Ваш ключ был понижен до уровня %d." + }, + ["Your keystone was destroyed."] = { + [0] = "Your keystone was destroyed.", + [1] = "당신의 쐐기돌이 파괴되었습니다.", + [2] = "Votre clé a été détruite.", + [3] = "Dein Schlüsselstein wurde zerstört.", + [4] = "你的钥石被摧毁了。", + [5] = "你的鑰石被摧毀了。", + [6] = "Tu piedra angular fue destruida.", + [7] = "Tu piedra angular fue destruida.", + [8] = "Ваш ключ был уничтожен." + }, + ["You left the dungeon. The run is over."] = { + [0] = "You left the dungeon. The run is over.", + [1] = "던전을 떠났습니다. 런이 종료되었습니다.", + [2] = "Vous avez quitté le donjon. La course est terminée.", + [3] = "Du hast den Dungeon verlassen. Der Durchlauf ist vorbei.", + [4] = "你离开了地下城。本次挑战结束。", + [5] = "你離開了地下城。本次挑戰結束。", + [6] = "Dejaste la mazmorra. La carrera ha terminado.", + [7] = "Dejaste la mazmorra. La carrera ha terminado.", + [8] = "Вы покинули подземелье. Забег окончен." + }, + ["Time expired! Tier %d failed. You can continue in overtime for the chance of loot."] = { + [0] = "Time expired! Tier %d failed. You can continue in overtime for the chance of loot.", + [1] = "시간 초과! %d단계 실패. 추가 시간에 계속하면 전리품을 얻을 기회가 있습니다.", + [2] = "Temps expiré ! Niveau %d échoué. Vous pouvez continuer en prolongation pour une chance de butin.", + [3] = "Zeit abgelaufen! Stufe %d gescheitert. Du kannst in der Verlängerung weitermachen, um die Chance auf Beute zu bekommen.", + [4] = "时间到!%d级失败。你可以继续加时赛以获得掉落物品的机会。", + [5] = "時間到!%d階失敗。你可以繼續加時賽以獲得掉落物品的機會。", + [6] = "¡Tiempo expirado! Nivel %d fallido. Puedes continuar en tiempo extra para tener la posibilidad de conseguir botín.", + [7] = "¡Tiempo expirado! Nivel %d fallido. Puedes continuar en tiempo extra para tener la posibilidad de conseguir botín.", + [8] = "Время истекло! Уровень %d провален. Вы можете продолжить в дополнительное время, чтобы получить шанс на добычу." + }, + ["No loot available in your vault this week."] = { + [0] = "No loot available in your vault this week.", + [1] = "이번 주 금고에 이용 가능한 전리품이 없습니다.", + [2] = "Pas de butin disponible dans votre coffre cette semaine.", + [3] = "Diese Woche ist keine Beute in deinem Tresor verfügbar.", + [4] = "本周你的宝库中没有可用的战利品。", + [5] = "本週你的寶庫中沒有可用的戰利品。", + [6] = "No hay botín disponible en tu cámara esta semana.", + [7] = "No hay botín disponible en tu cámara esta semana.", + [8] = "На этой неделе в вашем хранилище нет доступной добычи." + }, + ["No loot available to collect."] = { + [0] = "No loot available to collect.", + [1] = "수집할 수 있는 전리품이 없습니다.", + [2] = "Pas de butin disponible à collecter.", + [3] = "Keine Beute zum Einsammeln verfügbar.", + [4] = "没有可收集的战利品。", + [5] = "沒有可收集的戰利品。", + [6] = "No hay botín disponible para recolectar.", + [7] = "No hay botín disponible para recolectar.", + [8] = "Нет доступной для сбора добычи." + }, + ["Reward:"] = { + [0] = "Reward:", + [1] = "보상:", + [2] = "Récompense:", + [3] = "Belohnung:", + [4] = "奖励:", + [5] = "獎勵:", + [6] = "Recompensa:", + [7] = "Recompensa:", + [8] = "Награда:" + }, + ["Reward: Spell learned!"] = { + [0] = "Reward: Spell learned!", + [1] = "보상: 주문 학습!", + [2] = "Récompense: Sort appris!", + [3] = "Belohnung: Zauber gelernt!", + [4] = "奖励: 已学会法术!", + [5] = "獎勵: 已學會法術!", + [6] = "Recompensa: ¡Hechizo aprendido!", + [7] = "Recompensa: ¡Hechizo aprendido!", + [8] = "Награда: Изучено заклинание!" + }, + ["+2 Performance"] = { + [0] = "+2 Performance", + [1] = "+2 성과", + [2] = "+2 Performance", + [3] = "+2 Leistung", + [4] = "+2 性能", + [5] = "+2 表現", + [6] = "+2 Rendimiento", + [7] = "+2 Rendimiento", + [8] = "+2 Производительность" + }, + ["+3 Performance"] = { + [0] = "+3 Performance", + [1] = "+3 성과", + [2] = "+3 Performance", + [3] = "+3 Leistung", + [4] = "+3 性能", + [5] = "+3 表現", + [6] = "+3 Rendimiento", + [7] = "+3 Rendimiento", + [8] = "+3 Производительность" + }, + ["Enhanced loot chances applied!"] = { + [0] = "Enhanced loot chances applied!", + [1] = "향상된 전리품 확률이 적용되었습니다!", + [2] = "Chances de butin améliorées appliquées!", + [3] = "Verbesserte Beutechancen angewendet!", + [4] = "增强的掉落几率已应用!", + [5] = "增強的掉落機率已應用!", + [6] = "¡Probabilidades de botín mejoradas aplicadas!", + [7] = "¡Probabilidades de botín mejoradas aplicadas!", + [8] = "Применены улучшенные шансы на добычу!" + }, + ["Overview"] = { + [0] = "Overview", + [1] = "개요", + [2] = "Aperçu", + [3] = "Übersicht", + [4] = "概览", + [5] = "概述", + [6] = "Vista general", + [7] = "Vista general", + [8] = "Обзор", + }, + ["Score"] = { + [0] = "Score", + [1] = "점수", + [2] = "Score", + [3] = "Punktzahl", + [4] = "分数", + [5] = "分數", + [6] = "Puntuación", + [7] = "Puntuación", + [8] = "Счет", + }, + ["Leaderboard"] = { + [0] = "Leaderboard", + [1] = "리더보드", + [2] = "Classement", + [3] = "Rangliste", + [4] = "排行榜", + [5] = "排行榜", + [6] = "Clasificación", + [7] = "Clasificación", + [8] = "Таблица лидеров", + }, + ["This week's affixes:"] = { + [0] = "This week's affixes:", + [1] = "이번 주 어픽스:", + [2] = "Affixes de cette semaine:", + [3] = "Affixe dieser Woche:", + [4] = "本周词缀:", + [5] = "本週詞綴:", + [6] = "Afijos de esta semana:", + [7] = "Afijos de esta semana:", + [8] = "Аффиксы этой недели:", + }, + ["Enrage"] = { + [0] = "Enrage", + [1] = "분노", + [2] = "Enrager", + [3] = "Wutanfall", + [4] = "激怒", + [5] = "狂怒", + [6] = "Enfurecer", + [7] = "Enfurecer", + [8] = "Берсерк", + }, + ["Rejuvenating"] = { + [0] = "Rejuvenating", + [1] = "재생", + [2] = "Régénérant", + [3] = "Verjüngend", + [4] = "恢复", + [5] = "恢復", + [6] = "Rejuvenecedor", + [7] = "Rejuvenecedor", + [8] = "Омоложение", + }, + ["Turtling"] = { + [0] = "Turtling", + [1] = "방어적", + [2] = "Tortue", + [3] = "Schildkröte", + [4] = "龟壳", + [5] = "龜甲", + [6] = "Caparazón", + [7] = "Caparazón", + [8] = "Панцирь", + }, + ["Shamanism"] = { + [0] = "Shamanism", + [1] = "주술", + [2] = "Chamanisme", + [3] = "Schamanismus", + [4] = "萨满教", + [5] = "薩滿教", + [6] = "Chamanismo", + [7] = "Chamanismo", + [8] = "Шаманизм", + }, + ["Magus"] = { + [0] = "Magus", + [1] = "마법사", + [2] = "Mage", + [3] = "Magier", + [4] = "法师", + [5] = "法師", + [6] = "Mago", + [7] = "Mago", + [8] = "Маг", + }, + ["Priest Empowered"] = { + [0] = "Priest Empowered", + [1] = "사제 강화", + [2] = "Prêtre renforcé", + [3] = "Priestermacht", + [4] = "牧师增强", + [5] = "牧師強化", + [6] = "Sacerdote potenciado", + [7] = "Sacerdote potenciado", + [8] = "Усиленный жрец", + }, + ["Demonism"] = { + [0] = "Demonism", + [1] = "악마주의", + [2] = "Démonisme", + [3] = "Dämonismus", + [4] = "恶魔主义", + [5] = "惡魔主義", + [6] = "Demonismo", + [7] = "Demonismo", + [8] = "Демонизм", + }, + ["Falling Stars"] = { + [0] = "Falling Stars", + [1] = "떨어지는 별", + [2] = "Étoiles filantes", + [3] = "Fallende Sterne", + [4] = "流星", + [5] = "流星", + [6] = "Estrellas fugaces", + [7] = "Estrellas fugaces", + [8] = "Падающие звезды", + }, + ["Total Score:"] = { + [0] = "Total Score:", + [1] = "총 점수:", + [2] = "Score total:", + [3] = "Gesamtpunktzahl:", + [4] = "总分:", + [5] = "總分:", + [6] = "Puntuación total:", + [7] = "Puntuación total:", + [8] = "Общий счет:", + }, + ["Level"] = { + [0] = "Level", + [1] = "레벨", + [2] = "Niveau", + [3] = "Stufe", + [4] = "等级", + [5] = "等級", + [6] = "Nivel", + [7] = "Nivel", + [8] = "Уровень", + }, + ["No Keystone"] = { + [0] = "No Keystone", + [1] = "쐐기돌 없음", + [2] = "Pas de clé", + [3] = "Kein Schlüsselstein", + [4] = "没有钥石", + [5] = "沒有鑰石", + [6] = "Sin piedra angular", + [7] = "Sin piedra angular", + [8] = "Нет ключа", + }, + ["Loading..."] = { + [0] = "Loading...", + [1] = "로딩 중...", + [2] = "Chargement...", + [3] = "Wird geladen...", + [4] = "加载中...", + [5] = "載入中...", + [6] = "Cargando...", + [7] = "Cargando...", + [8] = "Загрузка...", + }, + ["Enemy Forces"] = { + [0] = "Enemy Forces", + [1] = "적군 병력", + [2] = "Forces ennemies", + [3] = "Feindliche Streitkräfte", + [4] = "敌方部队", + [5] = "敵方部隊", + [6] = "Fuerzas enemigas", + [7] = "Fuerzas enemigas", + [8] = "Вражеские силы", + }, + ["Mythic+"] = { + [0] = "Mythic+", + [1] = "신화+", + [2] = "Mythique+", + [3] = "Mythisch+", + [4] = "史诗+", + [5] = "傳奇+", + [6] = "Mítica+", + [7] = "Mítica+", + [8] = "Эпохальный+", + }, + ["Mythic"] = { + [0] = "Mythic", + [1] = "신화", + [2] = "Mythique", + [3] = "Mythisch", + [4] = "史诗", + [5] = "傳奇", + [6] = "Mítico", + [7] = "Mítico", + [8] = "Эпохальный" + }, + ["There is loot in your Vault in Dalaran City"] = { + [0] = "There is loot in your Vault in Dalaran City", + [1] = "달라란 도시에 있는 금고에 전리품이 있습니다", + [2] = "Il y a du butin dans votre Coffre à Dalaran", + [3] = "Es gibt Beute in deinem Tresor in Dalaran", + [4] = "达拉然的宝库里有你的战利品", + [5] = "達拉然城有你的寶庫戰利品", + [6] = "Hay botín en tu Cámara en la ciudad de Dalaran", + [7] = "Hay botín en tu Cámara en la ciudad de Dalaran", + [8] = "В вашем хранилище в Даларане есть добыча", + }, + ["You received a Mythic Keystone!"] = { + [0] = "You received a Mythic Keystone!", + [1] = "신화 쐐기돌을 받았습니다!", + [2] = "Vous avez reçu une Clé mythique!", + [3] = "Du hast einen Mythischen Schlüsselstein erhalten!", + [4] = "你获得了一个神话钥石!", + [5] = "你獲得了一個傳奇鑰石!", + [6] = "¡Has recibido una Piedra angular mítica!", + [7] = "¡Has recibido una Piedra angular mítica!", + [8] = "Вы получили Эпохальный ключ!", + }, + ["You received a Tier %d Mythic Keystone!"] = { + [0] = "You received a Tier %d Mythic Keystone!", + [1] = "%d단계 신화 쐐기돌을 받았습니다!", + [2] = "Vous avez reçu une Clé mythique de niveau %d!", + [3] = "Du hast einen Mythischen Schlüsselstein der Stufe %d erhalten!", + [4] = "你获得了一个%d级神话钥石!", + [5] = "你獲得了一個%d階傳奇鑰石!", + [6] = "¡Has recibido una Piedra angular mítica de nivel %d!", + [7] = "¡Has recibido una Piedra angular mítica de nivel %d!", + [8] = "Вы получили Эпохальный ключ уровня %d!", + }, + ["Mythic+ Vault"] = { + [0] = "Mythic+ Vault", + [1] = "신화+ 금고", + [2] = "Coffre Mythique+", + [3] = "Mythisch+ Tresor", + [4] = "史诗+宝库", + [5] = "傳奇+寶庫", + [6] = "Cámara de Mítica+", + [7] = "Cámara de Mítica+", + [8] = "Хранилище эпохального+", + }, + ["Choose item reward"] = { + [0] = "Choose item reward", + [1] = "아이템 보상 선택", + [2] = "Choisir récompense", + [3] = "Belohnung wählen", + [4] = "选择物品奖励", + [5] = "選擇物品獎勵", + [6] = "Elegir recompensa", + [7] = "Elegir recompensa", + [8] = "Выбрать награду", + }, + ["Tier"] = { + [0] = "Tier", + [1] = "단계", + [2] = "Niveau", + [3] = "Stufe", + [4] = "等级", + [5] = "階層", + [6] = "Nivel", + [7] = "Nivel", + [8] = "Уровень", + }, + ["No item selected"] = { + [0] = "No item selected", + [1] = "선택된 아이템 없음", + [2] = "Aucun objet sélectionné", + [3] = "Kein Gegenstand ausgewählt", + [4] = "未选择物品", + [5] = "未選擇物品", + [6] = "No hay objeto seleccionado", + [7] = "No hay objeto seleccionado", + [8] = "Предмет не выбран", + }, + ["Selected:"] = { + [0] = "Selected:", + [1] = "선택됨:", + [2] = "Sélectionné:", + [3] = "Ausgewählt:", + [4] = "已选择:", + [5] = "已選擇:", + [6] = "Seleccionado:", + [7] = "Seleccionado:", + [8] = "Выбрано:", + }, + ["Select an Item"] = { + [0] = "Select an Item", + [1] = "아이템 선택", + [2] = "Sélectionner un objet", + [3] = "Wähle einen Gegenstand", + [4] = "选择一个物品", + [5] = "選擇一個物品", + [6] = "Seleccionar un objeto", + [7] = "Seleccionar un objeto", + [8] = "Выберите предмет", + }, + ["Confirm Selection"] = { + [0] = "Confirm Selection", + [1] = "선택 확인", + [2] = "Confirmer la sélection", + [3] = "Auswahl bestätigen", + [4] = "确认选择", + [5] = "確認選擇", + [6] = "Confirmar selección", + [7] = "Confirmar selección", + [8] = "Подтвердить выбор", + }, + ["Cancel"] = { + [0] = "Cancel", + [1] = "취소", + [2] = "Annuler", + [3] = "Abbrechen", + [4] = "取消", + [5] = "取消", + [6] = "Cancelar", + [7] = "Cancelar", + [8] = "Отмена", + }, + ["Insert Keystone (Tier %d)"] = { + [0] = "Insert Keystone (Tier %d)", + [1] = "쐐기돌 넣기 (%d단계)", + [2] = "Insérer la clé (Niveau %d)", + [3] = "Schlüsselstein einsetzen (Stufe %d)", + [4] = "插入钥石(%d级)", + [5] = "插入鑰石(%d階)", + [6] = "Insertar piedra angular (Nivel %d)", + [7] = "Insertar piedra angular (Nivel %d)", + [8] = "Вставить ключ (Уровень %d)", + }, + ["Step away"] = { + [0] = "Step away", + [1] = "물러서기", + [2] = "S'éloigner", + [3] = "Zurücktreten", + [4] = "退后", + [5] = "退後", + [6] = "Alejarse", + [7] = "Alejarse", + [8] = "Отойти", + }, + ["Reward:"] = { + [0] = "Reward:", + [1] = "보상:", + [2] = "Récompense:", + [3] = "Belohnung:", + [4] = "奖励:", + [5] = "獎勵:", + [6] = "Recompensa:", + [7] = "Recompensa:", + [8] = "Награда:", + }, + ["Tier %d completed in %s%s."] = { + [0] = "Tier %d completed in %s%s.", + [1] = "%d단계를 %s%s에 완료했습니다.", + [2] = "Niveau %d terminé en %s%s.", + [3] = "Stufe %d in %s%s abgeschlossen.", + [4] = "%d级完成于%s%s。", + [5] = "%d階完成於%s%s。", + [6] = "Nivel %d completado en %s%s.", + [7] = "Nivel %d completado en %s%s.", + [8] = "Уровень %d завершен за %s%s.", + }, + ["(Key +%d)"] = { + [0] = "(Key +%d)", + [1] = "(열쇠 +%d)", + [2] = "(Clé +%d)", + [3] = "(Schlüssel +%d)", + [4] = "(钥石 +%d)", + [5] = "(鑰石 +%d)", + [6] = "(Llave +%d)", + [7] = "(Llave +%d)", + [8] = "(Ключ +%d)", + }, + ["(Key -1)"] = { + [0] = "(Key -1)", + [1] = "(열쇠 -1)", + [2] = "(Clé -1)", + [3] = "(Schlüssel -1)", + [4] = "(钥石 -1)", + [5] = "(鑰石 -1)", + [6] = "(Llave -1)", + [7] = "(Llave -1)", + [8] = "(Ключ -1)", + }, + ["Rating: %d (+%d gained, -%d death penalty)"] = { + [0] = "Rating: %d (+%d gained, -%d death penalty)", + [1] = "평점: %d (+%d 획득, -%d 사망 패널티)", + [2] = "Score: %d (+%d gagné, -%d pénalité de mort)", + [3] = "Bewertung: %d (+%d erhalten, -%d Todesstrafe)", + [4] = "评分: %d (+%d 获得, -%d 死亡惩罚)", + [5] = "評分: %d (+%d 獲得, -%d 死亡懲罰)", + [6] = "Puntuación: %d (+%d ganada, -%d penalización por muerte)", + [7] = "Puntuación: %d (+%d ganada, -%d penalización por muerte)", + [8] = "Рейтинг: %d (+%d получено, -%d штраф за смерть)", + }, + ["Tier %d completed in overtime."] = { + [0] = "Tier %d completed in overtime.", + [1] = "%d단계를 초과 시간으로 완료했습니다.", + [2] = "Niveau %d terminé en prolongation.", + [3] = "Stufe %d in Überstunden abgeschlossen.", + [4] = "%d级超时完成。", + [5] = "%d階超時完成。", + [6] = "Nivel %d completado en tiempo extra.", + [7] = "Nivel %d completado en tiempo extra.", + [8] = "Уровень %d завершен в дополнительное время.", + }, + ["Highest Score:"] = { + [0] = "Highest Score:", + [1] = "최고 점수:", + [2] = "Score le plus élevé:", + [3] = "Höchste Punktzahl:", + [4] = "最高分数:", + [5] = "最高分數:", + [6] = "Puntuación más alta:", + [7] = "Puntuación más alta:", + [8] = "Наивысший счет:" + }, + ["Highest Key +%d by:"] = { + [0] = "Highest Key +%d by:", + [1] = "최고 쐐기돌 +%d 완료자:", + [2] = "Clé la plus élevée +%d par:", + [3] = "Höchster Schlüssel +%d von:", + [4] = "最高钥石 +%d 完成者:", + [5] = "最高鑰石 +%d 完成者:", + [6] = "Piedra angular más alta +%d por:", + [7] = "Piedra angular más alta +%d por:", + [8] = "Ключ наивысшего уровня +%d от:" + }, + ["Highest Key: None completed in time"] = { + [0] = "Highest Key: None completed in time", + [1] = "최고 쐐기돌: 시간 내에 완료한 기록 없음", + [2] = "Clé la plus élevée: Aucune terminée à temps", + [3] = "Höchster Schlüssel: Keine rechtzeitig abgeschlossen", + [4] = "最高钥石: 无按时完成的记录", + [5] = "最高鑰石: 無按時完成的記錄", + [6] = "Piedra angular más alta: Ninguna completada a tiempo", + [7] = "Piedra angular más alta: Ninguna completada a tiempo", + [8] = "Ключ наивысшего уровня: Не пройдено вовремя" + }, + ["No records available"] = { + [0] = "No records available", + [1] = "기록 없음", + [2] = "Aucun enregistrement disponible", + [3] = "Keine Aufzeichnungen verfügbar", + [4] = "无可用记录", + [5] = "無可用記錄", + [6] = "No hay registros disponibles", + [7] = "No hay registros disponibles", + [8] = "Нет доступных записей" + }, + ["Unknown"] = { + [0] = "Unknown", + [1] = "알 수 없음", + [2] = "Inconnu", + [3] = "Unbekannt", + [4] = "未知", + [5] = "未知", + [6] = "Desconocido", + [7] = "Desconocido", + [8] = "Неизвестно" + }, + ["No data available"] = { + [0] = "No data available", + [1] = "사용 가능한 데이터 없음", + [2] = "Aucune donnée disponible", + [3] = "Keine Daten verfügbar", + [4] = "无可用数据", + [5] = "無可用數據", + [6] = "No hay datos disponibles", + [7] = "No hay datos disponibles", + [8] = "Нет доступных данных" + } +} + +return L diff --git a/lua_scripts/MythicPlus/Mythic_Server.lua b/lua_scripts/MythicPlus/Mythic_Server.lua new file mode 100644 index 0000000..e665789 --- /dev/null +++ b/lua_scripts/MythicPlus/Mythic_Server.lua @@ -0,0 +1,2354 @@ +local AIO = AIO or require("AIO") +local L = require("MythicPlus.Mythic_Locale") +MythicHandlers = AIO.AddHandlers("AIO_Mythic", {}) + +local function GetLocalizedText(player, category, key) + local localeIndex = player:GetDbLocaleIndex() + return L:Text(category, key, localeIndex) +end + +function MythicHandlers.RequestLocaleData(player, category) + if category and L[category] and type(L[category]) == "table" then + AIO.Handle(player, "AIO_Mythic", "ReceiveLocaleData", category, L[category]) + end +end + +local AFFIX_EXCLUDE_CREATURES = { + 29830, -- Living Mojo, Gundrak Boss Drakkari Colossus // would break its boss script + 24849 -- Proto-Drake Rider, Utgarde Keep +} + +local MYTHIC_LOOT_BRACKETS = { + ["low_tier"] = {1, 2, 3}, + ["mid_tier"] = {4, 5, 6, 7}, + ["high_tier"] = {8, 9, 10, 11, 12}, + ["endgame"] = {15, 16, 17, 18, 19, 20}, + ["pets"] = {5, 6, 7, 8, 9}, + ["all"] = "all" +} + +local MythicRewardConfig = { + pets = true, + mounts = true, + equipment = false, + spells = false, +} + +local VAULT_LOOT_BRACKETS = { + ["vault_low"] = {1, 2, 3, 4, 5}, + ["vault_mid"] = {6, 7, 8, 9, 10}, + ["vault_high"] = {11, 12, 13, 14, 15}, + ["vault_legendary"] = {16, 17, 18, 19, 20}, + ["all"] = "all" +} + +local VaultGenerationTracker = { + lastProcessedDate = nil, + eventId = nil +} + +local VaultItemCache = {} +local VAULT_GAMEOBJECT_ID = 900000 +local CLASS_ARMOR_TYPES = { + [1] = 4, -- Warrior -> Plate + [2] = 4, -- Paladin -> Plate + [3] = 3, -- Hunter -> Mail + [4] = 2, -- Rogue -> Leather + [5] = 1, -- Priest -> Cloth + [6] = 4, -- Death Knight -> Plate + [7] = 3, -- Shaman -> Mail + [8] = 1, -- Mage -> Cloth + [9] = 1, -- Warlock -> Cloth + [11] = 2, -- Druid -> Leather +} +local WEAPON_PROFICIENCY = { + [176] = {[3] = true, [4] = true, [1] = true}, -- Thrown + [172] = {[6] = true, [3] = true, [2] = true, [7] = true, [1] = true}, -- Two-Handed Axes + [43] = {[6] = true, [3] = true, [8] = true, [2] = true, [4] = true, [9] = true, [1] = true}, -- Swords + [44] = {[6] = true, [3] = true, [2] = true, [7] = true, [1] = true}, -- Axes + [136] = {[11] = true, [3] = true, [8] = true, [5] = true, [7] = true, [9] = true, [1] = true}, -- Staves + [160] = {[6] = true, [11] = true, [2] = true, [7] = true, [1] = true}, -- Two-Handed Maces + [46] = {[3] = true, [4] = true, [1] = true}, -- Guns + [229] = {[6] = true, [3] = true, [2] = true, [1] = true}, -- Polearms + [473] = {[11] = true, [3] = true, [4] = true, [7] = true, [1] = true}, -- Fist Weapons + [54] = {[6] = true, [11] = true, [2] = true, [5] = true, [4] = true, [7] = true, [1] = true}, -- Maces + [226] = {[3] = true, [4] = true, [1] = true}, -- Crossbows + [45] = {[3] = true, [4] = true, [1] = true}, -- Bows + [55] = {[6] = true, [3] = true, [2] = true, [1] = true}, -- Two-Handed Swords + [228] = {[8] = true, [5] = true, [9] = true}, -- Wands + [173] = {[11] = true, [3] = true, [8] = true, [5] = true, [4] = true, [7] = true, [9] = true, [1] = true}, -- Daggers +} + +local WEAPON_SUBCLASS_TO_SKILL = { + [0] = 44, + [1] = 172, + [2] = 45, + [3] = 46, + [4] = 54, + [5] = 160, + [6] = 229, + [7] = 43, + [8] = 55, + [10] = 136, + [13] = 473, + [15] = 173, + [16] = 176, + [18] = 226, + [19] = 228, +} + +local BossNameCache = { + ["enUS"] = {}, + ["deDE"] = {}, + ["esES"] = {}, + ["esMX"] = {}, + ["frFR"] = {}, + ["koKR"] = {}, + ["ruRU"] = {}, + ["zhCN"] = {}, + ["zhTW"] = {} +} + +local function LoadBossNames() + local bossEntries = {} + local entriesStr = "" + + for mapId, data in pairs(MythicBosses) do + if data.bosses then + for _, entry in ipairs(data.bosses) do + if not bossEntries[entry] then + bossEntries[entry] = true + if entriesStr ~= "" then + entriesStr = entriesStr .. "," + end + entriesStr = entriesStr .. entry + end + end + end + end + + if entriesStr == "" then + print("[Mythic+] No boss entries found to load names for") + return + end + + local query = WorldDBQuery("SELECT entry, name FROM creature_template WHERE entry IN (" .. entriesStr .. ")") + local count = 0 + + if query then + repeat + local entry = query:GetUInt32(0) + local name = query:GetString(1) + BossNameCache["enUS"][entry] = name + count = count + 1 + until not query:NextRow() + end + + local locales = {"deDE", "esES", "esMX", "frFR", "koKR", "ruRU", "zhCN", "zhTW"} + + for _, locale in ipairs(locales) do + local localeQuery = WorldDBQuery( + "SELECT entry, Name FROM creature_template_locale WHERE entry IN (" .. + entriesStr .. ") AND locale = '" .. locale .. "'" + ) + + if localeQuery then + local localeCount = 0 + repeat + local entry = localeQuery:GetUInt32(0) + local name = localeQuery:GetString(1) + BossNameCache[locale][entry] = name + localeCount = localeCount + 1 + until not localeQuery:NextRow() + end + end +end + +local function GetLocalizedBossNames(player, mapId) + if not MythicBosses[mapId] or not MythicBosses[mapId].bosses then + return {} + end + + local localeIndex = player:GetDbLocaleIndex() + local localeMap = { + [0] = "enUS", -- Default English + [1] = "koKR", -- Korean + [2] = "frFR", -- French + [3] = "deDE", -- German + [4] = "zhCN", -- Chinese (China) + [5] = "zhTW", -- Chinese (Taiwan) + [6] = "esES", -- Spanish (Spain) + [7] = "esMX", -- Spanish (Mexico) + [8] = "ruRU" -- Russian + } + + local locale = localeMap[localeIndex] or "enUS" + local bossNames = {} + + for _, bossId in ipairs(MythicBosses[mapId].bosses) do + local name = BossNameCache[locale][bossId] or + BossNameCache["enUS"][bossId] or + ("Boss #" .. bossId) + table.insert(bossNames, name) + end + + return bossNames +end + +MythicBosses = { + [574] = { -- Utgarde Keep + bosses = {23953, 24200, 24201, 23954}, final = 23954, timer = 1500, enemies = 45}, + [575] = { -- Utgarde Pinnacle + bosses = {26668, 26687, 26693, 26861}, final = 26861, timer = 1500, enemies = 52}, + [576] = { -- The Nexus + bosses = {26731, 26763, 26794, 26723}, final = 26723, timer = 1500, enemies = 38}, + [599] = { -- Halls of Stone + bosses = {27977, 27975, 27978}, final = 27978, timer = 1500, enemies = 41}, + [600] = { -- Drak'Tharon Keep + bosses = {26630, 26631, 27483, 26632}, final = 26632, timer = 1500, enemies = 48}, + [601] = { -- Azjol-Nerub + bosses = {28684, 28921, 29120}, final = 29120, timer = 1500, enemies = 33}, + [602] = { -- Halls of Lightning + bosses = {28586, 28587, 28546, 28923}, final = 28923, timer = 1500, enemies = 42}, + [604] = { -- Gundrak + bosses = {29304, 29573, 29305, 29932, 29306}, final = 29306, timer = 1500, enemies = 55}, + [608] = { -- The Violet Hold + bosses = {31134}, final = 31134, timer = 1500, enemies = 0}, + [619] = { -- Ahn'kahet: The Old Kingdom + bosses = {29309, 29308, 29310, 30258, 29311}, final = 29311, timer = 1500, enemies = 46}, + [578] = { -- The Oculus + bosses = {27654, 27447, 27655, 27656}, final = 27656, timer = 1500, enemies = 35}, + [595] = { -- The Culling of Stratholme + bosses = {26529, 26530, 26532, 26533}, final = 26533, timer = 1500, enemies = 58}, + [650] = { -- Trial of the Champion + bosses = {35451}, final = 35451, timer = 1500, enemies = 0}, + [632] = { -- Forge of Souls + bosses = {36497, 36502}, final = 36502, timer = 1500, enemies = 32}, + [658] = { -- Pit of Saron + bosses = {36494, 36476, 36658}, final = 36658, timer = 1500, enemies = 44}, + [668] = { -- Halls of Reflection + bosses = {38112, 38113, 36954}, final = 36954, timer = 1500, enemies = 0} +} + +LoadBossNames() + +local DungeonNames = { + [574] = "Utgarde Keep", + [575] = "Utgarde Pinnacle", + [576] = "The Nexus", + [578] = "The Oculus", + [595] = "The Culling of Stratholme", + [599] = "Halls of Stone", + [600] = "Drak'Tharon Keep", + [601] = "Azjol-Nerub", + [602] = "Halls of Lightning", + [604] = "Gundrak", + [608] = "The Violet Hold", + [619] = "Ahn'kahet: The Old Kingdom", + [632] = "The Forge of Souls", + [650] = "Trial of the Champion", + [658] = "Pit of Saron", + [668] = "Halls of Reflection" +} + +local function GetLocalizedDungeonName(player, mapId) + local nameKey = DungeonNames[mapId] + if nameKey then + return GetLocalizedText(player, "Dungeons", nameKey) + else + return nameKey or ("Unknown (" .. mapId .. ")") + end +end + +local mythicDungeonIds = { + [574]=true, [575]=true, [576]=true, [578]=true, [595]=true, + [599]=true, [600]=true, [601]=true, [602]=true, [604]=true, + [608]=true, [619]=true, [632]=true, [650]=true, [658]=true, [668]=true +} + +VaultLootTable = {} +PlayerKeysCache = {} +PlayerVaultCache = {} +local PlayerRatingCache = {} +local PlayerNamesCache = {} +local WeeklyAffixesCache = nil +local ActiveRunsCache = {} +local LeaderboardCache = { + topThree = {}, + dungeonTop = {}, + lastUpdate = 0 +} +local LEADERBOARD_CACHE_DURATION = 900 -- 15 minutes + +local function LoadPlayerCache(guid) + if PlayerRatingCache[guid] then return end + CharDBQueryAsync(string.format([[ + SELECT total_points, total_runs, completed_runs, out_of_time_runs, too_many_death_runs, + `574`, `575`, `576`, `578`, `595`, `599`, `600`, `601`, `602`, `604`, `608`, `619`, `632`, `650`, `658`, `668` + FROM character_mythic_rating WHERE guid = %d + ]], guid), function(result) + if result then + PlayerRatingCache[guid] = { + total_points = result:GetDouble(0), + total_runs = result:GetUInt32(1), + completed_runs = result:GetUInt32(2), + out_of_time_runs = result:GetUInt32(3), + too_many_death_runs = result:GetUInt32(4), + [574] = result:GetUInt32(5), + [575] = result:GetUInt32(6), + [576] = result:GetUInt32(7), + [578] = result:GetUInt32(8), + [595] = result:GetUInt32(9), + [599] = result:GetUInt32(10), + [600] = result:GetUInt32(11), + [601] = result:GetUInt32(12), + [602] = result:GetUInt32(13), + [604] = result:GetUInt32(14), + [608] = result:GetUInt32(15), + [619] = result:GetUInt32(16), + [632] = result:GetUInt32(17), + [650] = result:GetUInt32(18), + [658] = result:GetUInt32(19), + [668] = result:GetUInt32(20) + } + else + PlayerRatingCache[guid] = { + total_points = 0, + total_runs = 0, + completed_runs = 0, + out_of_time_runs = 0, + too_many_death_runs = 0, + [574] = 0, [575] = 0, [576] = 0, [578] = 0, [595] = 0, [599] = 0, + [600] = 0, [601] = 0, [602] = 0, [604] = 0, [608] = 0, [619] = 0, + [632] = 0, [650] = 0, [658] = 0, [668] = 0 + } + end + end) + if not PlayerKeysCache[guid] then + local keyQuery = CharDBQuery(string.format("SELECT mapId, tier FROM character_mythic_keys WHERE guid = %d", guid)) + if keyQuery then + PlayerKeysCache[guid] = { + mapId = keyQuery:GetUInt32(0), + tier = keyQuery:GetUInt32(1) + } + else + PlayerKeysCache[guid] = nil + end + end +end + +local function SavePlayerRatingCache(guid) + local cache = PlayerRatingCache[guid] + if not cache then return end + + CharDBQuery(string.format([[ + INSERT INTO character_mythic_rating + (guid, total_runs, total_points, completed_runs, out_of_time_runs, too_many_death_runs, + `574`, `575`, `576`, `578`, `595`, `599`, `600`, `601`, `602`, `604`, `608`, `619`, `632`, `650`, `658`, `668`, last_updated) + VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, FROM_UNIXTIME(%d)) + ON DUPLICATE KEY UPDATE + total_runs = %d, total_points = %d, completed_runs = %d, out_of_time_runs = %d, too_many_death_runs = %d, + `574` = %d, `575` = %d, `576` = %d, `578` = %d, `595` = %d, `599` = %d, `600` = %d, `601` = %d, `602` = %d, + `604` = %d, `608` = %d, `619` = %d, `632` = %d, `650` = %d, `658` = %d, `668` = %d, last_updated = FROM_UNIXTIME(%d) + ]], + guid, cache.total_runs, cache.total_points, cache.completed_runs, cache.out_of_time_runs, cache.too_many_death_runs, + cache[574], cache[575], cache[576], cache[578], cache[595], cache[599], cache[600], cache[601], cache[602], + cache[604], cache[608], cache[619], cache[632], cache[650], cache[658], cache[668], os.time(), + cache.total_runs, cache.total_points, cache.completed_runs, cache.out_of_time_runs, cache.too_many_death_runs, + cache[574], cache[575], cache[576], cache[578], cache[595], cache[599], cache[600], cache[601], cache[602], + cache[604], cache[608], cache[619], cache[632], cache[650], cache[658], cache[668], os.time())) +end + +local function UpdateLeaderboardCache() + local now = os.time() + if now - LeaderboardCache.lastUpdate < LEADERBOARD_CACHE_DURATION then + return + end + + local top3Query = CharDBQuery([[ SELECT guid, total_points FROM character_mythic_rating ORDER BY total_points DESC LIMIT 3 ]]) + LeaderboardCache.topThree = {} + if top3Query then + repeat + local guid = top3Query:GetUInt32(0) + local points = top3Query:GetDouble(1) + local name = PlayerNamesCache[guid] + if not name then + local nameQ = CharDBQuery("SELECT name FROM characters WHERE guid = " .. guid) + if nameQ then + name = nameQ:GetString(0) + PlayerNamesCache[guid] = name + else + name = "Unknown" + end + end + table.insert(LeaderboardCache.topThree, { name = name, points = points }) + until not top3Query:NextRow() + end + + LeaderboardCache.dungeonTop = {} + local dungeonIds = {574, 575, 576, 578, 595, 599, 600, 601, 602, 604, 608, 619, 632, 650, 658, 668} + for _, dungeonId in ipairs(dungeonIds) do + local scoreQuery = CharDBQuery("SELECT guid, `" .. dungeonId .. "` FROM character_mythic_rating ORDER BY `" .. dungeonId .. "` DESC LIMIT 1") + if scoreQuery and scoreQuery:GetUInt32(1) > 0 then + local guid = scoreQuery:GetUInt32(0) + local score = scoreQuery:GetUInt32(1) + local name = PlayerNamesCache[guid] + if not name then + local nameQ = CharDBQuery("SELECT name FROM characters WHERE guid = " .. guid) + if nameQ then + name = nameQ:GetString(0) + PlayerNamesCache[guid] = name + else + name = "Unknown" + end + end + local bossData = MythicBosses[dungeonId] + local timerLimit = bossData and bossData.timer or 1500 + local keyQuery = CharDBQuery(string.format([[ + SELECT h.tier, h.member_1, h.member_2, h.member_3, h.member_4, h.member_5 + FROM character_mythic_history h + WHERE h.mapId = %d + AND h.completed = 1 + AND h.duration <= %d + AND h.duration > 0 + ORDER BY h.tier DESC + LIMIT 1 + ]], dungeonId, timerLimit)) + + local highestKey = 0 + local keyHolderNames = {} + if keyQuery then + highestKey = keyQuery:GetUInt32(0) or 0 + for i = 1, 5 do + local memberGuid = keyQuery:GetUInt32(i) + if memberGuid and memberGuid > 0 then + local memberName = PlayerNamesCache[memberGuid] + if not memberName then + local nameQ = CharDBQuery("SELECT name FROM characters WHERE guid = " .. memberGuid) + if nameQ then + memberName = nameQ:GetString(0) + PlayerNamesCache[memberGuid] = memberName + else + memberName = "Unknown" + end + end + table.insert(keyHolderNames, memberName) + end + end + end + LeaderboardCache.dungeonTop[tostring(dungeonId)] = { + name = name, + score = score, + highestKey = highestKey, + keyHolderNames = keyHolderNames + } + end + end + + LeaderboardCache.lastUpdate = now +end + +local PEDESTAL_NPC_ENTRY = 900001 + +local WEEKLY_AFFIX_POOL = { + { spell = 8599, name = "Enrage" }, + { spell = {48441, 61301}, name = "Rejuvenating" }, + { spell = 871, name = "Turtling" }, + { spell = {57662, 57621, 58738, 8515}, name = "Shamanism" }, + { spell = {43015, 43008, 43046, 57531, 12043}, name = "Magus" }, + { spell = {48161, 48066, 6346, 48168, 15286}, name = "Priest Empowered" }, + { spell = {47893, 50589}, name = "Demonism" }, + { spell = 53201, name = "Falling Stars" } +} + +local function GetCurrentMythicResetDate() + local now = os.time() + local t = os.date("*t", now) + local daysToSubtract = (t.wday - 4) % 7 + t.day = t.day - daysToSubtract + t.hour = 6 + t.min = 0 + t.sec = 0 + return os.date("%Y-%m-%d", os.time(t)) +end + +local function LoadOrRollWeeklyAffixes() + if WeeklyAffixesCache then return end + local resetDate = GetCurrentMythicResetDate() + local result = CharDBQuery(string.format("SELECT affix1, affix2, affix3 FROM character_mythic_weekly_affixes WHERE week_start = '%s'", resetDate)) + if result then + local names = { result:GetString(0), result:GetString(1), result:GetString(2) } + WeeklyAffixesCache = {} + for _, name in ipairs(names) do + for _, affix in ipairs(WEEKLY_AFFIX_POOL) do + if affix.name == name then + table.insert(WeeklyAffixesCache, affix) + break + end + end + end + else + local shuffled = {} + for i = 1, #WEEKLY_AFFIX_POOL do shuffled[i] = WEEKLY_AFFIX_POOL[i] end + for i = #shuffled, 2, -1 do + local j = math.random(i) + shuffled[i], shuffled[j] = shuffled[j], shuffled[i] + end + local affix1, affix2, affix3 = shuffled[1], shuffled[2], shuffled[3] + WeeklyAffixesCache = { affix1, affix2, affix3 } + CharDBQuery(string.format([[ + INSERT INTO character_mythic_weekly_affixes (week_start, affix1, affix2, affix3) + VALUES ('%s', '%s', '%s', '%s') + ]], resetDate, affix1.name, affix2.name, affix3.name)) + end +end + +LoadOrRollWeeklyAffixes() + +local penaltyPerDeath = 10 +local fmt, floor = string.format, math.floor + +if MYTHIC_BOSS_KILL_TRACKER == nil then MYTHIC_BOSS_KILL_TRACKER = {} end +if MYTHIC_FLAG_TABLE == nil then MYTHIC_FLAG_TABLE = {} end +if MYTHIC_AFFIXES_TABLE == nil then MYTHIC_AFFIXES_TABLE = {} end +if MYTHIC_LOOP_HANDLERS == nil then MYTHIC_LOOP_HANDLERS = {} end +if MYTHIC_REWARD_CHANCE_TABLE == nil then MYTHIC_REWARD_CHANCE_TABLE = {} end +if MYTHIC_ENEMY_FORCES_TRACKER == nil then MYTHIC_ENEMY_FORCES_TRACKER = {} end + +local MythicLootTable = {} +local function LoadMythicLootTable() + MythicLootTable = {} + local q = WorldDBQuery("SELECT itemid, itemname, amount, type, faction, loot_bracket, chancePercent, additionalID, additionalType FROM world_mythic_loot") + if q then + repeat + table.insert(MythicLootTable, { + itemid = q:GetUInt32(0), + itemname = q:GetString(1), + amount = q:GetUInt32(2), + type = q:GetString(3), + faction = q:GetString(4), + loot_bracket = q:GetString(5), + chancePercent = q:GetFloat(6), + additionalID = q:IsNull(7) and nil or q:GetUInt32(7), + additionalType = q:IsNull(8) and nil or q:GetString(8), + }) + until not q:NextRow() + end + print("[Mythic+] Loaded " .. #MythicLootTable .. " mythic+ loot entries.") +end + +local function LoadVaultLootTable() + local q = WorldDBQuery("SELECT itemid, loot_bracket, chancePercent, faction FROM world_vault_loot") + if q then + repeat + table.insert(VaultLootTable, { + itemid = q:GetUInt32(0), + loot_bracket = q:GetString(1), + chancePercent = q:GetFloat(2), + faction = q:GetString(3) + }) + until not q:NextRow() + end + print("[Mythic+] Loaded " .. #VaultLootTable .. " vault loot entries.") +end + +LoadMythicLootTable() +LoadVaultLootTable() + +local function CacheItemTemplate(itemId) + if VaultItemCache[itemId] then + return VaultItemCache[itemId] + end + local item = GetItemTemplate(itemId) + if not item then + VaultItemCache[itemId] = nil + return nil + end + VaultItemCache[itemId] = { + allowableClass = item:GetAllowableClass(), + allowableRace = item:GetAllowableRace(), + class = item:GetClass(), + subClass = item:GetSubClass(), + inventoryType = item:GetInventoryType(), + name = item:GetName(), + itemLevel = item:GetItemLevel() + } + + return VaultItemCache[itemId] +end + +local function HasClassFlag(allowableClass, playerClass) + if allowableClass == 0 then return true end + + local classFlags = { + [1] = 1, -- Warrior + [2] = 2, -- Paladin + [3] = 4, -- Hunter + [4] = 8, -- Rogue + [5] = 16, -- Priest + [6] = 32, -- Death Knight + [7] = 64, -- Shaman + [8] = 128, -- Mage + [9] = 256, -- Warlock + [11] = 1024 -- Druid + } + + local playerFlag = classFlags[playerClass] + if not playerFlag then return false end + + return math.floor(allowableClass / playerFlag) % 2 == 1 +end + +local function HasRaceFlag(allowableRace, playerRace) + if allowableRace == 0 then return true end + + local raceFlags = { + [1] = 1, -- Human + [2] = 2, -- Orc + [3] = 4, -- Dwarf + [4] = 8, -- Night Elf + [5] = 16, -- Undead + [6] = 32, -- Tauren + [7] = 64, -- Gnome + [8] = 128, -- Troll + [10] = 512, -- Blood Elf + [11] = 1024 -- Draenei + } + + local playerFlag = raceFlags[playerRace] + if not playerFlag then return false end + return math.floor(allowableRace / playerFlag) % 2 == 1 +end + +local function CanPlayerUseItem(player, itemId) + local itemData = CacheItemTemplate(itemId) + if not itemData then return false end + local playerClass = player:GetClass() + local playerRace = player:GetRace() + if not HasClassFlag(itemData.allowableClass, playerClass) then + return false + end + if not HasRaceFlag(itemData.allowableRace, playerRace) then + return false + end + if itemData.class == 4 then + local playerArmorType = CLASS_ARMOR_TYPES[playerClass] + if playerArmorType and itemData.subClass > 0 then + if itemData.subClass > playerArmorType then + return false + end + end + end + if itemData.class == 2 then + local skillId = WEAPON_SUBCLASS_TO_SKILL[itemData.subClass] + if skillId and WEAPON_PROFICIENCY[skillId] then + if not WEAPON_PROFICIENCY[skillId][playerClass] then + return false + end + end + end + return true +end + +local function GetCurrentVaultWeek() + local now = os.time() + local t = os.date("*t", now) + + -- Find last Wednesday 8 AM + local daysToSubtract = (t.wday + 3) % 7 + if t.wday == 4 and t.hour < 8 then + daysToSubtract = daysToSubtract + 7 + end + + t.day = t.day - daysToSubtract + t.hour = 8 + t.min = 0 + t.sec = 0 + + return os.date("%Y-%m-%d", os.time(t)) +end + +function LoadPlayerVaultCache(guid) + local currentWeek = GetCurrentVaultWeek() + local query = CharDBQuery(string.format([[ + SELECT highest_tier_1, highest_tier_2, highest_tier_3, successful_runs, + item_1_id, item_2_id, item_3_id, items_generated, has_collected, can_collect, week_start + FROM character_mythic_vault + WHERE guid = %d AND week_start = '%s' + ]], guid, currentWeek)) + + if not query or (query and query:GetUInt32(9) == 0) then + query = CharDBQuery(string.format([[ + SELECT highest_tier_1, highest_tier_2, highest_tier_3, successful_runs, + item_1_id, item_2_id, item_3_id, items_generated, has_collected, can_collect, week_start + FROM character_mythic_vault + WHERE guid = %d AND can_collect = 1 AND has_collected = 0 + ORDER BY week_start DESC + LIMIT 1 + ]], guid)) + end + + if query then + local weekStart = query:GetString(10) or currentWeek + PlayerVaultCache[guid] = { + week_start = weekStart, + highest_tier_1 = query:IsNull(0) and nil or query:GetUInt32(0), + highest_tier_2 = query:IsNull(1) and nil or query:GetUInt32(1), + highest_tier_3 = query:IsNull(2) and nil or query:GetUInt32(2), + successful_runs = query:GetUInt32(3), + item_1_id = query:IsNull(4) and nil or query:GetUInt32(4), + item_2_id = query:IsNull(5) and nil or query:GetUInt32(5), + item_3_id = query:IsNull(6) and nil or query:GetUInt32(6), + items_generated = query:GetUInt32(7) == 1, + has_collected = query:GetUInt32(8) == 1, + can_collect = query:GetUInt32(9) == 1 + } + else + PlayerVaultCache[guid] = { + week_start = currentWeek, + highest_tier_1 = nil, + highest_tier_2 = nil, + highest_tier_3 = nil, + successful_runs = 0, + item_1_id = nil, + item_2_id = nil, + item_3_id = nil, + items_generated = false, + has_collected = false, + can_collect = false + } + end +end + +function SavePlayerVaultCache(guid) + local cache = PlayerVaultCache[guid] + if not cache then return end + + CharDBQuery(string.format([[ + INSERT INTO character_mythic_vault + (guid, week_start, highest_tier_1, highest_tier_2, highest_tier_3, successful_runs, + item_1_id, item_2_id, item_3_id, items_generated, has_collected, can_collect) + VALUES (%d, '%s', %s, %s, %s, %d, %s, %s, %s, %d, %d, %d) + ON DUPLICATE KEY UPDATE + highest_tier_1 = %s, highest_tier_2 = %s, highest_tier_3 = %s, successful_runs = %d, + item_1_id = %s, item_2_id = %s, item_3_id = %s, items_generated = %d, has_collected = %d, can_collect = %d + ]], + guid, cache.week_start, + cache.highest_tier_1 or "NULL", cache.highest_tier_2 or "NULL", cache.highest_tier_3 or "NULL", cache.successful_runs, + cache.item_1_id or "NULL", cache.item_2_id or "NULL", cache.item_3_id or "NULL", + cache.items_generated and 1 or 0, cache.has_collected and 1 or 0, cache.can_collect and 1 or 0, + cache.highest_tier_1 or "NULL", cache.highest_tier_2 or "NULL", cache.highest_tier_3 or "NULL", cache.successful_runs, + cache.item_1_id or "NULL", cache.item_2_id or "NULL", cache.item_3_id or "NULL", + cache.items_generated and 1 or 0, cache.has_collected and 1 or 0, cache.can_collect and 1 or 0)) +end + +local function GetEligibleVaultLoot(player, tier, faction) + local eligible = {} + for _, loot in ipairs(VaultLootTable) do + if loot.faction ~= "N" and loot.faction ~= faction then + goto continue + end + if not isVaultTierEligibleForBracket(loot.loot_bracket, tier) then + goto continue + end + if not CanPlayerUseItem(player, loot.itemid) then + goto continue + end + table.insert(eligible, loot) + ::continue:: + end + return eligible +end + +function GenerateVaultItemsForPlayer(player) + local guid = player:GetGUIDLow() + local cache = PlayerVaultCache[guid] + + if not cache or cache.items_generated or cache.successful_runs == 0 then + return + end + if not player or not player:IsInWorld() then + return + end + + local faction = player:GetTeam() == 67 and "A" or (player:GetTeam() == 469 and "H" or "N") + local tiers = {cache.highest_tier_1, cache.highest_tier_2, cache.highest_tier_3} + local usedItems = {} + + for i, tier in ipairs(tiers) do + if tier then + local eligible = GetEligibleVaultLoot(player, tier, faction) + if #eligible > 0 then + local availableItems = {} + + for _, loot in ipairs(eligible) do + if not usedItems[loot.itemid] then + table.insert(availableItems, loot) + end + end + + if #availableItems == 0 then + availableItems = eligible + end + + local totalWeight = 0 + for _, loot in ipairs(availableItems) do + totalWeight = totalWeight + loot.chancePercent + end + + if totalWeight > 0 then + local roll = math.random() * totalWeight + local currentWeight = 0 + + for _, loot in ipairs(availableItems) do + currentWeight = currentWeight + loot.chancePercent + if roll <= currentWeight then + usedItems[loot.itemid] = true + if i == 1 then cache.item_1_id = loot.itemid + elseif i == 2 then cache.item_2_id = loot.itemid + elseif i == 3 then cache.item_3_id = loot.itemid + end + break + end + end + end + end + end + end + cache.items_generated = true + cache.can_collect = (cache.item_1_id or cache.item_2_id or cache.item_3_id) and true or false + SavePlayerVaultCache(guid) +end + +local function UpdateVaultProgress(player, tier, wasSuccessful) + local guid = player:GetGUIDLow() + if not PlayerVaultCache[guid] then + LoadPlayerVaultCache(guid) + end + + local cache = PlayerVaultCache[guid] + + if wasSuccessful then + cache.successful_runs = cache.successful_runs + 1 + local tiers = {cache.highest_tier_1, cache.highest_tier_2, cache.highest_tier_3} + table.insert(tiers, tier) + table.sort(tiers, function(a, b) return (a or 0) > (b or 0) end) + + cache.highest_tier_1 = tiers[1] + cache.highest_tier_2 = tiers[2] + cache.highest_tier_3 = tiers[3] + + SavePlayerVaultCache(guid) + end +end + +function WeeklyVaultInteract(event, go, player) + local guid = player:GetGUIDLow() + if not PlayerVaultCache[guid] then + LoadPlayerVaultCache(guid) + end + + local cache = PlayerVaultCache[guid] + + if not cache.can_collect or cache.has_collected then + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "No loot available in your vault this week.")) + return + end + + if not cache.items_generated then + GenerateVaultItemsForPlayer(player) + end + + local tiers = {cache.highest_tier_1, cache.highest_tier_2, cache.highest_tier_3} + local items = {cache.item_1_id or 0, cache.item_2_id or 0, cache.item_3_id or 0} + + local itemLevels = {0, 0, 0} + for i, itemId in ipairs(items) do + if itemId and itemId > 0 then + local itemData = CacheItemTemplate(itemId) + itemLevels[i] = itemData and itemData.itemLevel or 0 + end + end + + AIO.Handle(player, "AIO_Mythic", "QueryItemData", items[1], items[2], items[3]) + AIO.Handle(player, "AIO_Mythic", "ShowVaultGUI", items[1], items[2], items[3], tiers[1] or 0, tiers[2] or 0, tiers[3] or 0, itemLevels[1], itemLevels[2], itemLevels[3]) + + local playerGUID = player:GetGUID() + local goX, goY, goZ = go:GetX(), go:GetY(), go:GetZ() + local goMapId = go:GetMapId() + local proximityEventId = CreateLuaEvent(function() + local p = GetPlayerByGUID(playerGUID) + + if not p then + return false + end + + if p:GetMapId() ~= goMapId then + AIO.Handle(p, "AIO_Mythic", "CloseVaultGUI") + return false + end + + local pX, pY, pZ = p:GetX(), p:GetY(), p:GetZ() + local distance = math.sqrt((pX - goX)^2 + (pY - goY)^2 + (pZ - goZ)^2) + + if distance > 6 then + AIO.Handle(p, "AIO_Mythic", "CloseVaultGUI") + return false + end + + return true + end, 1000, 0) +end + +function MythicHandlers.SelectVaultItem(player, itemIndex) + local guid = player:GetGUIDLow() + local cache = PlayerVaultCache[guid] + + if not cache or not cache.can_collect or cache.has_collected then + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "No loot available to collect.")) + return + end + + local itemId = nil + if itemIndex == 1 then itemId = cache.item_1_id + elseif itemIndex == 2 then itemId = cache.item_2_id + elseif itemIndex == 3 then itemId = cache.item_3_id + end + + if itemId then + player:AddItem(itemId, 1) + cache.has_collected = true + SavePlayerVaultCache(guid) + + local itemData = CacheItemTemplate(itemId) + local itemName = itemData and itemData.name or "Unknown Item" + + AIO.Handle(player, "AIO_Mythic", "UpdateVaultStatus", false) + end +end + +function MythicHandlers.RequestVaultStatus(player) + if not player or not player:IsInWorld() then + return + end + + local guid = player:GetGUIDLow() + if not PlayerVaultCache[guid] then + LoadPlayerVaultCache(guid) + end + + local cache = PlayerVaultCache[guid] + + if cache.successful_runs > 0 and not cache.items_generated and not cache.has_collected then + GenerateVaultItemsForPlayer(player) + end + + local hasLoot = cache.can_collect and not cache.has_collected + AIO.Handle(player, "AIO_Mythic", "UpdateVaultStatus", hasLoot) +end + +local function ProcessWeeklyVaultGeneration() + local currentWeek = GetCurrentVaultWeek() + local query = CharDBQuery(string.format([[ + SELECT DISTINCT guid FROM character_mythic_vault + WHERE week_start = '%s' AND successful_runs > 0 AND NOT items_generated + ]], currentWeek)) + + if query then + repeat + local guid = query:GetUInt32(0) + local player = GetPlayerByGUID(guid) + + if player then + GenerateVaultItemsForPlayer(player) + else + if not PlayerVaultCache[guid] then + LoadPlayerVaultCache(guid) + end + + local cache = PlayerVaultCache[guid] + if cache then + cache.can_collect = true + SavePlayerVaultCache(guid) + end + end + until not query:NextRow() + end +end + +local function ScheduleVaultGeneration() + if VaultGenerationTracker.eventId then + RemoveEventById(VaultGenerationTracker.eventId) + end + + local now = os.time() + local t = os.date("*t", now) + + local nextWednesday = os.time(t) + local daysUntilWednesday = (11 - t.wday) % 7 + if daysUntilWednesday == 0 and t.hour >= 8 then + daysUntilWednesday = 7 + end + + nextWednesday = nextWednesday + (daysUntilWednesday * 24 * 60 * 60) + local wednesdayTable = os.date("*t", nextWednesday) + wednesdayTable.hour = 8 + wednesdayTable.min = 0 + wednesdayTable.sec = 0 + + local targetTime = os.time(wednesdayTable) + local delayMs = (targetTime - now) * 1000 + + VaultGenerationTracker.eventId = CreateLuaEvent(function() + local currentDate = os.date("%Y-%m-%d") + local currentTime = os.date("*t") + + if currentTime.wday == 4 and currentTime.hour >= 8 and + VaultGenerationTracker.lastProcessedDate ~= currentDate then + + VaultGenerationTracker.lastProcessedDate = currentDate + ProcessWeeklyVaultGeneration() + print("[Mythic+] Weekly vault generation processed for " .. currentDate) + end + + ScheduleVaultGeneration() + end, delayMs, 1) +end + +local function CheckAndProcessVaultOnStartup() + local now = os.time() + local t = os.date("*t", now) + local currentDate = os.date("%Y-%m-%d") + + if t.wday == 4 and t.hour >= 8 and + VaultGenerationTracker.lastProcessedDate ~= currentDate then + + VaultGenerationTracker.lastProcessedDate = currentDate + ProcessWeeklyVaultGeneration() + print("[Mythic+] Weekly vault generation processed on startup for " .. currentDate) + end + + ScheduleVaultGeneration() +end + +local function GetAffixSet(tier) + local affixes = {} + local baseAffixes = math.min(tier, 4) + for i = 1, baseAffixes do + local affix = WeeklyAffixesCache[i] + if affix then + if type(affix.spell) == "table" then + for _, s in ipairs(affix.spell) do + table.insert(affixes, s) + end + else + table.insert(affixes, affix.spell) + end + end + end + + if tier >= 5 then + table.insert(affixes, 58549) + end + + return affixes +end + +local function GetAffixNameSet(tier) + local names = {} + for i = 1, tier do + local affix = WeeklyAffixesCache[i] + if affix then + table.insert(names, affix.name) + end + end + return table.concat(names, ", ") +end + +local function LeaveDungeonMap(event, player) + local mapId = player:GetMapId() + if not mythicDungeonIds[mapId] then + AIO.Handle(player, "AIO_Mythic", "KillMythicTimerGUI") + end +end + +local function GetRandomMythicMapId() + local ids = {574, 575, 576, 578, 595, 599, 600, 601, 602, 604, 608, 619, 632, 650, 658, 668} + return ids[math.random(1, #ids)] +end + +local function PlayerHasAnyKeystone(player) + return player:HasItem(900100) +end + +local function IsRunActive(instanceId) + return MYTHIC_FLAG_TABLE[instanceId] == true +end + +local function HasValidKeyForCurrentDungeon(player) + local guid = player:GetGUIDLow() + local mapId = player:GetMapId() + local keyData = PlayerKeysCache[guid] + return keyData and keyData.mapId == mapId +end + +local function CalculateMythicRating(tier, timeRemainingPercent) + -- https://www.wowhead.com/guide/blizzard-mythic-plus-rating-score-in-game + -- base rating: 37.5 + (tier * 7.5) + (affixes * 7.5) + local baseRating = 37.5 + (tier * 7.5) + local affixCount = math.min(tier, 3) + local affixBonus = affixCount * 7.5 + + local totalBaseRating = baseRating + affixBonus + + local timeBonus = 0 + if timeRemainingPercent > 0 then + local bonusPercent = math.min(timeRemainingPercent, 40) / 40 + timeBonus = bonusPercent * 7.5 + else + local penaltyPercent = math.min(math.abs(timeRemainingPercent), 40) / 40 + timeBonus = -penaltyPercent * 15 + end + + return math.max(0, totalBaseRating + timeBonus) +end + +local function CalculateKeystoneUpgrade(timeRemainingPercent, completed) + if not completed then + return -1 + end + + if timeRemainingPercent >= 40 then + return 3 + elseif timeRemainingPercent >= 20 then + return 2 + elseif timeRemainingPercent > 0 then + return 1 + else + return -1 + end +end + +local function CalculateLootBonus(upgradeLevel) + local chanceMultiplier = 1.0 + local maxItems = 1 + + if upgradeLevel == 2 then + chanceMultiplier = 1.25 + maxItems = 1 + elseif upgradeLevel >= 3 then + chanceMultiplier = 1.5 + maxItems = 2 + end + + return chanceMultiplier, maxItems +end + +local function RecalculateTotalPoints(guid) + local cache = PlayerRatingCache[guid] + if not cache then return end + + local dungeonColumns = {574, 575, 576, 578, 595, 599, 600, 601, 602, 604, 608, 619, 632, 650, 658, 668} + local total = 0 + + for _, mapId in ipairs(dungeonColumns) do + total = total + (cache[mapId] or 0) + end + + local avg = total / #dungeonColumns + cache.total_points = avg + + SavePlayerRatingCache(guid) +end + +local function ApplyAuraToNearbyCreatures(player, affixes, tier) + local seen = {} + local map = player:GetMap() + if not map then + return + end + + local MYTHIC_SCAN_RADIUS = 240 + local count = 0 + + local creatures = player:GetCreaturesInRange(MYTHIC_SCAN_RADIUS, nil, 1, 1) + for _, creature in ipairs(creatures) do + local guid = creature:GetGUIDLow() + local entry = creature:GetEntry() + local faction = creature:GetFaction() + local shouldExclude = false + for _, excludeId in ipairs(AFFIX_EXCLUDE_CREATURES) do + if entry == excludeId then + shouldExclude = true + break + end + end + + if not seen[guid] + and creature:IsAlive() + and creature:IsInWorld() + and not creature:IsPlayer() + and creature:IsElite() + and not shouldExclude + and faction ~= 2 and faction ~= 3 and faction ~= 4 + and faction ~= 31 and faction ~= 35 and faction ~= 188 and faction ~= 1629 + and faction ~= 114 and faction ~= 115 and faction ~= 1 + then + seen[guid] = true + count = count + 1 + + for _, spellId in ipairs(affixes) do + if spellId == 58549 then + if tier >= 5 then + local stacks = tier - 4 + if not creature:HasAura(58549) then + creature:AddAura(58549, creature) + end + local aura = creature:GetAura(58549) + if aura then + aura:SetStackAmount(stacks) + end + end + else + if not creature:HasAura(spellId) then + creature:AddAura(spellId, creature) + end + end + end + end + end +end + +local function DowngradeKeystoneOnFail(player, tier) + local guid = player:GetGUIDLow() + + if not PlayerHasAnyKeystone(player) then + if tier > 1 then + local newTier = tier - 1 + local newMapId = GetRandomMythicMapId() + PlayerKeysCache[guid] = {mapId = newMapId, tier = newTier} + CharDBQuery(string.format("REPLACE INTO character_mythic_keys (guid, mapId, tier) VALUES (%d, %d, %d)", guid, newMapId, newTier)) + player:AddItem(900100, 1) + player:SendBroadcastMessage(string.format("[Mythic+] " .. GetLocalizedText(player, "UI", "Your keystone has been downgraded to Tier %d."), newTier)) + + CreateLuaEvent(function() + local p = GetPlayerByGUID(guid) + if p then + MythicHandlers.RequestMapNameAndTier(p) + end + end, 500, 1) + else + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "Your keystone was destroyed.")) + end + end +end + +local function SetEndOfRunUnitFlags(player) + if not player or not player:IsInWorld() then return end + local map = player:GetMap() + if not map then return end + local instanceId = map:GetInstanceId() + + local UNIT_FLAG_NOT_SELECTABLE = 33554432 + local UNIT_FLAG_NON_ATTACKABLE = 2 + local UNIT_FLAG_IMMUNE_TO_PC = 256 + local UNIT_FLAG_IMMUNE_TO_NPC = 512 + + local flagsToAdd = UNIT_FLAG_NOT_SELECTABLE + UNIT_FLAG_NON_ATTACKABLE + UNIT_FLAG_IMMUNE_TO_PC + UNIT_FLAG_IMMUNE_TO_NPC + + local function bitwise_or(a, b) + local result, bitval = 0, 1 + while a > 0 or b > 0 do + if (a % 2 == 1) or (b % 2 == 1) then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result + end + + local creatures = player:GetCreaturesInRange(2000, nil, 1, 1) + for _, creature in ipairs(creatures) do + local cMap = creature:GetMap() + if cMap and cMap:GetInstanceId() == instanceId then + local currentFlags = creature:GetUnitFlags() or 0 + local newFlags = bitwise_or(currentFlags, flagsToAdd) + if newFlags ~= currentFlags then + creature:SetUnitFlags(newFlags) + end + end + end +end + +local function CheckHallsOfReflectionProgress(event, player) + if player:GetMapId() ~= 668 then return end + local instanceId = player:GetMap():GetInstanceId() + if not IsRunActive(instanceId) then return end + local tracker = MYTHIC_BOSS_KILL_TRACKER[instanceId] + if not tracker then return end + + local lichKingStillRemaining = false + for _, bossEntry in ipairs(tracker.remaining) do + if bossEntry == 36954 then + lichKingStillRemaining = true + break + end + end + + if not lichKingStillRemaining then return end + local x, y = player:GetX(), player:GetY() + local completionPoint = {x = 5265.987, y = 1682.8047} + local function getDistance2D(x1, y1, x2, y2) + return math.sqrt((x1 - x2)^2 + (y1 - y2)^2) + end + local distance = getDistance2D(x, y, completionPoint.x, completionPoint.y) + if distance <= 15 then + local group = player:GetGroup() + local members = group and group:GetMembers() or { player } + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == 668 then + member:KilledMonsterCredit(36954) + end + end + + for i, bossEntry in ipairs(tracker.remaining) do + if bossEntry == 36954 then + table.remove(tracker.remaining, i) + local bossIndex = tracker.indexMap[36954] + + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == 668 then + AIO.Handle(member, "AIO_Mythic", "MarkBossKilled", 668, bossIndex) + end + end + break + end + end + CheckRunCompletion(instanceId, 668) + end +end + +local function StartAuraLoop(player, instanceId, mapId, affixes, interval, tier) + local guid = player:GetGUIDLow() + if MYTHIC_LOOP_HANDLERS[instanceId] then + RemoveEventById(MYTHIC_LOOP_HANDLERS[instanceId]) + end + local eventId = CreateLuaEvent(function() + local p = GetPlayerByGUID(guid) + if not p then return end + if not MYTHIC_FLAG_TABLE[instanceId] then return end + if p:GetMapId() ~= mapId then + if p:HasAura(8326) then + return + end + local runData = ActiveRunsCache[instanceId] + if runData and runData.run_id then + local now = os.time() + local duration = math.max(0, now - runData.start_time) + CharDBQuery(string.format([[ + UPDATE character_mythic_history + SET completed = 2, + end_time = FROM_UNIXTIME(%d), + duration = %d + WHERE run_id = %d + ]], now, duration, runData.run_id)) + p:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(p, "UI", "You left the dungeon. The run is over.")) + local validPlayer = GetPlayerByGUID(guid) + if validPlayer and validPlayer:IsInWorld() then + SetEndOfRunUnitFlags(validPlayer) + end + DowngradeKeystoneOnFail(p, runData.tier) + end + AIO.Handle(p, "AIO_Mythic", "KillMythicTimerGUI") + MYTHIC_FLAG_TABLE[instanceId] = nil + MYTHIC_AFFIXES_TABLE[instanceId] = nil + MYTHIC_LOOP_HANDLERS[instanceId] = nil + MYTHIC_REWARD_CHANCE_TABLE[instanceId] = nil + ActiveRunsCache[instanceId] = nil + if eventId ~= nil then + RemoveEventById(eventId) + end + return + end + + local bossData = MythicBosses[mapId] + if bossData then + local runData = ActiveRunsCache[instanceId] + if runData then + local now = os.time() + local elapsed = now - runData.start_time + + if elapsed >= (bossData.timer or 900) then + local runData = ActiveRunsCache[instanceId] + if runData and not runData.overtime_started then + runData.overtime_started = true + runData.overtime_start_time = now + + local group = p:GetGroup() + local members = group and group:GetMembers() or { p } + + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == mapId then + AIO.Handle(member, "AIO_Mythic", "StartOvertimeMode") + + DowngradeKeystoneOnFail(member, runData.tier) + + member:SendBroadcastMessage(string.format( + "[Mythic+] " .. GetLocalizedText(member, "UI", "Time expired! Tier %d failed. You can continue in overtime for the chance of loot."), + runData.tier + )) + end + end + + CharDBQuery(string.format([[ + UPDATE character_mythic_history + SET completed = 3 + WHERE run_id = %d + ]], runData.run_id)) + end + end + end + end + + ApplyAuraToNearbyCreatures(p, affixes, tier) + if mapId == 668 then + CheckHallsOfReflectionProgress(nil, p) + end + end, interval, 0) + MYTHIC_LOOP_HANDLERS[instanceId] = eventId +end + +function Pedestal_OnGossipHello(_, player, creature) + if not player:HasItem(900100) then + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "You do not have a Mythic Keystone.")) + player:GossipComplete() + return + end + + if not HasValidKeyForCurrentDungeon(player) then + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "Your keystone does not appear to fit.")) + player:GossipComplete() + return + end + + player:GossipClearMenu() + + local guid = player:GetGUIDLow() + local keyData = PlayerKeysCache[guid] + local tier = keyData and keyData.tier or 1 + + player:GossipMenuAddItem(5, GetLocalizedText(player, "UI", "Insert Keystone (Tier %d)"):format(tier), 0, 100, false, "", 0) + player:GossipMenuAddItem(2, GetLocalizedText(player, "UI", "Step away"), 0, 999) + player:GossipSendMenu(1, creature) +end + +function Pedestal_OnGossipSelect(_, player, _, _, intid) + if not HasValidKeyForCurrentDungeon(player) then + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "This keystone is not for this dungeon.")) + player:GossipComplete() + return + end + + if intid == 999 then + player:GossipComplete() + return + end + + if intid == 100 then + if not player:HasItem(900100) then + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "You do not have a Mythic Keystone.")) + player:GossipComplete() + return + end + + local map = player:GetMap() + if not map or map:GetDifficulty() == 0 then + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "Keystones cannot be used in Normal mode dungeons.")) + player:GossipComplete() + return + end + + local guid = player:GetGUIDLow() + local keyData = PlayerKeysCache[guid] + local tier = keyData and keyData.tier or 1 + local now = os.time() + local mapId = player:GetMapId() + local instanceId = map:GetInstanceId() + local group = player:GetGroup() + local members = group and group:GetMembers() or {player} + local today = os.date("%Y-%m-%d") + local affixes = GetAffixSet(tier) + local affixNames = {} + + for i = 1, math.min(tier, 4) do + local affix = WeeklyAffixesCache[i] + if affix then + table.insert(affixNames, affix.name) + end + end + + local safeAffixNames = table.concat(affixNames, ", "):gsub("'", "''") + + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == mapId then + local mguid = member:GetGUIDLow() + if not PlayerRatingCache[mguid] then + LoadPlayerCache(mguid) + end + + local cache = PlayerRatingCache[mguid] + if cache then + cache.total_runs = cache.total_runs + 1 + SavePlayerRatingCache(mguid) + end + end + end + + local guids = {} + for i = 1, 5 do guids[i] = 0 end + for i, member in ipairs(members) do + if i > 5 then break end + guids[i] = member:GetGUIDLow() + end + + CharDBQuery(string.format([[ + INSERT INTO character_mythic_history (member_1, member_2, member_3, member_4, member_5, date, mapId, instanceId, tier, completed, deaths, affixes) + VALUES (%d, %d, %d, %d, %d, '%s', %d, %d, %d, 0, 0, '%s'); + ]], guids[1], guids[2], guids[3], guids[4], guids[5], today, mapId, instanceId, tier, safeAffixNames)) + + local runIdQuery = CharDBQuery("SELECT LAST_INSERT_ID()") + local runId = runIdQuery and runIdQuery:GetUInt32(0) or 0 + + ActiveRunsCache[instanceId] = { + guid = guid, + mapId = mapId, + tier = tier, + start_time = nil, + deaths = 0, + run_id = runId, + members = {} + } + + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == mapId then + table.insert(ActiveRunsCache[instanceId].members, member:GetGUIDLow()) + end + end + + MYTHIC_FLAG_TABLE[instanceId] = false + MYTHIC_AFFIXES_TABLE[instanceId] = affixes + MYTHIC_REWARD_CHANCE_TABLE[instanceId] = tier <= 2 and 1.5 or tier <= 4 and 2.0 or 5.0 + + local cache = PlayerRatingCache[guid] + local currentRating = cache and cache[mapId] or 0 + local potentialGain = CalculateMythicRating(tier, 0) + + player:RemoveItem(900100, 1) + PlayerKeysCache[guid] = nil + CharDBQuery(string.format("DELETE FROM character_mythic_keys WHERE guid = %d", guid)) + + local map_x, map_y, map_z, map_o = GetMapEntrance(mapId) + local memberGuids = {} + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == mapId then + table.insert(memberGuids, member:GetGUID()) + member:NearTeleport(map_x, map_y, map_z, map_o) + member:SetRooted(true) + end + end + + local dungeoncreatures = player:GetCreaturesInRange(2000, nil, 0, 2) + for _, creature in ipairs(dungeoncreatures) do + creature:Respawn() + end + + for _, guid in ipairs(memberGuids) do + local member = GetPlayerByGUID(guid) + if member and member:IsInWorld() and member:GetMapId() == mapId then + AIO.Handle(member, "AIO_Mythic", "StartCountdown", 10) + end + end + + local starterGuid = player:GetGUID() + + CreateLuaEvent(function() + local starter = GetPlayerByGUID(starterGuid) + if not starter then return end + + for _, guid in ipairs(memberGuids) do + local member = GetPlayerByGUID(guid) + if member and member:IsInWorld() and member:GetMapId() == mapId then + member:SetRooted(false) + end + end + MYTHIC_FLAG_TABLE[instanceId] = true + ApplyAuraToNearbyCreatures(starter, affixes, tier) + StartAuraLoop(starter, instanceId, mapId, affixes, 6000, tier) + StartBossScanLoop(starter, instanceId, mapId, tier) + local bossData = MythicBosses[mapId] + if bossData then + local tracker = { remaining = {}, indexMap = {}, tier = tier } + for idx, entry in ipairs(bossData.bosses) do + tracker.remaining[#tracker.remaining + 1] = entry + tracker.indexMap[entry] = idx + end + MYTHIC_BOSS_KILL_TRACKER[instanceId] = tracker + + local actualStartTime = os.time() + if ActiveRunsCache[instanceId] then + ActiveRunsCache[instanceId].start_time = actualStartTime + + CharDBQuery(string.format([[ + UPDATE character_mythic_history + SET start_time = FROM_UNIXTIME(%d) + WHERE run_id = %d + ]], actualStartTime, ActiveRunsCache[instanceId].run_id)) + end + + for _, guid in ipairs(memberGuids) do + local member = GetPlayerByGUID(guid) + if member and member:IsInWorld() and member:GetMapId() == mapId then + AIO.Handle(member, "AIO_Mythic", "StartMythicTimerGUI", mapId, tier, bossData.timer or 900, GetLocalizedBossNames(member, mapId), potentialGain, bossData.enemies or 50) + end + end + end + end, 10000, 1) + + player:GossipComplete() + end +end + +function StartBossScanLoop(player, instanceId, mapId, tier) + local bosses = MythicBosses[mapId] + if not bosses or not bosses.bosses then + print("[Mythic+][Error] No boss data for mapId: " .. mapId) + return + end + + MYTHIC_BOSS_KILL_TRACKER[instanceId] = { + remaining = {}, + indexMap = {}, + tier = tier + } + + for idx, entry in ipairs(bosses.bosses) do + table.insert(MYTHIC_BOSS_KILL_TRACKER[instanceId].remaining, entry) + MYTHIC_BOSS_KILL_TRACKER[instanceId].indexMap[entry] = idx + end +end + +local function MythicBossKillCheck(event, player, killed) + local map = player:GetMap() + if not map then return end + local mapId = map:GetMapId() + local instanceId = map:GetInstanceId() + + if not IsRunActive(instanceId) then return end + + local NO_CORPSE_REMOVE_IDS = { + [26692] = true, -- 'Ymirjar Harpooner' in Utgarde Pinnacle // would not spawn harpoons otherwise + [28585] = true, -- 'Slag' in Halls of Lightning // respawn would be too fast + } + + local entry = killed:GetEntry() + local tracker = MYTHIC_BOSS_KILL_TRACKER[instanceId] + if not tracker then return end + + if not NO_CORPSE_REMOVE_IDS[entry] then + killed:RemoveCorpse() + end + + for i, bossEntry in ipairs(tracker.remaining) do + if bossEntry == entry then + table.remove(tracker.remaining, i) + local bossIndex = tracker.indexMap[entry] + local group = player:GetGroup() + local members = group and group:GetMembers() or { player } + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == mapId then + AIO.Handle(member, "AIO_Mythic", "MarkBossKilled", mapId, bossIndex) + end + end + break + end + end + + if #tracker.remaining == 0 then + CheckRunCompletion(instanceId, mapId) + end +end + +if MYTHIC_ENEMY_FORCES_TRACKER == nil then MYTHIC_ENEMY_FORCES_TRACKER = {} end +local function MythicEnemyKillCheck(event, player, killed) + local map = player:GetMap() + if not map then return end + local mapId = map:GetMapId() + local instanceId = map:GetInstanceId() + if not IsRunActive(instanceId) then return end + local entry = killed:GetEntry() + local bossData = MythicBosses[mapId] + if not bossData then return end + if not bossData.enemies or bossData.enemies == 0 then + return + end + for _, bossEntry in ipairs(bossData.bosses) do + if bossEntry == entry then + return + end + end + + local faction = killed:GetFaction() + if faction == 2 or faction == 3 or faction == 4 or faction == 31 or faction == 35 or + faction == 188 or faction == 1629 or faction == 114 or faction == 115 or faction == 1 then + return + end + + if not MYTHIC_ENEMY_FORCES_TRACKER[instanceId] then + MYTHIC_ENEMY_FORCES_TRACKER[instanceId] = { + current = 0, + required = bossData.enemies or 50, + completed = false + } + end + + local tracker = MYTHIC_ENEMY_FORCES_TRACKER[instanceId] + tracker.current = tracker.current + 1 + local percentage = math.min((tracker.current / tracker.required) * 100, 100) + if tracker.current >= tracker.required and not tracker.completed then + tracker.completed = true + end + + local group = player:GetGroup() + local members = group and group:GetMembers() or { player } + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == mapId then + AIO.Handle(member, "AIO_Mythic", "UpdateEnemyForces", tracker.current, tracker.required, percentage, tracker.completed) + end + end + CheckRunCompletion(instanceId, mapId) +end + +function CheckRunCompletion(instanceId, mapId) + local bossTracker = MYTHIC_BOSS_KILL_TRACKER[instanceId] + local enemyTracker = MYTHIC_ENEMY_FORCES_TRACKER[instanceId] + local bossData = MythicBosses[mapId] + local bossesComplete = bossTracker and #bossTracker.remaining == 0 + local enemyForcesComplete = true + if bossData and bossData.enemies and bossData.enemies > 0 then + enemyForcesComplete = enemyTracker and enemyTracker.completed + end + + if bossesComplete and enemyForcesComplete then + local runData = ActiveRunsCache[instanceId] + if not runData then return end + + local player = GetPlayerByGUID(runData.guid) + if not player then return end + + local group = player:GetGroup() + local members = group and group:GetMembers() or { player } + local now = os.time() + local bossData = MythicBosses[mapId] + local timerTotal = bossData and bossData.timer or 900 + + if not runData.start_time then return end + + local startTimeRaw = runData.start_time + local deaths = runData.deaths or 0 + local duration = math.max(0, now - startTimeRaw) + local remainingTime = math.max(0, timerTotal - duration) + local runId = runData.run_id + local wasOvertime = runData.overtime_started or false + + if runId and runId > 0 then + CharDBQuery(string.format([[ + UPDATE character_mythic_history + SET completed = 1, + end_time = FROM_UNIXTIME(%d), + duration = %d + WHERE run_id = %d; + ]], now, duration, runId)) + end + + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == mapId then + if wasOvertime then + AwardOvertimeLoot(member, bossTracker.tier) + else + AwardMythicPoints(member, bossTracker.tier, duration, deaths, remainingTime) + end + SetEndOfRunUnitFlags(member) + end + end + + MYTHIC_BOSS_KILL_TRACKER[instanceId] = nil + MYTHIC_ENEMY_FORCES_TRACKER[instanceId] = nil + MYTHIC_FLAG_TABLE[instanceId] = nil + MYTHIC_AFFIXES_TABLE[instanceId] = nil + MYTHIC_REWARD_CHANCE_TABLE[instanceId] = nil + ActiveRunsCache[instanceId] = nil + if MYTHIC_LOOP_HANDLERS[instanceId] then + RemoveEventById(MYTHIC_LOOP_HANDLERS[instanceId]) + MYTHIC_LOOP_HANDLERS[instanceId] = nil + end + end +end + +local function MythicPlayerDeath(event, killer, killed) + local map = killed:GetMap() + if not map or map:GetDifficulty() == 0 then return end + + local instanceId = map:GetInstanceId() + local runData = ActiveRunsCache[instanceId] + if not runData then return end + + runData.deaths = runData.deaths + 1 + local newDeaths = runData.deaths + local tier = runData.tier + CharDBQuery("UPDATE character_mythic_history SET deaths = " .. newDeaths .. " WHERE run_id = " .. runData.run_id) + local penalty = newDeaths * penaltyPerDeath + local group = killed:GetGroup() + local members = group and group:GetMembers() or { killed } + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == map:GetMapId() then + AIO.Handle(member, "AIO_Mythic", "UpdateMythicScore", penalty, newDeaths) + end + end + + local limit = (tier == 1) and 6 or 4 + if newDeaths >= limit then + local now = os.time() + local startTime = runData.start_time + if not startTime then return end + local duration = math.max(0, now - startTime) + local runId = runData.run_id + CharDBQuery(string.format([[ + UPDATE character_mythic_history + SET completed = 2, + end_time = FROM_UNIXTIME(%d), + duration = %d + WHERE run_id = %d + ]], now, duration, runId)) + + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == map:GetMapId() then + local mguid = member:GetGUIDLow() + local cache = PlayerRatingCache[mguid] + if cache then + cache.too_many_death_runs = cache.too_many_death_runs + 1 + SavePlayerRatingCache(mguid) + end + AIO.Handle(member, "AIO_Mythic", "StopMythicTimerGUI", 0) + DowngradeKeystoneOnFail(member, tier) + end + end + + MYTHIC_FLAG_TABLE[instanceId] = nil + MYTHIC_AFFIXES_TABLE[instanceId] = nil + MYTHIC_REWARD_CHANCE_TABLE[instanceId] = nil + ActiveRunsCache[instanceId] = nil + MYTHIC_ENEMY_FORCES_TRACKER[instanceId] = nil + if MYTHIC_LOOP_HANDLERS[instanceId] then + RemoveEventById(MYTHIC_LOOP_HANDLERS[instanceId]) + MYTHIC_LOOP_HANDLERS[instanceId] = nil + end + end +end + +local function CheckMalGanisEvade(event, creature) + if creature:GetEntry() ~= 26533 then return end + local map = creature:GetMap() + if not map or map:GetMapId() ~= 595 then return end + local instanceId = map:GetInstanceId() + if not IsRunActive(instanceId) then return end + if creature:IsInEvadeMode() then + local players = creature:GetPlayersInRange(100) + for _, player in ipairs(players) do + if player:IsInWorld() and player:GetMapId() == 595 then + player:KilledMonsterCredit(26533) + local group = player:GetGroup() + local members = group and group:GetMembers() or { player } + local tracker = MYTHIC_BOSS_KILL_TRACKER[instanceId] + if tracker then + for i, bossEntry in ipairs(tracker.remaining) do + if bossEntry == 26533 then + table.remove(tracker.remaining, i) + local bossIndex = tracker.indexMap[26533] + for _, member in ipairs(members) do + if member:IsInWorld() and member:GetMapId() == 595 then + AIO.Handle(member, "AIO_Mythic", "MarkBossKilled", 595, bossIndex) + end + end + break + end + end + if #tracker.remaining == 0 then + CheckRunCompletion(instanceId, mapId) + end + end + end + end + end +end + +local function isTierEligibleForBracket(bracket, tier) + if MYTHIC_LOOT_BRACKETS[bracket] then + local bracketData = MYTHIC_LOOT_BRACKETS[bracket] + if bracketData == "all" then + return true + elseif type(bracketData) == "table" then + for _, validTier in ipairs(bracketData) do + if validTier == tier then + return true + end + end + return false + end + end + + if bracket == "all" then + return true + end + + local singleTier = tonumber(bracket) + if singleTier then + return tier == singleTier + end + + local rangeStart, rangeEnd = bracket:match("^(%d+)-(%d+)$") + if rangeStart and rangeEnd then + rangeStart, rangeEnd = tonumber(rangeStart), tonumber(rangeEnd) + return tier >= rangeStart and tier <= rangeEnd + end + + local minTier = bracket:match("^(%d+)%+$") + if minTier then + minTier = tonumber(minTier) + return tier >= minTier + end + + local maxTier = bracket:match("^(%d+)-$") + if maxTier then + maxTier = tonumber(maxTier) + return tier <= maxTier + end + return false +end + +function isVaultTierEligibleForBracket(bracket, tier) + if VAULT_LOOT_BRACKETS[bracket] then + local bracketData = VAULT_LOOT_BRACKETS[bracket] + if bracketData == "all" then + return true + elseif type(bracketData) == "table" then + for _, validTier in ipairs(bracketData) do + if validTier == tier then + return true + end + end + return false + end + end + + if bracket == "all" then + return true + end + + local singleTier = tonumber(bracket) + if singleTier then + return tier == singleTier + end + + local rangeStart, rangeEnd = bracket:match("^(%d+)-(%d+)$") + if rangeStart and rangeEnd then + rangeStart, rangeEnd = tonumber(rangeStart), tonumber(rangeEnd) + return tier >= rangeStart and tier <= rangeEnd + end + + local minTier = bracket:match("^(%d+)%+$") + if minTier then + minTier = tonumber(minTier) + return tier >= minTier + end + + local maxTier = bracket:match("^(%d+)-$") + if maxTier then + maxTier = tonumber(maxTier) + return tier <= maxTier + end + + return false +end + +local function TryRewardMythicLoot(player, tier, upgradeLevel) + local faction = player:GetTeam() == 67 and "A" or (player:GetTeam() == 469 and "H" or "N") + local eligible = {} + local chanceMultiplier, maxItems = CalculateLootBonus(upgradeLevel) + + for _, loot in ipairs(MythicLootTable) do + if loot.type == "pet" and not MythicRewardConfig.pets then goto continue end + if loot.type == "mount" and not MythicRewardConfig.mounts then goto continue end + if loot.type == "gear" and not MythicRewardConfig.equipment then goto continue end + if loot.type == "spell" and not MythicRewardConfig.spells then goto continue end + if not isTierEligibleForBracket(loot.loot_bracket, tier) then goto continue end + if loot.faction ~= "N" and loot.faction ~= faction then goto continue end + if loot.type == "gear" then + if not CanPlayerUseItem(player, loot.itemid) then goto continue end + end + + table.insert(eligible, loot) + ::continue:: + end + + if #eligible == 0 then return end + + local rewardsGiven = 0 + local awardedItems = {} + + for attempt = 1, maxItems do + if rewardsGiven >= maxItems then break end + + local availableItems = {} + for _, loot in ipairs(eligible) do + if not awardedItems[loot.itemid] then + table.insert(availableItems, loot) + end + end + + if #availableItems == 0 then break end + + local reward = availableItems[math.random(1, #availableItems)] + local adjustedChance = reward.chancePercent * chanceMultiplier + if math.random() * 100 <= adjustedChance then + if reward.type == "gear" or reward.type == "pet" or reward.type == "mount" then + player:AddItem(reward.itemid, reward.amount) + local itemData = CacheItemTemplate(reward.itemid) + local itemName = itemData and itemData.name or "Unknown Item" + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "Reward:") .. " " .. itemName) + elseif reward.type == "spell" then + player:LearnSpell(reward.itemid) + player:SendBroadcastMessage("[Mythic+] " .. GetLocalizedText(player, "UI", "Reward: Spell learned!")) + end + if reward.additionalID and reward.additionalType then + if reward.additionalType == "item" then + player:AddItem(reward.additionalID, 1) + elseif reward.additionalType == "spell" then + player:LearnSpell(reward.additionalID) + elseif reward.additionalType == "skill" then + player:AdvanceSkill(reward.additionalID, 1) + end + end + awardedItems[reward.itemid] = true + rewardsGiven = rewardsGiven + 1 + end + end + + if upgradeLevel >= 2 then + local upgradeText = upgradeLevel == 2 and GetLocalizedText(player, "UI", "+2 Performance") or GetLocalizedText(player, "UI", "+3 Performance") + player:SendBroadcastMessage("[Mythic+] " .. upgradeText .. " - " .. GetLocalizedText(player, "UI", "Enhanced loot chances applied!")) + end +end + +function AwardMythicPoints(player, tier, duration, deaths, remainingTime) + local now = os.time() + local map = player:GetMap() + if not map then return end + local mapId = map:GetMapId() + local instanceId = map:GetInstanceId() + local guid = player:GetGUIDLow() + + local cache = PlayerRatingCache[guid] + if not cache then + cache = { + total_points = 0, total_runs = 0, completed_runs = 0, out_of_time_runs = 0, too_many_death_runs = 0, + [mapId] = 0 + } + PlayerRatingCache[guid] = cache + end + + local previous = cache[mapId] or 0 + local bossData = MythicBosses[mapId] + local timerTotal = bossData and bossData.timer or 900 + local timeRemainingPercent = (remainingTime / timerTotal) * 100 + local gainedRating = CalculateMythicRating(tier, timeRemainingPercent) + local deathPenalty = deaths * 10 + local finalRating = math.max(0, gainedRating - deathPenalty) + local newRating = math.max(previous, finalRating) + + cache[mapId] = newRating + cache.completed_runs = cache.completed_runs + 1 + cache.total_runs = cache.total_runs + 1 + + SavePlayerRatingCache(guid) + RecalculateTotalPoints(guid) + + local upgradeLevel = CalculateKeystoneUpgrade(timeRemainingPercent, true) + + AIO.Handle(player, "AIO_Mythic", "StopMythicTimerGUI", remainingTime) + AIO.Handle(player, "AIO_Mythic", "FinalizeMythicScore", deathPenalty, deaths, finalRating - gainedRating + deathPenalty) + + local dfmt = string.format("%02d:%02d", math.floor(duration/60), duration%60) + + local upgradeText = "" + if upgradeLevel > 0 then + upgradeText = string.format(" (Key +%d)", upgradeLevel) + elseif upgradeLevel < 0 then + upgradeText = " (Key -1)" + end + + player:SendBroadcastMessage(string.format( + "[Mythic+] %s\n%s", + GetLocalizedText(player, "UI", "Tier %d completed in %s%s."):format(tier, dfmt, upgradeText), + GetLocalizedText(player, "UI", "Rating: %d (+%d gained, -%d death penalty)"):format(newRating, gainedRating, deathPenalty) + )) + + if not PlayerHasAnyKeystone(player) then + local newTier = math.max(1, tier + upgradeLevel) + local newMapId = GetRandomMythicMapId() + + PlayerKeysCache[guid] = {mapId = newMapId, tier = newTier} + CharDBQuery(string.format("REPLACE INTO character_mythic_keys (guid, mapId, tier) VALUES (%d, %d, %d)", guid, newMapId, newTier)) + + player:AddItem(900100, 1) + player:SendBroadcastMessage(string.format("[Mythic+] %s", GetLocalizedText(player, "UI", "You received a Tier %d Mythic Keystone!"):format(newTier))) + + CreateLuaEvent(function() + local p = GetPlayerByGUID(guid) + if p then + MythicHandlers.RequestMapNameAndTier(p) + end + end, 500, 1) + end + + TryRewardMythicLoot(player, tier, upgradeLevel) + UpdateVaultProgress(player, tier, true) + LeaderboardCache.lastUpdate = 0 +end + +function AwardOvertimeLoot(player, tier) + local map = player:GetMap() + if not map then return end + local mapId = map:GetMapId() + local guid = player:GetGUIDLow() + local cache = PlayerRatingCache[guid] + if cache then + cache.completed_runs = cache.completed_runs + 1 + SavePlayerRatingCache(guid) + end + + AIO.Handle(player, "AIO_Mythic", "StopMythicTimerGUI", 0) + + player:SendBroadcastMessage(string.format( + "[Mythic+] %s", + GetLocalizedText(player, "UI", "Tier %d completed in overtime."):format(tier) + )) + + TryRewardMythicLoot(player, tier, 0) + LeaderboardCache.lastUpdate = 0 +end + +function BindKeystoneToDungeon(event, player, item, count) + local guid = player:GetGUIDLow() + + if item:GetEntry() == 900100 then + local existingKey = PlayerKeysCache[guid] + if not existingKey then + local newMapId = GetRandomMythicMapId() + local newTier = 1 + + PlayerKeysCache[guid] = {mapId = newMapId, tier = newTier} + CharDBQuery(string.format("REPLACE INTO character_mythic_keys (guid, mapId, tier) VALUES (%d, %d, %d)", guid, newMapId, newTier)) + end + + CreateLuaEvent(function() + local p = GetPlayerByGUID(guid) + if p then + MythicHandlers.RequestMapNameAndTier(p) + end + end, 100, 1) + end +end + +function MythicHandlers.RequestMapNameAndTier(player) + local guid = player:GetGUIDLow() + local keyData = PlayerKeysCache[guid] + + if keyData then + local mapName = GetLocalizedDungeonName(player, keyData.mapId) + AIO.Handle(player, "AIO_Mythic", "ReceiveMapNameAndTier", mapName, keyData.tier) + else + AIO.Handle(player, "AIO_Mythic", "ReceiveMapNameAndTier", + GetLocalizedText(player, "UI", "No Keystone"), 0) + end +end + +local function HeroicEndbossKeyReward(event, player, killed) + local map = player:GetMap() + if not map then return end + local mapId = map:GetMapId() + local difficulty = map:GetDifficulty() + if difficulty ~= 1 then return end + + local instanceId = map:GetInstanceId() + if MYTHIC_FLAG_TABLE[instanceId] then return end + + local bossData = MythicBosses[mapId] + if bossData and killed:GetEntry() == bossData.final then + if not PlayerHasAnyKeystone(player) then + player:AddItem(900100, 1) + player:SendBroadcastMessage(string.format("[Mythic+] %s", GetLocalizedText(player, "UI", "You received a Mythic Keystone!"))) + end + end +end + +local function OnPlayerLogin(event, player) + local guid = player:GetGUIDLow() + LoadPlayerCache(guid) + LoadPlayerVaultCache(guid) + PlayerNamesCache[guid] = player:GetName() + CreateLuaEvent(function() + local p = GetPlayerByGUID(guid) + if p then + MythicHandlers.RequestVaultStatus(p) + end + end, 2000, 1) +end + +local function OnPlayerLogout(event, player) + local guid = player:GetGUIDLow() + if PlayerRatingCache[guid] then + SavePlayerRatingCache(guid) + end +end + +RegisterCreatureGossipEvent(PEDESTAL_NPC_ENTRY, 1, Pedestal_OnGossipHello) +RegisterCreatureGossipEvent(PEDESTAL_NPC_ENTRY, 2, Pedestal_OnGossipSelect) +RegisterPlayerEvent(7, MythicBossKillCheck) +RegisterPlayerEvent(7, HeroicEndbossKeyReward) +RegisterPlayerEvent(7, MythicEnemyKillCheck) +RegisterPlayerEvent(8, MythicPlayerDeath) +RegisterPlayerEvent(53, BindKeystoneToDungeon) +RegisterPlayerEvent(28, LeaveDungeonMap) +RegisterPlayerEvent(3, OnPlayerLogin) +RegisterPlayerEvent(4, OnPlayerLogout) +RegisterCreatureEvent(26533, 1, CheckMalGanisEvade) +RegisterGameObjectEvent(VAULT_GAMEOBJECT_ID, 14, WeeklyVaultInteract) +CheckAndProcessVaultOnStartup() + +function MythicHandlers.RequestWeeklyAffixes(player) + if WeeklyAffixesCache and #WeeklyAffixesCache >= 3 then + local affix1 = WeeklyAffixesCache[1].name + local affix2 = WeeklyAffixesCache[2].name or "-" + local affix3 = WeeklyAffixesCache[3].name or "-" + AIO.Handle(player, "AIO_Mythic", "ReceiveWeeklyAffixes", affix1, affix2, affix3) + else + AIO.Handle(player, "AIO_Mythic", "ReceiveWeeklyAffixes", "?", "?", "?") + end +end + +function MythicHandlers.RequestTotalPoints(player) + local guid = player:GetGUIDLow() + local cache = PlayerRatingCache[guid] + + if not cache then + LoadPlayerCache(guid) + AIO.Handle(player, "AIO_Mythic", "ReceiveTotalPoints", 0, {}) + return + end + + local dungeonScores = {} + local dungeonIds = {574, 575, 576, 578, 595, 599, 600, 601, 602, 604, 608, 619, 632, 650, 658, 668} + for _, mapId in ipairs(dungeonIds) do + dungeonScores[tostring(mapId)] = cache[mapId] or 0 + end + + AIO.Handle(player, "AIO_Mythic", "ReceiveTotalPoints", cache.total_points, dungeonScores) +end + +function MythicHandlers.RequestLeaderboard(player) + UpdateLeaderboardCache() + AIO.Handle(player, "AIO_Mythic", "ReceiveLeaderboard", LeaderboardCache.topThree, LeaderboardCache.dungeonTop) +end + +function MythicHandlers.RequestRunState(player) + local map = player:GetMap() + if not map then return end + + local mapId = map:GetMapId() + local instanceId = map:GetInstanceId() + + if not IsRunActive(instanceId) then + AIO.Handle(player, "AIO_Mythic", "ClearRunState") + return + end + + local runData = ActiveRunsCache[instanceId] + if not runData then + AIO.Handle(player, "AIO_Mythic", "ClearRunState") + return + end + + local bossData = MythicBosses[mapId] + if not bossData then + AIO.Handle(player, "AIO_Mythic", "ClearRunState") + return + end + + local now = os.time() + local elapsed = now - runData.start_time + local duration = bossData.timer or 900 + local bossTracker = MYTHIC_BOSS_KILL_TRACKER[instanceId] + local enemyTracker = MYTHIC_ENEMY_FORCES_TRACKER[instanceId] + + local killedBosses = {} + if bossTracker then + for idx, entry in ipairs(bossData.bosses) do + local killed = true + for _, remaining in ipairs(bossTracker.remaining) do + if remaining == entry then + killed = false + break + end + end + if killed then + table.insert(killedBosses, idx) + end + end + end + + local enemyForces = { + current = 0, + required = bossData.enemies or 0, + percentage = 0, + completed = false + } + + if enemyTracker then + enemyForces.current = enemyTracker.current + enemyForces.required = enemyTracker.required + enemyForces.percentage = math.min((enemyTracker.current / enemyTracker.required) * 100, 100) + enemyForces.completed = enemyTracker.completed + end + + local cache = PlayerRatingCache[player:GetGUIDLow()] + local currentRating = cache and cache[mapId] or 0 + local potentialGain = CalculateMythicRating(runData.tier, 0) + + AIO.Handle(player, "AIO_Mythic", "RestoreRunState", { + active = true, + mapId = mapId, + tier = runData.tier, + duration = duration, + elapsed = elapsed, + bossNames = GetLocalizedBossNames(player, mapId), + killedBosses = killedBosses, + potentialGain = potentialGain, + penalty = runData.deaths * penaltyPerDeath, + deaths = runData.deaths, + maxDeaths = (runData.tier == 1) and 6 or 4, + enemyForces = enemyForces, + overtime = runData.overtime_started or false + }) +end \ No newline at end of file diff --git a/sql/Mythic+/characters/character_mythic_history.sql b/sql/Mythic+/characters/character_mythic_history.sql new file mode 100644 index 0000000..77960b9 --- /dev/null +++ b/sql/Mythic+/characters/character_mythic_history.sql @@ -0,0 +1,45 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- Server version: 8.0.42 - MySQL Community Server - GPL +-- Server OS: Linux +-- HeidiSQL Version: 12.11.0.7065 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table acore_characters.character_mythic_history +DROP TABLE IF EXISTS `character_mythic_history`; +CREATE TABLE IF NOT EXISTS `character_mythic_history` ( + `run_id` int unsigned NOT NULL AUTO_INCREMENT, + `date` date NOT NULL, + `mapId` smallint unsigned NOT NULL, + `instanceId` int unsigned DEFAULT NULL, + `tier` tinyint unsigned NOT NULL, + `start_time` timestamp NULL DEFAULT NULL, + `end_time` timestamp NULL DEFAULT NULL, + `duration` int unsigned DEFAULT NULL, + `deaths` tinyint unsigned DEFAULT '0', + `completed` tinyint(1) DEFAULT '0', + `affixes` text COLLATE utf8mb4_general_ci, + `member_1` int unsigned DEFAULT NULL, + `member_2` int unsigned DEFAULT NULL, + `member_3` int unsigned DEFAULT NULL, + `member_4` int unsigned DEFAULT NULL, + `member_5` int unsigned DEFAULT NULL, + PRIMARY KEY (`run_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Data exporting was unselected. + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/sql/Mythic+/characters/character_mythic_keys.sql b/sql/Mythic+/characters/character_mythic_keys.sql new file mode 100644 index 0000000..3003ce0 --- /dev/null +++ b/sql/Mythic+/characters/character_mythic_keys.sql @@ -0,0 +1,32 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- Server version: 8.0.42 - MySQL Community Server - GPL +-- Server OS: Linux +-- HeidiSQL Version: 12.11.0.7065 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table acore_characters.character_mythic_keys +DROP TABLE IF EXISTS `character_mythic_keys`; +CREATE TABLE IF NOT EXISTS `character_mythic_keys` ( + `guid` int unsigned NOT NULL, + `mapId` int unsigned NOT NULL, + `tier` int unsigned NOT NULL DEFAULT 1, + PRIMARY KEY (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Data exporting was unselected. + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/sql/Mythic+/characters/character_mythic_rating.sql b/sql/Mythic+/characters/character_mythic_rating.sql new file mode 100644 index 0000000..96a08c9 --- /dev/null +++ b/sql/Mythic+/characters/character_mythic_rating.sql @@ -0,0 +1,52 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- Server version: 8.0.42 - MySQL Community Server - GPL +-- Server OS: Linux +-- HeidiSQL Version: 12.11.0.7065 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table acore_characters.character_mythic_rating +DROP TABLE IF EXISTS `character_mythic_rating`; +CREATE TABLE IF NOT EXISTS `character_mythic_rating` ( + `guid` int unsigned NOT NULL, + `total_runs` int DEFAULT '0', + `total_points` decimal(7,2) DEFAULT '0.00', + `completed_runs` int DEFAULT '0' COMMENT 'Successfully completed runs', + `out_of_time_runs` int DEFAULT '0' COMMENT 'Runs that failed due to time limit', + `too_many_death_runs` int DEFAULT '0' COMMENT 'Runs that failed due to death limit', + `574` int DEFAULT '0' COMMENT 'Utgarde Keep', + `575` int DEFAULT '0' COMMENT 'Utgarde Pinnacle', + `576` int DEFAULT '0' COMMENT 'The Nexus', + `578` int DEFAULT '0' COMMENT 'The Oculus', + `595` int DEFAULT '0' COMMENT 'The Culling of Stratholme', + `599` int DEFAULT '0' COMMENT 'Halls of Stone', + `600` int DEFAULT '0' COMMENT 'Drak''Tharon Keep', + `601` int DEFAULT '0' COMMENT 'Azjol-Nerub', + `602` int DEFAULT '0' COMMENT 'Halls of Lightning', + `604` int DEFAULT '0' COMMENT 'Gundrak', + `608` int DEFAULT '0' COMMENT 'The Violet Hold', + `619` int DEFAULT '0' COMMENT 'Ahn''kahet: The Old Kingdom', + `632` int DEFAULT '0' COMMENT 'Devourer of Souls', + `650` int DEFAULT '0' COMMENT 'Trial of the Champion', + `658` int DEFAULT '0' COMMENT 'Pit of Saron', + `668` int DEFAULT '0' COMMENT 'Halls of Reflection', + `last_updated` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`guid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Data exporting was unselected. + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/sql/Mythic+/characters/character_mythic_vault.sql b/sql/Mythic+/characters/character_mythic_vault.sql new file mode 100644 index 0000000..b0f3b8b --- /dev/null +++ b/sql/Mythic+/characters/character_mythic_vault.sql @@ -0,0 +1,41 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- Server version: 8.0.42 - MySQL Community Server - GPL +-- Server OS: Linux +-- HeidiSQL Version: 12.11.0.7065 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table acore_characters.character_mythic_vault +DROP TABLE IF EXISTS `character_mythic_vault`; +CREATE TABLE IF NOT EXISTS `character_mythic_vault` ( + `guid` int unsigned NOT NULL, + `week_start` date NOT NULL, + `highest_tier_1` tinyint unsigned DEFAULT NULL, + `highest_tier_2` tinyint unsigned DEFAULT NULL, + `highest_tier_3` tinyint unsigned DEFAULT NULL, + `successful_runs` tinyint unsigned DEFAULT '0', + `item_1_id` int unsigned DEFAULT NULL, + `item_2_id` int unsigned DEFAULT NULL, + `item_3_id` int unsigned DEFAULT NULL, + `items_generated` tinyint(1) DEFAULT '0', + `has_collected` tinyint(1) DEFAULT '0', + `can_collect` tinyint(1) DEFAULT '0', + PRIMARY KEY (`guid`,`week_start`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- Data exporting was unselected. + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/sql/Mythic+/characters/character_mythic_weekly_affixes.sql b/sql/Mythic+/characters/character_mythic_weekly_affixes.sql new file mode 100644 index 0000000..1147b60 --- /dev/null +++ b/sql/Mythic+/characters/character_mythic_weekly_affixes.sql @@ -0,0 +1,33 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- Server version: 8.0.42 - MySQL Community Server - GPL +-- Server OS: Linux +-- HeidiSQL Version: 12.11.0.7065 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table acore_characters.character_mythic_weekly_affixes +DROP TABLE IF EXISTS `character_mythic_weekly_affixes`; +CREATE TABLE IF NOT EXISTS `character_mythic_weekly_affixes` ( + `week_start` date NOT NULL, + `affix1` varchar(50) COLLATE utf8mb4_general_ci NOT NULL, + `affix2` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL, + `affix3` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL, + PRIMARY KEY (`week_start`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Data exporting was unselected. + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/sql/Mythic+/world/creature_and_keystones.sql b/sql/Mythic+/world/creature_and_keystones.sql new file mode 100644 index 0000000..eaaa9c8 --- /dev/null +++ b/sql/Mythic+/world/creature_and_keystones.sql @@ -0,0 +1,1304 @@ +DELETE FROM + `creature_template` +WHERE + (`entry` = 900001); + +INSERT INTO + `creature_template` ( + `entry`, + `difficulty_entry_1`, + `difficulty_entry_2`, + `difficulty_entry_3`, + `KillCredit1`, + `KillCredit2`, + `name`, + `subname`, + `IconName`, + `gossip_menu_id`, + `minlevel`, + `maxlevel`, + `exp`, + `faction`, + `npcflag`, + `speed_walk`, + `speed_run`, + `speed_swim`, + `speed_flight`, + `detection_range`, + `scale`, + `rank`, + `dmgschool`, + `DamageModifier`, + `BaseAttackTime`, + `RangeAttackTime`, + `BaseVariance`, + `RangeVariance`, + `unit_class`, + `unit_flags`, + `unit_flags2`, + `dynamicflags`, + `family`, + `trainer_type`, + `trainer_spell`, + `trainer_class`, + `trainer_race`, + `type`, + `type_flags`, + `lootid`, + `pickpocketloot`, + `skinloot`, + `PetSpellDataId`, + `VehicleId`, + `mingold`, + `maxgold`, + `AIName`, + `MovementType`, + `HoverHeight`, + `HealthModifier`, + `ManaModifier`, + `ArmorModifier`, + `ExperienceModifier`, + `RacialLeader`, + `movementId`, + `RegenHealth`, + `mechanic_immune_mask`, + `spell_school_immune_mask`, + `flags_extra`, + `ScriptName`, + `VerifiedBuild` + ) +VALUES + ( + 900001, + 0, + 0, + 0, + 0, + 0, + 'Font of Power', + '', + 'Speak', + 900001, + 80, + 80, + 0, + 35, + 1, + 1, + 1.14286, + 1, + 1, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 768, + 32768, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1048576, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + '', + 0, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 1, + 0, + 0, + 0, + 'MythicPedestal', + 12340 + ); + +DELETE FROM + `creature_template_model` +WHERE + (`CreatureID` = 900001); + +INSERT INTO + `creature_template_model` ( + `CreatureID`, + `Idx`, + `CreatureDisplayID`, + `DisplayScale`, + `Probability`, + `VerifiedBuild` + ) +VALUES + (900001, 1, 24868, 1, 1, 0); + +DELETE FROM + `item_template` +WHERE + (`entry` = 900100); + +INSERT INTO + `item_template` ( + `entry`, + `class`, + `subclass`, + `SoundOverrideSubclass`, + `name`, + `displayid`, + `Quality`, + `Flags`, + `FlagsExtra`, + `BuyCount`, + `BuyPrice`, + `SellPrice`, + `InventoryType`, + `AllowableClass`, + `AllowableRace`, + `ItemLevel`, + `RequiredLevel`, + `RequiredSkill`, + `RequiredSkillRank`, + `requiredspell`, + `requiredhonorrank`, + `RequiredCityRank`, + `RequiredReputationFaction`, + `RequiredReputationRank`, + `maxcount`, + `stackable`, + `ContainerSlots`, + `stat_type1`, + `stat_value1`, + `stat_type2`, + `stat_value2`, + `stat_type3`, + `stat_value3`, + `stat_type4`, + `stat_value4`, + `stat_type5`, + `stat_value5`, + `stat_type6`, + `stat_value6`, + `stat_type7`, + `stat_value7`, + `stat_type8`, + `stat_value8`, + `stat_type9`, + `stat_value9`, + `stat_type10`, + `stat_value10`, + `ScalingStatDistribution`, + `ScalingStatValue`, + `dmg_min1`, + `dmg_max1`, + `dmg_type1`, + `dmg_min2`, + `dmg_max2`, + `dmg_type2`, + `armor`, + `holy_res`, + `fire_res`, + `nature_res`, + `frost_res`, + `shadow_res`, + `arcane_res`, + `delay`, + `ammo_type`, + `RangedModRange`, + `spellid_1`, + `spelltrigger_1`, + `spellcharges_1`, + `spellppmRate_1`, + `spellcooldown_1`, + `spellcategory_1`, + `spellcategorycooldown_1`, + `spellid_2`, + `spelltrigger_2`, + `spellcharges_2`, + `spellppmRate_2`, + `spellcooldown_2`, + `spellcategory_2`, + `spellcategorycooldown_2`, + `spellid_3`, + `spelltrigger_3`, + `spellcharges_3`, + `spellppmRate_3`, + `spellcooldown_3`, + `spellcategory_3`, + `spellcategorycooldown_3`, + `spellid_4`, + `spelltrigger_4`, + `spellcharges_4`, + `spellppmRate_4`, + `spellcooldown_4`, + `spellcategory_4`, + `spellcategorycooldown_4`, + `spellid_5`, + `spelltrigger_5`, + `spellcharges_5`, + `spellppmRate_5`, + `spellcooldown_5`, + `spellcategory_5`, + `spellcategorycooldown_5`, + `bonding`, + `description`, + `PageText`, + `LanguageID`, + `PageMaterial`, + `startquest`, + `lockid`, + `Material`, + `sheath`, + `RandomProperty`, + `RandomSuffix`, + `block`, + `itemset`, + `MaxDurability`, + `area`, + `Map`, + `BagFamily`, + `TotemCategory`, + `socketColor_1`, + `socketContent_1`, + `socketColor_2`, + `socketContent_2`, + `socketColor_3`, + `socketContent_3`, + `socketBonus`, + `GemProperties`, + `RequiredDisenchantSkill`, + `ArmorDamageModifier`, + `duration`, + `ItemLimitCategory`, + `HolidayId`, + `ScriptName`, + `DisenchantID`, + `FoodType`, + `minMoneyLoot`, + `maxMoneyLoot`, + `flagsCustom`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 12, + 0, + -1, + 'Mythic Keystone', + 62471, + 4, + 64, + 0, + 1, + 0, + 0, + 0, + -1, + -1, + 0, + 80, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 9999, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1000, + 0, + 0, + 0, + 0, + 0, + 0, + -1, + 0, + -1, + 0, + 0, + 0, + 0, + -1, + 0, + -1, + 0, + 0, + 0, + 0, + -1, + 0, + -1, + 0, + 0, + 0, + 0, + -1, + 0, + -1, + 0, + 0, + 0, + 0, + -1, + 0, + -1, + 1, + 'Place within the Font of Power inside the dungeon on Heroic difficulty.', + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + -1, + 0, + 0, + 0, + 0, + '', + 0, + 0, + 0, + 0, + 0, + 0 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'deDE', + 'Mythischer Schlüsselstein', + 'Setzt ihn im Dungeon auf dem Schwierigkeitsgrad ''Heroisch'' im Born der Macht ein.', + 15050 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'esES', + 'Piedra angular mítica', + 'Colócala dentro de la fuente de poder que hay en la mazmorra en dificultad heroica.', + 15050 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'esMX', + 'Piedra angular mítica', + 'Colócala dentro de la fuente de poder que hay en la mazmorra en dificultad heroica.', + 15050 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'frFR', + 'Clé mythique', + 'À insérer dans la fontaine de puissance à l''intérieur d''un donjon en mode héroïque.', + 15050 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'koKR', + '신화 쐐기돌', + '영웅 난이도 던전 안에 있는 마력의 샘에 넣으십시오.', + 15050 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'ruRU', + 'Эпохальный ключ', + 'Положите ключ в Чашу силы в подземелье в героическом режиме.', + 15050 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'zhCN', + '[Mythic Keystone]', + '[Place within the Font of Power inside the dungeon on Heroic difficulty.]', + 15050 + ); + +INSERT INTO + `item_template_locale` ( + `ID`, + `locale`, + `Name`, + `Description`, + `VerifiedBuild` + ) +VALUES + ( + 900100, + 'zhTW', + '傳奇鑰石', + '在英雄難度中,放置在地城裡的能量之泉內。', + 15050 + ); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + (900001, 'deDE', 'Born der Macht', '', 15050); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + (900001, 'esES', 'Fuente de poder', '', 15050); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + (900001, 'esMX', 'Fuente de poder', '', 15050); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + ( + 900001, + 'frFR', + 'Fontaine de puissance', + '', + 15050 + ); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + (900001, 'koKR', '마력의 샘', '', 15050); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + (900001, 'ruRU', 'Чаша силы', '', 15050); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + (900001, 'zhCN', 'Font of Power', '', 15050); + +INSERT INTO + `creature_template_locale` ( + `entry`, + `locale`, + `Name`, + `Title`, + `VerifiedBuild` + ) +VALUES + (900001, 'zhTW', '能量之泉', '', 15050); + +INSERT INTO + `creature` ( + `id1`, + `id2`, + `id3`, + `map`, + `zoneId`, + `areaId`, + `spawnMask`, + `phaseMask`, + `equipment_id`, + `position_x`, + `position_y`, + `position_z`, + `orientation`, + `spawntimesecs`, + `wander_distance`, + `currentwaypoint`, + `curhealth`, + `curmana`, + `MovementType`, + `npcflag`, + `unit_flags`, + `dynamicflags`, + `ScriptName`, + `VerifiedBuild`, + `CreateObject`, + `Comment` + ) +VALUES + ( + 900001, + 0, + 0, + 574, + 0, + 0, + 2, + 1, + 0, + 173.299, + -92.1371, + 12.5535, + 2.75347, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 575, + 0, + 0, + 2, + 1, + 0, + 571.32, + -333.858, + 110.14, + 0.844163, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 576, + 0, + 0, + 2, + 1, + 0, + 172.834, + -10.6004, + -16.636, + 1.61453, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 578, + 0, + 0, + 2, + 1, + 0, + 1061.64, + 995.343, + 361.072, + 4.26374, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 595, + 0, + 0, + 2, + 1, + 0, + 1430.53, + 549.692, + 35.8522, + 2.32704, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 599, + 0, + 0, + 2, + 1, + 0, + 1133.13, + 811.837, + 195.835, + 0.0394203, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 600, + 0, + 0, + 2, + 1, + 0, + -502.675, + -513.078, + 11.0454, + 3.12431, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 601, + 0, + 0, + 2, + 1, + 0, + 423.48, + 802.589, + 827.911, + 4.61237, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 602, + 0, + 0, + 2, + 1, + 0, + 1311.83, + 268.017, + 53.2948, + 0.450539, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 604, + 0, + 0, + 2, + 1, + 0, + 1889.09, + 629.347, + 176.694, + 2.4522, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 604, + 0, + 0, + 2, + 1, + 0, + 1886.73, + 855.669, + 176.694, + 3.92115, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 608, + 0, + 0, + 2, + 1, + 0, + 1819.33, + 809.071, + 44.3639, + 4.73869, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 619, + 0, + 0, + 2, + 1, + 0, + 390.893, + -1089.21, + 47.3606, + 2.55577, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 632, + 0, + 0, + 2, + 1, + 0, + 4910.44, + 2180.07, + 638.734, + 0.161806, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 650, + 0, + 0, + 2, + 1, + 0, + 799.102, + 609.297, + 412.364, + 3.01206, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 658, + 0, + 0, + 2, + 1, + 0, + 435.35, + 202.332, + 528.718, + 1.50715, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ), + ( + 900001, + 0, + 0, + 668, + 0, + 0, + 2, + 1, + 0, + 5231.32, + 1945.65, + 707.695, + 5.5679, + 300, + 0, + 0, + 5342, + 0, + 0, + 0, + 0, + 0, + '', + NULL, + 0, + NULL + ); + +DELETE FROM + `gameobject_template` +WHERE + `entry` = 900000; + +INSERT INTO + `gameobject_template` ( + `entry`, + `type`, + `displayId`, + `name`, + `IconName`, + `castBarCaption`, + `unk1`, + `size`, + `Data0`, + `Data1`, + `Data2`, + `Data3`, + `Data4`, + `Data5`, + `Data6`, + `Data7`, + `Data8`, + `Data9`, + `Data10`, + `Data11`, + `Data12`, + `Data13`, + `Data14`, + `Data15`, + `Data16`, + `Data17`, + `Data18`, + `Data19`, + `Data20`, + `Data21`, + `Data22`, + `Data23`, + `AIName`, + `ScriptName`, + `VerifiedBuild` + ) +VALUES + ( + 900000, + 3, + 8685, + 'Mythic Vault', + '', + '', + '', + 2.5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + '', + '', + 0 + ); + +INSERT INTO + `gameobject` ( + `id`, + `map`, + `zoneId`, + `areaId`, + `spawnMask`, + `phaseMask`, + `position_x`, + `position_y`, + `position_z`, + `orientation`, + `rotation0`, + `rotation1`, + `rotation2`, + `rotation3`, + `spawntimesecs`, + `animprogress`, + `state`, + `ScriptName`, + `VerifiedBuild`, + `Comment` + ) +VALUES + ( + 900000, + 571, + 0, + 0, + 1, + 1, + 5732.36, + 515.232, + 647.452, + 0.358227, + 0, + 0, + 0.178157, + 0.984002, + 300, + 0, + 1, + '', + NULL, + NULL + ); \ No newline at end of file diff --git a/sql/Mythic+/world/world_mythic_loot.sql b/sql/Mythic+/world/world_mythic_loot.sql new file mode 100644 index 0000000..d3ec1c4 --- /dev/null +++ b/sql/Mythic+/world/world_mythic_loot.sql @@ -0,0 +1,67 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- Server version: 8.0.42 - MySQL Community Server - GPL +-- Server OS: Linux +-- HeidiSQL Version: 12.11.0.7065 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table acore_world.world_mythic_loot +DROP TABLE IF EXISTS `world_mythic_loot`; +CREATE TABLE IF NOT EXISTS `world_mythic_loot` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `itemid` int unsigned NOT NULL, + `itemname` varchar(255) NOT NULL COMMENT 'Item name for reference only - not used by script', + `amount` int unsigned NOT NULL DEFAULT '1', + `type` varchar(32) NOT NULL, + `faction` char(1) NOT NULL DEFAULT 'N', + `loot_bracket` varchar(50) NOT NULL COMMENT 'Tier eligibility: bracket names, ranges (1-3), single tiers (5), or conditions (5+, 3-)', + `chancePercent` float NOT NULL, + `additionalID` int unsigned DEFAULT NULL, + `additionalType` varchar(32) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- Dumping data for table acore_world.world_mythic_loot: ~27 rows (approximately) +INSERT INTO `world_mythic_loot` (`id`, `itemid`, `itemname`, `amount`, `type`, `faction`, `loot_bracket`, `chancePercent`, `additionalID`, `additionalType`) VALUES + (1, 12354, 'Palomino Bridle', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (2, 12353, 'White Stallion Bridle', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (3, 12302, 'Reins of the Ancient Frostsaber', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (4, 12303, 'Reins of the Nightsaber', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (5, 13328, 'Black Ram', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (6, 13329, 'Frost Ram', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (7, 13327, 'Icy Blue Mechanostrider Mod A', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (8, 13326, 'White Mechanostrider Mod B', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (9, 13325, 'Fluorescent Green Mechanostrider', 1, 'mount', 'A', '10+', 0.2, NULL, NULL), + (10, 12351, 'Horn of the Arctic Wolf', 1, 'mount', 'H', '10+', 0.2, NULL, NULL), + (11, 12330, 'Horn of the Red Wolf', 1, 'mount', 'H', '10+', 0.2, NULL, NULL), + (12, 15292, 'Green Kodo', 1, 'mount', 'H', '10+', 0.2, NULL, NULL), + (13, 15293, 'Teal Kodo', 1, 'mount', 'H', '10+', 0.2, NULL, NULL), + (14, 13317, 'Whistle of the Ivory Raptor', 1, 'mount', 'H', '10+', 0.2, NULL, NULL), + (15, 8586, 'Whistle of the Mottled Red Raptor', 1, 'mount', 'H', '10+', 0.2, NULL, NULL), + (16, 33809, 'Amani War Bear', 1, 'mount', 'N', '10+', 0.2, NULL, NULL), + (17, 33976, 'Brefest Ram', 1, 'mount', 'N', '10+', 0.2, NULL, NULL), + (18, 8630, 'Reins of the Bengal Tiger', 1, 'mount', 'N', '10+', 0.2, 828, 'spell'), + (19, 40110, 'Haunted Memento', 1, 'pet', 'N', 'all', 0.2, NULL, NULL), + (20, 45942, 'XS-001 Constructor Bot', 1, 'pet', 'N', 'pets', 0.2, NULL, NULL), + (21, 18964, 'Turtle Egg (Loggerhead)', 1, 'pet', 'N', 'pets', 0.2, NULL, NULL), + (22, 21168, 'Baby Shark', 1, 'pet', 'N', 'pets', 0.2, NULL, NULL), + (23, 49662, 'Gryphon Hatchling', 1, 'pet', 'N', 'pets', 0.2, NULL, NULL), + (24, 49663, 'Wind Rider Cub', 1, 'pet', 'N', 'pets', 0.2, NULL, NULL), + (25, 45180, 'Murkimus\' Little Spear', 1, 'pet', 'N', 'pets', 0.2, NULL, NULL), + (26, 13262, 'Ashbringer', 1, 'gear', 'N', '11+', 0.2, NULL, NULL), + (27, 22691, 'Corrupted Ashbringer', 1, 'gear', 'N', '11+', 0.2, NULL, NULL); + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/sql/Mythic+/world/world_vault_loot.sql b/sql/Mythic+/world/world_vault_loot.sql new file mode 100644 index 0000000..a474f58 --- /dev/null +++ b/sql/Mythic+/world/world_vault_loot.sql @@ -0,0 +1,34 @@ +-- -------------------------------------------------------- +-- Host: localhost +-- Server version: 8.0.42 - MySQL Community Server - GPL +-- Server OS: Linux +-- HeidiSQL Version: 12.11.0.7065 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- Dumping structure for table acore_world.world_vault_loot +DROP TABLE IF EXISTS `world_vault_loot`; +CREATE TABLE IF NOT EXISTS `world_vault_loot` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `itemid` int unsigned NOT NULL, + `loot_bracket` varchar(50) NOT NULL, + `chancePercent` float NOT NULL, + `faction` char(1) NOT NULL DEFAULT 'N', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- Data exporting was unselected. + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;