From f9d2e81564fc39009da63ac74174720b3275d186 Mon Sep 17 00:00:00 2001 From: Tercio Jose Date: Tue, 12 Mar 2024 21:32:20 -0300 Subject: [PATCH] Added combat:GetBossHealth(); combat:GetBossName(); combat:GetCurrentPhase() --- Definitions.lua | 7 ++ Libs/DF/definitions.lua | 17 +++ Libs/DF/fw.lua | 110 +++++++++++++----- classes/class_combat.lua | 61 ++++++++++ core/control.lua | 4 + core/gears.lua | 13 ++- core/network.lua | 85 +++++++++++--- frames/window_main.lua | 16 ++- .../window_mythicplus/window_end_of_run.lua | 6 +- functions/bossmods.lua | 34 +++--- functions/mythicdungeon/mythicdungeon.lua | 2 +- images/icons.tga | Bin 1048620 -> 1048620 bytes sounds/bassdrop2.mp3 | Bin 0 -> 18733 bytes textures.lua | 14 +++ 14 files changed, 294 insertions(+), 75 deletions(-) create mode 100644 sounds/bassdrop2.mp3 diff --git a/Definitions.lua b/Definitions.lua index b70d06ee..78629ece 100644 --- a/Definitions.lua +++ b/Definitions.lua @@ -223,6 +223,8 @@ ---@field last_events_tables table[] where the death log of each player is stored ---@field boss_hp number percentage of the health points of the boss ---@field training_dummy boolean if true, the combat is against a training dummy +---@field playerTalents table [playerName] = "talent string" +---@field bossName string? the name of the boss, if the combat has no unitId "boss1", this value is nil ---@field ---@field ---@field __call table @@ -256,6 +258,8 @@ ---@field is_world_trash_combat boolean when true this combat is a regular combat done in the world, not in a dungeon, raid, battleground, arena, ... ---@field player_last_events table record the latest events of each player, latter used to build the death log ---@field +---@field GetCurrentPhase fun(combat: combat) : number return the current phase of the combat or the phase where the combat ended +---@field StoreTalents fun(self:combat) ---@field FindEnemyName fun(combat: combat) : string attempt to get the name of the enemy in the combat by getting the top most damaged unit by the player ---@field GetTryNumber fun(combat: combat) : number? ---@field GetFormattedCombatTime fun(combat: combat) : string @@ -298,6 +302,9 @@ ---@field GetEncounterName fun(combat: combat) : string get the name of the encounter ---@field GetBossImage fun(combat: combat) : texturepath|textureid get the icon of the encounter ---@field SetDateToNow fun(combat: combat, bSetStartDate: boolean?, bSetEndDate: boolean?) set the date to the current time. format: "H:M:S" +---@field GetBossHealth fun(combat: combat) : number get the percentage of the boss health when the combat ended +---@field GetBossName fun(combat: combat) : string? return the name of the unitId "boss1", nil if the unit doesn't existed during the combat + ---@class actorcontainer : table contains two tables _ActorTable and _NameIndexTable, the _ActorTable contains the actors, the _NameIndexTable contains the index of the actors in the _ActorTable, making quick to reorder them without causing overhead ---@field need_refresh boolean when true the container is dirty and needs to be refreshed diff --git a/Libs/DF/definitions.lua b/Libs/DF/definitions.lua index 822548cf..2f413928 100644 --- a/Libs/DF/definitions.lua +++ b/Libs/DF/definitions.lua @@ -157,8 +157,23 @@ ---@field SliderCounter number when no name is given, a string plus an incremental number is used instead ---@field SwitchCounter number when no name is given, a string plus an incremental number is used instead ---@field SplitBarCounter number when no name is given, a string plus an incremental number is used instead +---@field TalentExporter table ---@field FormatNumber fun(number:number) : string abbreviate a number, e.g. 1000 -> 1k 1000 -> 1천, depending on the client language ---@field UnitGroupRolesAssigned fun(unitId: unit, bUseSupport:boolean?, specId: specializationid?) : string there's no self here +---@field IsDragonflight fun():boolean +---@field IsDragonflightAndBeyond fun():boolean +---@field IsTimewalkWoW fun():boolean +---@field IsClassicWow fun():boolean +---@field IsTBCWow fun():boolean +---@field IsWotLKWow fun():boolean +---@field IsCataWow fun():boolean +---@field IsPandaWow fun():boolean +---@field IsWarlordsWow fun():boolean +---@field IsLegionWow fun():boolean +---@field IsBFAWow fun():boolean +---@field IsShadowlandsWow fun():boolean +---@field IsDragonflightWow fun():boolean +---@field IsWarWow fun():boolean ---@field LoadSpellCache fun(self:table, hashMap:table, indexTable:table, allSpellsSameName:table) : hashMap:table, indexTable:table, allSpellsSameName:table load all spells in the game and add them into the passed tables ---@field UnloadSpellCache fun(self:table) wipe the table contents filled with LoadSpellCache() ---@field GetCurrentClassName fun(self:table) : string return the name of the class the player is playing @@ -167,7 +182,9 @@ ---@field GetCurrentSpec fun(self:table):number? ---@field GetCurrentSpecId fun(self:table):number? return the specId of the current spec, retuns nil if the expansion the player is playing does not support specs ---@field GetClassSpecIds fun(self:table, engClass:string):number[] +---@field GetClassSpecIDs fun(self:table, engClass:string):number[] ---@field IsValidSpecId fun(self:table, specId:number):boolean check if the passed specId is valid for the player class, also return false for tutorial specs +---@field GetDragonlightTalentString fun(self:table):string return the talent config string ---@field GetClassList fun(self:table):{ID:number, Name:string, FileString:string, Texture:string, TexCoord:number[]}[] ---@field DebugVisibility fun(self:table, object:uiobject) print the reason why the frame isn't shown in the screen ---@field Dispatch fun(self:table, callback:function, ...) : any dispatch a function call using xpcall, print to chat if the function passed is invalid diff --git a/Libs/DF/fw.lua b/Libs/DF/fw.lua index c065784b..beb9bf28 100644 --- a/Libs/DF/fw.lua +++ b/Libs/DF/fw.lua @@ -1,6 +1,6 @@ -local dversion = 521 +local dversion = 522 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) @@ -31,6 +31,8 @@ local UnitIsTapDenied = UnitIsTapDenied SMALL_NUMBER = 0.000001 ALPHA_BLEND_AMOUNT = 0.8400251 +local _, _, _, buildInfo = GetBuildInfo() + DF.dversion = dversion DF.AuthorInfo = { @@ -77,62 +79,104 @@ function DF:GetDefaultBackdropColor() return 0.1215, 0.1176, 0.1294, 0.8 end +---return if the wow version the player is playing is dragonflight +---@return boolean +function DF.IsDragonflight() + if (buildInfo < 110000 and buildInfo >= 100000) then return true end + return false +end + ---return if the wow version the player is playing is dragonflight or an expansion after it ---@return boolean function DF.IsDragonflightAndBeyond() return select(4, GetBuildInfo()) >= 100000 end ----return if the wow version the player is playing is dragonflight ----@return boolean -function DF.IsDragonflight() - local _, _, _, buildInfo = GetBuildInfo() - if (buildInfo < 110000 and buildInfo >= 100000) then - return true - end - return false -end - ---return if the wow version the player is playing is a classic version of wow ---@return boolean function DF.IsTimewalkWoW() - local _, _, _, buildInfo = GetBuildInfo() - if (buildInfo < 40000) then - return true - end + if (buildInfo < 40000) then return true end return false end ---return if the wow version the player is playing is the vanilla version of wow ---@return boolean function DF.IsClassicWow() - local _, _, _, buildInfo = GetBuildInfo() - if (buildInfo < 20000) then - return true - end + if (buildInfo < 20000) then return true end return false end ---return true if the player is playing in the TBC version of wow ---@return boolean function DF.IsTBCWow() - local _, _, _, buildInfo = GetBuildInfo() - if (buildInfo < 30000 and buildInfo >= 20000) then - return true - end + if (buildInfo < 30000 and buildInfo >= 20000) then return true end return false end ---return true if the player is playing in the WotLK version of wow ---@return boolean function DF.IsWotLKWow() - local _, _, _, buildInfo = GetBuildInfo() - if (buildInfo < 40000 and buildInfo >= 30000) then - return true - end + if (buildInfo < 40000 and buildInfo >= 30000) then return true end return false end +---return true if the player is playing in the Cataclysm version of wow +---@return boolean +function DF.IsCataWow() + if (buildInfo < 50000 and buildInfo >= 40000) then return true end + return false +end + +---return true if the player is playing in the Mists version of wow +---@return boolean +function DF.IsPandaWow() + if (buildInfo < 60000 and buildInfo >= 50000) then return true end + return false +end + +---return true if the player is playing in the Warlords of Draenor version of wow +---@return boolean +function DF.IsWarlordsWow() + if (buildInfo < 70000 and buildInfo >= 60000) then return true end + return false +end + +---return true if the player is playing in the Legion version of wow +---@return boolean +function DF.IsLegionWow() + if (buildInfo < 80000 and buildInfo >= 70000) then return true end + return false +end + +---return true if the player is playing in the BFA version of wow +---@return boolean +function DF.IsBFAWow() + if (buildInfo < 90000 and buildInfo >= 80000) then return true end + return false +end + +---return true if the player is playing in the Shadowlands version of wow +---@return boolean +function DF.IsShadowlandsWow() + if (buildInfo < 100000 and buildInfo >= 90000) then return true end + return false +end + +---return if the wow version the player is playing is dragonflight +---@return boolean +function DF.IsDragonflightWow() + if (buildInfo < 110000 and buildInfo >= 100000) then return true end + return false +end + +---return if the wow version the player is playing is the war within +---@return boolean +function DF.IsWarWow() + if (buildInfo < 120000 and buildInfo >= 110000) then return true end + return false +end + + ---return true if the player is playing in the WotLK version of wow with the retail api ---@return boolean function DF.IsNonRetailWowWithRetailAPI() @@ -4287,6 +4331,9 @@ local specs_per_class = { } +---return an array table with the spec ids the class can have +---@param engClass string +---@return table function DF:GetClassSpecIDs(engClass) return specs_per_class[engClass] end @@ -4320,7 +4367,6 @@ local getDragonflightTalents = function() local treeInfo = C_Traits.GetTreeInfo(configId, configInfo.treeIDs[1]) local treeHash = C_Traits.GetTreeHash(treeInfo.ID) - local serializationVersion = C_Traits.GetLoadoutSerializationVersion() DF.TalentExporter:WriteLoadoutHeader(exportStream, serializationVersion, currentSpecID, treeHash) @@ -4329,12 +4375,14 @@ local getDragonflightTalents = function() return exportStream:GetExportString() end ---/dump DetailsFramework:GetDragonlightTalentExportString() +--/dump DetailsFramework:GetDragonlightTalentString() function DF:GetDragonlightTalentString() - local talentString, errorText = pcall(getDragonflightTalents) - if (errorText) then + local runOkay, errorText = pcall(getDragonflightTalents) + if (not runOkay) then + DF:Msg("error 0x4517", errorText) return "" else + local talentString = errorText return talentString end end diff --git a/classes/class_combat.lua b/classes/class_combat.lua index 3edc3e61..13c189c7 100644 --- a/classes/class_combat.lua +++ b/classes/class_combat.lua @@ -863,6 +863,7 @@ local segmentTypeToString = { end ---Return how many attempts were made for this boss + ---@param self combat ---@return number|nil function classCombat:GetTryNumber() ---@type bossinfo @@ -872,6 +873,32 @@ local segmentTypeToString = { end end + ---Return the percentage of the boss health when the combat ended + ---1 = 100% 0.5 = 50% + ---@param self combat + ---@return number + function classCombat:GetBossHealth() + return self.boss_hp + end + + ---Get the boss name + ---@param self combat + ---@return string? + function classCombat:GetBossName() + return self.bossName + end + + ---Return the current phase of the combat or which phase the combat was when it ended + ---@param self combat + ---@return number + function classCombat:GetCurrentPhase() + local phaseData = self.PhaseData + local lastPhase = #phaseData + --the phase data has on its first index the ID of the phase and on the second the time when it started + local lastPhaseId = phaseData[lastPhase][1] + return lastPhaseId + end + ---copy deaths from combat2 into combat1 ---if bMythicPlus is true it'll check if the death has mythic plus death time and use it instead of the normal death time ---@param combat1 combat @@ -896,6 +923,11 @@ local segmentTypeToString = { --return the total of a specific attribute local power_table = {0, 1, 3, 6, 0, "alternatepower"} + ---return the total of a specific attribute, example: total damage, total healing, total resources, etc + ---@param attribute number + ---@param subAttribute number + ---@param onlyGroup boolean? + ---@return number function classCombat:GetTotal(attribute, subAttribute, onlyGroup) if (attribute == 1 or attribute == 2) then if (onlyGroup) then @@ -936,6 +968,20 @@ local segmentTypeToString = { return alternatePowerTable end + ---transfer talents from Details talent cache to the combat combat + ---@param self combat + function classCombat:StoreTalents() + local talentStorage = Details.cached_talents + local damageContainer = self:GetContainer(DETAILS_ATTRIBUTE_DAMAGE) + for idx, actorObject in damageContainer:ListActors() do + local thisActorTalents = talentStorage[actorObject.serial] + if (thisActorTalents) then + local actorName = actorObject:Name() + self.playerTalents[actorName] = thisActorTalents + end + end + end + --delete an actor from the combat ~delete ~erase ~remove function classCombat:DeleteActor(attribute, actorName, removeDamageTaken, cannotRemap) local container = self[attribute] @@ -1026,6 +1072,15 @@ function classCombat:CreateNewCombatTable() return classCombat:NovaTabela() end +local getBossName = function() + if (UnitExists("boss1") and Details.in_combat) then + local bossName = UnitName("boss1") + if (bossName) then + Details:GetCurrentCombat().bossName = bossName + end + end +end + ---class constructor ---@param bTimeStarted boolean if true set the start time to now with GetTime ---@param overallCombatObject combat @@ -1084,11 +1139,17 @@ function classCombat:NovaTabela(bTimeStarted, overallCombatObject, combatId, ... combatObject.boss_hp = 1 + C_Timer.After(0.5, getBossName) + combatObject.bossTimers = {} ---store trinket procs combatObject.trinketProcs = {} + --store talents of players + ---@type table + combatObject.playerTalents = {} + ---store the amount of casts of each player ---@type table> combatObject.amountCasts = {} diff --git a/core/control.lua b/core/control.lua index 721f33d2..6048cc39 100644 --- a/core/control.lua +++ b/core/control.lua @@ -339,6 +339,7 @@ local combatCounter = Details:GetOrSetCombatId(1) --create a new combat object and preplace the current one + ---@type combat local newCombatObject = Details.combate:NovaTabela(true, Details.tabela_overall, combatCounter, ...) Details:SetCurrentCombat(newCombatObject) @@ -771,6 +772,9 @@ if (not bShouldForceDiscard and (zoneType == "none" or tempo_do_combate >= Details.minimum_combat_time or not segmentsTable[1])) then --combat accepted Details.tabela_historico:AddCombat(currentCombat) --move a tabela atual para dentro do hist�rico + + currentCombat:StoreTalents() + if (currentCombat.is_boss) then if (IsInRaid()) then local cleuID = currentCombat.is_boss.id diff --git a/core/gears.lua b/core/gears.lua index 3e1446e8..7c5498cf 100644 --- a/core/gears.lua +++ b/core/gears.lua @@ -1903,7 +1903,7 @@ function _detalhes:IlvlFromNetwork (player, realm, core, serialNumber, itemLevel end --won't inspect this actor - _detalhes.trusted_characters [serialNumber] = true + _detalhes.trusted_characters[serialNumber] = true if (type(serialNumber) ~= "string") then return @@ -1911,19 +1911,22 @@ function _detalhes:IlvlFromNetwork (player, realm, core, serialNumber, itemLevel --store the item level if (type(itemLevel) == "number") then - _detalhes.item_level_pool [serialNumber] = {name = player, ilvl = itemLevel, time = time()} + _detalhes.item_level_pool[serialNumber] = {name = player, ilvl = itemLevel, time = time()} end --store talents if (type(talentsSelected) == "table") then - if (talentsSelected [1]) then - _detalhes.cached_talents [serialNumber] = talentsSelected + if (talentsSelected[1]) then + _detalhes.cached_talents[serialNumber] = talentsSelected end + + elseif (type(talentsSelected) == "string" and talentsSelected ~= "") then + _detalhes.cached_talents[serialNumber] = talentsSelected end --store the spec the player is playing if (type(currentSpec) == "number") then - _detalhes.cached_specs [serialNumber] = currentSpec + _detalhes.cached_specs[serialNumber] = currentSpec end end diff --git a/core/network.lua b/core/network.lua index 08535751..889d56c6 100644 --- a/core/network.lua +++ b/core/network.lua @@ -3,6 +3,9 @@ local Loc = LibStub("AceLocale-3.0"):GetLocale( "Details" ) local _ + ---@type detailsframework + local detailsFramework = DetailsFramework + --register namespace Details.network = {} @@ -77,6 +80,30 @@ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --item level + + local getHorizontalTalentsAsString = function() + local talents = "" + for i = 1, 7 do + for o = 1, 3 do + local talentID, name, texture, selected, available = GetTalentInfo(i, o, 1) + if (selected) then + talents = "" .. talentID .. "," + break + end + end + end + + --remove the comma after the last talent id + if (talents:sub(-1) == ",") then + talents = talents:sub(1, -2) + end + + return talents + end + + ---send item level data to the group the player is in + ---@param self details + ---@return nil function Details:SendCharacterData() --only send if in group if (not IsInGroup() and not IsInRaid()) then @@ -87,7 +114,8 @@ return end - --check the player level + --check the player level to be at least 60 + ---@type number local playerLevel = UnitLevel("player") if (not playerLevel) then return @@ -97,23 +125,52 @@ --delay to sent information again if (Details.LastPlayerInfoSync and Details.LastPlayerInfoSync + 10 > GetTime()) then - --do not send info if recently sent + --do not send info if it was recently sent return end - --get player item level + --get the equipped player item level local overall, equipped = GetAverageItemLevel() + local talentsAsString = "" + --get player talents - local talents = {} - for i = 1, 7 do - for o = 1, 3 do - local talentID, name, texture, selected, available = GetTalentInfo(i, o, 1) - if (selected) then - tinsert(talents, talentID) - break - end - end + --depending on the game version, the talent API is different + + --vertical tree + if (DetailsFramework.IsClassicWow()) then --vanilla + talentsAsString = "" + + elseif (DetailsFramework.IsTBCWow()) then --burning crusade + talentsAsString = "" + + elseif (DetailsFramework.IsWotLKWow()) then --wrath of the lich king + talentsAsString = "" + + elseif (DetailsFramework.IsCataWow()) then --cataclysm + talentsAsString = "" + end + + --horizontal pick one + if (DetailsFramework.IsPandaWow()) then + talentsAsString = getHorizontalTalentsAsString() + + elseif (DetailsFramework.IsWarlordsWow()) then + talentsAsString = getHorizontalTalentsAsString() + + elseif (DetailsFramework.IsLegionWow()) then + talentsAsString = getHorizontalTalentsAsString() + + elseif (DetailsFramework.IsBFAWow()) then + talentsAsString = getHorizontalTalentsAsString() + + elseif (DetailsFramework.IsShadowlandsWow()) then + talentsAsString = getHorizontalTalentsAsString() + end + + --vertical, horizonal tree + if (DetailsFramework.IsDragonflight()) then + talentsAsString = detailsFramework:GetDragonlightTalentString() end --get the spec ID @@ -130,13 +187,13 @@ local serial = UnitGUID("player") if (IsInRaid()) then - Details:SendRaidData(CONST_ITEMLEVEL_DATA, serial, equipped, talents, currentSpec) + Details:SendRaidData(CONST_ITEMLEVEL_DATA, serial, equipped, talentsAsString, currentSpec) if (Details.debugnet) then Details:Msg("(debug) sent ilevel data to Raid") end elseif (IsInGroup()) then - Details:SendPartyData(CONST_ITEMLEVEL_DATA, serial, equipped, talents, currentSpec) + Details:SendPartyData(CONST_ITEMLEVEL_DATA, serial, equipped, talentsAsString, currentSpec) if (Details.debugnet) then Details:Msg("(debug) sent ilevel data to Party") end diff --git a/frames/window_main.lua b/frames/window_main.lua index 3f27e1af..3e87b0f5 100644 --- a/frames/window_main.lua +++ b/frames/window_main.lua @@ -2119,11 +2119,14 @@ local iconFrame_OnEnter = function(self) Details:AddTooltipHeaderStatusbar() local talentString = "" - if (talents and not (DetailsFramework.IsClassicWow() or DetailsFramework.IsTBCWow() or DetailsFramework.IsWotLKWow())) then - for i = 1, #talents do - local talentID, talentName, texture, selected, available = GetTalentInfoByID(talents [i]) - if (texture) then - talentString = talentString .. " |T" .. texture .. ":" .. 24 .. ":" .. 24 ..":0:0:64:64:4:60:4:60|t" + + if (type(talents) == "table") then + if (talents and not (DetailsFramework.IsClassicWow() or DetailsFramework.IsTBCWow() or DetailsFramework.IsWotLKWow())) then + for i = 1, #talents do + local talentID, talentName, texture, selected, available = GetTalentInfoByID(talents [i]) + if (texture) then + talentString = talentString .. " |T" .. texture .. ":" .. 24 .. ":" .. 24 ..":0:0:64:64:4:60:4:60|t" + end end end end @@ -6628,7 +6631,8 @@ local buildSegmentTooltip = function(self, deltaTime) elseif (bossInfo.killed) then gameCooltip:AddLine(combatName, formattedElapsedTime, 1, "lime", combatTimeColor) else - gameCooltip:AddLine(combatName, formattedElapsedTime, 1, "orange", combatTimeColor) + --include phase string: "P" .. thisCombat:GetCurrentPhase() .. " " .. + gameCooltip:AddLine(combatName, math.floor(thisCombat:GetBossHealth()*100) .. "%", 1, "orange", combatTimeColor) --formattedElapsedTime end gameCooltip:AddIcon(combatIcon, "main", "left") diff --git a/frames/window_mythicplus/window_end_of_run.lua b/frames/window_mythicplus/window_end_of_run.lua index ac50afe1..190a9cef 100644 --- a/frames/window_mythicplus/window_end_of_run.lua +++ b/frames/window_mythicplus/window_end_of_run.lua @@ -943,17 +943,21 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() readyFrame.DungeonBackdropTexture:SetTexture(overallMythicDungeonCombat.is_mythic_dungeon.DungeonTexture) end - wipe(readyFrame.playerCacheByName) if (Details222.MythicPlus.OnTime) then readyFrame.YouBeatTheTimerLabel:SetFormattedText(CHALLENGE_MODE_COMPLETE_BEAT_TIMER .. " | " .. CHALLENGE_MODE_COMPLETE_KEYSTONE_UPGRADED, Details222.MythicPlus.KeystoneUpgradeLevels) --"You beat the timer!" readyFrame.YouBeatTheTimerLabel.textcolor = "limegreen" --readyFrame.KeystoneUpgradeLabel:SetFormattedText(CHALLENGE_MODE_COMPLETE_KEYSTONE_UPGRADED, Details222.MythicPlus.KeystoneUpgradeLevels) + PlaySound(SOUNDKIT.UI_70_CHALLENGE_MODE_KEYSTONE_UPGRADE) + C_Timer.After(0.020, function() + --PlaySoundFile([[Interface\AddOns\Details\sounds\bassdrop2.mp3]]) + end) else readyFrame.YouBeatTheTimerLabel.textcolor = "white" readyFrame.YouBeatTheTimerLabel.text = CHALLENGE_MODE_COMPLETE_TIME_EXPIRED --"Time expired!" --readyFrame.KeystoneUpgradeLabel.text = CHALLENGE_MODE_COMPLETE_TRY_AGAIN --"Try again! Beat the timer to upgrade your keystone!" + PlaySound(SOUNDKIT.UI_70_CHALLENGE_MODE_COMPLETE_NO_UPGRADE) end if (Details222.MythicPlus.NewDungeonScore and Details222.MythicPlus.OldDungeonScore) then diff --git a/functions/bossmods.lua b/functions/bossmods.lua index 19eaa355..7d147fe0 100644 --- a/functions/bossmods.lua +++ b/functions/bossmods.lua @@ -6,33 +6,33 @@ local addonName, Details222 = ... function Details:OnCombatPhaseChanged() local current_combat = Details:GetCurrentCombat() - local current_phase = current_combat.PhaseData [#current_combat.PhaseData][1] + local current_phase = current_combat.PhaseData[#current_combat.PhaseData][1] - local phase_damage_container = current_combat.PhaseData.damage [current_phase] - local phase_healing_container = current_combat.PhaseData.heal [current_phase] + local phaseDamageContainer = current_combat.PhaseData.damage[current_phase] + local phaseHealingContainer = current_combat.PhaseData.heal[current_phase] - local phase_damage_section = current_combat.PhaseData.damage_section - local phase_healing_section = current_combat.PhaseData.heal_section + local phaseDamageSection = current_combat.PhaseData.damage_section + local phaseHealingSection = current_combat.PhaseData.heal_section - if (not phase_damage_container) then - phase_damage_container = {} - current_combat.PhaseData.damage [current_phase] = phase_damage_container + if (not phaseDamageContainer) then + phaseDamageContainer = {} + current_combat.PhaseData.damage[current_phase] = phaseDamageContainer end - if (not phase_healing_container) then - phase_healing_container = {} - current_combat.PhaseData.heal [current_phase] = phase_healing_container + if (not phaseHealingContainer) then + phaseHealingContainer = {} + current_combat.PhaseData.heal[current_phase] = phaseHealingContainer end for index, damage_actor in ipairs(Details.cache_damage_group) do - local phase_damage = damage_actor.total - (phase_damage_section [damage_actor.nome] or 0) - phase_damage_section [damage_actor.nome] = damage_actor.total - phase_damage_container [damage_actor.nome] = (phase_damage_container [damage_actor.nome] or 0) + phase_damage + local phase_damage = damage_actor.total - (phaseDamageSection [damage_actor.nome] or 0) + phaseDamageSection [damage_actor.nome] = damage_actor.total + phaseDamageContainer [damage_actor.nome] = (phaseDamageContainer [damage_actor.nome] or 0) + phase_damage end for index, healing_actor in ipairs(Details.cache_healing_group) do - local phase_heal = healing_actor.total - (phase_healing_section [healing_actor.nome] or 0) - phase_healing_section [healing_actor.nome] = healing_actor.total - phase_healing_container [healing_actor.nome] = (phase_healing_container [healing_actor.nome] or 0) + phase_heal + local phase_heal = healing_actor.total - (phaseHealingSection [healing_actor.nome] or 0) + phaseHealingSection [healing_actor.nome] = healing_actor.total + phaseHealingContainer [healing_actor.nome] = (phaseHealingContainer [healing_actor.nome] or 0) + phase_heal end end diff --git a/functions/mythicdungeon/mythicdungeon.lua b/functions/mythicdungeon/mythicdungeon.lua index f5149e8c..28ef5904 100644 --- a/functions/mythicdungeon/mythicdungeon.lua +++ b/functions/mythicdungeon/mythicdungeon.lua @@ -433,7 +433,7 @@ function DetailsMythicPlusFrame.SaveMythicPlusStats(combatObject) ---@type mythicplusrunstats local runStats = { - date = time(), + date = _G.time(), runTime = math.floor(time/1000), onTime = onTime, deaths = amountDeaths, diff --git a/images/icons.tga b/images/icons.tga index 43a8abda95c1ea15bd684b952bd381a3582c6d64..a2fe7f0b8b616559cb4a96ac5fd365b431cfd7e2 100644 GIT binary patch delta 2757 zcmchZe@q)?7{}@D?zW0d&bFZh#&!hK+0lB99V2vBMyO!YnKcnH1n43^WdeT?jr_4t zU6=XC3S5rTKqA->qa>|(iAw_s|Di?_Bfkn1;us0|pMT6`|2fa+30~9I-Z|s+k|%HP z`aGZa`@ZkcgC0BcD#!Fs(#3phG<>1eYTp*w4&Z%4UZbxS78VYSkB|RjTxJ)+U{FGq z$X*BZXQZ2MG8hc+?(FO+qKAivA3B}RDGZYhzpbyYXN}q4-@wY4QxS62%n`xYp_N7m6* zE|+UI9*?InCnqN*o6Yty@X{WW$#m1_^ZnY`*tp2E`eO7!-_&kU&v6%TKo?774Q(tf zE&X6)V>iHx-&lBqW_q7rNpkJYf#F-;l4+ z1})~5>~?r~DEa;VM=*ZoOe7LX{&5T~HfA!Xr>CXd+}sY4bp!AR@-l7ocs%!H9=1zM zOA<`LiwexM26AFzBH0l}xZUpPF)&U#Xv2lntjGKS+Et!Cn@!=l0*>SE;4owfi;IgG z5Yu!KuYts$fSBKe^CUYA4i1KFZJM%dxH4UPd;5>FC$_bG_u z5{*WauCueVlF;^L56?Y_nI8cu|Mk?pG&D36y7Io}d^XZ~eRp?vTvoNZx{4F#64^Q! z;bSCu_V)IY9W^&M3*x5=AdAJ~mqFM9sO1<$Q3@b^TB5bKwth$}gh=Tx+vL0-1GxBLaTy}d2rfO)o9WeLgw zm^Cf90(kmYR#uX0gGw=N`XBdeUt%gbrZ{{H^^uqth^oXJNmlP^*V zyR6E73F|@Xj9RVM+es+=$Sk?(JMLv5D^&#hOQ>;VWF)y_m<=-fZwiBpQmtL~3Ot?g z(3+c@lkiOZldDp_u2RbN)lq%|0EMzR^}5puAx2b=3_X|)rX!ZN$9dnQZqrpysodVN>Jp+~t Q_AFQ)*lD;&K1ba61HmIecmMzZ delta 405 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 a6S)kuW`