chore: hoist plugins to root and move main into Details/

Each Details_* plugin and the main Details addon now lives in its own
repo-root folder, matching the Exiles fork-layout convention.
This commit is contained in:
2026-05-25 10:59:28 +02:00
parent 0b0a5004eb
commit 5bb7be4968
421 changed files with 7 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+284
View File
@@ -0,0 +1,284 @@
--[[ Attributes: Damage, Heal, Energy, Miscellaneous ]]
do
local _detalhes = _G.Details
local addonName, Details222 = ...
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
--Globals
--[[global]] DETAILS_ATTRIBUTE_DAMAGE = 1
--[[global]] DETAILS_SUBATTRIBUTE_DAMAGEDONE = 1
--[[global]] DETAILS_SUBATTRIBUTE_DPS = 2
--[[global]] DETAILS_SUBATTRIBUTE_DAMAGETAKEN = 3
--[[global]] DETAILS_SUBATTRIBUTE_FRIENDLYFIRE = 4
--[[global]] DETAILS_SUBATTRIBUTE_FRAGS = 5
--[[global]] DETAILS_SUBATTRIBUTE_ENEMIES = 6
--[[global]] DETAILS_SUBATTRIBUTE_VOIDZONES = 7
--[[global]] DETAILS_SUBATTRIBUTE_BYSPELLS = 8
--[[global]] DETAILS_ATTRIBUTE_HEAL = 2
--[[global]] DETAILS_SUBATTRIBUTE_HEALDONE = 1
--[[global]] DETAILS_SUBATTRIBUTE_HPS = 2
--[[global]] DETAILS_SUBATTRIBUTE_OVERHEAL = 3
--[[global]] DETAILS_SUBATTRIBUTE_HEALTAKEN = 4
--[[global]] DETAILS_SUBATTRIBUTE_HEALENEMY = 5
--[[global]] DETAILS_SUBATTRIBUTE_HEALPREVENTED = 6
--[[global]] DETAILS_SUBATTRIBUTE_HEALABSORBED = 7
--[[global]] DETAILS_ATTRIBUTE_ENERGY = 3
--[[global]] DETAILS_SUBATTRIBUTE_REGENMANA = 1
--[[global]] DETAILS_SUBATTRIBUTE_REGENRAGE = 2
--[[global]] DETAILS_SUBATTRIBUTE_REGENENERGY = 3
--[[global]] DETAILS_SUBATTRIBUTE_REGENRUNE = 4
--[[global]] DETAILS_SUBATTRIBUTE_RESOURCES = 5
--[[global]] DETAILS_SUBATTRIBUTE_ALTERNATEPOWER = 6
--[[global]] DETAILS_ATTRIBUTE_MISC = 4
--[[global]] DETAILS_SUBATTRIBUTE_CCBREAK = 1
--[[global]] DETAILS_SUBATTRIBUTE_RESS = 2
--[[global]] DETAILS_SUBATTRIBUTE_INTERRUPT = 3
--[[global]] DETAILS_SUBATTRIBUTE_DISPELL = 4
--[[global]] DETAILS_SUBATTRIBUTE_DEATH = 5
--[[global]] DETAILS_SUBATTRIBUTE_DCOOLDOWN = 6
--[[global]] DETAILS_SUBATTRIBUTE_BUFFUPTIME = 7
--[[global]] DETAILS_SUBATTRIBUTE_DEBUFFUPTIME = 8
--[[global]] DETAILS_ATTRIBUTE_CUSTOM = 5
_detalhes.atributos_capture = {
"damage", --damage done
"damage", --dps
"damage", --damage taken
"damage", --friendly fire
"miscdata", --frags
"damage", --enemy damage
"aura", --auras
"damage", --damage taken by spell
"heal", --healing done
"heal", --hps
"heal", --overhealing
"heal", --healing taken
"heal", --enemy healed
"heal", --damage prevented
"heal", --heal absorbed/denied
"energy", --mana restored
"energy", --rage gained
"energy", --energy generated
"energy", --runic power generated
"energy", --resources
"energy", --alternate power
"miscdata", --cc breaks
"miscdata", --ress
"miscdata", --interrupts
"miscdata", --dispells
"miscdata", --deaths
"miscdata", --cooldowns
"aura", --buff uptime
"aura", --debuff uptime
}
--Main Attributes
_detalhes.atributos = {
--Attributes amount = 4: damage / heal / energy / misc
[0] = 4,
--[[ DAMAGE ]]
dano = 1, --identifier
[1] = 8, -- sub attributes
--[[ HEALING ]]
cura = 2, --identifier
[2] = 7, -- sub attributes
--[[ ENERGY ]]
e_energy = 3,--identifier
[3] = 6, -- sub attributes
--[[ MISC ]]
misc = 4, --identifier
[4] = 8, -- sub attributes
--[[ CUSTOM ]]
custom = 5,
[5] = false,
--[[ String Names ]]
lista = {
Loc ["STRING_ATTRIBUTE_DAMAGE"],--Damage
Loc ["STRING_ATTRIBUTE_HEAL"], --Healing
Loc ["STRING_ATTRIBUTE_ENERGY"], --Energy
Loc ["STRING_ATTRIBUTE_MISC"], --Misc
Loc ["STRING_SCRIPTS_TITLE"] --Custom
}
}
--Sub Attributes
_detalhes.sub_atributos = {
{
--Damage sub classes information
damage_done = 1,
dps = 2,
damage_taken = 3,
friendly_fire = 4,
frags = 5,
enemies = 6,
voidzones = 7,
damage_taken_by_spells = 8,
lista = { --[[ String Names ]]
Loc ["STRING_ATTRIBUTE_DAMAGE_DONE"],
Loc ["STRING_ATTRIBUTE_DAMAGE_DPS"],
Loc ["STRING_ATTRIBUTE_DAMAGE_TAKEN"],
Loc ["STRING_ATTRIBUTE_DAMAGE_FRIENDLYFIRE"],
Loc ["STRING_ATTRIBUTE_DAMAGE_FRAGS"],
Loc ["STRING_ATTRIBUTE_DAMAGE_ENEMIES"],
Loc ["STRING_ATTRIBUTE_DAMAGE_DEBUFFS"],
Loc ["STRING_ATTRIBUTE_DAMAGE_BYSPELL"],
},
icones = {
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {0, .125, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {.125, .25, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {.25, .375, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {.375, .5, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {.5, 0.625, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {0.625, .75, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {.75, 0.875, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_damage", {0.875, 1, 0, 1}},
},
internal = {"total", "last_dps", "damage_taken", "friendlyfire_total", "frags", "enemies", "voidzones", "damage_taken_by_spells"}
},
{
--Healing sub classes information
healing_done = 1,
hps = 2,
overheal = 3,
healing_tanken = 4,
heal_enemy_amt = 5,
totalabsorb = 6,
totaldenied = 7,
lista = { --[[ String Names ]]
Loc ["STRING_ATTRIBUTE_HEAL_DONE"],
Loc ["STRING_ATTRIBUTE_HEAL_HPS"],
Loc ["STRING_ATTRIBUTE_HEAL_OVERHEAL"],
Loc ["STRING_ATTRIBUTE_HEAL_TAKEN"],
Loc ["STRING_ATTRIBUTE_HEAL_ENEMY"],
Loc ["STRING_ATTRIBUTE_HEAL_PREVENT"],
Loc ["STRING_ATTRIBUTE_HEAL_ABSORBED"],
},
icones = {
{"Interface\\AddOns\\Details\\images\\atributos_icones_heal", {0, .125, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_heal", {.125, .25, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_heal", {.25, .375, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_heal", {.375, .5, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_heal", {.5, 0.625, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_heal", {0.625, 0.75, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_heal", {0.75, 0.75+.125, 0, 1}},
},
internal = {"total", "last_hps", "totalover", "healing_taken", "heal_enemy_amt", "totalabsorb", "totaldenied"}
},
{
--Energy sub classes information
mana_ganha = 1, -- id 0
rage_ganha = 2, -- id 1
energy_ganha = 3, --id 3
rune_ganha = 4, --id 6
resources = 5, --
alternatepower = 6, --
lista = { --[[ String Names ]]
Loc ["STRING_ATTRIBUTE_ENERGY_MANA"],
Loc ["STRING_ATTRIBUTE_ENERGY_RAGE"],
Loc ["STRING_ATTRIBUTE_ENERGY_ENERGY"],
Loc ["STRING_ATTRIBUTE_ENERGY_RUNEPOWER"],
Loc ["STRING_ATTRIBUTE_ENERGY_RESOURCES"],
Loc ["STRING_ATTRIBUTE_ENERGY_ALTERNATEPOWER"],
},
icones = {
{"Interface\\AddOns\\Details\\images\\atributos_icones_energyze", {0, .125, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_energyze", {.125, .25, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_energyze", {.25, .375, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_energyze", {.375, .5, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_energyze", {.5, .625, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_energyze", {.625, .75, 0, 1}},
},
internal = {"mana", "e_rage", "e_energy", "runepower", "resource", "alternatepower"}
},
{
--Misc sub classes information
cc_break = 1,
ress = 2,
kick = 3,
dispell = 4,
deaths = 5,
cooldowns_defensive = 6,
buff_uptime = 7,
lista = { --[[ String Names ]]
Loc ["STRING_ATTRIBUTE_MISC_CCBREAK"],
Loc ["STRING_ATTRIBUTE_MISC_RESS"],
Loc ["STRING_ATTRIBUTE_MISC_INTERRUPT"],
Loc ["STRING_ATTRIBUTE_MISC_DISPELL"],
Loc ["STRING_ATTRIBUTE_MISC_DEAD"],
Loc ["STRING_ATTRIBUTE_MISC_DEFENSIVE_COOLDOWNS"],
Loc ["STRING_ATTRIBUTE_MISC_BUFF_UPTIME"],
Loc ["STRING_ATTRIBUTE_MISC_DEBUFF_UPTIME"],
},
icones = {
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {0, .125, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {.125, .25, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {.25, .375, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {.375, .5, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {.5, .625, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {.625, 0.75, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {0.75, 0.875, 0, 1}},
{"Interface\\AddOns\\Details\\images\\atributos_icones_misc", {0.875, 1, 0, 1}}
},
internal = {"cc_break", "ress", "interrupt", "dispell", "dead", "cooldowns_defensive", "buff_uptime", "debuff_uptime"}
}
}
function _detalhes:GetAttributeName (attribute)
return _detalhes.atributos.lista [attribute]
end
function _detalhes:GetSubAttributeName (attribute, subAttribute, customName)
if (attribute == 5) then
local CustomObject = _detalhes.custom [subAttribute]
if (CustomObject and customName and CustomObject.name == customName) then
return CustomObject.name
elseif (CustomObject and customName and CustomObject.name ~= customName) then
for _, custom in ipairs(_detalhes.custom) do
if (custom.name == customName) then
return custom.name
end
end
return "--x--x--"
elseif (CustomObject and not customName) then
return CustomObject.name
end
if (not CustomObject) then
return "--x--x--"
end
end
return _detalhes.sub_atributos [attribute].lista [subAttribute]
end
function _detalhes:GetInternalSubAttributeName (attribute, subAttribute)
return _detalhes.sub_atributos [attribute].internal [subAttribute]
end
end
+92
View File
@@ -0,0 +1,92 @@
local Details = _G.Details
local detailsFramework = _G.DetailsFramework
local C_Timer = _G.C_Timer
local addonName, Details222 = ...
local load = loadstring
--auto run scripts
local functionCache = {}
--compile and store code
function Details222.AutoRunCode.RecompileAutoRunCode()
for codeKey, code in pairs(Details222.AutoRunCode.CodeTable) do
local func, errorText = load(code)
if (func) then
detailsFramework:SetEnvironment(func)
functionCache[codeKey] = func
else
--if the code didn't pass, create a dummy function for it without triggering errors
functionCache[codeKey] = function() end
end
end
end
--function to dispatch events
function Details222.AutoRunCode.DispatchAutoRunCode(codeKey)
local func = functionCache[codeKey]
if (type(func) ~= "function") then
Details:Msg("error running function for auto run script", codeKey)
return
end
local okay, errortext = pcall(func)
if (not okay) then
Details:Msg("error running auto run script: ", codeKey, errortext)
return
end
end
--auto run frame to dispatch scrtips for some events that details! doesn't handle
local autoRunCodeEventFrame = CreateFrame("frame")
autoRunCodeEventFrame.OnEventFunc = function(self, event)
--ignore events triggered more than once in a small time window
if (autoRunCodeEventFrame[event] and not autoRunCodeEventFrame[event]:IsCancelled()) then
return
end
if (event == "PLAYER_SPECIALIZATION_CHANGED") then
--create a trigger for the event, many times it is triggered more than once
--so if the event is triggered a second time, it will be ignored
local newTimer = C_Timer.NewTimer(1, function()
Details222.AutoRunCode.DispatchAutoRunCode("on_specchanged")
--clear and invalidate the timer
autoRunCodeEventFrame[event]:Cancel()
autoRunCodeEventFrame[event] = nil
end)
--store the trigger
autoRunCodeEventFrame[event] = newTimer
end
end
autoRunCodeEventFrame:SetScript("OnEvent", autoRunCodeEventFrame.OnEventFunc)
--dispatch scripts at startup
C_Timer.After(2, function()
Details222.AutoRunCode.DispatchAutoRunCode("on_init")
Details222.AutoRunCode.DispatchAutoRunCode("on_specchanged")
Details222.AutoRunCode.DispatchAutoRunCode("on_zonechanged")
if (_G.InCombatLockdown()) then
Details222.AutoRunCode.DispatchAutoRunCode("on_entercombat")
else
Details222.AutoRunCode.DispatchAutoRunCode("on_leavecombat")
end
Details222.AutoRunCode.DispatchAutoRunCode("on_groupchange")
end)
function Details222.AutoRunCode.StartAutoRun()
local newData = detailsFramework.table.copy({}, Details.run_code)
Details.run_code = nil
Details222.AutoRunCode.CodeTable = newData
Details222.AutoRunCode.RecompileAutoRunCode()
end
function Details222.AutoRunCode.OnLogout()
_detalhes_global.run_code = Details222.AutoRunCode.CodeTable
end
+9
View File
@@ -0,0 +1,9 @@
local Details = _G.Details
local detailsFramework = _G.DetailsFramework
local _
local benchmark = {}
--[===[
--]===]
+560
View File
@@ -0,0 +1,560 @@
do
local Details = _G.Details
local addonName, Details222 = ...
Details.EncounterInformation = {}
local ipairs = ipairs --lua local
local detailsFramework = DetailsFramework
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--details api functions
--return if the player is inside a raid supported by details
function Details:IsInInstance()
local _, _, _, _, _, _, _, zoneMapID = GetInstanceInfo()
if (Details.EncounterInformation [zoneMapID]) then
return true
else
return false
end
end
--return the full table with all data for the instance
function Details:GetRaidInfoFromEncounterID (encounterID, encounterEJID)
for id, raidTable in pairs(Details.EncounterInformation) do
if (encounterID) then
local ids = raidTable.encounter_ids2 --combatlog
if (ids) then
if (ids [encounterID]) then
return raidTable
end
end
end
if (encounterEJID) then
local ejids = raidTable.encounter_ids --encounter journal
if (ejids) then
if (ejids [encounterEJID]) then
return raidTable
end
end
end
end
end
--return the ids of trash mobs in the instance
function Details:GetInstanceTrashInfo (mapid)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].trash_ids
end
function Details:GetInstanceIdFromEncounterId (encounterId)
for id, instanceTable in pairs(Details.EncounterInformation) do
--combatlog encounter id
local ids = instanceTable.encounter_ids2
if (ids) then
if (ids[encounterId]) then
return id
end
end
--encounter journal id
local ids_ej = instanceTable.encounter_ids
if (ids) then
if (ids_ej[encounterId]) then
return id
end
end
end
end
--return the boss table using a encounter id
function Details:GetBossEncounterDetailsFromEncounterId (mapid, encounterid)
if (not mapid) then
local bossIndex, instance
for id, instanceTable in pairs(Details.EncounterInformation) do
local ids = instanceTable.encounter_ids2
if (ids) then
bossIndex = ids [encounterid]
if (bossIndex) then
instance = instanceTable
break
end
end
end
if (instance) then
local bosses = instance.encounters
if (bosses) then
return bosses [bossIndex], instance
end
end
return
end
local bossindex = Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounter_ids and Details.EncounterInformation [mapid].encounter_ids [encounterid]
if (bossindex) then
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounters [bossindex], bossindex
else
local bossindex = Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounter_ids2 and Details.EncounterInformation [mapid].encounter_ids2 [encounterid]
if (bossindex) then
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounters [bossindex], bossindex
end
end
end
--return the EJ boss id
function Details:GetEncounterIdFromBossIndex (mapid, index)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounter_ids and Details.EncounterInformation [mapid].encounter_ids [index]
end
--return the table which contain information about the start of a encounter
function Details:GetEncounterStartInfo (mapid, encounterid)
local bossindex = Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounter_ids and Details.EncounterInformation [mapid].encounter_ids [encounterid]
if (bossindex) then
return Details.EncounterInformation [mapid].encounters [bossindex] and Details.EncounterInformation [mapid].encounters [bossindex].encounter_start
end
end
--generic boss find function
function Details:GetRaidBossFindFunction (mapid)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].find_boss_encounter
end
--return if the boss need sync
function Details:GetEncounterEqualize (mapid, bossindex)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounters [bossindex] and Details.EncounterInformation [mapid].encounters [bossindex].equalize
end
--return the boss table with information about name, adds, spells, etc
function Details:GetBossDetails (mapid, bossindex)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounters [bossindex]
end
--return a table with all names of boss enemies
function Details:GetEncounterActors (mapid, bossindex)
end
--return a table with spells id of specified encounter
function Details:GetEncounterSpells (mapid, bossindex)
local encounter = Details:GetBossDetails (mapid, bossindex)
local habilidades_poll = {}
if (encounter.continuo) then
for index, spellid in ipairs(encounter.continuo) do
habilidades_poll [spellid] = true
end
end
local fases = encounter.phases
if (fases) then
for fase_id, fase in ipairs(fases) do
if (fase.spells) then
for index, spellid in ipairs(fase.spells) do
habilidades_poll [spellid] = true
end
end
end
end
return habilidades_poll
end
--return a table with all boss ids from a raid instance
function Details:GetBossIds (mapid)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].boss_ids
end
function Details:InstanceIsRaid (mapid)
return Details:InstanceisRaid (mapid)
end
function Details:InstanceisRaid (mapid)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].is_raid
end
--return a table with all encounter names present in raid instance
function Details:GetBossNames(mapId)
return Details.EncounterInformation[mapId] and Details.EncounterInformation[mapId].boss_names
end
--return the encounter name
function Details:GetBossName(mapid, bossindex)
return Details.EncounterInformation[mapid] and Details.EncounterInformation[mapid].boss_names[bossindex]
end
--same thing as GetBossDetails, just a alias
function Details:GetBossEncounterDetails (mapid, bossindex)
return Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounters [bossindex]
end
---return a textureId, width, height, left, right, top, bottom coords
---@param encounterName string
---@return any
---@return width
---@return height
---@return number
---@return number
---@return number
---@return number
function Details:GetBossEncounterTexture(encounterName)
---@type details_encounterinfo
local encounterInfo = Details:GetEncounterInfo(encounterName)
if (not encounterInfo) then
--Details:Msg("did not find encounter info for: " .. (encounterName or "no-name") .. ".")
--print(debugstack())
return "", 32, 20, 0, 1, 0, 1
end
return encounterInfo.creatureIcon, 32, 20, 0, 1, 0, 0.9
end
function Details:GetEncounterInfoFromEncounterName (EJID, encountername)
DetailsFramework.EncounterJournal.EJ_SelectInstance (EJID) --11ms per call
for i = 1, 20 do
local name = DetailsFramework.EncounterJournal.EJ_GetEncounterInfoByIndex (i, EJID)
if (not name) then
return
end
if (name == encountername or name:find(encountername)) then
return i, DetailsFramework.EncounterJournal.EJ_GetEncounterInfoByIndex (i, EJID)
end
end
end
--return the wallpaper for the raid instance
function Details:GetRaidBackground (mapid)
local bosstables = Details.EncounterInformation [mapid]
if (bosstables) then
local bg = bosstables.backgroundFile
if (bg) then
return bg.file, unpack(bg.coords)
end
end
end
--return the icon for the raid instance
function Details:GetRaidIcon (mapid, ejID, instanceType)
local raidIcon = Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].icon
if (raidIcon) then
return raidIcon
end
if (ejID and ejID ~= 0) then
local name, description, bgImage, buttonImage, loreImage, dungeonAreaMapID, link = DetailsFramework.EncounterJournal.EJ_GetInstanceInfo (ejID)
if (name) then
if (instanceType == "party") then
return loreImage --bgImage
elseif (instanceType == "raid") then
return loreImage
end
end
end
return nil
end
function Details:GetBossIndex (mapid, encounterCLID, encounterEJID, encounterName)
local raidInfo = Details.EncounterInformation [mapid]
if (raidInfo) then
local index = raidInfo.encounter_ids2 [encounterCLID] or raidInfo.encounter_ids [encounterEJID]
if (not index) then
for i = 1, #raidInfo.boss_names do
if (raidInfo.boss_names [i] == encounterName) then
index = i
break
end
end
end
return index
end
end
--return the boss icon
function Details:GetBossIcon (mapid, bossindex)
if (Details.EncounterInformation [mapid]) then
local line = math.ceil (bossindex / 4)
local x = ( bossindex - ( (line-1) * 4 ) ) / 4
return x-0.25, x, 0.25 * (line-1), 0.25 * line, Details.EncounterInformation [mapid].icons
end
end
--return the boss portrit
function Details:GetBossPortrait(mapid, bossindex, encounterName, ejID)
if (mapid and bossindex) then
local haveIcon = Details.EncounterInformation [mapid] and Details.EncounterInformation [mapid].encounters [bossindex] and Details.EncounterInformation [mapid].encounters [bossindex].portrait
if (haveIcon) then
return haveIcon
end
end
if (encounterName and ejID and ejID ~= 0) then
local index, name, description, encounterID, rootSectionID, link = Details:GetEncounterInfoFromEncounterName (ejID, encounterName)
if (index and name and encounterID) then
local id, name, description, displayInfo, iconImage = DetailsFramework.EncounterJournal.EJ_GetCreatureInfo (1, encounterID)
if (iconImage) then
return iconImage
end
end
end
return nil
end
--return a list with names of adds and bosses
function Details:GetEncounterActorsName (EJ_EncounterID)
--code snippet from wowpedia
local actors = {}
local stack, encounter, _, _, curSectionID = {}, DetailsFramework.EncounterJournal.EJ_GetEncounterInfo (EJ_EncounterID)
if (not curSectionID) then
return actors
end
repeat
local title, description, depth, abilityIcon, displayInfo, siblingID, nextSectionID, filteredByDifficulty, link, startsOpen, flag1, flag2, flag3, flag4 = DetailsFramework.EncounterJournal.EJ_GetSectionInfo (curSectionID)
if (displayInfo ~= 0 and abilityIcon == "") then
actors [title] = {model = displayInfo, info = description}
end
table.insert(stack, siblingID)
table.insert(stack, nextSectionID)
curSectionID = table.remove (stack)
until not curSectionID
return actors
end
function Details:GetInstanceEJID(mapId)
mapId = mapId or select(8, GetInstanceInfo())
if (mapId) then
local instanceInfo = Details.EncounterInformation[mapId]
if (instanceInfo) then
return instanceInfo.ej_id or 0
end
end
return 0
end
function Details:GetCurrentDungeonBossListFromEJ()
local mapID = C_Map.GetBestMapForUnit ("player")
if (not mapID) then
return
end
local instanceId = DetailsFramework.EncounterJournal.EJ_GetInstanceForMap(mapID)
if (instanceId and instanceId ~= 0) then
if (Details.encounter_dungeons[instanceId]) then
return Details.encounter_dungeons[instanceId]
end
DetailsFramework.EncounterJournal.EJ_SelectInstance(instanceId)
local name, description, bgImage, buttonImage, loreImage, dungeonAreaMapID, link = DetailsFramework.EncounterJournal.EJ_GetInstanceInfo(instanceId)
local bossList = {
[instanceId] = {name, description, bgImage, buttonImage, loreImage, dungeonAreaMapID, link}
}
for i = 1, 20 do
local encounterName, description, encounterID, rootSectionID, link = DetailsFramework.EncounterJournal.EJ_GetEncounterInfoByIndex(i, instanceId)
if (encounterName) then
for o = 1, 6 do
local id, creatureName, creatureDescription, displayInfo, iconImage = DetailsFramework.EncounterJournal.EJ_GetCreatureInfo(o, encounterID)
if (id) then
bossList[creatureName] = {encounterName, encounterID, creatureName, iconImage, instanceId}
else
break
end
end
else
break
end
end
Details.encounter_dungeons[instanceId] = bossList
return bossList
end
end
function Details:IsRaidRegistered(mapId)
return Details.EncounterInformation[mapId] and true
end
--this cache is local and isn't shared with other components of the addon
local expansionBossList_Cache = {build = false}
---@class details_bossinfo : table
---@field bossName string name
---@field journalEncounterID number journalEncounterID
---@field bossRaidName string instanceName
---@field bossIcon number iconImage
---@field bossIconCoords table<number, number, number, number> {0, 1, 0, 0.95}
---@field bossIconSize table<number, number> {70, 36}
---@field instanceId number raidInstanceID
---@field uiMapId number UiMapID
---@field instanceIndex number instanceIndex
---@field journalInstanceId number journalInstanceID
---@field dungeonEncounterID number dungeonEncounterID
function Details:GetExpansionBossList() --~bosslist - load on demand from gears-gsync and statistics-valid boss for exp
if (expansionBossList_Cache.build) then
return expansionBossList_Cache.bossIndexedTable, expansionBossList_Cache.bossInfoTable, expansionBossList_Cache.raidInfoTable
end
local bossIndexedTable = {}
local bossInfoTable = {} --[bossId] = bossInfo
local raidInfoTable = {}
--check if can load the adventure guide on demand
if (not EncounterJournal_LoadUI) then
return bossIndexedTable, bossInfoTable, raidInfoTable
--don't load if details! isn't full loaded
elseif (not Details.AddOnStartTime) then
return bossIndexedTable, bossInfoTable, raidInfoTable
--don't load at login where other addons are still getting their stuff processing
elseif (Details.AddOnStartTime + 10 > GetTime()) then
return bossIndexedTable, bossInfoTable, raidInfoTable
end
for instanceIndex = 10, 2, -1 do
local raidInstanceID, instanceName, description, bgImage, buttonImage1, loreImage, buttonImage2, dungeonAreaMapID = EJ_GetInstanceByIndex(instanceIndex, true)
if (raidInstanceID) then
--EncounterJournal_DisplayInstance(raidInstanceID)
EJ_SelectInstance(raidInstanceID)
raidInfoTable[raidInstanceID] = {
raidName = instanceName,
raidIcon = buttonImage1,
raidIconCoords = {0.01, .67, 0.025, .725},
raidIconSize = {70, 36},
raidIconTexture = buttonImage2,
raidIconTextureCoords = {0, 1, 0, 0.95},
raidIconTextureSize = {70, 36},
raidIconLore = loreImage,
raidIconLoreCoords = {0, 1, 0, 0.95},
raidIconLoreSize = {70, 36},
raidMapID = dungeonAreaMapID,
raidEncounters = {},
}
for i = 20, 1, -1 do
local name, description, journalEncounterID, rootSectionID, link, journalInstanceID, dungeonEncounterID, UiMapID = EJ_GetEncounterInfoByIndex(i, raidInstanceID)
if (name) then
local id, creatureName, creatureDescription, displayInfo, iconImage = EJ_GetCreatureInfo(1, journalEncounterID)
---@type details_bossinfo
local thisbossIndexedTable = {
bossName = name,
journalEncounterID = journalEncounterID,
bossRaidName = instanceName,
bossIcon = iconImage,
bossIconCoords = {0, 1, 0, 0.95},
bossIconSize = {70, 36},
instanceId = raidInstanceID,
uiMapId = UiMapID,
instanceIndex = instanceIndex,
journalInstanceId = journalInstanceID,
dungeonEncounterID = dungeonEncounterID,
}
bossIndexedTable[#bossIndexedTable+1] = thisbossIndexedTable
bossInfoTable[journalEncounterID] = thisbossIndexedTable
end
end
end
end
expansionBossList_Cache.bossIndexedTable = bossIndexedTable
expansionBossList_Cache.bossInfoTable = bossInfoTable
expansionBossList_Cache.raidInfoTable = raidInfoTable
expansionBossList_Cache.build = true
C_Timer.After(0.5, function()
if (EncounterJournal_ResetDisplay) then
EncounterJournal_ResetDisplay(nil, "none")
end
end)
return bossIndexedTable, bossInfoTable, raidInfoTable
end
function Details222.EJCache.GetInstanceData(...)
for i = 1, select("#", ...) do
local value = select(i, ...)
local instanceData = Details222.EJCache.GetInstanceDataByName(value) or Details222.EJCache.GetInstanceDataByInstanceId(value) or Details222.EJCache.GetInstanceDataByMapId(value)
if (instanceData) then
return instanceData
end
end
end
function Details222.EJCache.GetEncounterDataFromInstanceData(instanceData, ...)
if (not instanceData) then
if (Details.debug) then
Details:Msg("GetEncounterDataFromInstanceData expects instanceData on first parameter.")
end
end
for i = 1, select("#", ...) do
local value = select(i, ...)
if (value) then
local encounterData = instanceData.encountersArray[value]
if (encounterData) then
return encounterData
end
encounterData = instanceData.encountersByName[value]
if (encounterData) then
return encounterData
end
encounterData = instanceData.encountersByDungeonEncounterId[value]
if (encounterData) then
return encounterData
end
encounterData = instanceData.encountersByJournalEncounterId[value]
if (encounterData) then
return encounterData
end
end
end
end
function Details222.EJCache.GetInstanceDataByName(instanceName)
local raidData = Details222.EJCache.CacheRaidData_ByInstanceName[instanceName]
return raidData
end
function Details222.EJCache.GetInstanceDataByInstanceId(instanceId)
local raidData = Details222.EJCache.CacheRaidData_ByInstanceId[instanceId]
return raidData
end
function Details222.EJCache.GetInstanceDataByMapId(mapId)
local raidData = Details222.EJCache.CacheRaidData_ByMapId[mapId]
return raidData
end
function Details222.EJCache.GetRaidDataByName(instanceName)
return Details222.EJCache.CacheRaidData_ByInstanceName[instanceName]
end
function Details222.EJCache.GetRaidDataByInstanceId(instanceId)
return Details222.EJCache.CacheRaidData_ByInstanceId[instanceId]
end
function Details222.EJCache.GetRaidDataByMapId(instanceId)
return Details222.EJCache.CacheRaidData_ByMapId[instanceId]
end
function Details222.EJCache.GetDungeonDataByName(instanceName)
return Details222.EJCache.CacheRaidData_ByInstanceName[instanceName]
end
function Details222.EJCache.GetDungeonDataByInstanceId(instanceId)
return Details222.EJCache.CacheRaidData_ByInstanceId[instanceId]
end
function Details222.EJCache.GetDungeonDataByMapId(instanceId)
return Details222.EJCache.CacheRaidData_ByMapId[instanceId]
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--core
function Details:InstallEncounter(InstanceTable)
Details.EncounterInformation[InstanceTable.id] = InstanceTable
return true
end
end
+189
View File
@@ -0,0 +1,189 @@
local Details = _G.Details
local addonName, Details222 = ...
--get the total of damage and healing of a phase of an encounter
function Details:OnCombatPhaseChanged()
local current_combat = Details:GetCurrentCombat()
local current_phase = current_combat.PhaseData[#current_combat.PhaseData][1]
local phaseDamageContainer = current_combat.PhaseData.damage[current_phase]
local phaseHealingContainer = current_combat.PhaseData.heal[current_phase]
local phaseDamageSection = current_combat.PhaseData.damage_section
local phaseHealingSection = current_combat.PhaseData.heal_section
if (not phaseDamageContainer) then
phaseDamageContainer = {}
current_combat.PhaseData.damage[current_phase] = phaseDamageContainer
end
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 - (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 - (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
function Details:BossModsLink()
if (_G.DBM) then
local DBM = _G.DBM
local DBMCallbackPhase2 = function(event, mod, modId, phase, encounterId, stageTotal)
local encounterTable = Details.encounter_table
if (phase and encounterTable.phase ~= phase) then
--Details:Msg("Current phase is now:", phase)
Details:OnCombatPhaseChanged()
encounterTable.phase = phase
local currentCombat = Details:GetCurrentCombat()
local combatTime = currentCombat:GetCombatTime()
if (combatTime > 5) then
table.insert(currentCombat.PhaseData, {phase, combatTime})
end
Details:SendEvent("COMBAT_ENCOUNTER_PHASE_CHANGED", nil, phase)
end
end
local DBMCallbackPull = function(event, mod, delay, synced, startHp)
local encounterTable = Details.encounter_table
encounterTable.DBM_Mod = mod
encounterTable.DBM_ModTime = time()
end
DBM:RegisterCallback("pull", DBMCallbackPull)
DBM:RegisterCallback("DBM_SetStage", DBMCallbackPhase2)
end
if (BigWigsLoader and not _G.DBM) then
--Bigwigs change the phase of an encounter
function Details:BigWigs_SetStage (event, module, phase)
phase = tonumber(phase)
if (phase and type(phase) == "number" and Details.encounter_table.phase ~= phase) then
Details:OnCombatPhaseChanged()
Details.encounter_table.phase = phase
local currentCombat = Details:GetCurrentCombat()
local combatTime = currentCombat:GetCombatTime()
if (combatTime > 5) then
table.insert(currentCombat.PhaseData, {phase, combatTime})
end
Details:SendEvent("COMBAT_ENCOUNTER_PHASE_CHANGED", nil, phase)
--Details:Msg("Current phase is now:", phase)
end
end
if (BigWigsLoader.RegisterMessage) then
BigWigsLoader.RegisterMessage (Details, "BigWigs_SetStage")
end
end
Details:CreateCallbackListeners()
end
function Details:CreateCallbackListeners()
Details.DBM_timers = {}
local current_encounter = false
local current_table_dbm = {}
local current_table_bigwigs = {}
local event_frame = CreateFrame("frame", nil, UIParent, "BackdropTemplate")
event_frame:SetScript("OnEvent", function(self, event, ...)
if (event == "ENCOUNTER_START") then
local encounterID, encounterName, difficultyID, raidSize = select(1, ...)
current_encounter = encounterID
elseif (event == "ENCOUNTER_END" or event == "PLAYER_REGEN_ENABLED") then
if (current_encounter) then
if (_G.DBM) then
local db = Details.boss_mods_timers
for spell, timer_table in pairs(current_table_dbm) do
if (not db.encounter_timers_dbm [timer_table[1]]) then
timer_table.id = current_encounter
db.encounter_timers_dbm [timer_table[1]] = timer_table
end
end
end
if (BigWigs) then
local db = Details.boss_mods_timers
for timer_id, timer_table in pairs(current_table_bigwigs) do
if (not db.encounter_timers_bw [timer_id]) then
timer_table.id = current_encounter
db.encounter_timers_bw [timer_id] = timer_table
end
end
end
end
current_encounter = false
Details:Destroy(current_table_dbm)
Details:Destroy(current_table_bigwigs)
end
end)
event_frame:RegisterEvent("ENCOUNTER_START")
event_frame:RegisterEvent("ENCOUNTER_END")
event_frame:RegisterEvent("PLAYER_REGEN_ENABLED")
if (_G.DBM) then
local dbm_timer_callback = function(bar_type, id, msg, timer, icon, bartype, spellId, colorId, modid)
local currentCombat = Details:GetCurrentCombat()
if (not currentCombat.__destroyed) then --async events, need to check for combat destruction
---@type combattime
local combatTime = currentCombat:GetCombatTime()
table.insert(currentCombat.bossTimers, {"dbm", combatTime, bar_type, id, msg, timer, icon, bartype, spellId, colorId, modid})
--print("dbm event", bar_type, id, msg, timer, icon, bartype, spellId, colorId, modid)
local spell = tostring(spellId)
if (spell and not current_table_dbm[spell]) then
current_table_dbm[spell] = {spell, id, msg, timer, icon, bartype, spellId, colorId, modid}
end
end
end
DBM:RegisterCallback ("DBM_TimerStart", dbm_timer_callback)
end
--record Bigwigs timers shown at /details spells.
--this is also usage to create weakauras directly from details!
function Details:RegisterBigWigsCallBack()
--if the user is also using DBM, ignore registering another callback
if (BigWigsLoader and not _G.DBM) then
function Details:BigWigs_StartBar(event, module, spellid, bar_text, time, icon, ...)
local currentCombat = Details:GetCurrentCombat()
if (not currentCombat.__destroyed) then --async events, need to check for combat destruction
---@type combattime
local combatTime = currentCombat:GetCombatTime()
table.insert(currentCombat.bossTimers, {"bw", combatTime, spellid, bar_text, time, icon})
spellid = tostring(spellid)
if (not current_table_bigwigs[spellid]) then
current_table_bigwigs[spellid] = {(type(module) == "string" and module) or (module and module.moduleName) or "", spellid or "", bar_text or "", time or 0, icon or ""}
end
end
end
if (BigWigsLoader.RegisterMessage) then
BigWigsLoader.RegisterMessage(Details, "BigWigs_StartBar")
end
end
end
Details.Schedules.NewTimer(5, Details.RegisterBigWigsCallBack, Details)
end
+197
View File
@@ -0,0 +1,197 @@
--File Revision: 1
--Last Modification: 27/07/2013
-- Change Log:
-- 27/07/2013: Finished alpha version.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
local _detalhes = _G.Details
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local _
local addonName, Details222 = ...
local GetSpellInfo = Details222.GetSpellInfo
--initialize buffs name container
_detalhes.Buffs.BuffsTable = {} -- armazenara o [nome do buff] = { tabela do buff }
_detalhes.Buffs.__index = _detalhes.Buffs
--switch off recording buffs by default
_detalhes.RecordPlayerSelfBuffs = false
_detalhes.RecordPlayerAbilityWithBuffs = false
_detalhes.RecordPlayerSelfDebuffs = false
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--local pointers
local pairs = pairs --lua local
local ipairs = ipairs --lua local
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--details api functions
--return if the buff is already registred or not
function _detalhes.Buffs:IsRegistred (buff)
if (type(buff) == "number") then
for _, buffObject in pairs(_detalhes.Buffs.BuffsTable) do
if (buffObject.id == buff) then
return true
end
end
return false
elseif (type(buff) == "string") then
for name, _ in pairs(_detalhes.Buffs.BuffsTable) do
if (name == buff) then
return true
end
end
return false
end
end
--register a new buff name
function _detalhes.Buffs:NewBuff (BuffName, BuffId)
if (not BuffName) then
BuffName = GetSpellInfo(BuffId)
end
if (_detalhes.Buffs.BuffsTable [BuffName]) then
return false
else
_detalhes.Buffs.BuffsTable [BuffName] = _detalhes.Buffs:BuildBuffTable (BuffName, BuffId)
end
end
--remove a registred buff
function _detalhes.Buffs:RemoveBuff (BuffName)
if (not _detalhes.Buffs.BuffsTable [BuffName]) then
return false
else
_detalhes.Buffs.BuffsTable = _detalhes:tableRemove (_detalhes.Buffs.BuffsTable, BuffName)
return true
end
end
--return a list of registred buffs
function _detalhes.Buffs:GetBuffList()
local list = {}
for name, _ in pairs(_detalhes.Buffs.BuffsTable) do
list [#list+1] = name
end
return list
end
--return a list of registred buffs ids
function _detalhes.Buffs:GetBuffListIds()
local list = {}
for name, buffObject in pairs(_detalhes.Buffs.BuffsTable) do
list [#list+1] = buffObject.id
end
return list
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--internal functions
function _detalhes.Buffs:UpdateBuff (method)
-- self = buff table
if (method == "new") then
self.start = _detalhes._tempo
self.castedAmt = self.castedAmt + 1
self.active = true
self.appliedAt [#self.appliedAt+1] = _detalhes.tabela_vigente:GetCombatTime()
_detalhes:SendEvent("BUFF_UPDATE")
elseif (method == "refresh") then
self.refreshAmt = self.refreshAmt + 1
self.duration = self.duration + (_detalhes._tempo - self.start)
self.start = _detalhes._tempo
self.appliedAt [#self.appliedAt+1] = _detalhes.tabela_vigente:GetCombatTime()
_detalhes:SendEvent("BUFF_UPDATE")
elseif (method == "remove") then
if (self.start) then
self.duration = self.duration + (_detalhes._tempo - self.start)
else
self.duration = 0
end
self.droppedAmt = self.droppedAmt + 1
self.start = nil
self.active = false
_detalhes:SendEvent("BUFF_UPDATE")
end
end
--build buffs
function _detalhes.Buffs:BuildTables()
_detalhes.Buffs.built = true
if (_detalhes.savedbuffs) then
for _, BuffId in ipairs(_detalhes.savedbuffs) do
_detalhes.Buffs:NewBuff (nil, BuffId)
end
end
end
--save buff list when addon exit
function _detalhes.Buffs:SaveBuffs()
_detalhes_database.savedbuffs = _detalhes.Buffs:GetBuffListIds()
end
--construct a buff table of the new buff registred
function _detalhes.Buffs:BuildBuffTable (BuffName, BuffId)
local bufftable = {name = BuffName, id = BuffId, duration = 0, start = nil, castedAmt = 0, refreshAmt = 0, droppedAmt = 0, active = false, appliedAt = {}}
bufftable.IsBuff = true
setmetatable(bufftable, _detalhes.Buffs)
return bufftable
end
--update player buffs
function _detalhes.Buffs:CatchBuffs()
if (not _detalhes.Buffs.built) then
_detalhes.Buffs:BuildTables()
end
for _, BuffTable in pairs(_detalhes.Buffs.BuffsTable) do
if (BuffTable.active) then
BuffTable.start = _detalhes._tempo
BuffTable.castedAmt = 1
else
BuffTable.start = nil
BuffTable.castedAmt = 0
end
BuffTable.appliedAt = {}
BuffTable.duration = 0
BuffTable.refreshAmt = 0
BuffTable.droppedAmt = 0
end
--catch buffs untracked yet
for buffIndex = 1, 41 do
local name = UnitAura ("player", buffIndex)
if (name) then
local bufftable = _detalhes.Buffs.BuffsTable [name]
if (bufftable and not bufftable.active) then
bufftable.active = true
bufftable.castedAmt = 1
bufftable.start = _detalhes._tempo
end
--[[
for index, BuffName in pairs(_detalhes.SoloTables.BuffsTableNameCache) do
if (BuffName == name) then
local BuffObject = _detalhes.SoloTables.SoloBuffUptime [name]
if (not BuffObject) then
_detalhes.SoloTables.SoloBuffUptime [name] = {name = name, duration = 0, start = nil, castedAmt = 1, refreshAmt = 0, droppedAmt = 0, Active = true, tableIndex = index, appliedAt = {}}
--_detalhes.SoloTables.BuffTextEntry [index].backgroundFrame:Active()
end
end
end
--]]
end
end
end
+393
View File
@@ -0,0 +1,393 @@
--[[ Declare all Details classes and container indexes ]]
do
---@type details
local Details = _G.Details
local addonName, Details222 = ...
local setmetatable = setmetatable
-------- time machine controla o tempo em combate dos jogadores
Details.timeMachine = {}
Details.timeMachine.__index = Details.timeMachine
setmetatable(Details.timeMachine, Details)
-------- classe da tabela que armazenar todos os combates efetuados
Details.historico = {}
Details.historico.__index = Details.historico
setmetatable(Details.historico, Details)
---------------- classe da tabela onde sero armazenados cada combate efetuado
Details.combate = {}
Details.combate.__index = Details.combate
setmetatable(Details.combate, Details.historico)
------------------------ armazenas classes de jogadores ou outros derivados
Details.container_combatentes = {}
Details.container_combatentes.__index = Details.container_combatentes
setmetatable(Details.container_combatentes, Details.combate)
-------------------------------- dano das habilidades.
Details.atributo_damage = {}
Details.atributo_damage.__index = Details.atributo_damage
setmetatable(Details.atributo_damage, Details.container_combatentes)
-------------------------------- cura das habilidades.
Details.atributo_heal = {}
Details.atributo_heal.__index = Details.atributo_heal
setmetatable(Details.atributo_heal, Details.container_combatentes)
-------------------------------- e_energy ganha
Details.atributo_energy = {}
Details.atributo_energy.__index = Details.atributo_energy
setmetatable(Details.atributo_energy, Details.container_combatentes)
-------------------------------- outros atributos
Details.atributo_misc = {}
Details.atributo_misc.__index = Details.atributo_misc
setmetatable(Details.atributo_misc, Details.container_combatentes)
-------------------------------- atributos customizados
Details.atributo_custom = {}
Details.atributo_custom.__index = Details.atributo_custom
setmetatable(Details.atributo_custom, Details.container_combatentes)
-------------------------------- armazena as classes de habilidades usadas pelo combatente
Details.container_habilidades = {}
Details.container_habilidades.__index = Details.container_habilidades
setmetatable(Details.container_habilidades, Details.combate)
---------------------------------------- classe das habilidades que do cura
Details.habilidade_cura = {}
Details.habilidade_cura.__index = Details.habilidade_cura
setmetatable(Details.habilidade_cura, Details.container_habilidades)
---------------------------------------- classe das habilidades que do danos
Details.habilidade_dano = {}
Details.habilidade_dano.__index = Details.habilidade_dano
setmetatable(Details.habilidade_dano, Details.container_habilidades)
---------------------------------------- classe das habilidades que do e_energy
Details.habilidade_e_energy = {}
Details.habilidade_e_energy.__index = Details.habilidade_e_energy
setmetatable(Details.habilidade_e_energy, Details.container_habilidades)
---------------------------------------- classe das habilidades variadas
Details.habilidade_misc = {}
Details.habilidade_misc.__index = Details.habilidade_misc
setmetatable(Details.habilidade_misc, Details.container_habilidades)
---------------------------------------- classe dos alvos das habilidads
Details.alvo_da_habilidade = {}
Details.alvo_da_habilidade.__index = Details.alvo_da_habilidade
setmetatable(Details.alvo_da_habilidade, Details.container_combatentes)
---return the class object for the given displayId (attributeId)
---@param displayId attributeid
---@return table
function Details:GetDisplayClassByDisplayId(displayId)
if (displayId == DETAILS_ATTRIBUTE_DAMAGE) then
return Details.atributo_damage
elseif (displayId == DETAILS_ATTRIBUTE_HEAL) then
return Details.atributo_heal
elseif (displayId == DETAILS_ATTRIBUTE_ENERGY) then
return Details.atributo_energy
elseif (displayId == DETAILS_ATTRIBUTE_MISC) then
return Details.atributo_misc
elseif (displayId == DETAILS_ATTRIBUTE_CUSTOM) then
return Details.atributo_custom
end
return {}
end
--[[ Armazena os diferentes tipos de containers ]] --[[ Container Types ]]
Details.container_type = {
CONTAINER_PLAYERNPC = 1,
CONTAINER_DAMAGE_CLASS = 2,
CONTAINER_HEAL_CLASS = 3,
CONTAINER_HEALTARGET_CLASS = 4,
CONTAINER_FRIENDLYFIRE = 5,
CONTAINER_DAMAGETARGET_CLASS = 6,
CONTAINER_ENERGY_CLASS = 7,
CONTAINER_ENERGYTARGET_CLASS = 8,
CONTAINER_MISC_CLASS = 9,
CONTAINER_MISCTARGET_CLASS = 10,
CONTAINER_ENEMYDEBUFFTARGET_CLASS = 11
}
local UnitName = UnitName
local GetRealmName = GetRealmName
---@param self actor
---@param specId number
function Details:SetSpecId(specId)
self.spec = specId
end
---@param self details|actor
---@param actor actor?
function Details:Name(actor)
return self.nome or actor and actor.nome
end
---Retrieves the name of the actor.
---If the name is not available in the current object (self), it checks the provided actor object.
---@param actor (optional) The actor object to retrieve the name from.
---@return The name of the actor.
function Details:GetName(actor)
return self.nome or actor and actor.nome
end
---Retrieves the name of the actor without the realm information.
---If the name is not available in the current object (self), it checks the provided actor object.
---@param actor (optional) The actor object to retrieve the name from.
---@return The name of the actor without the realm information.
function Details:GetNameNoRealm(actor)
local name = self.nome or actor and actor.nome
return Details:GetOnlyName(name)
end
---Retrieves the display name of the actor.
---If the display name is not available in the current object (self), it checks the provided actor object.
---@param actor actor The actor object to retrieve the display name from.
---@return string displayName display name of the actor.
function Details:GetDisplayName(actor)
return self.displayName or actor and actor.displayName
end
---Sets the display name of the actor.
---If the new display name is not provided, it sets the display name of the current object (self) to the provided actor object.
---@param actor actor The actor object to set the display name for.
---@param newDisplayName string The new display name to set.
function Details:SetDisplayName(actor, newDisplayName)
if (not newDisplayName) then
local thisActor = self
---@cast thisActor actor
local displayName = tostring(actor)
thisActor.displayName = displayName
else
actor.displayName = newDisplayName
end
end
function Details:GetOnlyName(string)
if (string) then
return string:gsub(("%-.*"), "")
end
return self.nome:gsub(("%-.*"), "")
end
function Details:RemoveOwnerName(string)
if (string) then
return string:gsub((" <.*"), "")
end
return self.nome:gsub((" <.*"), "")
end
function Details:GetCLName(id)
local name, realm = UnitName(id)
if (name) then
if (realm and realm ~= "") then
name = name .. "-" .. realm
end
return name
end
end
---return the class file name of the unit passed
local getFromCache = Details222.ClassCache.GetClassFromCache
local Ambiguate = Ambiguate
local UnitClass = UnitClass
function Details:GetUnitClass(unitId)
local class, classFileName = getFromCache(unitId)
if (not classFileName) then
unitId = Ambiguate(unitId, "none")
classFileName = select(2, UnitClass(unitId))
end
return classFileName
end
function Details:Ambiguate(unitName)
--if (toc >= 100200) then
unitName = Ambiguate(unitName, "none")
--end
return unitName
end
---return the class name, class file name and class id of the unit passed
function Details:GetUnitClassFull(unitId)
unitId = Ambiguate(unitId, "none")
local locClassName, classFileName, classId = UnitClass(unitId)
return locClassName, classFileName, classId
end
local UnitFullName = UnitFullName
--Details:GetCurrentCombat():GetActor(DETAILS_ATTRIBUTE_DAMAGE, Details:GetFullName("player")):GetSpell(1)
---create a CLEU compatible name of the unit passed
---return string is in the format "playerName-realmName"
---the string will also be ambiguated using the ambiguateString passed
---@param unitId any
---@param ambiguateString any
function Details:GetFullName(unitId, ambiguateString) --not in use, get replace by Details.GetCLName a few lines below
--UnitFullName is guarantee to return the realm name of the unit queried
local playerName, realmName = UnitFullName(unitId)
if (playerName) then
if (not realmName) then
realmName = GetRealmName()
end
realmName = realmName:gsub("[%s-]", "")
playerName = playerName .. "-" .. realmName
if (ambiguateString) then
playerName = Ambiguate(playerName, ambiguateString)
end
return playerName
end
end
function Details:GetUnitNameForAPI(unitId)
return Details:GetFullName(unitId, "none")
end
--if (toc < 100200) then
Details.GetFullName = Details.GetCLName
--end
function Details:Class(actor)
return self.classe or actor and actor.classe
end
function Details:GetActorClass(actor)
return self.classe or actor and actor.classe
end
function Details:GetGUID(actor)
return self.serial or actor and actor.serial
end
function Details:GetFlag(actor)
return self.flag_original or actor and actor.flag_original
end
function Details:GetSpells()
return self.spells._ActorTable
end
function Details:GetActorSpells()
return self.spells._ActorTable
end
function Details:GetSpell(spellid)
return self.spells._ActorTable [spellid]
end
---return an array of pet names
---@return table
function Details:GetPets()
return self.pets
end
---return an array of pet names
---@return table
function Details:Pets()
return self.pets
end
function Details:GetSpec(actor)
return self.spec or actor and actor.spec
end
function Details:Spec(actor)
return self.spec or actor and actor.spec
end
---add the class color to the string passed
---@param thisString string
---@param class string
---@return string
function Details:AddColorString(thisString, class)
--check if the class colors exists
local classColors = _G["RAID_CLASS_COLORS"]
if (classColors) then
local color = classColors[class]
--check if the player name is valid
if (type(thisString) == "string" and color) then
thisString = "|c" .. color.colorStr .. thisString .. "|r"
return thisString
end
end
--if failed, return the string without modifications
return thisString
end
---add the role icon to the string passed
---@param thisString string
---@param role string
---@param size number|nil default is 14
---@return string
function Details:AddRoleIcon(thisString, role, size)
--check if is a valid role
local roleIcon = Details.role_texcoord [role]
if (type(thisString) == "string" and roleIcon and role ~= "NONE") then
--add the role icon
size = size or 14
thisString = "|TInterface\\LFGFRAME\\UI-LFG-ICON-ROLES:" .. size .. ":" .. size .. ":0:0:256:256:" .. roleIcon .. "|t " .. thisString
return thisString
end
--if failed, return the string without modifications
return thisString
end
---add the spec icon or class icon to the string passed
---@param thisString string
---@param class string|nil
---@param spec number|nil
---@param iconSize number|nil default is 16
---@param useAlphaIcons boolean|nil default is false
---@return string
function Details:AddClassOrSpecIcon(thisString, class, spec, iconSize, useAlphaIcons)
iconSize = iconSize or 16
if (spec) then
local specString = ""
local L, R, T, B = unpack(Details.class_specs_coords[spec])
if (L) then
if (useAlphaIcons) then
specString = "|TInterface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES-SPECS:" .. iconSize .. ":" .. iconSize .. ":0:0:512:512:" .. (L * 512) .. ":" .. (R * 512) .. ":" .. (T * 512) .. ":" .. (B * 512) .. "|t"
else
specString = "|TInterface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES-SPECS:" .. iconSize .. ":" .. iconSize .. ":0:0:512:512:" .. (L * 512) .. ":" .. (R * 512) .. ":" .. (T * 512) .. ":" .. (B * 512) .. "|t"
end
return specString .. " " .. thisString
end
end
if (class) then
local classString = ""
local L, R, T, B = unpack(Details.class_coords[class])
if (L) then
local imageSize = 128
if (useAlphaIcons) then
classString = "|TInterface\\AddOns\\Details\\images\\classes_small_alpha:" .. iconSize .. ":" .. iconSize .. ":0:0:" .. imageSize .. ":" .. imageSize .. ":" .. (L * imageSize) .. ":" .. (R * imageSize) .. ":" .. (T * imageSize) .. ":" .. (B * imageSize) .. "|t"
else
classString = "|TInterface\\AddOns\\Details\\images\\classes_small:" .. iconSize .. ":" .. iconSize .. ":0:0:" .. imageSize .. ":" .. imageSize .. ":" .. (L * imageSize) .. ":" .. (R * imageSize) .. ":" .. (T * imageSize) .. ":" .. (B * imageSize) .. "|t"
end
return classString .. " " .. thisString
end
end
return thisString
end
--inherits to all actors without placing it on _detalhes namespace.
Details.container_combatentes.guid = Details.GetGUID
Details.container_combatentes.name = Details.GetName
Details.container_combatentes.class = Details.GetActorClass
Details.container_combatentes.flag = Details.GetFlag
end
+645
View File
@@ -0,0 +1,645 @@
local Details = _G.Details
local addonName, Details222 = ...
--stop yellow warning on my editor
local IsInRaid = _G.IsInRaid
local UnitIsGroupAssistant = _G.UnitIsGroupAssistant
local UnitName = _G.UnitName
local GetRealmName = _G.GetRealmName
local GetTime = _G.GetTime
local GetNumGroupMembers = _G.GetNumGroupMembers
--return if the player is inside a raid zone
local isInRaidZone = function()
return Details.zone_type == "raid"
end
--create a namespace using capital letter 'C' for coach feature, the profile entry is lower character .coach
Details.Coach = {
Client = { --regular player
enabled = false,
coachName = "",
},
Server = { --the coach
enabled = false,
lastCombatStartTime = 0,
lastCombatEndTime = 0,
},
isInRaidGroup = false,
isInRaidZone = false,
}
function Details.Coach.AskRLForCoachStatus()
Details:SendRaidData(DETAILS_PREFIX_COACH, "CIEA")
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] asked the coach the coach status.")
end
end
function Details.Coach.SendRLCombatStartNotify(coachName)
Details:SendCommMessage(_G.DETAILS_PREFIX_NETWORK, Details:Serialize(_G.DETAILS_PREFIX_COACH, UnitName("player"), GetRealmName(), Details.realversion, "CCS"), "WHISPER", coachName)
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] sent to coach a combat start notification.")
end
end
function Details.Coach.SendRLCombatEndNotify(coachName)
Details:SendCommMessage(_G.DETAILS_PREFIX_NETWORK, Details:Serialize(_G.DETAILS_PREFIX_COACH, UnitName("player"), GetRealmName(), Details.realversion, "CCE"), "WHISPER", coachName)
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] sent to coach a combat end notification.")
end
end
--the coach is no more a coach
function Details.Coach.SendRaidCoachEndNotify()
Details:SendRaidData(DETAILS_PREFIX_COACH, "CE")
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] sent to raid a coach end notification.")
end
end
--there's a new coach, notify players
function Details.Coach.SendRaidCoachStartNotify()
Details:SendRaidData(DETAILS_PREFIX_COACH, "CS")
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] sent to raid a coach start notification.")
end
end
--player send his death to the coach
function Details.Coach.SendDeathToRL(deathTable)
Details:SendRaidData(DETAILS_PREFIX_COACH, "CDD", deathTable)
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] your death has been sent to coach.")
end
end
--send data to coach
function Details.Coach.Client.SendDataToRL()
if (Details.debug) then
print("Details Coach sending data to RL.")
end
--local data = Details.packFunctions.GetAllData()
local okay, data = pcall(Details.packFunctions.GetAllData)
if (not okay) then
Details:Msg("Error on GetAllData():", data)
Details.Coach.Client.UpdateTicker:Cancel()
return
end
if (data and Details.Coach.Client.coachName) then
Details:SendCommMessage(_G.DETAILS_PREFIX_NETWORK, Details:Serialize(_G.DETAILS_PREFIX_COACH, UnitName("player"), GetRealmName(), Details.realversion, "CDT", data), "WHISPER", Details.Coach.Client.coachName)
end
end
--on details startup
function Details.Coach.StartUp()
Details.Coach.isInRaidGroup = IsInRaid()
Details.Coach.isInRaidZone = select(2, _G.GetInstanceInfo())
--server
if (Details.coach.enabled) then --profile
Details.Coach.Server.EnableCoach(true)
elseif (not Details.coach.enabled) then --profile
if (IsInRaid()) then
if (isInRaidZone()) then
--client ask in the raid if Coach is enabled
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] sent ask to coach, is coach?")
end
Details.Coach.AskRLForCoachStatus()
end
end
end
local eventListener = Details:CreateEventListener()
Details.Coach.Listener = eventListener
function eventListener.OnEnterGroup() --client
--when entering a group, check is there's a coach
if (IsInRaid()) then
if (isInRaidZone()) then
Details.Coach.AskRLForCoachStatus()
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] sent to raid, is there a coach?")
end
end
end
Details.Coach.isInRaidGroup = true
end
function eventListener.OnLeaveGroup()
--disable coach feature on server and client if the player leaves the group
Details.Coach.Disable()
Details.Coach.isInRaidGroup = false
end
function eventListener.OnEnterCombat()
--[=[ --debug solo
Details.Coach.SendRLCombatStartNotify("Ditador")
--start a timer to send data to the coach
if (Details.Coach.Client.UpdateTicker) then
Details.Coach.Client.UpdateTicker:Cancel()
end
Details.Coach.Client.UpdateTicker = Details.Schedules.NewTicker(1.5, Details.Coach.Client.SendDataToRL)
--]=]
--send a notify to coach telling a new combat has started
if (Details.Coach.Client.IsEnabled()) then
if (IsInRaid() and isInRaidZone()) then
if (UnitIsGroupAssistant("player")) then
local coachName = Details.coach.last_coach_name
if (coachName) then
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] i'm a raid assistant, sent combat start notification to coach.")
end
Details.Coach.SendRLCombatStartNotify(coachName)
end
end
--start a timer to send data to the coach
if (Details.Coach.Client.UpdateTicker) then
Details.Coach.Client.UpdateTicker:Cancel()
end
Details.Coach.Client.UpdateTicker = Details.Schedules.NewTicker(1.5, Details.Coach.Client.SendDataToRL)
end
end
end
function eventListener.OnLeaveCombat()
--send a notify to coach telling the combat has finished
if (Details.Coach.Client.IsEnabled()) then
if (IsInRaid() and isInRaidZone()) then
if (UnitIsGroupAssistant("player")) then
local raidLeaderName = Details.Coach.Client.GetLeaderName()
if (raidLeaderName) then
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] i'm a raid assistant, sent combat end notification to coach.")
end
Details.Coach.SendRLCombatEndNotify(raidLeaderName)
end
end
end
Details.Schedules.Cancel(Details.Coach.Client.UpdateTicker)
end
end
function eventListener.OnZoneChanged()
--if the coach entered in a raid, disable the coach
if (Details.Coach.Server.IsEnabled()) then
if (isInRaidZone()) then
--the coach entered a raid instance
Details.Coach.Disable()
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] Coach feature stopped: you entered in a raid instance.")
end
end
return
end
--when entering a new zone, check if there's a coach
if (not Details.Coach.isInRaidZone and isInRaidZone()) then
if (IsInRaid()) then
if (not Details.Coach.Client.IsEnabled()) then
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] sent in the raid, there's a coach?")
end
Details.Coach.AskRLForCoachStatus()
return
end
end
end
--check if the player has left the raid zone
if (Details.Coach.isInRaidZone and Details.Coach.Client.IsEnabled()) then
if (not isInRaidZone()) then
--player left the raid zone
Details.Schedules.Cancel(Details.Coach.Client.UpdateTicker)
Details.Coach.Disable()
end
end
Details.Coach.isInRaidZone = isInRaidZone()
end
eventListener:RegisterEvent("GROUP_ONENTER", "OnEnterGroup")
eventListener:RegisterEvent("GROUP_ONLEAVE", "OnLeaveGroup")
eventListener:RegisterEvent("COMBAT_PLAYER_ENTER", "OnEnterCombat")
eventListener:RegisterEvent("COMBAT_PLAYER_LEAVE", "OnLeaveCombat")
eventListener:RegisterEvent("ZONE_TYPE_CHANGED", "OnZoneChanged")
end
C_Timer.After(0.1, function()
--Details.debug = true
end)
--received an answer from server telling if the raidleader has the coach feature enabled
--the request is made when the player enters a new group or reconnects
function Details.Coach.Client.CoachIsEnabled_Response(isCoachEnabled, coachName)
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] Coach sent response about the status of Coach Mode:", isCoachEnabled, raidLeaderName)
end
if (isCoachEnabled) then
--coach confirmed the coach feature is enabled and running
Details.Coach.Client.EnableCoach(coachName)
Details:Msg("[|cFFAAFFAADetails! Coach|r] current coach:", coachName)
end
end
function Details.Coach.Server.CoachIsEnabled_Answer(sourcePlayer)
if (Details.Coach.Server.IsEnabled()) then
--send the answer
Details:SendCommMessage(_G.DETAILS_PREFIX_NETWORK, Details:Serialize(_G.DETAILS_PREFIX_COACH, sourcePlayer, GetRealmName(), Details.realversion, "CIER", Details.Coach.Server.IsEnabled()), "WHISPER", sourcePlayer)
end
end
function Details.Coach.Disable()
Details.coach.enabled = false --profile
--if the player is the coach and the coach feature is enabled
if (Details.Coach.Server.IsEnabled()) then
Details.Coach.SendRaidCoachEndNotify()
end
Details.Coach.Server.enabled = false
Details.Coach.Client.enabled = false
Details.Coach.Client.coachName = nil
Details.coach.last_coach_name = false
Details.Coach.EventFrame:UnregisterEvent("GROUP_ROSTER_UPDATE")
end
--the player used '/details coach' or it's Details! initialization
function Details.Coach.Server.EnableCoach(fromStartup)
if (not IsInRaid()) then
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] cannot enabled coach: not in raid.")
end
Details.coach.enabled = false
Details.Coach.Server.enabled = false
Details.coach.last_coach_name = false
return
elseif (isInRaidZone()) then
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] cannot enabled coach: you are inside a raid zone.")
end
Details.coach.enabled = false
Details.Coach.Server.enabled = false
Details.coach.last_coach_name = false
return
end
Details.coach.enabled = true
Details.Coach.Server.enabled = true
Details.coach.last_coach_name = UnitName("player")
--notify players about the new coach
Details.Coach.SendRaidCoachStartNotify()
--enable group roster to know if the server isn't coach any more
Details.Coach.EventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
if (fromStartup) then
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] coach feature enabled, welcome back captain!")
end
end
end
--the coach sent a coach end notify
function Details.Coach.Client.CoachEnd()
Details.Coach.Client.enabled = false
Details.Coach.Client.coachName = nil
Details.coach.last_coach_name = false
Details.Coach.EventFrame:UnregisterEvent("GROUP_ROSTER_UPDATE")
end
--a player in the raid asked to be the coach of the group
function Details.Coach.Client.EnableCoach(coachName)
if (not IsInRaid()) then
if (Details.debug) then
print("Details Coach can't enable coach on client: isn't in raid")
end
return
end
Details.Coach.Client.enabled = true
Details.Coach.Client.coachName = coachName
Details.coach.last_coach_name = coachName
--enable group roster to know if the coach has changed
Details.Coach.EventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] there's a new coach: ", coachName)
end
Details:Msg("[|cFFAAFFAADetails! Coach|r] current coach:", coachName)
end
--coach received a notification that a new combat has started
function Details.Coach.Server.CombatStarted()
if (Details.Coach.Server.lastCombatStartTime > GetTime()) then
return
else
Details.Coach.Server.lastCombatStartTime = GetTime() + 10
end
--stop the combat if already in one
if (Details.in_combat) then
Details:EndCombat()
end
--start a new combat
Details:StartCombat()
end
--coach received a notification that the current combat ended
function Details.Coach.Server.CombatEnded()
if (Details.Coach.Server.lastCombatEndTime > GetTime()) then
return
else
Details.Coach.Server.lastCombatEndTime = GetTime() + 10
end
Details:EndCombat()
end
--profile
function Details.Coach.IsEnabled()
return Details.coach.enabled
end
--server
function Details.Coach.Server.IsEnabled()
return Details.Coach.Server.enabled
end
--client
function Details.Coach.Client.IsEnabled()
return Details.Coach.Client.enabled
end
function Details.Coach.Client.GetLeaderName()
return Details.Coach.Client.coachName
end
Details.Coach.EventFrame = _G.CreateFrame("frame")
Details.Coach.EventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
Details.Coach.EventFrame:SetScript("OnEvent", function(event, ...)
if (event == "GROUP_ROSTER_UPDATE") then
--check who is coach to know if the leader is still the same
if (Details.Coach.Client.IsEnabled()) then
if (IsInRaid()) then
for i = 1, GetNumGroupMembers() do
local inRaid = UnitInRaid(Details.Coach.Client.coachName)
if (not inRaid) then
if (Details.debug) then
Details:Msg("[|cFFAAFFAADetails! Coach|r] coach isn't in the raid, coach feature has been disabled.")
end
Details.Coach.Client.CoachEnd()
end
end
end
end
end
end)
function Details.Coach.Client.SendMyDeath(_, _, _, _, _, _, playerGUID, _, playerFlag, deathTable)
if (Details.Coach.Client.enabled) then
if (Details.Coach.Client.coachName) then
if (Details.in_combat) then
if (playerGUID == UnitGUID("player")) then
Details.Coach.SendDeathToRL({deathTable, playerGUID, playerFlag})
end
end
end
end
end
function Details.Coach.Server.AddPlayerDeath(playerName, data)
local currentCombat = Details:GetCurrentCombat()
local utilityContainer = currentCombat[4]
local deathLog = data[1]
local playerGUID = data[2]
local playerFlag = data[3]
local utilityActorObject = utilityContainer:GetOrCreateActor(playerGUID, playerName, playerFlag, true)
if (utilityActorObject) then
table.insert(currentCombat.last_events_tables, deathLog)
--tag the misc container as need refresh
currentCombat[DETAILS_ATTRIBUTE_MISC].need_refresh = true
end
end
function Details.Coach.WelcomePanel()
local welcomePanel = _G.DETAILSCOACHPANEL
if (not welcomePanel) then
welcomePanel = DetailsFramework:CreateSimplePanel(UIParent)
welcomePanel:SetSize(400, 280)
welcomePanel:SetTitle("Details! Coach")
welcomePanel:ClearAllPoints()
welcomePanel:SetPoint("left", UIParent, "left", 10, 0)
welcomePanel:Hide()
DetailsFramework:ApplyStandardBackdrop(welcomePanel)
local LibWindow = _G.LibStub("LibWindow-1.1")
welcomePanel:SetScript("OnMouseDown", nil)
welcomePanel:SetScript("OnMouseUp", nil)
LibWindow.RegisterConfig(welcomePanel, Details.coach.welcome_panel_pos)
LibWindow.MakeDraggable(welcomePanel)
LibWindow.RestorePosition(welcomePanel)
local imageSize = 26
local detailsLogo = DetailsFramework:CreateImage(welcomePanel, [[Interface\AddOns\Details\images\logotipo]])
detailsLogo:SetPoint("topleft", welcomePanel, "topleft", 5, -30)
detailsLogo:SetSize(200, 50)
detailsLogo:SetTexCoord(36/512, 380/512, 128/256, 227/256)
local isLeaderTexture = DetailsFramework:CreateImage(welcomePanel, [[Interface\GLUES\LOADINGSCREENS\DynamicElements]], imageSize, imageSize)
isLeaderTexture:SetTexCoord(0, 0.5, 0, 0.5)
isLeaderTexture:SetPoint("topleft", detailsLogo, "topleft", 0, -60)
local isLeaderText = DetailsFramework:CreateLabel(welcomePanel, "In raid and all members are in the same guild.")
isLeaderText:SetPoint("left", isLeaderTexture, "right", 10, 0)
local isOutsideTexture = DetailsFramework:CreateImage(welcomePanel, [[Interface\GLUES\LOADINGSCREENS\DynamicElements]], imageSize, imageSize)
isOutsideTexture:SetTexCoord(0, 0.5, 0, 0.5)
isOutsideTexture:SetPoint("topleft", isLeaderTexture, "bottomleft", 0, -5)
local isOutsideText = DetailsFramework:CreateLabel(welcomePanel, "You're outside of the instance.")
isOutsideText:SetPoint("left", isOutsideTexture, "right", 10, 0)
local hasAssistantsTexture = DetailsFramework:CreateImage(welcomePanel, [[Interface\GLUES\LOADINGSCREENS\DynamicElements]], imageSize, imageSize)
hasAssistantsTexture:SetTexCoord(0, 0.5, 0, 0.5)
hasAssistantsTexture:SetPoint("topleft", isOutsideTexture, "bottomleft", 0, -5)
local hasAssistantsText = DetailsFramework:CreateLabel(welcomePanel, "There's an 'raid assistant' inside the raid.")
hasAssistantsText:SetPoint("left", hasAssistantsTexture, "right", 10, 0)
local beInGroupSevenTexture = DetailsFramework:CreateImage(welcomePanel, [[Interface\GLUES\LOADINGSCREENS\DynamicElements]], imageSize, imageSize)
beInGroupSevenTexture:SetTexCoord(0, 0.5, 0, 0.5)
beInGroupSevenTexture:SetPoint("topleft", hasAssistantsTexture, "bottomleft", 0, -5)
local beInGroupSevenText = DetailsFramework:CreateLabel(welcomePanel, "Stay in group 7 or 8.")
beInGroupSevenText:SetPoint("left", beInGroupSevenTexture, "right", 10, 0)
local allUpdatedTexture = DetailsFramework:CreateImage(welcomePanel, [[Interface\GLUES\LOADINGSCREENS\DynamicElements]], imageSize, imageSize)
allUpdatedTexture:SetTexCoord(0, 0.5, 0, 0.5)
allUpdatedTexture:SetPoint("topleft", beInGroupSevenTexture, "bottomleft", 0, -5)
local allUpdatedText = DetailsFramework:CreateLabel(welcomePanel, "Users with updated Details!.")
allUpdatedText:SetPoint("left", allUpdatedTexture, "right", 10, 0)
local startCoachButton = DetailsFramework:CreateButton(welcomePanel, function()
Details.coach.enabled = true
Details.Coach.Server.EnableCoach()
welcomePanel:Hide()
Details:Msg("welcome aboard commander!")
end, 80, 20, "Start Coaching!")
startCoachButton:SetPoint("bottomright", welcomePanel, "bottomright", -10, 10)
startCoachButton:SetTemplate(DetailsFramework:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE"))
function welcomePanel.Update()
local good = 0
local numRaidMembers = GetNumGroupMembers()
local playerName = UnitName("player")
local sameGuildAmount = 0
local guildName = GetGuildInfo("player")
for i = 1, numRaidMembers do
local unitId = "raid" .. i
if (guildName == GetGuildInfo(unitId)) then
sameGuildAmount = sameGuildAmount + 1
end
end
if (IsInRaid()) then -- and numRaidMembers == sameGuildAmount
isLeaderTexture:SetTexture([[Interface\COMMON\Indicator-Green]])
isLeaderTexture:SetTexCoord(0, 1, 0, 1)
good = good + 1
else
isLeaderTexture:SetTexture([[Interface\GLUES\LOADINGSCREENS\DynamicElements]])
isLeaderTexture:SetTexCoord(0, 0.5, 0, 0.5)
end
if (not IsInInstance()) then
isOutsideTexture:SetTexture([[Interface\COMMON\Indicator-Green]])
isOutsideTexture:SetTexCoord(0, 1, 0, 1)
good = good + 1
else
isOutsideTexture:SetTexture([[Interface\GLUES\LOADINGSCREENS\DynamicElements]])
isOutsideTexture:SetTexCoord(0, 0.5, 0, 0.5)
end
local hasAssistant = false
for i = 1, numRaidMembers do
local name, rank = GetRaidRosterInfo(i)
if (rank > 0 and name ~= UnitName("player")) then
hasAssistant = true
break
end
end
if (hasAssistant) then
hasAssistantsTexture:SetTexture([[Interface\COMMON\Indicator-Green]])
hasAssistantsTexture:SetTexCoord(0, 1, 0, 1)
good = good + 1
else
hasAssistantsTexture:SetTexture([[Interface\GLUES\LOADINGSCREENS\DynamicElements]])
hasAssistantsTexture:SetTexCoord(0, 0.5, 0, 0.5)
end
local isInCorrectGroup = true --debug
for i = 1, numRaidMembers do
local name, rank, subgroup, level, class, fileName, zone, online, isDead, role, isML = GetRaidRosterInfo(i)
if (name == playerName) then
if (subgroup == 7 or subgroup == 8) then
isInCorrectGroup = true
break
end
end
end
if (isInCorrectGroup) then
beInGroupSevenTexture:SetTexture([[Interface\COMMON\Indicator-Green]])
beInGroupSevenTexture:SetTexCoord(0, 1, 0, 1)
good = good + 1
else
beInGroupSevenTexture:SetTexture([[Interface\GLUES\LOADINGSCREENS\DynamicElements]])
beInGroupSevenTexture:SetTexCoord(0, 0.5, 0, 0.5)
end
local allUsersUpdated = false
local numRaidMembers = numRaidMembers
local updatedUsers = 0
local usersChecked = {}
for i = 1, #Details.users do
local thisUser = Details.users[i]
local userName = thisUser[1]
if (not usersChecked[userName]) then
local version = thisUser[3]
local buildCounter = version:match("%w%d%.%d%.%d%.(%d+)")
buildCounter = tonumber(buildCounter)
if (buildCounter and buildCounter >= Details.build_counter) then
updatedUsers = updatedUsers + 1
end
usersChecked[userName] = true
end
end
if (updatedUsers >= numRaidMembers) then
allUsersUpdated = true
end
allUsersUpdated = true
if (allUsersUpdated) then
allUpdatedTexture:SetTexture([[Interface\COMMON\Indicator-Green]])
allUpdatedTexture:SetTexCoord(0, 1, 0, 1)
good = good + 1
else
allUpdatedTexture:SetTexture([[Interface\GLUES\LOADINGSCREENS\DynamicElements]])
allUpdatedTexture:SetTexCoord(0, 0.5, 0, 0.5)
end
if (good == 5) then
startCoachButton:Enable()
else
startCoachButton:Disable()
end
end
end
Details.SendHighFive()
local nextHighFive = 10
local nextUpdate = 1
welcomePanel:SetScript("OnUpdate", function(self, deltaTime)
nextHighFive = nextHighFive - deltaTime
nextUpdate = nextUpdate - deltaTime
if (nextHighFive < 0) then
Details.SendHighFive()
nextHighFive = 10
end
if (nextUpdate < 0) then
welcomePanel:Update()
nextUpdate = 1
end
end)
welcomePanel:Show()
end
+167
View File
@@ -0,0 +1,167 @@
local Details = _G.Details
local addonName, Details222 = ...
--namespace
Details.CurrentDps = {}
local bIsEnabled = true
---@type combat
local currentCombatObject = nil
---@class details_currentdps_actorcache
---@field totalDamage number
---@field latestDamageAmount number
---@field cache number[]
---@type table<serial, details_currentdps_actorcache>
local currentDPSCache = Details222.CurrentDPS.Cache
---create a new cache table
---@return details_currentdps_actorcache
local createDpsCacheTable = function()
---@type details_currentdps_actorcache
local cache = {
totalDamage = 0,
latestDamageAmount = 0,
cache = {},
}
return cache
end
---get the actor cache from the current dps table
---@param serial serial
---@return details_currentdps_actorcache
local getActorDpsCache = function(serial)
local dpsCache = currentDPSCache[serial]
if (not dpsCache) then
dpsCache = createDpsCacheTable()
currentDPSCache[serial] = dpsCache
end
return dpsCache
end
---@type frame
local currentDpsFrame = CreateFrame("frame", "DetailsCurrentDpsUpdaterFrame", UIParent)
--amount of time to wait between each sample collection
local delayTimeBetweenUpdates = 0.10
--sample size in time to use to calculate the current dps
local secondsOfData = 5
--amount of time to wait until next update
local currentDelay = 0
--amount of small ticks that will be stored in the cache
local cacheSize = secondsOfData / delayTimeBetweenUpdates
--the index of the cache that will be removed when the cache is full
local cacheOverflowIndex = cacheSize + 1
---return how many seconds of data is being used to calculate the current dps
---@return number
function Details222.CurrentDPS.GetTimeSample()
return secondsOfData
end
---on tick function
---@param self frame
---@param deltaTime number time elapsed between frames
currentDpsFrame.OnUpdateFunc = function(self, deltaTime)
currentDelay = currentDelay + deltaTime
if (currentDelay < delayTimeBetweenUpdates) then
return
end
---@type actorcontainer
local damageContainer = currentCombatObject:GetContainer(DETAILS_ATTRIBUTE_DAMAGE)
for index, actorObject in damageContainer:ListActors() do
---@cast actorObject actor
if (actorObject.grupo) then
---@type details_currentdps_actorcache
local dpsCache = getActorDpsCache(actorObject.serial)
--get the damage done on this tick
local totalDamageThisTick = actorObject.total - dpsCache.latestDamageAmount
--add the damage to the cache
table.insert(dpsCache.cache, 1, totalDamageThisTick)
--set the latest damage amount
dpsCache.latestDamageAmount = actorObject.total
--sum the total damage the actor inflicted
dpsCache.totalDamage = dpsCache.totalDamage + totalDamageThisTick
--cut the damage
local damageRemoved = table.remove(dpsCache.cache, cacheOverflowIndex)
if (damageRemoved) then
dpsCache.totalDamage = dpsCache.totalDamage - damageRemoved
dpsCache.totalDamage = math.max(0, dpsCache.totalDamage) --safe guard
end
end
end
currentDelay = 0
end
---return the value of the current dps for the given player
---serial = guid
---@param serial guid
---@return number|nil
function Details.CurrentDps.GetCurrentDps(serial)
local dpsCache = currentDPSCache[serial]
if (dpsCache) then
local dps = dpsCache.totalDamage / secondsOfData
return math.floor(dps)
end
end
---return if the window bars can be sorted by real time dps
---@return boolean
function Details.CurrentDps.CanSortByRealTimeDps()
if (not Details.in_combat) then
return false
end
local bOrderDpsByRealTime = Details.use_realtimedps and Details.realtimedps_order_bars
if (not bOrderDpsByRealTime) then
bOrderDpsByRealTime = Details.realtimedps_always_arena and Details.zone_type == "arena"
end
return bOrderDpsByRealTime
end
--start the proccess of updating the current dps and hps for each player
function Details.CurrentDps.StartCurrentDpsTracker()
currentCombatObject = Details:GetCurrentCombat()
Details:Destroy(currentDPSCache)
currentDpsFrame:SetScript("OnUpdate", currentDpsFrame.OnUpdateFunc)
end
--stop what the function above started
function Details.CurrentDps.StopCurrentDpsTracker()
currentDpsFrame:SetScript("OnUpdate", nil)
end
--handle internal details! events
local eventListener = Details:CreateEventListener()
eventListener:RegisterEvent("COMBAT_PLAYER_ENTER", function()
--check if can start the real time dps tracker
local bCanStartRealTimeDpsTracker = Details.use_realtimedps
if (not bCanStartRealTimeDpsTracker) then
bCanStartRealTimeDpsTracker = Details.zone_type == "arena" and Details.realtimedps_always_arena
end
if (bCanStartRealTimeDpsTracker) then
Details.CurrentDps.StartCurrentDpsTracker()
end
end)
eventListener:RegisterEvent("COMBAT_PLAYER_LEAVE", function()
Details.CurrentDps.StopCurrentDpsTracker()
end)
+305
View File
@@ -0,0 +1,305 @@
local Details = _G.Details
local addonName, Details222 = ...
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--extra buttons at the death options (release, death recap)
local detailsOnDeathMenu = CreateFrame("frame", "DetailsOnDeathMenu", UIParent, "BackdropTemplate")
detailsOnDeathMenu:SetHeight(30)
detailsOnDeathMenu.Debug = false
detailsOnDeathMenu:RegisterEvent("PLAYER_REGEN_ENABLED")
detailsOnDeathMenu:RegisterEvent("ENCOUNTER_END")
DetailsFramework:ApplyStandardBackdrop(detailsOnDeathMenu)
detailsOnDeathMenu:SetAlpha(0.75)
--disable text
detailsOnDeathMenu.disableLabel = Details.gump:CreateLabel(detailsOnDeathMenu, "you can disable this at /details > Raid Tools", 9)
detailsOnDeathMenu.warningLabel = Details.gump:CreateLabel(detailsOnDeathMenu, "", 11)
detailsOnDeathMenu.warningLabel.textcolor = "red"
detailsOnDeathMenu.warningLabel:SetPoint("bottomleft", detailsOnDeathMenu, "bottomleft", 5, 2)
detailsOnDeathMenu.warningLabel:Hide()
detailsOnDeathMenu:SetScript("OnEvent", function(self, event, ...)
if (event == "ENCOUNTER_END") then
C_Timer.After(0.5, detailsOnDeathMenu.ShowPanel)
end
end)
function detailsOnDeathMenu.OpenEncounterBreakdown()
if (not Details:GetPlugin ("DETAILS_PLUGIN_ENCOUNTER_DETAILS")) then
detailsOnDeathMenu.warningLabel.text = "Encounter Breakdown plugin is disabled! Please enable it in the Addon Control Panel."
detailsOnDeathMenu.warningLabel:Show()
C_Timer.After(5, function()
detailsOnDeathMenu.warningLabel:Hide()
end)
end
Details:OpenPlugin ("Encounter Breakdown")
GameCooltip2:Hide()
end
function detailsOnDeathMenu.OpenPlayerEndurance()
if (not Details:GetPlugin ("DETAILS_PLUGIN_DEATH_GRAPHICS")) then
detailsOnDeathMenu.warningLabel.text = "Advanced Death Logs plugin is disabled! Please enable it (or download) in the Addon Control Panel."
detailsOnDeathMenu.warningLabel:Show()
C_Timer.After(5, function()
detailsOnDeathMenu.warningLabel:Hide()
end)
end
DetailsPluginContainerWindow.OnMenuClick (nil, nil, "DETAILS_PLUGIN_DEATH_GRAPHICS", true)
C_Timer.After(0, function()
local a = Details_DeathGraphsModeEnduranceButton and Details_DeathGraphsModeEnduranceButton.MyObject:Click()
end)
GameCooltip2:Hide()
end
function detailsOnDeathMenu.OpenPlayerSpells()
local window1 = Details:GetWindow (1)
local window2 = Details:GetWindow (2)
local window3 = Details:GetWindow (3)
local window4 = Details:GetWindow (4)
local assignedRole = UnitGroupRolesAssigned("player")
if (assignedRole == "HEALER") then
if (window1 and window1:GetDisplay() == 2) then
Details:OpenPlayerDetails(1)
elseif (window2 and window2:GetDisplay() == 2) then
Details:OpenPlayerDetails(2)
elseif (window3 and window3:GetDisplay() == 2) then
Details:OpenPlayerDetails(3)
elseif (window4 and window4:GetDisplay() == 2) then
Details:OpenPlayerDetails(4)
else
Details:OpenPlayerDetails (1)
end
else
if (window1 and window1:GetDisplay() == 1) then
Details:OpenPlayerDetails(1)
elseif (window2 and window2:GetDisplay() == 1) then
Details:OpenPlayerDetails(2)
elseif (window3 and window3:GetDisplay() == 1) then
Details:OpenPlayerDetails(3)
elseif (window4 and window4:GetDisplay() == 1) then
Details:OpenPlayerDetails(4)
else
Details:OpenPlayerDetails (1)
end
end
GameCooltip2:Hide()
end
--encounter breakdown button
detailsOnDeathMenu.breakdownButton = Details.gump:CreateButton(detailsOnDeathMenu, detailsOnDeathMenu.OpenEncounterBreakdown, 120, 20, "Encounter Breakdown", "breakdownButton")
detailsOnDeathMenu.breakdownButton:SetTemplate(Details.gump:GetTemplate("button", "DETAILS_PLUGINPANEL_BUTTON_TEMPLATE"))
detailsOnDeathMenu.breakdownButton:SetPoint("topleft", detailsOnDeathMenu, "topleft", 5, -5)
detailsOnDeathMenu.breakdownButton:Hide()
detailsOnDeathMenu.breakdownButton.CoolTip = {
Type = "tooltip",
BuildFunc = function()
GameCooltip2:Preset(2)
GameCooltip2:AddLine("Show a panel with:")
GameCooltip2:AddLine("- Player Damage Taken")
GameCooltip2:AddLine("- Damage Taken by Spell")
GameCooltip2:AddLine("- Enemy Damage Taken")
GameCooltip2:AddLine("- Player Deaths")
GameCooltip2:AddLine("- Interrupts and Dispels")
GameCooltip2:AddLine("- Damage Done Chart")
GameCooltip2:AddLine("- Damage Per Phase")
GameCooltip2:AddLine("- Weakauras Tool")
if (not Details:GetPlugin ("DETAILS_PLUGIN_ENCOUNTER_DETAILS")) then
GameCooltip2:AddLine("Encounter Breakdown plugin is disabled in the Addon Control Panel.", "", 1, "red")
end
end, --called when user mouse over the frame
OnEnterFunc = function(self)
detailsOnDeathMenu.button_mouse_over = true
end,
OnLeaveFunc = function(self)
detailsOnDeathMenu.button_mouse_over = false
end,
FixedValue = "none",
ShowSpeed = .5,
Options = function()
GameCooltip:SetOption("MyAnchor", "top")
GameCooltip:SetOption("RelativeAnchor", "bottom")
GameCooltip:SetOption("WidthAnchorMod", 0)
GameCooltip:SetOption("HeightAnchorMod", -13)
GameCooltip:SetOption("TextSize", 10)
GameCooltip:SetOption("FixedWidth", 220)
end
}
GameCooltip2:CoolTipInject (detailsOnDeathMenu.breakdownButton)
--player endurance button
detailsOnDeathMenu.enduranceButton = Details.gump:CreateButton(detailsOnDeathMenu, detailsOnDeathMenu.OpenPlayerEndurance, 120, 20, "Player Endurance", "enduranceButton")
detailsOnDeathMenu.enduranceButton:SetTemplate(Details.gump:GetTemplate("button", "DETAILS_PLUGINPANEL_BUTTON_TEMPLATE"))
detailsOnDeathMenu.enduranceButton:SetPoint("topleft", detailsOnDeathMenu.breakdownButton, "topright", 2, 0)
detailsOnDeathMenu.enduranceButton:Hide()
detailsOnDeathMenu.enduranceButton.CoolTip = {
Type = "tooltip",
BuildFunc = function()
GameCooltip2:Preset(2)
GameCooltip2:AddLine("Open Player Endurance Breakdown")
GameCooltip2:AddLine("")
GameCooltip2:AddLine("Player endurance is calculated using the amount of player deaths.")
GameCooltip2:AddLine("By default the plugin register the three first player deaths on each encounter to calculate who is under performing.")
--GameCooltip2:AddLine(" ")
if (not Details:GetPlugin ("DETAILS_PLUGIN_DEATH_GRAPHICS")) then
GameCooltip2:AddLine("Advanced Death Logs plugin is disabled or not installed, check the Addon Control Panel or download it from the Curseforge APP.", "", 1, "red")
end
end, --called when user mouse over the frame
OnEnterFunc = function(self)
detailsOnDeathMenu.button_mouse_over = true
end,
OnLeaveFunc = function(self)
detailsOnDeathMenu.button_mouse_over = false
end,
FixedValue = "none",
ShowSpeed = .5,
Options = function()
GameCooltip:SetOption("MyAnchor", "top")
GameCooltip:SetOption("RelativeAnchor", "bottom")
GameCooltip:SetOption("WidthAnchorMod", 0)
GameCooltip:SetOption("HeightAnchorMod", -13)
GameCooltip:SetOption("TextSize", 10)
GameCooltip:SetOption("FixedWidth", 220)
end
}
GameCooltip2:CoolTipInject (detailsOnDeathMenu.enduranceButton)
--spells
detailsOnDeathMenu.spellsButton = Details.gump:CreateButton(detailsOnDeathMenu, detailsOnDeathMenu.OpenPlayerSpells, 48, 20, "Spells", "SpellsButton")
detailsOnDeathMenu.spellsButton:SetTemplate(Details.gump:GetTemplate("button", "DETAILS_PLUGINPANEL_BUTTON_TEMPLATE"))
detailsOnDeathMenu.spellsButton:SetPoint("topleft", detailsOnDeathMenu.enduranceButton, "topright", 2, 0)
detailsOnDeathMenu.spellsButton:Hide()
detailsOnDeathMenu.spellsButton.CoolTip = {
Type = "tooltip",
BuildFunc = function()
GameCooltip2:Preset(2)
GameCooltip2:AddLine("Open your player Details! breakdown.")
end, --called when user mouse over the frame
OnEnterFunc = function(self)
detailsOnDeathMenu.button_mouse_over = true
end,
OnLeaveFunc = function(self)
detailsOnDeathMenu.button_mouse_over = false
end,
FixedValue = "none",
ShowSpeed = .5,
Options = function()
GameCooltip:SetOption("MyAnchor", "top")
GameCooltip:SetOption("RelativeAnchor", "bottom")
GameCooltip:SetOption("WidthAnchorMod", 0)
GameCooltip:SetOption("HeightAnchorMod", -13)
GameCooltip:SetOption("TextSize", 10)
GameCooltip:SetOption("FixedWidth", 220)
end
}
GameCooltip2:CoolTipInject (detailsOnDeathMenu.spellsButton)
function detailsOnDeathMenu.CanShowPanel()
if (StaticPopup_Visible ("DEATH")) then
if (not Details.on_death_menu) then
return
end
if (detailsOnDeathMenu.Debug) then
return true
end
--check if the player just wiped in an encounter
if (IsInRaid()) then
local isInInstance = IsInInstance()
if (isInInstance) then
--check if all players in the raid are out of combat
for i = 1, GetNumGroupMembers() do
if (UnitAffectingCombat("raid" .. i)) then
C_Timer.After(0.5, detailsOnDeathMenu.ShowPanel)
return false
end
end
if (Details.in_combat) then
C_Timer.After(0.5, detailsOnDeathMenu.ShowPanel)
return false
end
return true
end
end
end
end
function detailsOnDeathMenu.ShowPanel()
if (not detailsOnDeathMenu.CanShowPanel()) then
return
end
if (ElvUI) then
detailsOnDeathMenu:SetPoint("topleft", StaticPopup1, "bottomleft", 0, -1)
detailsOnDeathMenu:SetPoint("topright", StaticPopup1, "bottomright", 0, -1)
else
detailsOnDeathMenu:SetPoint("topleft", StaticPopup1, "bottomleft", 4, 2)
detailsOnDeathMenu:SetPoint("topright", StaticPopup1, "bottomright", -4, 2)
end
detailsOnDeathMenu.breakdownButton:Show()
detailsOnDeathMenu.enduranceButton:Show()
detailsOnDeathMenu.spellsButton:Show()
detailsOnDeathMenu:Show()
detailsOnDeathMenu:SetHeight(30)
if (not Details:GetTutorialCVar("DISABLE_ONDEATH_PANEL")) then
detailsOnDeathMenu.disableLabel:Show()
detailsOnDeathMenu.disableLabel:SetPoint("bottomleft", detailsOnDeathMenu, "bottomleft", 5, 1)
detailsOnDeathMenu.disableLabel.color = "gray"
detailsOnDeathMenu.disableLabel.alpha = 0.5
detailsOnDeathMenu:SetHeight(detailsOnDeathMenu:GetHeight() + 10)
if (math.random(1, 3) == 3) then
Details:SetTutorialCVar ("DISABLE_ONDEATH_PANEL", true)
end
end
end
hooksecurefunc ("StaticPopup_Show", function(which, text_arg1, text_arg2, data, insertedFrame)
if (which == "DEATH") then
if (detailsOnDeathMenu.Debug) then
C_Timer.After(0.5, detailsOnDeathMenu.ShowPanel)
end
end
end)
hooksecurefunc ("StaticPopup_Hide", function(which, data)
if (which == "DEATH") then
detailsOnDeathMenu:Hide()
end
end)
+19
View File
@@ -0,0 +1,19 @@
local Details = _G.Details
local AceLocale = LibStub("AceLocale-3.0")
local L = AceLocale:GetLocale("Details")
local addonName, Details222 = ...
function Details.RegisterDragonFlightEditMode()
if (EventRegistry and type(EventRegistry) == "table") then
local onEnterEditMode = function()
end
local onLeaveEditMode = function()
end
EventRegistry:RegisterCallback("EditMode.Enter", onEnterEditMode)
EventRegistry:RegisterCallback("EditMode.Edit", onLeaveEditMode)
end
end
+258
View File
@@ -0,0 +1,258 @@
local Details = _G.Details
local addonName, Details222 = ...
local ipairs = ipairs --lua local
local detailsFramework = DetailsFramework
local ejTable = Details222.EncounterJournalDump
--maybe also cache old expansions and perhaps current expansion dungeons that aren't in the current mythic+ season
---@class details_encounterinfo : table
---@field name string
---@field mapId number
---@field instanceId number
---@field dungeonEncounterId number
---@field journalEncounterId number
---@field journalInstanceId number
---@field creatureName string
---@field creatureIcon string
---@field creatureId number
---@field creatureDisplayId number
---@field creatureUIModelSceneId number
---@class details_instanceinfo : table
---@field name string
---@field bgImage string
---@field mapId number
---@field instanceId number
---@field journalInstanceId number
---@field encountersArray details_encounterinfo[]
---@field encountersByName table<string, details_encounterinfo>
---@field encountersByDungeonEncounterId table<number, details_encounterinfo>
---@field encountersByJournalEncounterId table<number, details_encounterinfo>
---@field icon string
---@field iconSize table<number, number>
---@field iconCoords table<number, number, number, number>
---@field iconLore string
---@field iconLoreSize table<number, number>
---@field iconLoreCoords table<number, number, number, number>
---@field iconTexture string
---@field iconTextureSize table<number, number>
---@field iconTextureCoords table<number, number, number, number>
---@return details_encounterinfo?
function Details:GetEncounterInfo(id)
---@type details_encounterinfo
local encounterData = Details222.EJCache.CacheEncountersBy_EncounterId[id]
if (encounterData) then
return encounterData
end
encounterData = Details222.EJCache.CacheEncountersBy_EncounterName[id]
if (encounterData) then
return encounterData
end
encounterData = Details222.EJCache.CacheEncountersBy_JournalEncounterId[id]
if (encounterData) then
return encounterData
end
end
---@param id instanceid|instancename|mapid
---@return details_instanceinfo?
function Details:GetInstanceInfo(id)
if (id == 463) then --fall
id = 1209
end
---@type details_instanceinfo
local instanceData = Details222.EJCache.CacheRaidData_ByInstanceId[id]
if (instanceData) then
return instanceData
end
instanceData = Details222.EJCache.CacheRaidData_ByInstanceName[id]
if (instanceData) then
return instanceData
end
instanceData = Details222.EJCache.CacheRaidData_ByMapId[id]
if (instanceData) then
return instanceData
end
end
function Details:DumpInstanceInfo()
dumpt(Details222.EJCache.CacheRaidData_ByInstanceId)
end
function Details:GetInstanceEJID(...)
for i = 1, select("#", ...) do
local id = select(i, ...)
local EJID = Details222.EJCache.Id_To_JournalInstanceID[id]
if (EJID) then
return EJID
end
end
end
function Details222.EJCache.CreateEncounterJournalDump()
Details222.EJCache.CacheRaidData_ByInstanceId = {}
Details222.EJCache.CacheRaidData_ByInstanceName = {} --this is localized name
Details222.EJCache.CacheRaidData_ByMapId = {} --retrivied from GetInstanceInfo()
Details222.EJCache.CacheEncountersByEncounterName = {}
Details222.EJCache.CacheEncountersBy_EncounterName = {}
Details222.EJCache.CacheEncountersBy_EncounterId = {}
Details222.EJCache.CacheEncountersBy_JournalEncounterId = {}
---cahe the uiMapID pointing to the instanceID
---this replace the need to call EJ_GetInstanceForMap to get the journalInstanceID
---@type table
local id_to_journalInstanceID = {}
Details222.EJCache.Id_To_JournalInstanceID = id_to_journalInstanceID
--if the expansion does not support the encounter journal, then return
if (not EncounterJournal_LoadUI) then
return
end
--if true then return end
local ejCacheSaved = Details.encounter_journal_cache
local data = {}
---returns the number of valid encounter journal tier indices
---@type number
local tierAmount = EJ_GetNumTiers() --return 11 for dragonisles
---returns the currently active encounter journal tier index
---could also be tierAmount - 1
---bacause the tier is "current season"
---@type number
local currentTierId = tierAmount --EJ_GetCurrentTier(), for some unknown reason, this function is returning 3 on retail
---maximum amount of dungeons in the expansion
---@type number
local maxAmountOfDungeons = 20
---the index of the first raid tier in the expansion, ignoring the first tier as it is open world bosses
---@type number
local raidTierStartIndex = 2
---max amount of bosses which a raid tier can have
---@type number
local maxRaidBosses = 20
---two iterations are required, one for dungeons and another for raids
---this table store two booleans that are passed to EJ_GetInstanceByIndex second argument, to indicate if we want to get dungeons or raids
local tGetDungeonsOrRaids = {false, true}
do --get raid instances data
for i = 1, #tGetDungeonsOrRaids do
local bIsRaid = tGetDungeonsOrRaids[i]
--select the tier, use current tier - 1 for raids, as the currentTier only shows the latest release raid
--use current tier for dungeons, as the current tier shows the dungeons used for the current season of Mythic+
local startIndex, endIndex
if (bIsRaid) then
EJ_SelectTier(currentTierId - 1) --print("tier selected:", currentTierId - 1, "raids") --debug
startIndex = raidTierStartIndex
endIndex = 20
else
EJ_SelectTier(currentTierId) --print("tier selected:", currentTierId, "dungeons", "currentTierId:", currentTierId) --debug
startIndex = 1
endIndex = maxAmountOfDungeons
end
for instanceIndex = endIndex, startIndex, -1 do
--instanceID: number - the unique ID of the instance, also returned by GetInstanceInfo() 8th return value
--journalInstanceID: number - the ID used by the Encounter Journal API
--dungeonUiMapID: number - the ID used by the world map API
--dungeonEncounterID: number - same ID passed by the ENCOUNTER_STAR and ENCOUNTER_END events
local journalInstanceID, instanceName, description, bgImage, buttonImage1, loreImage, buttonImage2, dungeonUiMapID, journalLink, shouldDisplayDifficulty, instanceID = EJ_GetInstanceByIndex(instanceIndex, bIsRaid)
if (journalInstanceID) then
id_to_journalInstanceID[dungeonUiMapID] = journalInstanceID
id_to_journalInstanceID[instanceName] = journalInstanceID
id_to_journalInstanceID[instanceID] = journalInstanceID
--select the raid instance, this allow to retrieve data about the encounters of the instance
EJ_SelectInstance(journalInstanceID)
--build a table with data of the raid instance
local instanceData = {
name = instanceName,
bgImage = bgImage,
mapId = dungeonUiMapID,
instanceId = instanceID,
journalInstanceId = journalInstanceID,
encountersArray = {},
encountersByName = {},
encountersByDungeonEncounterId = {},
encountersByJournalEncounterId = {},
icon = buttonImage1,
iconSize = {70, 36},
iconCoords = {0.01, .67, 0.025, .725},
iconLore = loreImage,
iconLoreSize = {70, 36},
iconLoreCoords = {0, 1, 0, 0.95},
iconTexture = buttonImage2,
iconTextureSize = {70, 36},
iconTextureCoords = {0, 1, 0, 0.95},
}
--cache the raidData, in different tables, using different keys
Details222.EJCache.CacheRaidData_ByInstanceId[journalInstanceID] = instanceData
Details222.EJCache.CacheRaidData_ByInstanceId[instanceID] = instanceData
Details222.EJCache.CacheRaidData_ByInstanceName[instanceName] = instanceData
Details222.EJCache.CacheRaidData_ByMapId[dungeonUiMapID] = instanceData
--get information about the bosses in the raid
for encounterIndex = 1, maxRaidBosses do
local encounterName, encounterDescription, journalEncounterID, rootSectionID, link, journalInstanceID, dungeonEncounterID, instanceID = EJ_GetEncounterInfoByIndex(encounterIndex, journalInstanceID)
if (encounterName) then
local encounterData = {
name = encounterName,
mapId = dungeonUiMapID,
instanceId = instanceID,
dungeonEncounterId = dungeonEncounterID,
journalEncounterId = journalEncounterID,
journalInstanceId = journalInstanceID,
}
local journalEncounterCreatureId, creatureName, creatureDescription, creatureDisplayID, iconImage, uiModelSceneID = EJ_GetCreatureInfo(1, journalEncounterID)
if (journalEncounterCreatureId) then
encounterData.creatureName = creatureName
encounterData.creatureIcon = iconImage
encounterData.creatureId = journalEncounterCreatureId
encounterData.creatureDisplayId = creatureDisplayID
encounterData.creatureUIModelSceneId = uiModelSceneID
end
instanceData.encountersArray[#instanceData.encountersArray+1] = encounterData
instanceData.encountersByName[encounterName] = encounterData
--print(instanceName, encounterName, journalEncounterID, journalInstanceID, dungeonEncounterID, instanceID)
instanceData.encountersByDungeonEncounterId[dungeonEncounterID] = encounterData
instanceData.encountersByJournalEncounterId[journalEncounterID] = encounterData
Details222.EJCache.CacheEncountersBy_EncounterName[encounterName] = encounterData
Details222.EJCache.CacheEncountersBy_EncounterId[dungeonEncounterID] = encounterData
Details222.EJCache.CacheEncountersBy_JournalEncounterId[journalEncounterID] = encounterData
id_to_journalInstanceID[encounterName] = journalInstanceID
id_to_journalInstanceID[dungeonEncounterID] = journalInstanceID
id_to_journalInstanceID[journalEncounterID] = journalInstanceID
end
end
end
end
end --end loop of raid or dungeon
end
end
+396
View File
@@ -0,0 +1,396 @@
local Details = _G.Details
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local _
local addonName, Details222 = ...
--Event types:
Details.RegistredEvents = {
--instances
["DETAILS_STARTED"] = {},
["DETAILS_INSTANCE_OPEN"] = {},
["DETAILS_INSTANCE_CLOSE"] = {},
["DETAILS_INSTANCE_SIZECHANGED"] = {},
["DETAILS_INSTANCE_STARTRESIZE"] = {},
["DETAILS_INSTANCE_ENDRESIZE"] = {},
["DETAILS_INSTANCE_STARTSTRETCH"] = {},
["DETAILS_INSTANCE_ENDSTRETCH"] = {},
["DETAILS_INSTANCE_CHANGESEGMENT"] = {},
["DETAILS_INSTANCE_CHANGEATTRIBUTE"] = {},
["DETAILS_INSTANCE_CHANGEMODE"] = {},
["DETAILS_INSTANCE_NEWROW"] = {},
--misc
["DETAILS_OPTIONS_MODIFIED"] = {},
["UNIT_SPEC"] = {},
["UNIT_TALENTS"] = {},
["PLAYER_TARGET"] = {},
["DETAILS_PROFILE_APPLYED"] = {},
--data
["DETAILS_DATA_RESET"] = {},
["DETAILS_DATA_SEGMENTREMOVED"] = {},
--combat
["COMBAT_ENCOUNTER_START"] = {},
["COMBAT_ENCOUNTER_END"] = {},
["COMBAT_PLAYER_ENTER"] = {},
["COMBAT_PLAYER_LEAVE"] = {},
["COMBAT_PLAYER_LEAVING"] = {},
["COMBAT_PLAYER_TIMESTARTED"] = {},
["COMBAT_BOSS_WIPE"] = {},
["COMBAT_BOSS_DEFEATED"] = {},
["COMBAT_BOSS_FOUND"] = {},
["COMBAT_INVALID"] = {},
["COMBAT_PREPOTION_UPDATED"] = {},
["COMBAT_CHARTTABLES_CREATING"] = {},
["COMBAT_CHARTTABLES_CREATED"] = {},
["COMBAT_ENCOUNTER_PHASE_CHANGED"] = {},
["COMBAT_ARENA_START"] = {},
["COMBAT_ARENA_END"] = {},
["COMBAT_MYTHICDUNGEON_START"] = {},
["COMBAT_MYTHICDUNGEON_END"] = {},
["COMBAT_MYTHICPLUS_OVERALL_READY"] = {},
--area
["ZONE_TYPE_CHANGED"] = {},
--roster
["GROUP_ONENTER"] = {},
["GROUP_ONLEAVE"] = {},
--buffs
["BUFF_UPDATE"] = {},
["BUFF_UPDATE_DEBUFFPOWER"] = {},
--network
["REALM_CHANNEL_ENTER"] = {}, --deprecated (realm channels are disabled)
["REALM_CHANNEL_LEAVE"] = {}, --deprecated
["COMM_EVENT_RECEIVED"] = {}, --added on core 129
["COMM_EVENT_SENT"] = {}, --added on core 129
}
local function isAlreadyRegistred(_tables, _object)
for index, _this_object in ipairs(_tables) do
if (_this_object.__eventtable) then
if (_this_object[1] == _object) then
return index
end
elseif (_this_object == _object) then
return index
end
end
return false
end
local common_events = {
["DETAILS_INSTANCE_OPEN"] = true,
["DETAILS_INSTANCE_CLOSE"] = true,
["DETAILS_INSTANCE_SIZECHANGED"] = true,
["DETAILS_INSTANCE_STARTRESIZE"] = true,
["DETAILS_INSTANCE_ENDRESIZE"] = true,
["DETAILS_INSTANCE_STARTSTRETCH"] = true,
["DETAILS_INSTANCE_ENDSTRETCH"] = true,
["DETAILS_INSTANCE_CHANGESEGMENT"] = true,
["DETAILS_INSTANCE_CHANGEATTRIBUTE"] = true,
["DETAILS_INSTANCE_CHANGEMODE"] = true,
["DETAILS_INSTANCE_NEWROW"] = true,
["DETAILS_OPTIONS_MODIFIED"] = true,
["DETAILS_DATA_RESET"] = true,
["DETAILS_DATA_SEGMENTREMOVED"] = true,
["COMBAT_ENCOUNTER_START"] = true,
["COMBAT_ENCOUNTER_END"] = true,
["COMBAT_PLAYER_ENTER"] = true,
["COMBAT_PLAYER_LEAVE"] = true,
["COMBAT_PLAYER_LEAVING"] = true,
["COMBAT_PLAYER_TIMESTARTED"] = true,
["COMBAT_BOSS_WIPE"] = true,
["COMBAT_BOSS_DEFEATED"] = true,
["COMBAT_BOSS_FOUND"] = true,
["COMBAT_INVALID"] = true,
["COMBAT_PREPOTION_UPDATED"] = true,
["COMBAT_CHARTTABLES_CREATING"] = true,
["COMBAT_CHARTTABLES_CREATED"] = true,
["COMBAT_ENCOUNTER_PHASE_CHANGED"] = true,
["COMBAT_ARENA_START"] = true,
["COMBAT_ARENA_END"] = true,
["COMBAT_MYTHICDUNGEON_START"] = true,
["COMBAT_MYTHICDUNGEON_END"] = true,
["COMBAT_MYTHICPLUS_OVERALL_READY"] = true,
["GROUP_ONENTER"] = true,
["GROUP_ONLEAVE"] = true,
["ZONE_TYPE_CHANGED"] = true,
["REALM_CHANNEL_ENTER"] = true,
["REALM_CHANNEL_LEAVE"] = true,
["COMM_EVENT_RECEIVED"] = true,
["COMM_EVENT_SENT"] = true,
["UNIT_SPEC"] = true,
["UNIT_TALENTS"] = true,
["PLAYER_TARGET"] = true,
["DETAILS_PROFILE_APPLYED"] = true,
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--register a event
function Details:RegisterEvent(object, event, func)
if (not Details.RegistredEvents[event]) then
if (object.Msg) then
object:DelayMsg("[debug] unknown event1: " .. (event or "no-event"))
else
Details:DelayMsg("[debug] unknown event2:", event, object.__name)
end
return
end
if (common_events[event]) then
if (not isAlreadyRegistred(Details.RegistredEvents[event], object)) then
if (func) then
table.insert(Details.RegistredEvents[event], {object, func, __eventtable = true})
else
table.insert(Details.RegistredEvents[event], object)
end
return true
else
return false
end
else
if (event == "BUFF_UPDATE") then
if (not isAlreadyRegistred(Details.RegistredEvents["BUFF_UPDATE"], object)) then
if (func) then
table.insert(Details.RegistredEvents["BUFF_UPDATE"], {object, func, __eventtable = true})
else
table.insert(Details.RegistredEvents["BUFF_UPDATE"], object)
end
Details.Buffs:CatchBuffs()
Details.RecordPlayerSelfBuffs = true
Details:UpdateParserGears()
return true
else
return false
end
elseif (event == "BUFF_UPDATE_DEBUFFPOWER") then
if (not isAlreadyRegistred(Details.RegistredEvents["BUFF_UPDATE_DEBUFFPOWER"], object)) then
if (func) then
table.insert(Details.RegistredEvents["BUFF_UPDATE_DEBUFFPOWER"], {object, func, __eventtable = true})
else
table.insert(Details.RegistredEvents["BUFF_UPDATE_DEBUFFPOWER"], object)
end
Details.RecordPlayerAbilityWithBuffs = true
Details:UpdateParserGears()
return true
else
return false
end
end
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Unregister a Event
function Details:UnregisterEvent(object, event)
if (not Details.RegistredEvents[event]) then
if (object.Msg) then
object:Msg("(debug) unknown event", event)
else
Details:Msg("(debug) unknown event", event)
end
return
end
if (common_events[event]) then
local index = isAlreadyRegistred(Details.RegistredEvents[event], object)
if (index) then
table.remove(Details.RegistredEvents[event], index)
return true
else
return false
end
else
if (event == "BUFF_UPDATE") then
local index = isAlreadyRegistred(Details.RegistredEvents["BUFF_UPDATE"], object)
if (index) then
table.remove(Details.RegistredEvents["BUFF_UPDATE"], index)
if (#Details.RegistredEvents["BUFF_UPDATE"] < 1) then
Details.RecordPlayerSelfBuffs = true
Details:UpdateParserGears()
end
return true
else
return false
end
elseif (event == "BUFF_UPDATE_DEBUFFPOWER") then
local index = isAlreadyRegistred(Details.RegistredEvents["BUFF_UPDATE_DEBUFFPOWER"], object)
if (index) then
table.remove(Details.RegistredEvents["BUFF_UPDATE_DEBUFFPOWER"], index)
if (#Details.RegistredEvents["BUFF_UPDATE_DEBUFFPOWER"] < 1) then
Details.RecordPlayerAbilityWithBuffs = false
Details:UpdateParserGears()
end
return true
else
return false
end
end
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--internal functions
local dispatch_error = function(name, errortext)
Details:Msg((name or "<no context>"), " |cFFFF9900error|r: ", errortext)
end
--safe call an external func with payload and without telling who is calling
function Details:QuickDispatchEvent(func, event, ...)
if (type(func) ~= "function") then
return
elseif (type(event) ~= "string") then
return
end
local okay, errortext = pcall(func, event, ...)
if (not okay) then
--trigger an error msg
dispatch_error(_, errortext)
return
end
return true
end
--quick dispatch with context, send the caller object within the payload
function Details:QuickDispatchEventWithContext(context, func, event, ...)
if (type(context) ~= "table") then
return
elseif (type(func) ~= "function") then
return
elseif (type(event) ~= "string") then
return
end
local okay, errortext = pcall(func, context, event, ...)
if (not okay) then
--attempt to get the context name
local objectName = context.__name or context._name or context.name or context.Name
--trigger an error msg
dispatch_error(objectName, errortext)
return
end
return true
end
--Send Event
function Details:SendEvent(event, object, ...)
--send event to all registred plugins
if (event == "PLUGIN_DISABLED" or event == "PLUGIN_ENABLED") then
return object:OnDetailsEvent(event, ...)
elseif (not object) then
--iterate among all plugins which registered a function for this event
for _, PluginObject in ipairs(Details.RegistredEvents[event]) do
--when __eventtable is true, the plugin registered a function or method name to callback
--if is false, we call OnDetailsEvent method on the plugin
if (PluginObject.__eventtable) then
local pluginTable = PluginObject[1]
--check if the plugin is enabled
if (pluginTable.Enabled and pluginTable.__enabled) then
--check if fegistered a function
if (type(PluginObject[2]) == "function") then
local func = PluginObject[2]
Details:QuickDispatchEvent(func, event, ...)
--if not it must be a method name
else
local methodName = PluginObject[2]
local func = pluginTable[methodName]
Details:QuickDispatchEventWithContext(pluginTable, func, event, ...)
end
end
--if no function(only registred the event) sent the event to OnDetailsEvent
else
if (PluginObject.Enabled and PluginObject.__enabled) then
Details:QuickDispatchEventWithContext (PluginObject, PluginObject.OnDetailsEvent, event, ...)
end
end
end
--plugin notifications (does not send to listeners)
elseif (type(object) == "string" and object == "SEND_TO_ALL") then
for _, PluginObject in ipairs(Details.RaidTables.Plugins) do
if (PluginObject.__enabled) then
Details:QuickDispatchEventWithContext(PluginObject, PluginObject.OnDetailsEvent, event)
end
end
for _, PluginObject in ipairs(Details.SoloTables.Plugins) do
if (PluginObject.__enabled) then
Details:QuickDispatchEventWithContext(PluginObject, PluginObject.OnDetailsEvent, event)
end
end
for _, PluginObject in ipairs(Details.ToolBar.Plugins) do
if (PluginObject.__enabled) then
Details:QuickDispatchEventWithContext(PluginObject, PluginObject.OnDetailsEvent, event)
end
end
else
--send the event only for requested plugin
if (object.Enabled and object.__enabled) then
return Details:QuickDispatchEventWithContext(object, object.OnDetailsEvent, event, ...)
end
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--special cases
function Details:SendOptionsModifiedEvent(instance)
Details.last_options_modified = Details.last_options_modified or (GetTime() - 5)
if (Details.last_options_modified + 0.3 < GetTime()) then
Details:SendEvent("DETAILS_OPTIONS_MODIFIED", nil, instance)
Details.last_options_modified = GetTime()
if (Details.last_options_modified_schedule) then
Details:CancelTimer(Details.last_options_modified_schedule)
Details.last_options_modified_schedule = nil
end
else
if (Details.last_options_modified_schedule) then
Details:CancelTimer(Details.last_options_modified_schedule)
end
Details.last_options_modified_schedule = Details:ScheduleTimer("SendOptionsModifiedEvent", 0.31, instance)
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--listeners
local listener_meta = setmetatable({}, Details)
listener_meta.__index = listener_meta
function listener_meta:RegisterEvent(event, func)
return Details:RegisterEvent(self, event, func)
end
function listener_meta:UnregisterEvent(event)
return Details:UnregisterEvent(self, event)
end
function Details:CreateEventListener()
local new = {Enabled = true, __enabled = true}
setmetatable(new, listener_meta)
return new
end
+58
View File
@@ -0,0 +1,58 @@
--[[global]] DETAILS_HOOK_COOLDOWN = "HOOK_COOLDOWN"
--[[global]] DETAILS_HOOK_DEATH = "HOOK_DEATH"
--[[global]] DETAILS_HOOK_BATTLERESS = "HOOK_BATTLERESS"
--[[global]] DETAILS_HOOK_INTERRUPT = "HOOK_INTERRUPT"
local Details = _G.Details
local addonName, Details222 = ...
local _
---@alias detailshook
---| '"HOOK_COOLDOWN"' # Hook for cooldowns
---| '"HOOK_DEATH"' # Hook for deaths
---| '"HOOK_BATTLERESS"' # Hook for battle ress
---| '"HOOK_INTERRUPT"' # Hook for interrupts
Details.hooks["HOOK_COOLDOWN"] = {}
Details.hooks["HOOK_DEATH"] = {}
Details.hooks["HOOK_BATTLERESS"] = {}
Details.hooks["HOOK_INTERRUPT"] = {}
function Details:InstallHook(hookType, func)
if (not Details.hooks[hookType]) then
return false, "Invalid hook type."
end
for _, thisFunc in ipairs(Details.hooks[hookType]) do
if (thisFunc == func) then
--already installed
return
end
end
Details.hooks[hookType][#Details.hooks[hookType] + 1] = func
Details.hooks[hookType].enabled = true
Details:UpdateParserGears()
return true
end
function Details:UnInstallHook(hookType, func)
if (not Details.hooks[hookType]) then
return false, "Invalid hook type."
end
for index, thisFunc in ipairs(Details.hooks[hookType]) do
if (thisFunc == func) then
table.remove(Details.hooks[hookType], index)
if (#Details.hooks[hookType] == 0) then
Details.hooks[hookType].enabled = false
end
Details:UpdateParserGears()
return true
end
end
end
+73
View File
@@ -0,0 +1,73 @@
--Immersion enables players that isn't in your group to show on the damage meter window
local Details = _G.Details
local C_Timer = _G.C_Timer
local C_Map = _G.C_Map
local ceil = math.ceil
local addonName, Details222 = ...
-- immersion namespace
Details.Immersion = {}
-- ASCENSION
-- @andrew
-- This is disabled by unregistering the event
-- its fixable but would require a small amount of work that is pointless at the movement
-- since ascension has no fights / activities that would use this
local immersionFrame = _G.CreateFrame("frame", "DetailsImmersionFrame", _G.UIParent)
--immersionFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
immersionFrame.DevelopmentDebug = false
--check if can enabled the immersion stuff
function immersionFrame.CheckIfCanEnableImmersion()
return false
end
--check events
immersionFrame:SetScript("OnEvent", function(_, event, ...)
if (event == "ZONE_CHANGED_NEW_AREA") then
C_Timer.After(3, immersionFrame.CheckIfCanEnableImmersion)
end
end)
--store the GUID of the npc or player and point to the coords there the icon is
local iconPath1 = [[Interface\AddOns\Details\images\special_bar_icons]]
Details.Immersion.IconDatabase = {
-- ["167826"] = {file = iconPath1, iconId = 1, interest = true, class = "MAGE"}, --lady jaina proudmoore
-- ["167827"] = {file = iconPath1, iconId = 2, interest = true, class = "SHAMAN"}, --Thrall
-- ["157432"] = {file = iconPath1, iconId = 3, interest = true, class = "WARRIOR"}, --bloodletter phantoriax, a npc inside torghast
-- ["166148"] = {file = iconPath1, iconId = 4, interest = true, class = "WARRIOR"}, --sawn, a npc inside torghast
-- ["171996"] = {file = iconPath1, iconId = 5, interest = true, class = "WARRIOR"}, --kythekios, a npc inside torghast
-- ["172007"] = {file = iconPath1, iconId = 6, interest = true, class = "WARRIOR"}, --thelia, a npc inside torghast
-- ["172024"] = {file = iconPath1, iconId = 7, interest = true, class = "WARRIOR"}, --telethakas, a npc inside torghast
-- ["157406"] = {file = iconPath1, iconId = 8, interest = true, class = "WARRIOR"}, --renavyth, a npc inside torghast
-- ["166151"] = {file = iconPath1, iconId = 9, interest = true, class = "WARRIOR"}, --moriaz the red, a npc inside torghast
}
local customIconsDB = Details.Immersion.IconDatabase
function Details.Immersion.GetIcon(aID)
local hasCustomIcon = customIconsDB[aID]
if (hasCustomIcon) then
local iconId = hasCustomIcon.iconId
local iconSizeNormalized = 0.03125
local line = ceil(iconId / 32)
local x = (iconId - ((line-1) * 32)) / 32
local L = x - iconSizeNormalized
local R = x
local T = iconSizeNormalized * (line-1)
local B = iconSizeNormalized * line
return {hasCustomIcon.file, {L, R, T, B}}
end
end
function Details.Immersion.IsNpcInteresting(aID)
local npcImmersion = customIconsDB[aID]
if (npcImmersion and npcImmersion.interest) then
return true, npcImmersion.class
end
end
+145
View File
@@ -0,0 +1,145 @@
local Details = _G.Details
local DF = _G.DetailsFramework
local C_Timer = _G.C_Timer
local addonName, Details222 = ...
local GetSpellInfo = Details222.GetSpellInfo
--get the sectionInfo and try to extract the spellID from it
--sectionInfo is always a valid table
local parseSectionInfoForSpellID = function(sectionInfo)
local spellId = sectionInfo.spellID
if (spellId) then
spellId = tonumber(spellId)
if (spellId) then
local spellName = GetSpellInfo(spellId)
if (spellName) then
return spellId
end
end
end
end
---this function is called when the player clicks on a link in the chat window to open a section in the encounter journal
---Details! then will check if that spell linked did damage to the raid and show a small box with the damage done
---@param tag any tag isn't used
---@param journalTypeString string
---@param idString string
function Details222.EJCache.OnClickEncounterJournalLink(tag, journalTypeString, idString) --not in use
local journalType = tonumber(journalTypeString)
local id = tonumber(idString)
local instanceId, encounterId, sectionId, tierIndex = EJ_HandleLinkPath(journalType, id)
if (sectionId) then
local sectionInfo = C_EncounterJournal.GetSectionInfo(sectionId)
if (sectionInfo and type(sectionInfo) == "table") then
local spellId = parseSectionInfoForSpellID(sectionInfo)
--spellId is guaranteed to be a valid spellId or nil
if (spellId) then
local damageDoneTable = Details222.DamageSpells.GetDamageDoneToPlayersBySpell(spellId)
local topDamage = damageDoneTable[1] and damageDoneTable[1][2]
if (topDamage and topDamage > 0) then
--build a cooltip with the damage done to players by the spellId
local gameCooltip = GameCooltip
gameCooltip:Preset(2)
gameCooltip:SetType("tooltip")
gameCooltip:SetOption("LeftPadding", -5)
gameCooltip:SetOption("RightPadding", 5)
gameCooltip:SetOption("LinePadding", 1)
gameCooltip:SetOption("StatusBarTexture", [[Interface\AddOns\Details\images\bar_hyanda]])
for i = 1, #damageDoneTable do
local targetName, damageDone = unpack(damageDoneTable[i])
local nameWithoutRealm = DF:RemoveRealmName(targetName)
local formattedDamage = Details:ToK2(damageDone)
local className = Details222.ClassCache.GetClass(targetName)
local classTexture, left, right, top, bottom = Details:GetClassIcon(className)
gameCooltip:AddLine(nameWithoutRealm, formattedDamage)
gameCooltip:AddIcon(classTexture, 1, 1, 14, 14, left, right, top, bottom)
gameCooltip:AddStatusBar(damageDone / topDamage * 100, 1, .5, .5, .5, 1, false, {value = 100, color = {.2, .2, .2, 0.9}, texture = [[Interface\AddOns\Details\images\bar_hyanda]]})
end
local abilityString = DF:MakeStringFromSpellId(spellId)
if (abilityString) then
abilityString = abilityString .. " (damage to)"
end
local anchor = {"bottom", "top", 0, 0}
gameCooltip:SetBannerImage(1, 2, [[Interface\PetBattles\Weather-Blizzard]], 220, 55, anchor, {0.85, 0.189609375, 1, 0}, {0, 0, 0, 1})
gameCooltip:SetBannerText(1, 2, abilityString or "Ability Damage Done", {"bottomleft", "topleft", 0, 2}, "white", 14)
gameCooltip:SetOwner(EncounterJournal, "topleft", "topright", 50, -10)
gameCooltip:Show()
end
end
end
end
if (not Details222.EJCache.HasJournalOnHideHooked) then
Details222.EJCache.HasJournalOnHideHooked = true
EncounterJournal:HookScript("OnHide", function()
GameCooltip:Hide()
end)
end
end
--search the damage container within the combatObject index[1] for actor that used the spellId passed and inflicted damage to players
---@param spellId number
---@param combatId any
---@return table
function Details222.DamageSpells.GetDamageDoneToPlayersBySpell(spellId, combatId)
local combatObject = Details:GetCombat(combatId)
if (not combatObject) then
return {}
end
local damageContainer = combatObject:GetContainer(DETAILS_ATTRIBUTE_DAMAGE)
if (not damageContainer) then
return {}
end
local damageDoneTable = {}
for index, actorObject in damageContainer:ListActors() do
if (actorObject:IsNeutralOrEnemy()) then
local spellTable = actorObject:GetSpell(spellId)
if (spellTable) then
for targetName, damageDone in pairs(spellTable.targets) do
damageDoneTable[targetName] = (damageDoneTable[targetName] or 0) + damageDone
end
end
end
end
local sortedResult = {}
for targetName, damageDone in pairs(damageDoneTable) do
local className = Details222.ClassCache.GetClass(targetName)
sortedResult[#sortedResult + 1] = {targetName, damageDone, className}
end
table.sort(sortedResult, function(a, b) return a[2] > b[2] end)
return sortedResult
end
--[=[
["description"] = "Eranog wreathes several players in flames. Upon expiration a Flamerift forms at each player's location, inflicting 144,550 Fire damage to players within 4 yards.
A Flamescale Tarasek spills forth from each Flamerift, leaving behind a Lava Flow.
On Mythic difficulty, Eranog also opens a Greater Flamerift that creates a Flamescale Captain.",
["link"] = "[Flamerift]",
["siblingSectionID"] = 26037,
["startsOpen"] = false,
["creatureDisplayID"] = 0,
["headerType"] = 2,
["title"] = "Flamerift",
["firstChildSectionID"] = 26036,
["uiModelSceneID"] = 0,
["filteredByDifficulty"] = false,
["abilityIcon"] = 134153,
["spellID"] = 390715,
--]=]
+379
View File
@@ -0,0 +1,379 @@
local Details = _G.Details
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local _
local addonName, Details222 = ...
local C_Timer = C_Timer
local UnitName = UnitName
--On Details! Load load default keys into the main object
function Details222.LoadSavedVariables.DefaultProfile()
for key, value in pairs(Details.default_profile) do
if (type(value) == "table") then
Details[key] = Details.CopyTable(value)
else
Details[key] = value
end
end
end
function Details222.LoadSavedVariables.CharacterData()
local defaultCharacterData = Details.default_player_data
local currentCharacterData = _detalhes_database
--check if the player data exists, if not, load from default
if (not currentCharacterData) then --NOT EXISTS
currentCharacterData = Details.CopyTable(defaultCharacterData)
--[[GLOBAL]] _detalhes_database = currentCharacterData
end
--verify if there's new data added to 'default_player_data' and copy it to the savedvariable table
--do this up to a deepness level of 2, example: currentCharacterData[key][subKey] = any
for key, value in pairs(defaultCharacterData) do
if (currentCharacterData[key] == nil) then --the key doesn't exists, add it
if (type(value) == "table") then
currentCharacterData[key] = Details.CopyTable(defaultCharacterData[key])
else
currentCharacterData[key] = value
end
elseif (type(currentCharacterData[key]) == "table") then
for subKey, subValue in pairs(defaultCharacterData[key]) do
if (currentCharacterData[key][subKey] == nil) then
if (type(subValue) == "table") then
currentCharacterData[key][subKey] = Details.CopyTable(defaultCharacterData[key][subKey])
else
currentCharacterData[key][subKey] = subValue
end
end
end
end
--copy the key from saved table to Details object
if (type(value) == "table") then
Details[key] = Details.CopyTable(currentCharacterData[key])
else
Details[key] = currentCharacterData[key]
end
end
end
--check if this is a first run, reset, or just load the saved data.
function Details222.LoadSavedVariables.SharedData()
local defaultAccountData = Details.default_global_data
local currentAccountData = _detalhes_global
if (not currentAccountData) then
currentAccountData = Details.CopyTable(defaultAccountData)
--[[GLOBAL]] _detalhes_global = currentAccountData
end
for key, value in pairs(defaultAccountData) do
if (currentAccountData[key] == nil) then
if (type(value) == "table") then
currentAccountData[key] = Details.CopyTable(defaultAccountData[key])
else
currentAccountData[key] = value
end
elseif (type(currentAccountData[key]) == "table") then
if (key == "always_use_profile_name") then
currentAccountData["always_use_profile_name"] = ""
end
if (type(currentAccountData[key]) == "table") then
for subKey, subValue in pairs(defaultAccountData[key]) do
if (currentAccountData[key][subKey] == nil) then
if (type(subValue) == "table") then
currentAccountData[key][subKey] = Details.CopyTable(defaultAccountData[key][subKey])
else
currentAccountData[key][subKey] = subValue
end
end
end
end
end
--copy the key from savedvariables to Details object
if (type(value) == "table") then
Details[key] = Details.CopyTable(currentAccountData[key])
else
Details[key] = currentAccountData[key]
end
end
end
--load previous saved combat data
function Details222.LoadSavedVariables.CombatSegments()
--this is the table where the character data is saved as well the combat data
local currentCharacterData = _G["_detalhes_database"] --no need to check if it exists, it's already checked
if (currentCharacterData == nil) then
currentCharacterData = {}
end
--custom displays - if there's no saved custom display, they will be filled from the StartMeUp() when a new version is installed
if (_detalhes_global.custom) then
Details.custom = _detalhes_global.custom
Details.refresh:r_atributo_custom()
end
local bShouldClearAndExit = not currentCharacterData.tabela_historico
--check integrity of the sub table 'tabelas' and its first index 'current segment'
if (not bShouldClearAndExit) then
if (not currentCharacterData.tabela_historico.tabelas or not currentCharacterData.tabela_historico.tabelas[1]) then
bShouldClearAndExit = true
end
end
--check if is a major version upgrade (usualy API or low level changes)
if (not bShouldClearAndExit) then
bShouldClearAndExit = currentCharacterData.last_realversion and currentCharacterData.last_realversion < Details.realversion
end
--if can just clear all data and exit
if (bShouldClearAndExit) then
Details.tabela_historico = Details.historico:CreateNewSegmentDatabase()
Details.tabela_overall = Details.combate:NovaTabela()
Details.tabela_vigente = Details.combate:NovaTabela(_, Details.tabela_overall)
Details222.PetContainer.Reset()
if (currentCharacterData.saved_pet_cache) then
Details:Destroy(currentCharacterData.saved_pet_cache) --saved pet data
currentCharacterData.saved_pet_cache = nil
end
if (currentCharacterData.tabela_overall) then --saved overall data
Details:Destroy(currentCharacterData.tabela_overall)
currentCharacterData.tabela_overall = nil
end
if (currentCharacterData.tabela_historico) then
Details:Destroy(currentCharacterData.tabela_historico)
currentCharacterData.tabela_historico = nil
end
return
else
--pet owners cache saved on logout
do
Details222.PetContainer.Reset()
if (currentCharacterData.saved_pet_cache) then
--pet ownership table only exists if the player logoff inside a raid or dungeon
Details222.PetContainer.SetPetData(currentCharacterData.saved_pet_cache)
Details:Destroy(currentCharacterData.saved_pet_cache)
currentCharacterData.saved_pet_cache = nil
end
end
--restore saved overall data
do
if (currentCharacterData.tabela_overall) then
Details.tabela_overall = Details.CopyTable(currentCharacterData.tabela_overall)
Details:RestoreOverallMetatables()
else
Details.tabela_overall = Details.combate:NovaTabela()
Details.tabela_overall.overall_refreshed = true
end
end
--restore saved segments
do
Details.tabela_historico = Details.CopyTable(currentCharacterData.tabela_historico)
Details:Destroy(currentCharacterData.tabela_historico)
currentCharacterData.tabela_historico = nil
end
--get the first segment saved and use it as current segment
Details.tabela_vigente = Details.tabela_historico.tabelas[1] --only low level access to this table allowed
--need refresh for all containers
for _, actorContainer in ipairs(Details.tabela_overall) do
actorContainer.need_refresh = true
end
for _, actorContainer in ipairs(Details.tabela_vigente) do
actorContainer.need_refresh = true
end
Details:RestoreMetatables()
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--On Details! Load:
--load the saved config on the addon
function Details:LoadConfig()
--plugins data
Details.plugin_database = _detalhes_database.plugin_database or {}
--startup
--set the nicktag cache host
Details:NickTagSetCache (_detalhes_database.nick_tag_cache)
--count data
Details:CountDataOnLoad()
--solo e raid plugin
if (_detalhes_database.SoloTablesSaved) then
if (_detalhes_database.SoloTablesSaved.Mode) then
Details.SoloTables.Mode = _detalhes_database.SoloTablesSaved.Mode
Details.SoloTables.LastSelected = _detalhes_database.SoloTablesSaved.LastSelected
else
Details.SoloTables.Mode = 1
end
end
--switch tables
Details.switch.slots = _detalhes_global.switchSaved.slots
Details.switch.table = _detalhes_global.switchSaved.table
if (Details.switch.table) then
for i = 1, #Details.switch.table do
if (not Details.switch.table [i]) then
Details.switch.table [i] = {}
end
end
end
--last boss
Details.last_encounter = _detalhes_database.last_encounter
--buffs
Details.savedbuffs = _detalhes_database.savedbuffs
Details.Buffs:BuildTables()
--initialize parser
Details.capture_current = {}
for captureType, captureValue in pairs(Details.capture_real) do
Details.capture_current [captureType] = captureValue
end
--row animations
Details:SetUseAnimations()
--initialize spell cache
Details:ClearSpellCache()
--version first run
if (not _detalhes_database.last_version or _detalhes_database.last_version ~= Details.userversion) then
Details.is_version_first_run = true
end
--profile
local unitname = UnitName ("player")
--fix for old versions
if (type(Details.always_use_profile) == "string") then
Details.always_use_profile = false
Details.always_use_profile_name = ""
end
if (type(Details.always_use_profile_name) ~= "string") then
Details.always_use_profile = false
Details.always_use_profile_name = ""
end
--check for "always use this profile"
if (Details.always_use_profile and not Details.always_use_profile_exception [unitname]) then
local profile_name = Details.always_use_profile_name
if (profile_name and profile_name ~= "" and Details:GetProfile (profile_name)) then
_detalhes_database.active_profile = profile_name
end
end
--character first run
if (_detalhes_database.active_profile == "") then
Details.character_first_run = true
-- a primeira vez que este character usa profiles, precisa copiar as keys existentes
local current_profile_name = Details:GetCurrentProfileName()
Details:GetProfile (current_profile_name, true)
Details:SaveProfileSpecial()
end
--load profile and active instances
local current_profile_name = Details:GetCurrentProfileName()
--check if exists, if not, create one
local profile = Details:GetProfile (current_profile_name, true)
--instances
Details.tabela_instancias = _detalhes_database.tabela_instancias or {}
--fix for version 1.21.0
if (#Details.tabela_instancias > 0) then --only happens once after the character logon
for index, saved_skin in ipairs(profile.instances) do
local instance = Details.tabela_instancias [index]
if (instance) then
saved_skin.__was_opened = instance.ativa
saved_skin.__pos = Details.CopyTable(instance.posicao)
saved_skin.__locked = instance.isLocked
saved_skin.__snap = Details.CopyTable(instance.snap)
saved_skin.__snapH = instance.horizontalSnap
saved_skin.__snapV = instance.verticalSnap
for key, value in pairs(instance) do
if (Details.instance_defaults [key] ~= nil) then
if (type(value) == "table") then
saved_skin [key] = Details.CopyTable(value)
else
saved_skin [key] = value
end
end
end
end
end
for index, instance in Details:ListInstances() do
Details.local_instances_config [index] = {
pos = Details.CopyTable(instance.posicao),
is_open = instance.ativa,
attribute = instance.atributo,
sub_attribute = instance.sub_atributo,
mode = instance.modo or 2,
modo = instance.modo or 2,
segment = instance.segmento,
snap = Details.CopyTable(instance.snap),
horizontalSnap = instance.horizontalSnap,
verticalSnap = instance.verticalSnap,
sub_atributo_last = instance.sub_atributo_last,
isLocked = instance.isLocked
}
if (Details.local_instances_config [index].isLocked == nil) then
Details.local_instances_config [index].isLocked = false
end
end
Details.tabela_instancias = {}
end
--apply the profile
Details:ApplyProfile(current_profile_name, true)
end
--On Details! Load count logons, tutorials, etc
function Details:CountDataOnLoad()
--basic
if (not _detalhes_global.got_first_run) then
Details.is_first_run = true
end
--tutorial
self.tutorial = self.tutorial or {}
self.tutorial.logons = self.tutorial.logons or 0
self.tutorial.logons = self.tutorial.logons + 1
self.tutorial.unlock_button = self.tutorial.unlock_button or 0
self.tutorial.version_announce = self.tutorial.version_announce or 0
self.tutorial.main_help_button = self.tutorial.main_help_button or 0
self.tutorial.alert_frames = self.tutorial.alert_frames or {false, false, false, false, false, false}
self.tutorial.main_help_button = self.tutorial.main_help_button + 1
self.character_data = self.character_data or {logons = 0}
self.character_data.logons = self.character_data.logons + 1
end
+17
View File
@@ -0,0 +1,17 @@
local addonName, Details222 = ...
-- @windowN: number a a window to open the player details breakdown
-- /run Details:OpenPlayerDetails(windowN)
function Details:OpenPlayerDetails(window)
window = window or 1
local instance = Details:GetInstance(window)
if (instance) then
local display, subDisplay = instance:GetDisplay()
if (display == 1) then
Details:OpenBreakdownWindow(instance, Details:GetPlayer(false, 1))
elseif (display == 2) then
Details:OpenBreakdownWindow(instance, Details:GetPlayer(false, 2))
end
end
end
+210
View File
@@ -0,0 +1,210 @@
local Details = _G.Details
local detailsFramework = _G.DetailsFramework
local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0", true)
local addonName, Details222 = ...
local bitBand = bit.band
local CONST_OBJECT_TYPE_PLAYER = 0x00000400
local CONST_OBJECT_TYPE_NEUTRAL_OR_ENEMY = 0x00000060
local actorSpellContainers = {
"debuff", "buff", "spell", "cooldowns", "crowdcontrol", "dispel"
}
Details222.Mixins.ActorMixin = {
---return a table containing the spellContainers names: 'debuff', 'buff', 'spell', 'cooldowns', 'crowdcontrol'
---@return string[]
GetSpellContainerNames = function()
return actorSpellContainers
end,
---return a spellContainer from an actor
---@param actor actor
---@param containerType string
---@return spellcontainer|nil
GetSpellContainer = function(actor, containerType)
if (containerType == "debuff") then
return actor.debuff_uptime_spells
elseif (containerType == "buff") then
return actor.buff_uptime_spells
elseif (containerType == "spell") then
return actor.spells
elseif (containerType == "cooldowns") then
return actor.cooldowns_defensive_spells
elseif (containerType == "crowdcontrol") then
---@cast actor actorutility
return actor.cc_done_spells
elseif (containerType == "dispel") then
---@cast actor actorutility
return actor.dispell_spells
elseif (containerType == "dispelwhat") then
---@cast actor actorutility
return actor.dispell_oque
elseif (containerType == "interrupt") then
---@cast actor actorutility
return actor.interrupt_spells
elseif (containerType == "interruptwhat") then
---@cast actor actorutility
return actor.interrompeu_oque --is intended to be in portuguese
elseif (containerType == "interrupttargets") then
---@cast actor actorutility
return actor.interrupt_targets
end
end,
---return a spellTable from a spellContainer
---@param actor actor
---@param spellContainerName string
---@param spellId number
---@return spelltable|nil
GetSpellTableFromContainer = function(actor, spellContainerName, spellId)
---@type spellcontainer
local spellContainer = actor[spellContainerName]
if (spellContainer) then
---@type spelltable
local spellTable = spellContainer._ActorTable[spellId]
return spellTable
end
end,
---return a table containing pet names
---@param actor actor
---@return table<number, string>
GetPets = function(actor)
return actor.pets
end,
---return a table containing the targets of the actor
---@param actor actor
---@param key string optional, if not provided, will use the default target table: 'targets'
---@return targettable
GetTargets = function(actor, key)
return actor[key or "targets"]
end,
---return a table containing spellTables
---@param actor actor
---@return table<number, spelltable>
GetSpellList = function(actor)
return actor.spells._ActorTable
end,
---this function sums all the targets of all spellTables conteining on a 'spelltableadv'
---@param actor actor
---@param bkSpellData spelltableadv
---@param targetTableName string
---@return table<string, number>
BuildSpellTargetFromBreakdownSpellData = function(actor, bkSpellData, targetTableName)
targetTableName = targetTableName or "targets"
local spellTables = bkSpellData.spellTables
---@type table<string, number> store the index of the target name in the result table
local cacheIndex = {}
---@type table<string, number> store the result which is returned by this function
local result = {}
for i = 1, #spellTables do
---@type spelltable
local spellTable = spellTables[i]
---@type table<string, number>
local targets = spellTable[targetTableName]
for targetName, value in pairs(targets) do
local index = cacheIndex[targetName]
if (index) then
result[index][2] = result[index][2] + value
else
result[#result+1] = {targetName, value}
cacheIndex[targetName] = #result
end
end
end
table.sort(result, function(t1, t2)
return t1[2] > t2[2]
end)
return result
end,
---this function receives a key for the name of the target table (usually is 'targets') and return a table containing the targets and the damage done in order of bigger to lower value
---@param actor actor
---@param spellTable spelltable
---@param targetKey string
---@return table<string, number>
BuildSpellTargetFromSpellTable = function(actor, spellTable, targetKey)
targetKey = targetKey or "targets"
---@type table<string, number>[] store the result which is returned by this function
local result = {}
---@type table<string, number>
local targets = spellTable[targetKey]
for targetName, value in pairs(targets) do
---@cast targetName string
---@cast value number
result[#result+1] = {targetName, value}
end
table.sort(result, function(t1, t2)
return t1[2] > t2[2]
end)
return result
end,
---return true if the actor is controlled by a player
---@param actorObject actor
---@return boolean
IsPlayer = function(actorObject)
if (actorObject.flag_original) then
if (bitBand(actorObject.flag_original, CONST_OBJECT_TYPE_PLAYER) ~= 0) then
return true
end
end
return false
end,
---return true if the actor is a pet or guardian
---@param actorObject actor
---@return boolean
IsPetOrGuardian = function(actorObject)
return actorObject.owner and true or false
end,
---return true if the actor is or was in the player group
---@param actorObject table
---@return boolean
IsGroupPlayer = function(actorObject)
return actorObject.grupo and true or false
end,
---return true if the actor is an enemy of neutral npc
---@param actorObject actor
---@return boolean
IsNeutralOrEnemy = function(actorObject)
if (actorObject.flag_original) then
if (bitBand(actorObject.flag_original, CONST_OBJECT_TYPE_NEUTRAL_OR_ENEMY) ~= 0) then
local npcId = Details:GetNpcIdFromGuid(actorObject.serial)
if (Details.IgnoredEnemyNpcsTable[npcId]) then
return false
end
return true
end
end
return false
end,
}
@@ -0,0 +1,280 @@
local Details = _G.Details
local addonName, Details222 = ...
local detailsFramework = DetailsFramework
local _
local debugmode = false --print debug lines
local verbosemode = false --auto open the chart panel
local UnitClass = UnitClass
local IsInInstance = IsInInstance
local GetNumGroupMembers = GetNumGroupMembers
local GetInstanceInfo = GetInstanceInfo
local time = time
local floor = math.floor
local C_Timer = C_Timer
local C_MythicPlus = C_MythicPlus
--constants
local CONST_USE_PLAYER_EDPS = false
--Generate damage chart for mythic dungeon runs
--[=[
The chart table needs to be stored saparated from the combat
Should the chart data be volatile?
--]=]
local mythicDungeonFrames = Details222.MythicPlus.Frames
local mythicDungeonCharts = Details222.MythicPlus.Charts.Listener
--debug
_G.DetailsMythicDungeonChartHandler = mythicDungeonCharts
function mythicDungeonCharts:Debug(...)
if (debugmode or verbosemode) then
print("Details! DungeonCharts: ", ...)
end
end
local addPlayerDamage = function(unitCleuName)
--get the player data
local playerData = mythicDungeonCharts.ChartTable.Players[unitCleuName]
--if this is the first tick for the player, ignore the damage done on this tick
--this is done to prevent a tick tick with all the damage the player did on the previous segment
local bIsFirstTick = false
--check if the player data doesn't exists
if (not playerData) then
playerData = {
Name = detailsFramework:RemoveRealmName(unitCleuName),
ChartData = {max_value = 0},
Class = select(2, UnitClass(Details:Ambiguate(unitCleuName))),
--spec zero for now, need to retrive later during combat
Spec = 0,
--last damage to calc difference
LastDamage = 0,
--if started a new combat, need to reset the lastdamage
LastCombatID = -1,
}
mythicDungeonCharts.ChartTable.Players[unitCleuName] = playerData
bIsFirstTick = true
end
--get the current combat
local currentCombat = Details:GetCombat(DETAILS_SEGMENTID_CURRENT)
if (currentCombat) then
local isOverallSegment = false
local mythicDungeonInfo = currentCombat.is_mythic_dungeon
if (mythicDungeonInfo) then
if (mythicDungeonInfo.TrashOverallSegment or mythicDungeonInfo.OverallSegment) then
isOverallSegment = true
end
end
if (not isOverallSegment) then
--check if the combat has changed
local segmentId = currentCombat.combat_id
if (segmentId ~= playerData.LastCombatID) then
playerData.LastDamage = 0
playerData.LastCombatID = segmentId
--mythicDungeonCharts:Debug("Combat changed for player", unitCleuName)
end
local actorTable = currentCombat:GetActor(DETAILS_ATTRIBUTE_DAMAGE, unitCleuName)
if (actorTable) then
--update the player spec
playerData.Spec = actorTable.spec
if (bIsFirstTick) then
--ignore previous damage
playerData.LastDamage = actorTable.total
end
--get the damage done
local damageDone = actorTable.total
--check which data is used, dps or damage done
if (CONST_USE_PLAYER_EDPS) then
local eDps = damageDone / currentCombat:GetCombatTime()
--add the damage to the chart table
table.insert(playerData.ChartData, eDps)
--mythicDungeonCharts:Debug("Added dps for " , unitCleuName, ":", eDps)
if (eDps > playerData.ChartData.max_value) then
playerData.ChartData.max_value = eDps
end
else
--calc the difference and add to the table
local damageDiff = floor(damageDone - playerData.LastDamage)
playerData.LastDamage = damageDone
--add the damage to the chart table
table.insert(playerData.ChartData, damageDiff)
--mythicDungeonCharts:Debug("Added damage for " , unitCleuName, ":", damageDiff)
if (damageDiff > playerData.ChartData.max_value) then
playerData.ChartData.max_value = damageDiff
end
end
else
--player still didn't made anything on this combat, so just add zero
table.insert(playerData.ChartData, 0)
end
end
end
end
local tickerCallback = function(tickerObject)
--check if is inside the dungeon
local inInstance = IsInInstance()
if (not inInstance) then
mythicDungeonCharts:OnEndMythicDungeon()
return
end
--check if still running the dungeon
if (not mythicDungeonCharts.ChartTable or not mythicDungeonCharts.ChartTable.Running) then
tickerObject:Cancel()
return
end
--tick damage
local totalPlayers = GetNumGroupMembers()
for i = 1, totalPlayers-1 do
---@type cleuname
local cleuName = Details:GetFullName("party" .. i)
if (cleuName) then
addPlayerDamage(cleuName)
end
end
addPlayerDamage(Details:GetFullName("player"))
end
function mythicDungeonCharts:OnBossDefeated()
local currentCombat = Details:GetCurrentCombat()
local segmentType = currentCombat:GetCombatType()
local bossInfo = currentCombat:GetBossInfo()
local activeKeystone = C_MythicPlus.IsKeystoneActive() and C_MythicPlus.GetActiveKeystoneInfo()
local mythicLevel = activeKeystone and activeKeystone.keystoneLevel
if (mythicLevel and mythicLevel > 0) then
if (mythicDungeonCharts.ChartTable and mythicDungeonCharts.ChartTable.Running and bossInfo) then
local tCopiedBossInfo = Details:GetFramework().table.copy({}, bossInfo)
table.insert(mythicDungeonCharts.ChartTable.BossDefeated, {time() - mythicDungeonCharts.ChartTable.StartTime, tCopiedBossInfo, currentCombat:GetCombatTime()})
mythicDungeonCharts:Debug("Boss defeated, time saved", currentCombat:GetCombatTime())
else
if (mythicDungeonCharts.ChartTable and mythicDungeonCharts.ChartTable.EndTime ~= -1) then
local now = time()
--check if the dungeon just ended
if (mythicDungeonCharts.ChartTable.EndTime + 2 >= now) then
if (bossInfo) then
local copiedBossInfo = Details:GetFramework().table.copy({}, bossInfo)
table.insert(mythicDungeonCharts.ChartTable.BossDefeated, {time() - mythicDungeonCharts.ChartTable.StartTime, copiedBossInfo, currentCombat:GetCombatTime()})
mythicDungeonCharts:Debug("Boss defeated, time saved, but used time aproximation:", mythicDungeonCharts.ChartTable.EndTime + 2, now, currentCombat:GetCombatTime())
end
end
else
mythicDungeonCharts:Debug("Boss defeated, but no chart capture is running")
end
end
else
mythicDungeonCharts:Debug("Boss defeated, but isn't a mythic dungeon boss fight")
end
end
function mythicDungeonCharts:OnStartMythicDungeon()
if (not Details.mythic_plus.show_damage_graphic) then
mythicDungeonCharts:Debug("Dungeon started, no capturing mythic dungeon chart data, disabled on profile")
if (verbosemode) then
mythicDungeonCharts:Debug("OnStartMythicDungeon() not allowed")
end
return
else
mythicDungeonCharts:Debug("Dungeon started, new capture started")
end
mythicDungeonCharts.ChartTable = {
Running = true,
Players = {},
ElapsedTime = 0,
StartTime = time(),
EndTime = -1,
DungeonName = "",
--store when each boss got defeated in comparison with the StartTime
BossDefeated = {},
}
mythicDungeonCharts.ChartTable.Ticker = C_Timer.NewTicker(1, tickerCallback)
--save the chart for development
if (Details222.Debug.MythicPlusChartWindowDebug) then
Details.mythic_plus.last_mythicrun_chart = mythicDungeonCharts.ChartTable
end
if (verbosemode) then
mythicDungeonCharts:Debug("OnStartMythicDungeon() success")
end
end
function mythicDungeonCharts:OnEndMythicDungeon()
if (mythicDungeonCharts.ChartTable and mythicDungeonCharts.ChartTable.Running) then
--stop capturinfg
mythicDungeonCharts.ChartTable.Running = false
mythicDungeonCharts.ChartTable.ElapsedTime = time() - mythicDungeonCharts.ChartTable.StartTime
mythicDungeonCharts.ChartTable.EndTime = time()
mythicDungeonCharts.ChartTable.Ticker:Cancel()
local name, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceMapID, instanceGroupSize = GetInstanceInfo()
mythicDungeonCharts.ChartTable.DungeonName = name
--check if is inside the dungeon
--many players just leave the dungeon in order the re-enter and start the run again, the chart window is showing in these cases data to an imcomplete run.
local isInsideDungeon = IsInInstance()
if (not isInsideDungeon) then
mythicDungeonCharts:Debug("OnEndMythicDungeon() player wasn't inside the dungeon.")
return
end
if (verbosemode) then
mythicDungeonCharts:Debug("OnEndMythicDungeon() success!")
end
else
mythicDungeonCharts:Debug("Dungeon ended, no chart data was running")
if (verbosemode) then
mythicDungeonCharts:Debug("OnEndMythicDungeon() fail")
end
end
end
mythicDungeonCharts:RegisterEvent("COMBAT_MYTHICDUNGEON_START", "OnStartMythicDungeon")
mythicDungeonCharts:RegisterEvent("COMBAT_MYTHICDUNGEON_END", "OnEndMythicDungeon")
mythicDungeonCharts:RegisterEvent("COMBAT_BOSS_DEFEATED", "OnBossDefeated")
--SetPortraitTexture(texture, unitId)
-- /run _G.DetailsMythicDungeonChartHandler.ShowChart(); DetailsMythicDungeonChartFrame.ShowChartFrame()
-- /run mythicDungeonFrames.ShowEndOfMythicPlusPanel()
@@ -0,0 +1,479 @@
local Details = _G.Details
local DF = _G.DetailsFramework
local addonName, Details222 = ...
local _
local time = time
local C_Timer = _G.C_Timer
local unpack = _G.unpack
local GetTime = _G.GetTime
local tremove = _G.tremove
local GetInstanceInfo = _G.GetInstanceInfo
local Loc = _G.LibStub("AceLocale-3.0"):GetLocale("Details")
--data for the current mythic + dungeon
Details.MythicPlus = {
RunID = 0,
}
local mythicDungeonFrames = Details222.MythicPlus.Frames
local mythicDungeonCharts = Details:CreateEventListener()
Details222.MythicPlus.Charts.Listener = mythicDungeonCharts
-- ~mythic ~dungeon
local DetailsMythicPlusFrame = _G.CreateFrame("frame", "DetailsMythicPlusFrame", UIParent)
DetailsMythicPlusFrame.DevelopmentDebug = false
--disabling the mythic+ feature if the user is playing in wow classic
DetailsMythicPlusFrame:RegisterEvent("MYTHIC_PLUS_STARTED")
DetailsMythicPlusFrame:RegisterEvent("MYTHIC_PLUS_COMPLETE")
DetailsMythicPlusFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
DetailsMythicPlusFrame:RegisterEvent("ENCOUNTER_END")
DetailsMythicPlusFrame:RegisterEvent("START_TIMER")
function Details222.MythicPlus.LogStep(log)
local today = date("%d/%m/%y %H:%M:%S")
table.insert(Details.mythic_plus_log, 1, today .. "|" .. log)
if #Details.mythic_plus_log >= 50 then
tremove(Details.mythic_plus_log, 50)
end
end
function DetailsMythicPlusFrame.BossDefeated(this_is_end_end, encounterID, encounterName, difficultyID, raidSize, endStatus) --hold your breath and count to ten
--this function is called right after defeat a boss inside a mythic dungeon
--it comes from details! control leave combat
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "BossDefeated() > boss defeated | SegmentID:", Details.MythicPlus.SegmentID, " | mapID:", Details.MythicPlus.DungeonID)
end
--local zoneName, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceMapID, instanceGroupSize = GetInstanceInfo()
Details222.MythicPlus.OnBossDefeated(encounterID, encounterName) --data capture
--increase the segment number for the mythic run
Details.MythicPlus.SegmentID = Details.MythicPlus.SegmentID + 1
--register the time when the last boss has been killed (started a clean up for the next trash)
Details.MythicPlus.PreviousBossKilledAt = time()
--update the saved table inside the profile
Details:UpdateState_CurrentMythicDungeonRun(true, Details.MythicPlus.SegmentID, Details.MythicPlus.PreviousBossKilledAt)
end
--this function is called 2 seconds after the event COMBAT_MYTHICDUNGEON_END
function DetailsMythicPlusFrame.MythicDungeonFinished(fromZoneLeft)
if (DetailsMythicPlusFrame.IsDoingMythicDungeon) then
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MythicDungeonFinished() > the dungeon was a Mythic+ and just ended.")
end
DetailsMythicPlusFrame.IsDoingMythicDungeon = false
Details.MythicPlus.Started = false
Details.MythicPlus.EndedAt = time()-1.9
Details:UpdateState_CurrentMythicDungeonRun()
--at this point, details! should not be in combat, but if something triggered a combat start, just close the combat right away
if (Details.in_combat) then
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MythicDungeonFinished() > was in combat, calling SairDoCombate():", InCombatLockdown())
end
Details:SairDoCombate()
Details222.MythicPlus.LogStep("MythicDungeonFinished() | Details was in combat.")
end
--check if there is trash segments after the last boss. need to merge these segments with the trash segment of the last boss
local bCanMergeBossTrash = Details.mythic_plus.merge_boss_trash
Details222.MythicPlus.LogStep("MythicDungeonFinished() | merge_boss_trash = " .. (bCanMergeBossTrash and "true" or "false"))
--check if there's trash after the last boss, if does, merge it with the trash of the last boss defeated
if (bCanMergeBossTrash and not Details.MythicPlus.IsRestoredState and not fromZoneLeft) then
--is the current combat not a boss fight?
--this mean a combat was opened after the last boss of the dungeon was killed
if (not Details.tabela_vigente.is_boss and Details.tabela_vigente:GetCombatTime() > 5) then
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MythicDungeonFinished() > the last combat isn't a boss fight, might have trash after bosses done.")
end
Details222.MythicPlus.MergeTrashAfterLastBoss()
end
end
--merge segments
if (Details.mythic_plus.make_overall_when_done and not Details.MythicPlus.IsRestoredState and not fromZoneLeft) then
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MythicDungeonFinished() > not in combat, creating overall segment now")
end
DetailsMythicPlusFrame.MergeSegmentsOnEnd()
end
Details.MythicPlus.IsRestoredState = nil
--the run is valid, schedule to open the chart window
--Details.mythic_plus.delay_to_show_graphic = 1
--C_Timer.After(Details.mythic_plus.delay_to_show_graphic, mythicDungeonFrames.ShowEndOfMythicPlusPanel)
--shutdown parser for a few seconds to avoid opening new segments after the run ends
if (not fromZoneLeft) then
Details:CaptureSet(false, "damage", false, 15)
Details:CaptureSet(false, "energy", false, 15)
Details:CaptureSet(false, "aura", false, 15)
Details:CaptureSet(false, "energy", false, 15)
Details:CaptureSet(false, "spellcast", false, 15)
end
end
end
function DetailsMythicPlusFrame.MythicDungeonStarted()
--flag as a mythic dungeon
DetailsMythicPlusFrame.IsDoingMythicDungeon = true
--this counter is individual for each character
Details.mythic_dungeon_id = Details.mythic_dungeon_id + 1
local activeKeystone = C_MythicPlus.IsKeystoneActive() and C_MythicPlus.GetActiveKeystoneInfo()
local mythicLevel = activeKeystone and activeKeystone.keystoneLevel
local currentZoneID = activeKeystone and activeKeystone.dungeonID
local zoneName, _, _, _, _, _, _ = GetInstanceInfo()
local mapID = activeKeystone.dungeonID
if (not mapID) then
return
end
local ejID = Details:GetInstanceEJID(mapID)
--setup the mythic run info
Details.MythicPlus.Started = true
Details.MythicPlus.DungeonName = zoneName
Details.MythicPlus.DungeonID = currentZoneID
--Details:Msg("(debug) mythic dungeon start time: ", time()+9.7, "time now:", time(), "diff:", time()+9.7-time())
Details.MythicPlus.StartedAt = time()+9.7 --there's the countdown timer of 10 seconds
Details.MythicPlus.EndedAt = nil --reset
Details.MythicPlus.SegmentID = 1
Details.MythicPlus.Level = mythicLevel
Details.MythicPlus.ejID = ejID
Details.MythicPlus.PreviousBossKilledAt = time()
Details:SaveState_CurrentMythicDungeonRun(Details.mythic_dungeon_id, zoneName, currentZoneID, time()+9.7, 1, mythicLevel, ejID, time())
local name, groupType, difficultyID, difficult = GetInstanceInfo()
if (groupType == "party" and Details.overall_clear_newchallenge) then
Details.historico:ResetOverallData()
Details:Msg("the overall data has been reset.") --localize-me
if (Details.debug) then
Details:Msg("(debug) timer is for a mythic+ dungeon, overall has been reseted.")
end
end
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MythicDungeonStarted() > State set to Mythic Dungeon, new combat starting in 10 seconds.")
end
end
function DetailsMythicPlusFrame.OnChallengeModeStart()
--is this a mythic dungeon?
local activeKeystone = C_MythicPlus.IsKeystoneActive() and C_MythicPlus.GetActiveKeystoneInfo()
local currentZoneID = activeKeystone and activeKeystone.dungeonID
local _, _, difficultyID, _, _, _, _ = GetInstanceInfo()
if (difficultyID == 3) then
--start the dungeon on Details!
DetailsMythicPlusFrame.MythicDungeonStarted()
Details222.MythicPlus.LogStep("OnChallengeModeStart()")
else
--print("D! mythic dungeon was already started!")
--from zone changed
local mythicLevel = MythicPlusUtil.GetActiveKeystoneLevel()
if (not Details.MythicPlus.Started and Details.MythicPlus.DungeonID == currentZoneID and Details.MythicPlus.Level == mythicLevel) then
Details.MythicPlus.Started = true
Details.MythicPlus.EndedAt = nil
Details.mythic_dungeon_currentsaved.started = true
DetailsMythicPlusFrame.IsDoingMythicDungeon = true
--print("D! mythic dungeon was NOT already started! debug 2")
end
end
end
--make an event listener to sync combat data
DetailsMythicPlusFrame.EventListener = Details:CreateEventListener()
DetailsMythicPlusFrame.EventListener:RegisterEvent("COMBAT_ENCOUNTER_START")
DetailsMythicPlusFrame.EventListener:RegisterEvent("COMBAT_ENCOUNTER_END")
DetailsMythicPlusFrame.EventListener:RegisterEvent("COMBAT_PLAYER_ENTER")
DetailsMythicPlusFrame.EventListener:RegisterEvent("COMBAT_PLAYER_LEAVE")
DetailsMythicPlusFrame.EventListener:RegisterEvent("COMBAT_MYTHICDUNGEON_START")
DetailsMythicPlusFrame.EventListener:RegisterEvent("COMBAT_MYTHICDUNGEON_END")
DetailsMythicPlusFrame.EventListener:RegisterEvent("COMBAT_MYTHICPLUS_OVERALL_READY")
function DetailsMythicPlusFrame.EventListener.OnDetailsEvent(contextObject, event, ...)
--these events triggers within Details control functions, they run exactly after details! creates or close a segment
if (event == "COMBAT_PLAYER_ENTER") then
elseif (event == "COMBAT_PLAYER_LEAVE") then
--ignore the event if ignoring mythic dungeon special treatment
if (Details.streamer_config.disable_mythic_dungeon) then
return
end
if (DetailsMythicPlusFrame.IsDoingMythicDungeon) then
local combatObject = ...
if (combatObject.is_boss) then
if (not combatObject.is_boss.killed) then
local encounterName = combatObject.is_boss.encounter
local zoneName = combatObject.is_boss.zone
local activeKeystone = C_MythicPlus.IsKeystoneActive() and C_MythicPlus.GetActiveKeystoneInfo()
local mythicLevel = activeKeystone and activeKeystone.keystoneLevel
local currentCombat = Details:GetCurrentCombat()
--just in case the combat get tagged as boss fight
combatObject.is_boss = nil
--tag the combat as mythic dungeon trash
local instanceMapID = activeKeystone and activeKeystone.dungeonID
local zoneName = instanceMapID and GetLFGDungeonInfo(instanceMapID)
---@type mythicdungeoninfo
local mythicPlusInfo = {
ZoneName = Details.MythicPlus.DungeonName or zoneName,
MapID = Details.MythicPlus.DungeonID or instanceMapID,
Level = Details.MythicPlus.Level,
EJID = Details.MythicPlus.ejID,
RunID = Details.mythic_dungeon_id,
StartedAt = time() - currentCombat:GetCombatTime(),
EndedAt = time(),
SegmentID = Details.MythicPlus.SegmentID, --segment number within the dungeon
OverallSegment = false,
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSWIPE,
SegmentName = (encounterName or Loc["STRING_UNKNOW"]) .. " (" .. string.lower(_G["BOSS"]) .. ")"
}
combatObject.is_mythic_dungeon = mythicPlusInfo
Details222.MythicPlus.LogStep("COMBAT_PLAYER_LEAVE | wiped on boss | key level: | " .. mythicLevel .. " | " .. (encounterName or "") .. " " .. zoneName)
else
DetailsMythicPlusFrame.BossDefeated(false, combatObject.is_boss.id, combatObject.is_boss.name, combatObject.is_boss.diff, 5, 1)
end
end
end
elseif (event == "COMBAT_ENCOUNTER_START") then
--ignore the event if ignoring mythic dungeon special treatment
if (Details.streamer_config.disable_mythic_dungeon) then
Details222.MythicPlus.LogStep("COMBAT_ENCOUNTER_START | streamer_config.disable_mythic_dungeon is true and the code cannot continue.")
return
end
local encounterID, encounterName, difficultyID, raidSize, endStatus = ...
--nothing
elseif (event == "COMBAT_ENCOUNTER_END") then
--ignore the event if ignoring mythic dungeon special treatment
if (Details.streamer_config.disable_mythic_dungeon) then
Details222.MythicPlus.LogStep("COMBAT_ENCOUNTER_END | streamer_config.disable_mythic_dungeon is true and the code cannot continue.")
return
end
local encounterID, encounterName, difficultyID, raidSize, endStatus = ...
--nothing
elseif (event == "COMBAT_MYTHICDUNGEON_START") then
local lowerInstance = Details:GetLowerInstanceNumber()
if (lowerInstance) then
lowerInstance = Details:GetInstance(lowerInstance)
if (lowerInstance) then
C_Timer.After(3, function()
--if (lowerInstance:IsEnabled()) then
--todo, need localization
--lowerInstance:InstanceAlert("Details!" .. " " .. "Damage" .. " " .. "Meter", {[[Interface\AddOns\Details\images\minimap]], 16, 16, false}, 3, {function() end}, false, true)
--end
end)
end
end
--ignore the event if ignoring mythic dungeon special treatment
if (Details.streamer_config.disable_mythic_dungeon) then
return
end
--reset spec cache if broadcaster requested
if (Details.streamer_config.reset_spec_cache) then
Details:Destroy(Details.cached_specs)
end
C_Timer.After(0.25, DetailsMythicPlusFrame.OnChallengeModeStart)
--debugging
local mPlusSettings = Details.mythic_plus
local result = ""
for key, value in pairs(Details.mythic_plus) do
if (type(value) ~= "table") then
result = result .. key .. " = " .. tostring(value) .. " | "
end
end
local activeKeystone = C_MythicPlus.IsKeystoneActive() and C_MythicPlus.GetActiveKeystoneInfo()
local mythicLevel = activeKeystone and activeKeystone.keystoneLevel
local zoneName, _, _, _, _, _, _ = GetInstanceInfo()
local currentZoneID = activeKeystone and activeKeystone.dungeonID
Details222.MythicPlus.LogStep("COMBAT_MYTHICDUNGEON_START | settings: " .. result .. " | level: " .. mythicLevel .. " | zone: " .. zoneName .. " | zoneId: " .. currentZoneID)
elseif (event == "COMBAT_MYTHICDUNGEON_END") then
--ignore the event if ignoring mythic dungeon special treatment
if (Details.streamer_config.disable_mythic_dungeon) then
Details222.MythicPlus.LogStep("COMBAT_MYTHICDUNGEON_END | streamer_config.disable_mythic_dungeon is true and the code cannot continue.")
return
end
--delay to wait the encounter_end trigger first
--assuming here the party cleaned the mobs kill objective before going to kill the last boss
C_Timer.After(2, DetailsMythicPlusFrame.MythicDungeonFinished)
elseif (event == "COMBAT_MYTHICPLUS_OVERALL_READY") then
DetailsMythicPlusFrame.SaveMythicPlusStats(...)
end
end
local playerLeftDungeonZoneTimer_Callback = function()
if (DetailsMythicPlusFrame.IsDoingMythicDungeon) then
local activeKeystone = C_MythicPlus.IsKeystoneActive() and C_MythicPlus.GetActiveKeystoneInfo()
local currentZoneID = activeKeystone and activeKeystone.dungeonID
if (not currentZoneID or currentZoneID ~= Details.MythicPlus.DungeonID) then
Details222.MythicPlus.LogStep("ZONE_CHANGED_NEW_AREA | player has left the dungeon and Details! finished the dungeon because of that.")
--send mythic dungeon end event
Details:SendEvent("COMBAT_MYTHICDUNGEON_END") --on leave dungeon
--finish the segment
DetailsMythicPlusFrame.BossDefeated(true)
--finish the mythic run
DetailsMythicPlusFrame.MythicDungeonFinished(true)
DetailsMythicPlusFrame.ZoneLeftTimer = nil
end
end
end
DetailsMythicPlusFrame:SetScript("OnEvent", function(_, event, ...)
if (event == "START_TIMER") then
--DetailsMythicPlusFrame.LastTimer = GetTime()
elseif (event == "ZONE_CHANGED_NEW_AREA") then
if (DetailsMythicPlusFrame.IsDoingMythicDungeon) then
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", event, ...)
print("Zone changed and is Doing Mythic Dungeon")
end
--ignore the event if ignoring mythic dungeon special treatment
if (Details.streamer_config.disable_mythic_dungeon) then
Details222.MythicPlus.LogStep("ZONE_CHANGED_NEW_AREA | streamer_config.disable_mythic_dungeon is true and the code cannot continue.")
return
end
local activeKeystone = C_MythicPlus.IsKeystoneActive() and C_MythicPlus.GetActiveKeystoneInfo()
local currentZoneID = activeKeystone and activeKeystone.dungeonID
if (not currentZoneID or currentZoneID ~= Details.MythicPlus.DungeonID) then
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Zone changed and the zone is different than the dungeon")
end
--player left the dungeon zone, start a timer to check if the player will return to the dungeon
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "ZONE_CHANGED_NEW_AREA | player left the dungeon zone, return to dungeon timer started.")
end
--check if the timer already exists, if does, ignore this event
if (DetailsMythicPlusFrame.ZoneLeftTimer and not DetailsMythicPlusFrame.ZoneLeftTimer:IsCancelled()) then
return
end
DetailsMythicPlusFrame.ZoneLeftTimer = C_Timer.NewTimer(40, playerLeftDungeonZoneTimer_Callback)
end
end
end
end)
---@param combatObject combat
function DetailsMythicPlusFrame.SaveMythicPlusStats(combatObject)
local mapChallengeModeID, mythicLevel, time, onTime = MythicPlusUtil.GetCompletionInfo(true)
if (mapChallengeModeID) then
local statName = "mythicdungeoncompletedDF2"
---@type table<challengemapid, table>
local mythicDungeonRuns = Details222.PlayerStats:GetStat(statName)
if (not mythicDungeonRuns) then
mythicDungeonRuns = mythicDungeonRuns or {}
end
--mythicDungeonRuns [mapChallengeModeID] [mythicLevel]
---@class mythicplusrunstats
---@field onTime boolean
---@field deaths number
---@field date number
---@field affix number
---@field runTime milliseconds
---@field combatTime number
---@class mythicplusstats
---@field completed number
---@field totalTime number
---@field minTime number
---@field history mythicplusrunstats[]
---@type table<keylevel, mythicplusstats>
local statsForDungeon = mythicDungeonRuns[mapChallengeModeID]
if (not statsForDungeon) then
statsForDungeon = {}
mythicDungeonRuns[mapChallengeModeID] = statsForDungeon
end
---@type mythicplusstats
local statsForLevel = statsForDungeon[mythicLevel]
if (not statsForLevel) then
---@type mythicplusstats
statsForLevel = {
completed = 0,
totalTime = 0,
minTime = 0,
history = {},
}
statsForDungeon[mythicLevel] = statsForLevel
end
statsForLevel.completed = (statsForLevel.completed or 0) + 1
statsForLevel.totalTime = (statsForLevel.totalTime or 0) + time
if (not statsForLevel.minTime or time < statsForLevel.minTime) then
statsForLevel.minTime = time
end
statsForLevel.history = statsForLevel.history or {}
local amountDeaths = 0
---@type mythicplusrunstats
local runStats = {
date = _G.time(),
runTime = math.floor(time/1000),
onTime = onTime,
deaths = amountDeaths,
affix = PrimaryAffix,
combatTime = combatObject:GetCombatTime(),
}
table.insert(statsForLevel.history, runStats)
Details222.PlayerStats:SetStat("mythicdungeoncompletedDF2", mythicDungeonRuns)
end
end
@@ -0,0 +1,542 @@
local Details = _G.Details
local addonName, Details222 = ...
local detailsFramework = DetailsFramework
local _
local GetTime = GetTime
local GetInstanceInfo = GetInstanceInfo
local time = time
local MythicPlusUtil = MythicPlusUtil
local InCombatLockdown = InCombatLockdown
local Loc = _G.LibStub("AceLocale-3.0"):GetLocale("Details")
--[[
all mythic segments have:
.is_mythic_dungeon_segment = true
.is_mythic_dungeon_run_id = run id from details.profile.mythic_dungeon_id
--]]
local DetailsMythicPlusFrame = _G["DetailsMythicPlusFrame"]
--empty
function Details222.MythicPlus.OnMythicDungeonFinished(encounterID, encounterName)end
function Details222.MythicPlus.OnBossDefeated(encounterID, encounterName)
local currentCombat = Details:GetCurrentCombat()
--add the mythic dungeon info to the combat
currentCombat.is_mythic_dungeon = {
StartedAt = Details.MythicPlus.StartedAt, --the start of the run
EndedAt = time(), --when the boss got killed
SegmentID = Details.MythicPlus.SegmentID, --segment number within the dungeon
EncounterID = encounterID,
EncounterName = encounterName or Loc["STRING_UNKNOW"],
RunID = Details.mythic_dungeon_id,
ZoneName = Details.MythicPlus.DungeonName,
MapID = Details.MythicPlus.DungeonID,
OverallSegment = false,
Level = Details.MythicPlus.Level,
EJID = Details.MythicPlus.ejID,
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSS,
SegmentName = (encounterName or Loc["STRING_UNKNOW"]) .. " (" .. string.lower(_G["BOSS"]) .. ")"
}
local mythicLevel = MythicPlusUtil.GetActiveKeystoneLevel()
local mPlusTable = currentCombat.is_mythic_dungeon
--logs
Details222.MythicPlus.LogStep("BossDefeated | key level: | " .. mythicLevel .. " | " .. (mPlusTable.EncounterName or "") .. " | " .. (mPlusTable.ZoneName or ""))
--check if need to merge the trash for this boss
if (Details.mythic_plus.merge_boss_trash and not Details.MythicPlus.IsRestoredState) then
--store on an table all segments which should be merged
local segmentsToMerge = DetailsMythicPlusFrame.TrashMergeScheduled or {}
--table with all past semgnets
local segmentsTable = Details:GetCombatSegments()
--iterate among segments
for i = 1, 25 do --from the newer combat to the oldest
---@type combat
local pastCombat = segmentsTable[i]
--does the combat exists
if (pastCombat and not pastCombat._trashoverallalreadyadded) then
--is the combat a mythic segment from this run?
local bIsMythicSegment, SegmentID = pastCombat:IsMythicDungeon()
if (bIsMythicSegment and SegmentID == Details.mythic_dungeon_id) then
local combatType = pastCombat:GetCombatType()
if (combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_TRASH or combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSWIPE) then
table.insert(segmentsToMerge, pastCombat)
end
end
end
end
--add encounter information
segmentsToMerge.EncounterID = encounterID
segmentsToMerge.EncounterName = encounterName
segmentsToMerge.PreviousBossKilledAt = Details.MythicPlus.PreviousBossKilledAt
--reduce this boss encounter time from the trash lenght time, since the boss doesn't count towards the time spent cleaning trash
segmentsToMerge.LastBossKilledAt = time() - currentCombat:GetCombatTime()
DetailsMythicPlusFrame.TrashMergeScheduled = segmentsToMerge
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "BossDefeated() > not in combat, merging trash now")
end
--merge the trash clean up
DetailsMythicPlusFrame.MergeTrashCleanup()
end
end
--after each boss fight, if enalbed on settings, create an extra segment with all trash segments from the boss just killed
--this function does not have agency over what segments to merge, it just receives a list of segments to merge
function DetailsMythicPlusFrame.MergeTrashCleanup()
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MergeTrashCleanup() > running", DetailsMythicPlusFrame.TrashMergeScheduled and #DetailsMythicPlusFrame.TrashMergeScheduled)
end
local segmentsToMerge = DetailsMythicPlusFrame.TrashMergeScheduled
--table exists and there's at least one segment
if (segmentsToMerge and segmentsToMerge[1]) then
Details222.MythicPlus.LogStep("MergeTrashCleanup started.")
--the first segment is the segment where all other trash segments will be added
local masterSegment = segmentsToMerge[1]
--get the current combat just created and the table with all past segments
local newCombat = masterSegment
local totalTime = newCombat:GetCombatTime()
local startDate, endDate = "", ""
local lastSegment
--add segments
for i = 2, #segmentsToMerge do --segment #1 is the host
local pastCombat = segmentsToMerge[i]
newCombat = newCombat + pastCombat
totalTime = totalTime + pastCombat:GetCombatTime()
newCombat:CopyDeathsFrom(pastCombat, true)
--tag this combat as already added to a boss trash overall
pastCombat._trashoverallalreadyadded = true
if (endDate == "") then
local _, whenEnded = pastCombat:GetDate()
endDate = whenEnded
end
lastSegment = pastCombat
end
--get the date where the first segment started
if (lastSegment) then
startDate = lastSegment:GetDate()
end
local zoneName, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceMapID, instanceGroupSize = GetInstanceInfo()
--tag the segment as mythic overall segment
newCombat.is_mythic_dungeon = {
StartedAt = segmentsToMerge.PreviousBossKilledAt, --start of the mythic run or when the previous boss got killed
EndedAt = segmentsToMerge.LastBossKilledAt, --the time() when encounter_end got triggered
SegmentID = "trashoverall",
RunID = Details.mythic_dungeon_id,
TrashOverallSegment = true,
ZoneName = Details.MythicPlus.DungeonName,
MapID = instanceMapID,
Level = Details.MythicPlus.Level,
EJID = Details.MythicPlus.ejID,
EncounterID = segmentsToMerge.EncounterID,
EncounterName = segmentsToMerge.EncounterName or Loc ["STRING_UNKNOW"],
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSTRASH,
SegmentName = (segmentsToMerge.EncounterName or Loc ["STRING_UNKNOW"]) .. " (" .. string.lower(Loc["STRING_SEGMENTS_LIST_TRASH"]) .. ")",
}
newCombat.is_challenge = true
newCombat.is_mythic_dungeon_segment = true
newCombat.is_mythic_dungeon_run_id = Details.mythic_dungeon_id
--set the segment time / using a sum of combat times, this combat time is reliable
newCombat:SetStartTime(GetTime() - totalTime)
newCombat:SetEndTime(GetTime())
--set the segment date
newCombat:SetDate(startDate, endDate)
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MergeTrashCleanup() > finished merging trash segments.", Details.tabela_vigente, Details.tabela_vigente.is_boss)
end
--delete all segments that were merged
local segmentsTable = Details:GetCombatSegments()
for segmentId = #segmentsTable, 1, -1 do
local segment = segmentsTable[segmentId]
if (segment and segment._trashoverallalreadyadded) then
table.remove(segmentsTable, segmentId)
end
end
for i = #segmentsToMerge, 1, -1 do
table.remove(segmentsToMerge, i)
end
--call the segment removed event to notify third party addons
Details:SendEvent("DETAILS_DATA_SEGMENTREMOVED")
--update all windows
Details:InstanceCallDetailsFunc(Details.FadeHandler.Fader, "IN", nil, "barras")
Details:InstanceCallDetailsFunc(Details.UpdateCombatObjectInUse)
Details:InstanceCallDetailsFunc(Details.AtualizaSoloMode_AfertReset)
Details:InstanceCallDetailsFunc(Details.ResetaGump)
Details:RefreshMainWindow(-1, true)
else
Details222.MythicPlus.LogStep("MergeTrashCleanup | no segments to merge.")
end
end
function DetailsMythicPlusFrame.MergeSegmentsOnEnd() --~merge
--at the end of a mythic run, if enable on settings, merge all the segments from the mythic run into only one
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MergeSegmentsOnEnd() > starting to merge mythic segments.", "InCombatLockdown():", InCombatLockdown())
end
Details222.MythicPlus.LogStep("MergeSegmentsOnEnd started | creating the overall segment at the end of the run.")
--create a new combat to be the overall for the mythic run
Details222.StartCombat()
--get the current combat just created and the table with all past segments
local newCombat = Details:GetCurrentCombat()
local segmentsTable = Details:GetCombatSegments()
newCombat.is_challenge = true
newCombat.is_mythic_dungeon_segment = true
newCombat.is_mythic_dungeon_run_id = Details.mythic_dungeon_id
local timeInCombat = 0
local startDate, endDate = "", ""
local lastSegment
local totalSegments = 0
--copy deaths occured on all segments to the new segment, also sum the activity combat time
if (Details.mythic_plus.reverse_death_log) then
for i = 1, 40 do --copy the deaths from the first segment to the last one
local thisCombat = segmentsTable[i]
if (thisCombat and thisCombat.is_mythic_dungeon_run_id == Details.mythic_dungeon_id) then
newCombat:CopyDeathsFrom(thisCombat, true)
timeInCombat = timeInCombat + thisCombat:GetCombatTime()
end
end
else
for i = 40, 1, -1 do --copy the deaths from the last segment to the new segment
local thisCombat = segmentsTable[i]
if (thisCombat) then
if (thisCombat.is_mythic_dungeon_run_id == Details.mythic_dungeon_id) then
newCombat:CopyDeathsFrom(thisCombat, true)
timeInCombat = timeInCombat + thisCombat:GetCombatTime()
end
end
end
end
local zoneName, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceMapID, instanceGroupSize = GetInstanceInfo()
--tag the segment as mythic overall segment
---@type mythicdungeoninfo
newCombat.is_mythic_dungeon = {
StartedAt = Details.MythicPlus.StartedAt, --the start of the run
EndedAt = Details.MythicPlus.EndedAt, --the end of the run
WorldStateTimerStart = Details222.MythicPlus.WorldStateTimerStartAt,
WorldStateTimerEnd = Details222.MythicPlus.WorldStateTimerEndAt,
RunTime = Details222.MythicPlus.time,
TimeInCombat = timeInCombat,
SegmentID = "overall", --segment number within the dungeon
RunID = Details.mythic_dungeon_id,
OverallSegment = true,
ZoneName = Details.MythicPlus.DungeonName,
EJID = Details.MythicPlus.ejID,
MapID = Details222.MythicPlus.MapID,
Level = Details222.MythicPlus.Level,
OnTime = Details222.MythicPlus.OnTime,
KeystoneUpgradeLevels = Details222.MythicPlus.KeystoneUpgradeLevels,
PracticeRun = Details222.MythicPlus.PracticeRun,
OldDungeonScore = Details222.MythicPlus.OldDungeonScore,
NewDungeonScore = Details222.MythicPlus.NewDungeonScore,
IsAffixRecord = Details222.MythicPlus.IsAffixRecord,
IsMapRecord = Details222.MythicPlus.IsMapRecord,
PrimaryAffix = Details222.MythicPlus.PrimaryAffix,
IsEligibleForScore = Details222.MythicPlus.IsEligibleForScore,
UpgradeMembers = Details222.MythicPlus.UpgradeMembers,
TimeLimit = Details222.MythicPlus.TimeLimit,
DungeonName = Details222.MythicPlus.DungeonName,
DungeonID = Details222.MythicPlus.DungeonID,
DungeonTexture = Details222.MythicPlus.Texture,
DungeonBackgroundTexture = Details222.MythicPlus.BackgroundTexture,
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_OVERALL,
SegmentName = Details.MythicPlus.DungeonName .. " +" .. Details222.MythicPlus.Level,
}
--add all boss segments from this run to this new segment
for i = 1, 40 do --from the newer combat to the oldest
local thisCombat = segmentsTable[i]
if (thisCombat and thisCombat.is_mythic_dungeon_run_id == Details.mythic_dungeon_id) then
local canAddThisSegment = true
if (Details.mythic_plus.make_overall_boss_only) then
if (not thisCombat.is_boss) then
--canAddThisSegment = false --disabled
end
end
if (canAddThisSegment) then
newCombat = newCombat + thisCombat
totalSegments = totalSegments + 1
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("MergeSegmentsOnEnd() > adding time:", thisCombat:GetCombatTime(), thisCombat.is_boss and thisCombat.is_boss.name)
end
if (endDate == "") then
local _, whenEnded = thisCombat:GetDate()
endDate = whenEnded
end
lastSegment = thisCombat
end
end
end
--get the date where the first segment started
if (lastSegment) then
startDate = lastSegment:GetDate()
end
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MergeSegmentsOnEnd() > totalTime:", timeInCombat, "startDate:", startDate)
end
newCombat.total_segments_added = totalSegments
newCombat.is_mythic_dungeon_run_id = Details.mythic_dungeon_id
--check if both values are valid, this can get invalid if the player leaves the dungeon before the timer ends or the game crashes
if (type(Details222.MythicPlus.time) == "number") then
newCombat.run_time = Details222.MythicPlus.time
Details222.MythicPlus.LogStep("GetCompletionInfo() Found, Time: " .. Details222.MythicPlus.time)
elseif (newCombat.is_mythic_dungeon.WorldStateTimerEnd and newCombat.is_mythic_dungeon.WorldStateTimerStart) then
local runTime = newCombat.is_mythic_dungeon.WorldStateTimerEnd - newCombat.is_mythic_dungeon.WorldStateTimerStart
newCombat.run_time = Details222.MythicPlus.time
Details222.MythicPlus.LogStep("World State Timers is Available, Run Time: " .. runTime .. "| start:" .. newCombat.is_mythic_dungeon.WorldStateTimerStart .. "| end:" .. newCombat.is_mythic_dungeon.WorldStateTimerEnd)
else
newCombat.run_time = timeInCombat
Details222.MythicPlus.LogStep("GetCompletionInfo() and World State Timers not Found, Activity Time: " .. timeInCombat)
end
newCombat:SetStartTime(GetTime() - timeInCombat)
newCombat:SetEndTime(GetTime())
Details222.MythicPlus.LogStep("Activity Time: " .. timeInCombat)
--set the segment time and date
newCombat:SetDate(startDate, endDate)
--immediatly finishes the segment just started
Details:SairDoCombate()
newCombat.is_mythic_dungeon_segment = true
--update all windows
Details:InstanceCallDetailsFunc(Details.FadeHandler.Fader, "IN", nil, "barras")
Details:InstanceCallDetailsFunc(Details.UpdateCombatObjectInUse)
Details:InstanceCallDetailsFunc(Details.AtualizaSoloMode_AfertReset)
Details:InstanceCallDetailsFunc(Details.ResetaGump)
Details:RefreshMainWindow(-1, true)
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MergeSegmentsOnEnd() > finished merging segments.")
print("Details!", "MergeSegmentsOnEnd() > all done, check in the segments list if everything is correct, if something is weird: '/details feedback' thanks in advance!")
end
local lower_instance = Details:GetLowerInstanceNumber()
if (lower_instance) then
local instance = Details:GetInstance(lower_instance)
if (instance) then
local func = {function() end}
instance:InstanceAlert ("Showing Mythic+ Run Segment", {[[Interface\AddOns\Details\images\icons]], 16, 16, false, 434/512, 466/512, 243/512, 273/512}, 6, func, true)
end
end
local bHasObject = false
Details:SendEvent("COMBAT_MYTHICPLUS_OVERALL_READY", bHasObject, newCombat)
end
--this function merges trash segments after all bosses of the mythic dungeon are defeated
--happens when the group finishes all bosses but don't complete the trash requirement
function DetailsMythicPlusFrame.MergeRemainingTrashAfterAllBossesDone()
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MergeRemainingTrashAfterAllBossesDone() > running, #segments: ", #DetailsMythicPlusFrame.TrashMergeScheduled2, "trash overall table:", DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat)
end
Details222.MythicPlus.LogStep("running MergeRemainingTrashAfterAllBossesDone.")
local segmentsToMerge = DetailsMythicPlusFrame.TrashMergeScheduled2
local latestBossTrashMergedCombat = DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat
--needs to merge, add the total combat time, set the date end to the date of the first segment
local totalTime = 0
local startDate, endDate = "", ""
--add segments
for i, pastCombat in ipairs(segmentsToMerge) do
latestBossTrashMergedCombat = latestBossTrashMergedCombat + pastCombat
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("MergeRemainingTrashAfterAllBossesDone() > segment added")
end
totalTime = totalTime + pastCombat:GetCombatTime()
--tag this combat as already added to a boss trash overall
pastCombat._trashoverallalreadyadded = true
if (endDate == "") then --get the end date of the first index only
local _, whenEnded = pastCombat:GetDate()
endDate = whenEnded
end
end
--set the segment time / using a sum of combat times, this combat time is reliable
local startTime = latestBossTrashMergedCombat:GetStartTime()
latestBossTrashMergedCombat:SetStartTime (startTime - totalTime)
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("MergeRemainingTrashAfterAllBossesDone() > total combat time:", totalTime)
end
--set the segment date
startDate = latestBossTrashMergedCombat:GetDate()
latestBossTrashMergedCombat:SetDate(startDate, endDate)
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("MergeRemainingTrashAfterAllBossesDone() > new end date:", endDate)
end
local mythicDungeonInfo = latestBossTrashMergedCombat:GetMythicDungeonInfo()
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("MergeRemainingTrashAfterAllBossesDone() > elapsed time before:", mythicDungeonInfo.EndedAt - mythicDungeonInfo.StartedAt)
end
mythicDungeonInfo.StartedAt = mythicDungeonInfo.StartedAt - (Details.MythicPlus.EndedAt - Details.MythicPlus.PreviousBossKilledAt)
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("MergeRemainingTrashAfterAllBossesDone() > elapsed time after:", mythicDungeonInfo.EndedAt - mythicDungeonInfo.StartedAt)
end
--remove trash segments from the segment history after the merge
local removedCurrentSegment = false
local segmentsTable = Details:GetCombatSegments()
for _, pastCombat in ipairs(segmentsToMerge) do
for i = #segmentsTable, 1, -1 do
local segment = segmentsTable[i]
if (segment == pastCombat) then
--remove the segment
if (Details:GetCurrentCombat() == segment) then
removedCurrentSegment = true
end
table.remove(segmentsTable, i)
break
end
end
end
for i = #segmentsToMerge, 1, -1 do
table.remove(segmentsToMerge, i)
end
if (removedCurrentSegment) then
--find another current segment
segmentsTable = Details:GetCombatSegments()
Details:SetCurrentCombat(segmentsTable[1])
if (not Details:GetCurrentCombat()) then
--assuming there's no segment from the dungeon run
Details222.StartCombat()
Details:EndCombat()
end
--update all windows
Details:InstanceCallDetailsFunc(Details.FadeHandler.Fader, "IN", nil, "barras")
Details:InstanceCallDetailsFunc(Details.UpdateCombatObjectInUse)
Details:InstanceCallDetailsFunc(Details.AtualizaSoloMode_AfertReset)
Details:InstanceCallDetailsFunc(Details.ResetaGump)
Details:RefreshMainWindow(-1, true)
end
Details222.MythicPlus.LogStep("delete_trash_after_merge | concluded")
Details:SendEvent("DETAILS_DATA_SEGMENTREMOVED")
DetailsMythicPlusFrame.TrashMergeScheduled2 = nil
DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat = nil
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MergeRemainingTrashAfterAllBossesDone() > done merging")
end
end
--does not do the merge, just scheduling, the merger is on the function above
function Details222.MythicPlus.MergeTrashAfterLastBoss()
local segmentsToMerge = {}
--table with all past segments
local segmentsTable = Details:GetCombatSegments()
for i = 1, #segmentsTable do
local pastCombat = segmentsTable[i]
--does the combat exists
if (pastCombat and not pastCombat._trashoverallalreadyadded and pastCombat:GetCombatTime() > 5) then
--is the last boss?
if (pastCombat.is_boss) then
break
end
--is the combat a mythic segment from this run?
local bIsMythicSegment, SegmentID = pastCombat:IsMythicDungeon()
if (bIsMythicSegment and SegmentID == Details.mythic_dungeon_id) then
local combatType = pastCombat:GetCombatType()
if (combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_TRASH or combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSWIPE) then
table.insert(segmentsToMerge, pastCombat)
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("MythicDungeonFinished() > found after last boss combat")
end
end
end
end
end
if (#segmentsToMerge > 0) then
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MythicDungeonFinished() > found ", #segmentsToMerge, "segments after the last boss")
end
--find the latest trash overall
segmentsTable = Details:GetCombatSegments()
local latestTrashOverall
for i = 1, #segmentsTable do
local pastCombat = segmentsTable[i]
if (pastCombat and pastCombat.is_mythic_dungeon) then
local combatType = pastCombat:GetCombatType()
if (combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSTRASH) then
latestTrashOverall = pastCombat
break
end
end
end
if (latestTrashOverall) then
--stores the segment table and the trash overall segment to use on the merge
DetailsMythicPlusFrame.TrashMergeScheduled2 = segmentsToMerge
DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat = latestTrashOverall
if (DetailsMythicPlusFrame.DevelopmentDebug) then
print("Details!", "MythicDungeonFinished() > not in combat, merging last pack of trash now")
end
DetailsMythicPlusFrame.MergeRemainingTrashAfterAllBossesDone()
end
end
end
File diff suppressed because it is too large Load Diff
+159
View File
@@ -0,0 +1,159 @@
local Details = _G.Details
local addonName, Details222 = ...
local plater_integration_frame = CreateFrame("frame", "DetailsPlaterFrame", UIParent, "BackdropTemplate")
plater_integration_frame.DamageTaken = {}
--aprox. 6 updates per second
local CONST_REALTIME_UPDATE_TIME = 0.166
--how many samples to store, 30 x .166 aprox 5 seconds buffer
local CONST_BUFFER_SIZE = 30
--Dps division factor
PLATER_DPS_SAMPLE_SIZE = CONST_BUFFER_SIZE * CONST_REALTIME_UPDATE_TIME
--separate CLEU events from the Tick event for performance
plater_integration_frame.OnTickFrame = CreateFrame("frame", "DetailsPlaterFrameOnTicker", UIParent, "BackdropTemplate")
--on tick function
plater_integration_frame.OnTickFrameFunc = function(self, deltaTime)
if (self.NextUpdate < 0) then
for targetGUID, damageTable in pairs(plater_integration_frame.DamageTaken) do
--total damage
local totalDamage = damageTable.TotalDamageTaken
local totalDamageFromPlayer = damageTable.TotalDamageTakenFromPlayer
--damage on this update
local damageOnThisUpdate = totalDamage - damageTable.LastTotalDamageTaken
local damageOnThisUpdateFromPlayer = totalDamageFromPlayer - damageTable.LastTotalDamageTakenFromPlayer
--update the last damage taken
damageTable.LastTotalDamageTaken = totalDamage
damageTable.LastTotalDamageTakenFromPlayer = totalDamageFromPlayer
--sum the current damage
damageTable.CurrentDamage = damageTable.CurrentDamage + damageOnThisUpdate
damageTable.CurrentDamageFromPlayer = damageTable.CurrentDamageFromPlayer + damageOnThisUpdateFromPlayer
--add to the buffer the damage added
table.insert(damageTable.RealTimeBuffer, 1, damageOnThisUpdate)
table.insert(damageTable.RealTimeBufferFromPlayer, 1, damageOnThisUpdateFromPlayer)
--remove the damage from the buffer
local damageRemoved = tremove(damageTable.RealTimeBuffer, CONST_BUFFER_SIZE + 1)
if (damageRemoved) then
damageTable.CurrentDamage = max(damageTable.CurrentDamage - damageRemoved, 0)
end
local damageRemovedFromPlayer = tremove(damageTable.RealTimeBufferFromPlayer, CONST_BUFFER_SIZE + 1)
if (damageRemovedFromPlayer) then
damageTable.CurrentDamageFromPlayer = max(damageTable.CurrentDamageFromPlayer - damageRemovedFromPlayer, 0)
end
end
--update time
self.NextUpdate = CONST_REALTIME_UPDATE_TIME
else
self.NextUpdate = self.NextUpdate - deltaTime
end
end
--parse the damage taken by unit
function plater_integration_frame.AddDamageToGUID (sourceGUID, targetGUID, time, amount)
local damageTable = plater_integration_frame.DamageTaken [targetGUID]
if (not damageTable) then
plater_integration_frame.DamageTaken [targetGUID] = {
LastEvent = time,
TotalDamageTaken = amount,
TotalDamageTakenFromPlayer = 0,
--for real time
RealTimeBuffer = {},
RealTimeBufferFromPlayer = {},
LastTotalDamageTaken = 0,
LastTotalDamageTakenFromPlayer = 0,
CurrentDamage = 0,
CurrentDamageFromPlayer = 0,
}
--is the damage from the player it self?
if (sourceGUID == plater_integration_frame.PlayerGUID) then
plater_integration_frame.DamageTaken [targetGUID].TotalDamageTakenFromPlayer = amount
end
else
damageTable.LastEvent = time
damageTable.TotalDamageTaken = damageTable.TotalDamageTaken + amount
if (sourceGUID == plater_integration_frame.PlayerGUID) then
damageTable.TotalDamageTakenFromPlayer = damageTable.TotalDamageTakenFromPlayer + amount
end
end
end
plater_integration_frame:SetScript("OnEvent", function(self, event, ...)
local time, token, hidding, sourceGUID, sourceName, sourceFlag, sourceFlag2, targetGUID, targetName, targetFlag, targetFlag2, spellID, spellName, spellType, amount, overKill, school, resisted, blocked, absorbed, isCritical = CombatLogGetCurrentEventInfo(...)
--damage taken by the GUID unit
if (token == "SPELL_DAMAGE" or token == "SPELL_PERIODIC_DAMAGE" or token == "RANGE_DAMAGE" or token == "DAMAGE_SHIELD") then
plater_integration_frame.AddDamageToGUID (sourceGUID, targetGUID, time, amount)
elseif (token == "SWING_DAMAGE") then
--the damage is passed in the spellID argument position
plater_integration_frame.AddDamageToGUID (sourceGUID, targetGUID, time, spellID)
end
end)
function Details:RefreshPlaterIntegration()
if (Plater and Details.plater.realtime_dps_enabled or Details.plater.realtime_dps_player_enabled or Details.plater.damage_taken_enabled) then
--wipe the cache
Details:Destroy(plater_integration_frame.DamageTaken)
--read cleu events
plater_integration_frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
--start the real time dps updater
plater_integration_frame.OnTickFrame.NextUpdate = CONST_REALTIME_UPDATE_TIME
plater_integration_frame.OnTickFrame:SetScript("OnUpdate", plater_integration_frame.OnTickFrameFunc)
--cache the player serial
plater_integration_frame.PlayerGUID = UnitGUID("player")
--cancel the timer if already have one
if (plater_integration_frame.CleanUpTimer and not plater_integration_frame.CleanUpTimer:IsCancelled()) then
plater_integration_frame.CleanUpTimer:Cancel()
end
--cleanup the old tables
plater_integration_frame.CleanUpTimer = C_Timer.NewTicker(10, function()
local now = time()
for GUID, damageTable in pairs(plater_integration_frame.DamageTaken) do
if (damageTable.LastEvent + 9.9 < now) then
plater_integration_frame.DamageTaken [GUID] = nil
end
end
end)
else
--unregister the cleu
plater_integration_frame:UnregisterEvent ("COMBAT_LOG_EVENT_UNFILTERED")
--stop the real time updater
plater_integration_frame.OnTickFrame:SetScript("OnUpdate", nil)
--stop the cleanup process
if (plater_integration_frame.CleanUpTimer and not plater_integration_frame.CleanUpTimer:IsCancelled()) then
plater_integration_frame.CleanUpTimer:Cancel()
end
end
end
+592
View File
@@ -0,0 +1,592 @@
do
local Details = _G.Details
local _
local addonName, Details222 = ...
local pairs = pairs
local ipairs = ipairs
local unpack = table.unpack or _G.unpack
local GetSpellInfo = Details222.GetSpellInfo
local UnitClass = UnitClass
local UnitGUID = UnitGUID
local CONST_UNKNOWN_CLASS_COORDS = { 0, 0.125, 0.5, 0.625 }
local CONST_DEFAULT_COLOR = {1, 1, 1, 1}
local roles = {
DAMAGER = {421/512, 466/512, 381/512, 427/512},
HEALER = {467/512, 512/512, 381/512, 427/512},
TANK = {373/512, 420/512, 381/512, 427/512},
NONE = {0, 50/512, 110/512, 150/512},
}
---return a table containing information about the texture to use for the actor icon
---@param actorObject actor
---@return texturetable
function Details:GetActorIcon(actorObject)
---@type instance
local instance = Details:GetInstance(1)
local spec = actorObject:Spec()
if (spec and spec > 0) then
---@type string
local fileName
--get the spec icon file currently in use
if (instance) then
fileName = instance.row_info.spec_file
else
fileName = Details.instance_defaults.row_info.spec_file
end
local left, right, top, bottom = unpack(Details.class_specs_coords[spec])
local textureTable = {
texture = fileName,
coords = {left = left, right = right, top = top, bottom = bottom},
size = {height = 16, width = 16},
}
return textureTable
end
local class = actorObject:Class() or "UNKNOW"
local left, right, top, bottom = unpack(Details.class_coords[class])
---@type string
local fileName
--get the spec icon file currently in use
if (instance) then
fileName = instance.row_info.icon_file
else
fileName = Details.instance_defaults.row_info.icon_file
end
local textureTable = {
texture = fileName,
coords = {left = left, right = right, top = top, bottom = bottom},
size = {height = 16, width = 16},
}
return textureTable
end
---return the path to a texture file and the texture coordinates
---@return string, number, number, number, number
function Details:GetUnknownClassIcon()
return [[Interface\AddOns\Details\images\classes_small]], unpack(CONST_UNKNOWN_CLASS_COORDS)
end
---return a path to a texture file
---@param iconType "spec"|"class"
---@param bWithAlpha boolean
---@return string texturePath
function Details:GetIconTexture(iconType, bWithAlpha)
iconType = string.lower(iconType)
if (iconType == "spec") then
if (bWithAlpha) then
return [[Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES-SPECS]]
else
return [[Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES-SPECS]]
end
else --if is class
if (bWithAlpha) then
return [[Interface\AddOns\Details\images\classes_small_alpha]]
else
return [[Interface\AddOns\Details\images\classes_small]]
end
end
end
---attempt to get the class of an actor by its name, if the actor isn't found, it searches the overall data for the actor
---@param actorName string
---@return string className, number left, number right, number top, number bottom, number red, number green, number blue, number alpha
function Details:GetClass(actorName)
local unitClass = Details:GetUnitClass(actorName)
if (unitClass) then
local left, right, top, bottom = unpack(Details.class_coords[unitClass])
local r, g, b = unpack(Details.class_colors[unitClass])
return unitClass, left, right, top, bottom, r or 1, g or 1, b or 1, 1
else
local overallCombatObject = Details:GetCombat(DETAILS_SEGMENTID_OVERALL)
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
local actorContainer = overallCombatObject:GetContainer(containerId)
local actorObject = actorContainer:GetActor(actorName)
if (actorObject) then
unitClass = actorObject:Class()
if (unitClass) then
--found the class of the actor
local left, right, top, bottom = unpack(Details.class_coords[unitClass] or CONST_UNKNOWN_CLASS_COORDS)
local r, g, b = unpack(Details.class_colors[unitClass])
return unitClass, left, right, top, bottom, r or 1, g or 1, b or 1, 1
end
end
end
return "UNKNOW", 0.75, 1, 0.75, 1, 1, 1, 1, 1
end
end
--note: this could return the coords and color as well to match Details:GetClass()
---attempt to get the spec of an actor by its name, if the actor isn't found, it searches the overall data for the actor
---@param actorName string
---@return number|nil
function Details:GetSpecFromActorName(actorName)
local GUID = UnitGUID(actorName)
local spec = Details:GetSpecByGUID(GUID)
if (spec) then
return spec
end
local overallCombatObject = Details:GetCombat(DETAILS_SEGMENTID_OVERALL)
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
local actorContainer = overallCombatObject:GetContainer(containerId)
local actorObject = actorContainer:GetActor(actorName)
if (actorObject) then
spec = actorObject:Spec()
if (spec) then
return spec
end
end
end
end
---return the path to a texture file and the texture coordinates
---@param role string
---@return string texturePath, number left, number right, number top, number bottom
function Details:GetRoleIcon(role)
return [[Interface\AddOns\Details\images\icons2]], unpack(roles[role])
end
---return the path to a texture file and the texture coordinates for the given class
---@param class string
---@return string texturePath, number left, number right, number top, number bottom
function Details:GetClassIcon(class)
if (self.classe) then
class = self.classe
elseif (type(class) == "table" and class.classe) then
class = class.classe
elseif (type(class) == "string") then
class = class
else
class = "UNKNOW"
end
if (class == "UNKNOW") then
return [[Interface\LFGFRAME\LFGROLE_BW]], 0.25, 0.5, 0, 1
elseif (class == "UNGROUPPLAYER") then
return [[Interface\ICONS\Achievement_Character_Orc_Male]], 0, 1, 0, 1
elseif (class == "PET") then
return [[Interface\AddOns\Details\images\classes_small]], 0.25, 0.49609375, 0.75, 1
else
return [[Interface\AddOns\Details\images\classes_small]], unpack(Details.class_coords[class])
end
end
---return the path to a texture file and the texture coordinates for the given spec
---@param spec number
---@param useAlpha boolean
---@return string texturePath, number left, number right, number top, number bottom
function Details:GetSpecIcon(spec, useAlpha)
if (not spec or spec == 0) then
--this returns the icon for "unknown" spec (gotten from the class icon file)
return [[Interface\AddOns\Details\images\classes_small]], unpack(Details.class_coords["UNKNOW"])
end
if (useAlpha) then
return [[Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES-SPECS]], unpack(Details.class_specs_coords [spec])
else
return [[Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES-SPECS]], unpack(Details.class_specs_coords[spec])
end
end
---return the red, green, blue and alpha values for the given class
---@param class string
---@return number red, number green, number blue, number alpha
function Details:GetClassColor(class)
if (self.classe) then
return unpack(Details.class_colors[self.classe] or CONST_DEFAULT_COLOR)
elseif (type(class) == "table" and class.classe) then
return unpack(Details.class_colors[class.classe] or CONST_DEFAULT_COLOR)
elseif (type(class) == "string") then
return unpack(Details.class_colors[class] or CONST_DEFAULT_COLOR)
elseif (self.color) then
return unpack(self.color)
else
return unpack(CONST_DEFAULT_COLOR)
end
end
---get the spec or class texture and coordinates for the given player name and combat object, if the actor isn't found return unknown icon
---@param playerName string
---@param combatObject combat
---@return string texturePath, number left, number right, number top, number bottom
function Details:GetPlayerIcon(playerName, combatObject)
combatObject = combatObject or Details:GetCurrentCombat()
local texturePath, left, right, top, bottom
---@type actor
local playerObject = combatObject:GetActor(DETAILS_ATTRIBUTE_DAMAGE, playerName)
if (not playerObject or not playerObject.spec) then
---@type actor
playerObject = combatObject:GetActor(DETAILS_ATTRIBUTE_HEAL, playerName)
end
if (playerObject) then
local spec = playerObject.spec
if (spec) then
texturePath = [[Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES-SPECS]]
left, right, top, bottom = unpack(Details.class_specs_coords[spec])
elseif (playerObject.classe) then
texturePath = [[Interface\AddOns\Details\images\classes_small]]
left, right, top, bottom = unpack(Details.class_coords[playerObject.classe or "UNKNOW"])
end
end
if (not texturePath) then
texturePath = [[Interface\AddOns\Details\images\classes_small]]
left, right, top, bottom = unpack(Details.class_coords["UNKNOW"])
end
return texturePath, left, right, top, bottom
end
---return specId if it exists in the spec cache
---@param unitSerial string this is also called GUID
---@return number|nil
function Details:GetSpecByGUID(unitSerial)
return Details.cached_specs[unitSerial]
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---comment
---@param payload table
---@return any
function Details:GuessClass(payload)
---@type actor, actorcontainer, number
local actorObject, actorContainer, attempts = payload[1], payload[2], payload[3]
if (not actorObject or actorObject.__destroyed) then
return false
end
local spellContainerNames = actorObject:GetSpellContainerNames() --1x Details/functions/playerclass.lua:293: attempt to call method 'GetSpellContainerNames' (a nil value)
for i = 1, #spellContainerNames do
local spellContainer = actorObject:GetSpellContainer(spellContainerNames[i])
if (spellContainer) then
for spellId in spellContainer:ListSpells() do
local class = Details.ClassSpellList[spellId]
if (class) then
actorObject.classe = class
actorObject.guessing_class = nil
if (actorContainer) then
actorContainer.need_refresh = true
end
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
actorObject.minha_barra.minha_tabela = nil
Details:ScheduleWindowUpdate(2, true)
end
return class
end
end
end
end
local class = Details:GetClass(actorObject:Name())
if (class and class ~= "UNKNOW") then
actorObject.classe = class
actorObject.need_refresh = true
actorObject.guessing_class = nil
if (actorContainer) then
actorContainer.need_refresh = true
end
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
actorObject.minha_barra.minha_tabela = nil
Details:ScheduleWindowUpdate(2, true)
end
return class
end
if (attempts and attempts < 10) then
payload[3] = attempts + 1 --thanks @Farmbuyer on curseforge
--_detalhes:ScheduleTimer("GuessClass", 2, {Actor, container, tries+1})
Details:ScheduleTimer("GuessClass", 2, payload) --passing the same table instead of creating a new one
end
return false
end
---comment
---@param payload table
---@return any
function Details:GuessSpec(payload)
---@type actor, actorcontainer, number
local actorObject, actorContainer, attempts = payload[1], payload[2], payload[3]
if (not actorObject or actorObject.__destroyed) then
return false
end
local actorSpec
--attempt the obvious
if (actorObject.spec) then
actorSpec = actorObject.spec
end
local specSpellList = Details.SpecSpellList
--attempt to get from OpenRaid
if (not actorSpec) then
local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0", true)
if (openRaidLib) then
local unitInfo = openRaidLib.GetUnitInfo(actorObject:Name()) --1x Details/functions/playerclass.lua:368: attempt to call method 'Name' (a nil value)
if (unitInfo and unitInfo.specId and unitInfo.specId ~= 0) then
actorSpec = unitInfo.specId
end
end
end
--attempt to get from the spec cache
if (not actorSpec) then
actorSpec = Details.cached_specs[actorObject.serial]
end
--attempt to get from the spells the actor used in the current combat
if (not actorSpec) then
local currentCombatObject = Details:GetCurrentCombat()
if (currentCombatObject.__destroyed) then
--schedule made before a destroy combat call, but not cancelled
return
end
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
if (actorSpec) then
break
end
---@type actorcontainer
local currentActorContainer = currentCombatObject:GetContainer(containerId)
---@type actor
local currentActorObject = currentActorContainer:GetActor(actorObject:Name())
if (currentActorObject) then
--iterate among all spells the actor used
if (not actorSpec) then
local spellContainerNames = currentActorObject:GetSpellContainerNames()
for i = 1, #spellContainerNames do
local spellContainer = currentActorObject:GetSpellContainer(spellContainerNames[i])
if (spellContainer) then
for spellId in spellContainer:ListSpells() do
local spec = specSpellList[spellId]
if (spec) then
actorSpec = spec
break
end
end
end
end
end
end
end
end
--attempt to get from overall combat object
if (not actorSpec) then
local overallCombatObject = Details:GetCombat(DETAILS_SEGMENTID_OVERALL)
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
if (actorSpec) then
break
end
local overallActorContainer = overallCombatObject:GetContainer(containerId)
local overallActorObject = overallActorContainer:GetActor(actorObject:Name())
if (overallActorObject) then
if (overallActorObject.spec and overallActorObject.spec ~= 0) then
actorSpec = overallActorObject.spec
break
end
--iterate among all spells the actor used
if (not actorSpec) then
local spellContainerNames = overallActorObject:GetSpellContainerNames()
for i = 1, #spellContainerNames do
local spellContainer = overallActorObject:GetSpellContainer(spellContainerNames[i])
if (spellContainer) then
for spellId in spellContainer:ListSpells() do
local spec = specSpellList[spellId]
if (spec) then
actorSpec = spec
break
end
end
end
end
end
end
end
end
if (actorSpec) then
Details.cached_specs[actorObject.serial] = actorSpec
actorObject:SetSpecId(actorSpec)
actorObject.classe = Details.SpecIDToClass[actorSpec] or actorObject.classe
actorObject.guessing_spec = nil
if (actorContainer) then
actorContainer.need_refresh = true
end
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
actorObject.minha_barra.minha_tabela = nil
Details:ScheduleWindowUpdate(2, true)
end
return actorSpec
end
if (Details.streamer_config.quick_detection) then
if (attempts and attempts < 30) then
payload[3] = attempts + 1
Details:ScheduleTimer("GuessSpec", 1, payload) --todo: replace schedule from ace3 and use our own
end
else
if (attempts and attempts < 4) then
payload[3] = attempts + 1
Details:ScheduleTimer("GuessSpec", 4, payload)
end
end
return false
end
function Details:ReGuessSpec(t) --deprecated
local actorObject, container = t[1], t[2]
local SpecSpellList = Details.SpecSpellList
---@type combat
local combatObject = Details:GetCurrentCombat()
--get from the spell cast list
if (combatObject) then
local spellCastTable = combatObject:GetSpellCastTable(actorObject.nome)
for spellName in pairs(spellCastTable) do
local _, _, _, _, _, _, spellid = GetSpellInfo(spellName)
local spec = SpecSpellList[spellid]
if (spec) then
Details.cached_specs[actorObject.serial] = spec
actorObject:SetSpecId(spec)
actorObject.classe = Details.SpecIDToClass[spec] or actorObject.classe
actorObject.guessing_spec = nil
Details:SendEvent("UNIT_SPEC", nil, actorObject:GetUnitId(), spec, actorObject.serial)
if (container) then
container.need_refresh = true
end
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
actorObject.minha_barra.minha_tabela = nil
Details:ScheduleWindowUpdate (2, true)
end
return spec
end
end
else
if (actorObject.spells) then
for spellid, _ in pairs(actorObject.spells._ActorTable) do
local spec = SpecSpellList [spellid]
if (spec) then
if (spec ~= actorObject.spec) then
Details.cached_specs [actorObject.serial] = spec
actorObject:SetSpecId(spec)
actorObject.classe = Details.SpecIDToClass [spec] or actorObject.classe
Details:SendEvent("UNIT_SPEC", nil, actorObject:GetUnitId(), spec, actorObject.serial)
if (container) then
container.need_refresh = true
end
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
actorObject.minha_barra.minha_tabela = nil
Details:ScheduleWindowUpdate (2, true)
end
return spec
else
break
end
end
end
if (actorObject.classe == "HUNTER") then
local container_misc = Details.tabela_vigente[4]
local index = container_misc._NameIndexTable [actorObject.nome]
if (index) then
local misc_actor = container_misc._ActorTable [index]
local buffs = misc_actor.buff_uptime_spells and misc_actor.buff_uptime_spells._ActorTable
if (buffs) then
for spellid, spell in pairs(buffs) do
local spec = SpecSpellList [spellid]
if (spec) then
if (spec ~= actorObject.spec) then
Details.cached_specs [actorObject.serial] = spec
actorObject:SetSpecId(spec)
actorObject.classe = Details.SpecIDToClass [spec] or actorObject.classe
Details:SendEvent("UNIT_SPEC", nil, actorObject:GetUnitId(), spec, actorObject.serial)
if (container) then
container.need_refresh = true
end
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
actorObject.minha_barra.minha_tabela = nil
Details:ScheduleWindowUpdate (2, true)
end
return spec
else
break
end
end
end
end
end
end
end
end
end
end
+32
View File
@@ -0,0 +1,32 @@
local addonName, details222 = ...
details222.Scheduler = {
Names = {},
Debug = false,
}
local printDebug = function(...)
if (details222.Scheduler.Debug) then
print("ISE:", ...)
end
end
function details222.Scheduler.NewTicker(seconds, callback, name)
local tickerHandler = C_Timer.NewTicker(seconds, callback)
if (name) then
details222.Scheduler.Names[name] = tickerHandler
end
return tickerHandler
end
function details222.Scheduler.Cancel(name)
local ticker = details222.Scheduler.Names[name]
if (ticker) then
ticker:Cancel()
details222.Scheduler.Names[name] = nil
printDebug("Ticker", name, "Cancelled")
else
printDebug("Ticker", name, " Not Found")
end
end
File diff suppressed because it is too large Load Diff
+261
View File
@@ -0,0 +1,261 @@
--install data for raiding tiers
local C_Timer = _G.C_Timer
local DetailsFramework = _G.DetailsFramework
local tinsert = tinsert
local addonName, Details222 = ...
function Details.InstallRaidInfo()
if true then return end -- this needs data for ascension probably
do
local INSTANCE_EJID = 1200
local INSTANCE_MAPID = 2522
local HDIMAGESPATH = [[Details\images\raid]]
local HDFILEPREFIX = 'VaultoftheIncarnatesRaid'
local EJ_LOREBG = 'UI-EJ-LOREBG-VaultoftheIncarnates'
local ENCOUNTER_ID_CL = {
[2587] = 1, --Eranog
[2639] = 2, --Terros
[2590] = 3, --The Primal Council
[2592] = 4, --Sennarth, the Cold Breath
[2635] = 5, --Dathea, Ascended
[2605] = 6, --Kurog Grimtotem
[2614] = 7, --Broodkeeper Diurna
[2607] = 8, --Raszageth the Storm-Eater
2587, 2639, 2590, 2592, 2635, 2605, 2614, 2607,
}
local ENCOUNTER_ID_EJ = {
[2480] = 1, --Eranog
[2500] = 2, --Terros
[2486] = 3, --The Primal Council
[2482] = 4, --Sennarth, the Cold Breath
[2502] = 5, --Dathea, Ascended
[2491] = 6, --Kurog Grimtotem
[2493] = 7, --Broodkeeper Diurna
[2499] = 8, --Raszageth the Storm-Eater
2480, 2500, 2486, 2482, 2502, 2491, 2493, 2499,
}
local BOSSNAMES = {
"Eranog", --1
"Terros", --2
"The Primal Council", --3
"Sennarth, the Cold Breath", --4
"Dathea, Ascended", --5
"Kurog Grimtotem", --6
"Broodkeeper Diurna", --7
"Raszageth the Storm-Eater", --8
}
local ENCOUNTERS = {
{boss = "Eranog", portrait = 4757695},
{boss = "Terros", portrait = 4757701},
{boss = "The Primal Council", portrait = 4757702},
{boss = "Sennarth, the Cold Breath", portrait = 4757699},
{boss = "Dathea, Ascended", portrait = 4757694},
{boss = "Kurog Grimtotem", portrait = 4757696},
{boss = "Broodkeeper Diurna", portrait = 4757693},
{boss = "Raszageth the Storm-Eater", portrait = 4757698},
}
--load encounter journal
DetailsFramework.EncounterJournal.EJ_SelectInstance(INSTANCE_EJID)
local InstanceName = DetailsFramework.EncounterJournal.EJ_GetInstanceInfo(INSTANCE_EJID)
Details:InstallEncounter({
id = INSTANCE_MAPID, --map id
ej_id = INSTANCE_EJID, --encounter journal id
name = InstanceName,
icons = "Interface\\AddOns\\" .. HDIMAGESPATH .. "\\" .. HDFILEPREFIX .. "_BossFaces",
icon = "Interface\\AddOns\\" .. HDIMAGESPATH .. "\\" .. HDFILEPREFIX .. "_Icon256x128",
is_raid = true,
backgroundEJ = "Interface\\EncounterJournal\\" .. EJ_LOREBG,
encounter_ids = ENCOUNTER_ID_EJ,
encounter_ids2 = ENCOUNTER_ID_CL,
boss_names = BOSSNAMES,
encounters = ENCOUNTERS,
boss_ids = {
--npc ids
},
})
end
do
--data for Sanctum of Domination (Shadowlands tier 1)
local INSTANCE_EJID = 1193
local INSTANCE_MAPID = 2450
local HDIMAGESPATH = [[Details\images\raid]]
local HDFILEPREFIX = 'SanctumofDominationRaid'
local EJ_LOREBG = 'UI-EJ-LOREBG-SanctumofDomination'
local ENCOUNTER_ID_CL = {
[2423] = 1, --The Tarragrue
[2433] = 2, --The Eye of the Jailer
[2429] = 3, --The Nine
[2432] = 4, --Remnant of Ner'zhul
[2434] = 5, --Soulrender Dormazain
[2430] = 6, --Painsmith Raznal
[2436] = 7, --Guardian of the First Ones
[2431] = 8, --Fatescribe Roh-Kalo
[2422] = 9, --Kel'Thuzad
[2435] = 10, --Sylvanas Windrunner
2423, 2433, 2429, 2432, 2434, 2430, 2436, 2431, 2422, 2435,
}
local ENCOUNTER_ID_EJ = {
[2435] = 1, --The Tarragrue
[2442] = 2, --The Eye of the Jailer
[2439] = 3, --The Nine
[2444] = 4, --Remnant of Ner'zhul
[2445] = 5, --Soulrender Dormazain
[2443] = 6, --Painsmith Raznal
[2446] = 7, --Guardian of the First Ones
[2447] = 8, --Fatescribe Roh-Kalo
[2440] = 9, --Kel'Thuzad
[2441] = 10, --Sylvanas Windrunner
2435, 2442, 2439, 2444, 2445, 2443, 2446, 2447, 2440, 2441,
}
--load encounter journal
DetailsFramework.EncounterJournal.EJ_SelectInstance(INSTANCE_EJID)
local InstanceName = DetailsFramework.EncounterJournal.EJ_GetInstanceInfo(INSTANCE_EJID)
local BOSSNAMES = {
"The Tarragrue", --1
"The Eye of the Jailer", --2
"The Nine", --3
"Remnant of Ner'zhul", --4
"Soulrender Dormazain", --5
"Painsmith Raznal", --6
"Guardian of the First Ones", --7
"Fatescribe Roh-Kalo", --8
"Kel'Thuzad", --9
"Sylvanas Windrunner", --10
}
local ENCOUNTERS = {
{boss = "The Tarragrue", portrait = 4071444},
{boss = "The Eye of the Jailer", portrait = 4071426},
{boss = "The Nine", portrait = 4071445},
{boss = "Remnant of Ner'zhul", portrait = 4071439},
{boss = "Soulrender Dormazain", portrait = 4071442},
{boss = "Painsmith Raznal", portrait = 4079051},
{boss = "Guardian of the First Ones", portrait = 4071428},
{boss = "Fatescribe Roh-Kalo", portrait = 4071427},
{boss = "Kel'Thuzad", portrait = 4071435},
{boss = "Sylvanas Windrunner", portrait = 4071443},
}
Details:InstallEncounter ({
id = INSTANCE_MAPID, --map id
ej_id = INSTANCE_EJID, --encounter journal id
name = InstanceName,
icons = "Interface\\AddOns\\" .. HDIMAGESPATH .. "\\" .. HDFILEPREFIX .. "_BossFaces",
icon = "Interface\\AddOns\\" .. HDIMAGESPATH .. "\\" .. HDFILEPREFIX .. "_Icon256x128",
is_raid = true,
backgroundEJ = "Interface\\EncounterJournal\\" .. EJ_LOREBG,
encounter_ids = ENCOUNTER_ID_EJ,
encounter_ids2 = ENCOUNTER_ID_CL,
boss_names = BOSSNAMES,
encounters = ENCOUNTERS,
boss_ids = {
--npc ids
},
})
end
do
--data for Castle Nathria (Shadowlands tier 1)
--F:\World of Warcraft\_retail_\BlizzardInterfaceArt\Interface\GLUES\LOADINGSCREENS\Expansion07\Main\LOADINGSCREEN_NzothRAID
local INSTANCE_EJID = 1190
local INSTANCE_MAPID = 2296
local HDIMAGESPATH = [[Details\images\raid]]
local HDFILEPREFIX = "CastleNathriaRaid"
local EJ_LOREBG = "UI-EJ-LOREBG-CastleNathria"
local PORTRAIT_LIST = {
3752190, --Shriekwing - Shriekwing
3753151, --Huntsman Altimor - Huntsman Altimor
3753157, --Kael'thas Sunstrider - Sun King's Salvation
3752156, --Artificer Xy'Mox - Artificer Xy'mox
3752174, --Hungering Destroyer - Hungering Destroyer
3752178, --Lady Inerva Darkvein - Lady Inerva Darkvein
3753159, --Castellan Niklaus - The Council of Blood
3752191, --Sludgefist - Sludgefist
3753156, --General Kaal - Stone Legion Generals
3752159, --Sire Denathrius - Sire Denathrius
}
local ENCOUNTER_ID_CL = {
2398, 2418, 2402, 2405, 2383, 2406, 2412, 2399, 2417, 2407,
[2398] = 1, --Shriekwing
[2418] = 2, --Huntsman Altimor
[2402] = 3, --Kael'thas
[2405] = 4, --Artificer Xy'mox
[2383] = 5, --Hungering Destroyer
[2406] = 6, --Lady Inerva Darkvein
[2412] = 7, --The Council of Blood
[2399] = 8, --Sludgefist
[2417] = 9, --Stone Legion Generals
[2407] = 10, --Sire Denathrius
}
local ENCOUNTER_ID_EJ = {
2393, 2429, 2422, 2418, 2428, 2420, 2426, 2394, 2425, 2424,
[2393] = 1, --Shriekwing
[2429] = 2, --Huntsman Altimor
[2422] = 3, --Sun King's Salvation
[2418] = 4, --Artificer Xy'mox
[2428] = 5, --Hungering Destroyer
[2420] = 6, --Lady Inerva Darkvein
[2426] = 7, --The Council of Blood
[2394] = 8, --Sludgefist
[2425] = 9, --Stone Legion Generals
[2424] = 10, --Sire Denathrius
}
--load encounter journal
DetailsFramework.EncounterJournal.EJ_SelectInstance (INSTANCE_EJID)
local InstanceName = DetailsFramework.EncounterJournal.EJ_GetInstanceInfo (INSTANCE_EJID)
--build the boss name list
local BOSSNAMES = {}
local ENCOUNTERS = {}
for i = 1, #PORTRAIT_LIST do
local bossName = DetailsFramework.EncounterJournal.EJ_GetEncounterInfoByIndex (i, INSTANCE_EJID)
if (bossName) then
tinsert(BOSSNAMES, bossName)
local encounterTable = {
boss = bossName,
portrait = PORTRAIT_LIST [i],
}
tinsert(ENCOUNTERS, encounterTable)
else
break
end
end
Details:InstallEncounter ({
id = INSTANCE_MAPID, --map id
ej_id = INSTANCE_EJID, --encounter journal id
name = InstanceName,
icons = "Interface\\AddOns\\" .. HDIMAGESPATH .. "\\" .. HDFILEPREFIX .. "_BossFaces",
icon = "Interface\\AddOns\\" .. HDIMAGESPATH .. "\\" .. HDFILEPREFIX .. "_Icon256x128",
is_raid = true,
backgroundEJ = "Interface\\EncounterJournal\\" .. EJ_LOREBG,
encounter_ids = ENCOUNTER_ID_EJ,
encounter_ids2 = ENCOUNTER_ID_CL,
boss_names = BOSSNAMES,
encounters = ENCOUNTERS,
boss_ids = {
--npc ids
},
})
end
end
+1
View File
@@ -0,0 +1 @@
local _
+73
View File
@@ -0,0 +1,73 @@
--File Revision: 1
--Last Modification: 19/04/2014
--Change Log:
-- 19/04/2014: File Created.
--Description:
-- this file maintain the main function for row animations
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
local _detalhes = _G.Details
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local _
local addonName, Details222 = ...
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--basic functions
_detalhes.current_row_animation = ""
_detalhes.row_animation_pool = {}
function _detalhes:InstallRowAnimation (name, desc, func, options)
if (not name) then
return false
elseif (not func) then
return false
end
if (not desc) then
desc = ""
end
table.insert(_detalhes.row_animation_pool, {name = name, desc = desc, func = func, options = options})
return true
end
function _detalhes:SelectRowAnimation (name)
for key, value in ipairs(_detalhes.row_animation_pool) do
if (value.name == name) then
_detalhes.current_row_animation = name
return true
end
end
return false
end
function _detalhes:GetRowAnimationList()
local t = {}
for key, value in ipairs(_detalhes.row_animation_pool) do
table.insert(t, value.name)
end
return t
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--install default animations
do
local fade_func = function(row, state)
if (state) then
Details.FadeHandler.Fader(row, "out")
else
Details.FadeHandler.Fader(row, "in")
end
end
local fade_desc = "Default animation, makes the bar fade in or fade out when showing or hiding in the window"
_detalhes:InstallRowAnimation ("Fade", fade_desc , fade_func, nil)
_detalhes:SelectRowAnimation ("Fade")
end
+150
View File
@@ -0,0 +1,150 @@
--[[this file save the data when player leave the game]]
local Details = _G.Details
local addonName, Details222 = ...
function Details:WipeConfig()
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local wipeButton = CreateFrame("button", "DetailsResetConfigButton", UIParent, "BackdropTemplate")
wipeButton:SetSize(270, 40)
wipeButton:SetScript("OnClick", function() Details.wipe_full_config = true; ReloadUI(); end)
wipeButton:SetPoint("center", UIParent, "center", 0, 0)
table.insert(UISpecialFrames, "DetailsResetConfigButton")
DetailsFramework:ApplyStandardBackdrop(wipeButton)
local label = DetailsFramework:CreateLabel(wipeButton, Loc ["STRING_SLASH_WIPECONFIG_CONFIRM"])
label:SetPoint("center", 0, 0)
wipeButton.close_button = CreateFrame("Button", nil, wipeButton, "UIPanelCloseButton")
wipeButton.close_button:SetWidth(16)
wipeButton.close_button:SetHeight(16)
wipeButton.close_button:SetPoint("TOPRIGHT", wipeButton, "TOPRIGHT", -1, -1)
wipeButton.close_button:SetText("X")
wipeButton.close_button:SetFrameLevel(wipeButton:GetFrameLevel()+5)
end
local is_exception = {
["nick_tag_cache"] = true
}
function Details:SaveLocalInstanceConfig()
for index, instance in Details:ListInstances() do
--check for the max size toggle, don't save it
if (instance.is_in_max_size) then
instance.is_in_max_size = false
instance:SetSize(instance.original_width, instance.original_height)
end
--save local instance data
local a1, a2 = instance:GetDisplay()
local t = {
pos = Details.CopyTable(instance:GetPosition()),
is_open = instance:IsEnabled(),
attribute = a1 or 1,
sub_attribute = a2 or 1,
modo = instance:GetMode() or 2,
mode = instance:GetMode() or 2,
segment = instance:GetSegment() or 0,
snap = Details.CopyTable(instance.snap),
horizontalSnap = instance.horizontalSnap,
verticalSnap = instance.verticalSnap,
sub_atributo_last = instance.sub_atributo_last or {1, 1, 1, 1, 1},
isLocked = instance.isLocked,
last_raid_plugin = instance.last_raid_plugin
}
if (t.isLocked == nil) then
t.isLocked = false
end
if (Details.profile_save_pos) then
local cprofile = Details:GetProfile()
local skin = cprofile.instances [instance:GetId()]
if (skin) then
t.pos = Details.CopyTable(skin.__pos)
t.horizontalSnap = skin.__snapH
t.verticalSnap = skin.__snapV
t.snap = Details.CopyTable(skin.__snap)
t.is_open = skin.__was_opened
t.isLocked = skin.__locked
end
end
Details.local_instances_config [index] = t
end
end
function Details:SaveConfig()
--save character instance settings, e.g. which attribute is selected, position, etc
Details:SaveLocalInstanceConfig()
--cleanup
Details:PrepareTablesForSave()
_detalhes_database.tabela_instancias = {} --Details.tabela_instancias --[[instances now saves only inside the profile --]]
_detalhes_database.tabela_historico = Details.tabela_historico
if (Details.overall_clear_logout) then
if (_detalhes_database.tabela_overall) then
_detalhes_database.tabela_overall = nil
end
else
_detalhes_database.tabela_overall = Details.tabela_overall
end
local name, instanceType = GetInstanceInfo()
if (instanceType == "party" or instanceType == "raid") then
--save pet ownership information
_detalhes_database.saved_pet_cache = Details222.PetContainer.GetPets()
end
--clear temporarly time data (charts)
xpcall(Details.TimeDataCleanUpTemporary, Details.saver_error_func)
--buffs - feature lost in time
xpcall(Details.Buffs.SaveBuffs, Details.saver_error_func)
--date
Details.last_day = date("%d")
--save character data (unique for each character)
for key in pairs(Details.default_player_data) do
if (not is_exception[key]) then
_detalhes_database[key] = Details[key]
end
end
--save shared data (shared among all characters)
for key in pairs(Details.default_global_data) do
if (key ~= "__profiles") then
_detalhes_global[key] = Details[key]
end
end
--plugin for solo mode (currently none exists)
if (Details.SoloTables.Mode) then
_detalhes_database.SoloTablesSaved = {}
_detalhes_database.SoloTablesSaved.Mode = Details.SoloTables.Mode
if (Details.SoloTables.Plugins[Details.SoloTables.Mode]) then
_detalhes_database.SoloTablesSaved.LastSelected = Details.SoloTables.Plugins[Details.SoloTables.Mode].real_name
end
end
_detalhes_database.RaidTablesSaved = nil
--save bookmark tables
_detalhes_global.switchSaved.slots = Details.switch.slots
_detalhes_global.switchSaved.table = Details.switch.table
--last boss (boss name)
_detalhes_database.last_encounter = Details.last_encounter
--save the details version of the last time the user logged out
_detalhes_database.last_realversion = Details.realversion --core number
_detalhes_database.last_version = Details.userversion --version
_detalhes_global.got_first_run = true
end
+71
View File
@@ -0,0 +1,71 @@
local Details = _G.Details
local DF = _G.DetailsFramework
local C_Timer = _G.C_Timer
local unpack = _G.unpack
local addonName, Details222 = ...
--make a namespace for schedules
Details.Schedules = {}
local errorHandler = function(str)
return str
end
--run a scheduled function with its payload
local triggerScheduledTick = function(tickerObject)
local payload = tickerObject.payload
local callback = tickerObject.callback
local result, errortext = xpcall(callback, geterrorhandler(), unpack(payload))
if (not result) then
--Details:Msg("Error:", errortext, tickerObject.name or "")
end
return result
end
--schedule to repeat a task with an interval of @time
function Details.Schedules.NewTicker(time, callback, ...)
local payload = {...}
local newTicker = C_Timer.NewTicker(time, triggerScheduledTick)
newTicker.payload = payload
newTicker.callback = callback
--debug
newTicker.path = debugstack()
--
return newTicker
end
--cancel an ongoing ticker
function Details.Schedules.Cancel(tickerObject)
--ignore if there's no ticker object
if (tickerObject) then
return tickerObject:Cancel()
end
end
--schedule a task with an interval of @time
function Details.Schedules.NewTimer(time, callback, ...)
local payload = {...}
local newTimer = C_Timer.NewTimer(time, triggerScheduledTick)
newTimer.payload = payload
newTimer.callback = callback
--debug
newTimer.path = debugstack()
--
return newTimer
end
--schedule a task with an interval of @time without payload
function Details.Schedules.After(time, callback)
C_Timer.After(time, callback)
end
function Details.Schedules.SetName(object, name)
object.name = name
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+379
View File
@@ -0,0 +1,379 @@
do
local Details = _G.Details
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local addonName, Details222 = ...
local _
local rawget = rawget
local rawset = rawset
local setmetatable = setmetatable
local unpack = unpack
local tinsert = table.insert
local tremove = tremove
local C_Timer = C_Timer
--is this a timewalking exp?
--default spell cache container
Details.spellcache = {}
local unknowSpell = {Loc ["STRING_UNKNOWSPELL"], _, "Interface\\Icons\\Ability_Druid_Eclipse"}
local allSpellNames
--build a cache with spell names poiting to their icons
allSpellNames = {}
local maxSpellId = 90000
for i = 1, maxSpellId do
local spellName, _, spellIcon = GetSpellInfo(i)
if spellName and spellIcon and spellIcon ~= "Interface\\Icons\\trade_engineering" and not allSpellNames[spellName] then
allSpellNames[spellName] = spellIcon
end
end
local GetSpellInfoClassic = function(spell)
local spellName, _, spellIcon
if (spell == 0) then
spellName = ATTACK or "It's Blizzard Fault!"
spellIcon = [[Interface\ICONS\INV_Sword_04]]
elseif (spell == "!Melee" or spell == 1) then
spellName = ATTACK or "It's Blizzard Fault!"
spellIcon = [[Interface\ICONS\INV_Sword_04]]
elseif (spell == "!Autoshot" or spell == 2) then
spellName = Loc ["STRING_AUTOSHOT"]
spellIcon = [[Interface\ICONS\INV_Weapon_Bow_07]]
else
spellName, _, spellIcon = GetSpellInfo(spell)
end
if (not spellName) then
return spell, _, (allSpellNames[spell] or [[Interface\ICONS\INV_Sword_04]])
end
return spellName, _, (allSpellNames[spell] or spellIcon)
end
--reset spell cache, called from the loaddata.lua and when the segments container get cleared
function Details:ClearSpellCache()
Details.spellcache = setmetatable({},
{__index = function(spellCache, key)
if (key) then
do
--check if the spell is already in the cache, if so, return it
local spellInfo = rawget(spellCache, key)
if (spellInfo) then
return spellInfo
end
end
local spellInfo
spellInfo = {GetSpellInfoClassic(key)}
spellCache[key] = spellInfo
return spellInfo
else
return unknowSpell
end
end}
)
--built-in overwrites
for spellId, spellTable in pairs(Details.SpellOverwrite) do
local spellName, _, spellIcon = GetSpellInfo(spellId)
rawset(Details.spellcache, spellId, {spellTable.name or spellName, 1, spellTable.icon or spellIcon})
end
--user overwrites
-- [1] spellid [2] spellname [3] spellicon
for index, spellTable in ipairs(Details.savedCustomSpells) do
rawset(Details.spellcache, spellTable[1], {spellTable[2], 1, spellTable[3]})
end
end
---@type table<number, customspellinfo>
local defaultSpellCustomization = {}
---@type table<number, customiteminfo>
local customItemList = {}
Details222.CustomItemList = customItemList
local iconSize = 14
local coords = {0.14, 0.86, 0.14, 0.86}
---@param itemId number
---@return string
local formatTextForItem = function(itemId)
local result = ""
local itemIcon = GetItemIconInstant(itemId)
local itemName = GetItemName(itemId)
if (itemIcon and itemName) then
--limit the amount of characters of the item name
if (GetLocale() == "zhCN" or GetLocale() == "zhTW" or GetLocale() == "koKR") then
if (#itemName > 56) then
itemName = string.sub(itemName, 1, 56)
end
else
if (#itemName > 20) then
itemName = string.sub(itemName, 1, 20)
end
end
result = "" .. CreateTextureMarkup(itemIcon, iconSize, iconSize, iconSize, iconSize, unpack(coords)) .. " " .. itemName .. ""
end
return result
end
defaultSpellCustomization = {
[1] = {name = _G["MELEE"], icon = [[Interface\ICONS\INV_Sword_04]]},
[2] = {name = Loc ["STRING_AUTOSHOT"], icon = [[Interface\ICONS\INV_Weapon_Bow_07]]},
[3] = {name = Loc ["STRING_ENVIRONMENTAL_FALLING"], icon = [[Interface\ICONS\Spell_Magic_FeatherFall]]},
[4] = {name = Loc ["STRING_ENVIRONMENTAL_DROWNING"], icon = [[Interface\ICONS\Ability_Suffocate]]},
[5] = {name = Loc ["STRING_ENVIRONMENTAL_FATIGUE"], icon = [[Interface\ICONS\Spell_Arcane_MindMastery]]},
[6] = {name = Loc ["STRING_ENVIRONMENTAL_FIRE"], icon = [[Interface\ICONS\INV_SummerFest_FireSpirit]]},
[7] = {name = Loc ["STRING_ENVIRONMENTAL_LAVA"], icon = [[Interface\ICONS\Ability_Rhyolith_Volcano]]},
[8] = {name = Loc ["STRING_ENVIRONMENTAL_SLIME"], icon = [[Interface\ICONS\Ability_Creature_Poison_02]]},
}
function Details222.Pets.GetPetNameFromCustomSpells(petName, spellId, npcId)
---@type customiteminfo
local customItem = Details222.CustomItemList[spellId]
if (customItem and customItem.isSummon) then
local defaultName = customItem.defaultName
if (defaultName) then
petName = defaultName
if (customItem.nameExtra) then
petName = petName .. " " .. customItem.nameExtra
end
return petName
end
end
return petName
end
if (LIB_OPEN_RAID_SPELL_CUSTOM_NAMES) then
for spellId, customTable in pairs(LIB_OPEN_RAID_SPELL_CUSTOM_NAMES) do
local customName = customTable.name
if (customName) then
defaultSpellCustomization[spellId] = customName
end
end
end
function Details:GetDefaultCustomSpellsList()
return defaultSpellCustomization
end
function Details:GetDefaultCustomItemList()
return customItemList
end
function Details:UserCustomSpellUpdate(index, spellName, spellIcon) --called from the options panel > rename spells
---@type savedspelldata
local savedSpellData = Details.savedCustomSpells[index]
if (savedSpellData) then
local spellId = savedSpellData[1]
savedSpellData[2], savedSpellData[3] = spellName or savedSpellData[2], spellIcon or savedSpellData[3]
rawset(Details.spellcache, spellId, {savedSpellData[2], 1, savedSpellData[3]})
Details.userCustomSpells[spellId] = true
return true
else
return false
end
end
function Details:UserCustomSpellReset(index)
---@type savedspelldata
local savedSpellData = Details.savedCustomSpells[index]
if (savedSpellData) then
local spellId = savedSpellData [1]
local spellName, _, spellIcon = GetSpellInfo(spellId)
if (defaultSpellCustomization[spellId]) then
spellName = defaultSpellCustomization[spellId].name
spellIcon = defaultSpellCustomization[spellId].icon or spellIcon or [[Interface\InventoryItems\WoWUnknownItem01]]
end
if (not spellName) then
spellName = "Unknown"
end
if (not spellIcon) then
spellIcon = [[Interface\InventoryItems\WoWUnknownItem01]]
end
rawset(Details.spellcache, spellId, {spellName, 1, spellIcon})
savedSpellData[2] = spellName
savedSpellData[3] = spellIcon
end
end
function Details:FillUserCustomSpells()
for spellId, spellTable in pairs(defaultSpellCustomization) do
local spellName, _, spellIcon = Details.GetSpellInfo(spellId)
Details:UserCustomSpellAdd(spellId, spellTable.name or spellName or "Unknown", spellTable.icon or spellIcon or [[Interface\InventoryItems\WoWUnknownItem01]])
end
--itens
--[381760] = {name = formatTextForItem(193786), isPassive = true, itemId = 193786, nameExtra = ""|nil},
---@type number, customiteminfo
for spellId, itemInfo in pairs(customItemList) do
local bIsPassive = itemInfo.isPassive
local itemId = itemInfo.itemId
local nameExtra = itemInfo.nameExtra
local spellName, _, spellIcon = GetSpellInfo(spellId)
spellIcon = itemInfo.icon or spellIcon or [[Interface\InventoryItems\WoWUnknownItem01]]
local itemName = formatTextForItem(itemId)
if (itemName ~= "") then
if (nameExtra) then
itemName = itemName .. " " .. nameExtra
end
Details:UserCustomSpellAdd(spellId, itemName, spellIcon or [[Interface\InventoryItems\WoWUnknownItem01]])
else
if (not Details.UpdateIconsTimer or Details.UpdateIconsTimer:IsCancelled()) then
Details.UpdateIconsTimer = C_Timer.NewTimer(3, Details.FillUserCustomSpells)
end
end
end
for i = #Details.savedCustomSpells, 1, -1 do
---@type savedspelldata
local savedSpellData = Details.savedCustomSpells[i]
local spellId = savedSpellData[1]
if (spellId > 10) then
local doesSpellExists = GetSpellInfo(spellId)
if (not doesSpellExists) then
tremove(Details.savedCustomSpells, i)
end
end
end
end
function Details:UserCustomSpellAdd(spellId, spellName, spellIcon, bAddedByUser)
if (Details.userCustomSpells[spellId]) then
if (not bAddedByUser) then
return
end
end
local isOverwrite = false
for index, savedSpellData in ipairs(Details.savedCustomSpells) do
if (savedSpellData[1] == spellId) then
savedSpellData[2] = spellName
savedSpellData[3] = spellIcon
isOverwrite = true
break
end
end
if (not isOverwrite) then
tinsert(Details.savedCustomSpells, {spellId, spellName, spellIcon})
end
rawset(Details.spellcache, spellId, {spellName, 1, spellIcon})
if (bAddedByUser) then
Details.userCustomSpells[spellId] = true
end
end
function Details:UserCustomSpellRemove(index)
---@type savedspelldata
local savedSpellData = Details.savedCustomSpells[index]
if (savedSpellData) then
local spellId = savedSpellData[1]
local spellName, _, spellIcon = GetSpellInfo(spellId)
if (spellName) then
rawset(Details.spellcache, spellId, {spellName, 1, spellIcon})
end
return tremove(Details.savedCustomSpells, index)
end
return false
end
--overwrite for API GetSpellInfo function
Details.getspellinfo = function(spellId)
return unpack(Details.spellcache[spellId]) --won't be nil due to the __index metatable in the spellcache table
end
Details.GetSpellInfo = Details.getspellinfo
function Details.GetCustomSpellInfo(spellId)
local spellName, _, spellIcon = Details.GetSpellInfo(spellId)
local customInfo = defaultSpellCustomization[spellId]
if (customInfo) then
local defaultName, bCanStack = customInfo.defaultName, customInfo.breakdownCanStack
return spellName, _, spellIcon, defaultName, bCanStack
end
return spellName, _, spellIcon
end
function Details.GetItemSpellInfo(spellId)
local spellInfo = customItemList[spellId]
if (spellInfo) then
local defaultSpellName, castSpellId, itemId, bIsPassive, bOnUse, nameExtra = spellInfo.defaultName, spellInfo.castId, spellInfo.itemId, spellInfo.onUse, spellInfo.isPassive, spellInfo.nameExtra
return defaultSpellName, castSpellId, itemId, bIsPassive, bOnUse, nameExtra
end
end
--overwrite SpellInfo if the spell is a DoT, so Details.GetSpellInfo will return the name modified
function Details:SetAsDotSpell(spellId)
--do nothing if this spell already has a customization
if (defaultSpellCustomization[spellId]) then
return
end
--do nothing if the spell is already cached
local spellInfo = rawget(Details.spellcache, spellId)
if (spellInfo) then
return
end
local spellName, rank, spellIcon = Details.GetSpellInfo(spellId)
if (not spellName) then
spellName, rank, spellIcon = GetSpellInfo(spellId)
end
if (spellName) then
rawset(Details.spellcache, spellId, {spellName .. Loc ["STRING_DOT"], rank, spellIcon})
else
rawset(Details.spellcache, spellId, {"Unknown DoT Spell? " .. Loc ["STRING_DOT"], rank, [[Interface\InventoryItems\WoWUnknownItem01]]})
end
end
--overwrite SpellInfo if the spell is a HoT, so Details.GetSpellInfo will return the name modified
function Details:SetAsHotSpell(spellId)
--do nothing if this spell already has a customization
if (defaultSpellCustomization[spellId]) then
return
end
--do nothing if the spell is already cached
local spellInfo = rawget(Details.spellcache, spellId)
if (spellInfo) then
return
end
local spellName, rank, spellIcon = Details.GetSpellInfo(spellId)
if (not spellName) then
spellName, rank, spellIcon = GetSpellInfo(spellId)
end
if (spellName) then
rawset(Details.spellcache, spellId, {spellName .. Loc ["STRING_HOT"], rank, spellIcon})
else
rawset(Details.spellcache, spellId, {"Unknown HoT Spell? " .. Loc ["STRING_HOT"], rank, [[Interface\InventoryItems\WoWUnknownItem01]]})
end
end
end
+311
View File
@@ -0,0 +1,311 @@
do
local _detalhes = _G.Details
local addonName, Details222 = ...
local GetSpellInfo = Details222.GetSpellInfo
--import potion list from the framework
_detalhes.PotionList = {}
for spellID, _ in pairs(DetailsFramework.PotionIDs) do
_detalhes.PotionList [spellID] = true
end
_detalhes.SpecSpellList = LIB_OPEN_RAID_SPEC_SPELL_LIST
_detalhes.ClassSpellList = LIB_OPEN_RAID_CLASS_SPELL_LIST
_detalhes.SpecIDToClass = {}
for _, class in ipairs(CLASS_SORT_ORDER) do
local specs = C_ClassInfo.GetAllSpecs(class)
for index, spec in ipairs(specs) do
local ok, specInfo = pcall(C_ClassInfo.GetSpecInfo, class, spec)
if ok and specInfo then
_detalhes.SpecIDToClass[specInfo.ID] = class
end
end
end
-- redirect AbsorbSpells to check IsAbsorbSpell
_detalhes.AbsorbSpells = setmetatable({}, { __index = function(t,k) local isAbsorb, mask = IsAbsorbSpell(k) return isAbsorb end })
-- checks if a spell absorbs a specific school
_detalhes.IsAbsorbSpellSchool = function(spellID, school)
local isAbsorb, mask = IsAbsorbSpell(spellID)
return isAbsorb and mask and (mask == 127 or bit.contains(mask, school))
end
local allowedCooldownTypes = { --LIB_OPEN_RAID_COOLDOWNS_INFO types
[1] = false, --attack
[2] = true, --defensive
[3] = true, --defensive
[4] = true, --defensive
[5] = false, --utility
[6] = false, --interrupt
[7] = false, --dispel
[8] = false, --crowd control
[9] = false, --racials
[10] = false, --item heal
[11] = false, --item power
[12] = false, --item utility
}
local getCooldownsForClass = function(class)
local result = {}
--Use LibOpenRaid if possible. Otherwise fallback to DF.
if (LIB_OPEN_RAID_COOLDOWNS_INFO) then
for spellId, spellInfo in pairs(LIB_OPEN_RAID_COOLDOWNS_INFO) do
if (class == spellInfo.class and allowedCooldownTypes[spellInfo.type]) then
result[#result+1] = spellId
end
end
else
for spellId, spellInfo in pairs(_G.DetailsFramework.CooldownsInfo) do
if (class == spellInfo.class) then
result[#result+1] = spellId
end
end
end
return result
end
_detalhes.DefensiveCooldownSpells = {}
for _, class in ipairs(CLASS_SORT_ORDER) do
_detalhes.DefensiveCooldownSpells[class] = getCooldownsForClass(class)
end
_detalhes.HarmfulSpells = {
-- [spellID] = true,
}
_detalhes.MiscClassSpells = {
-- [spellID] = true,
}
_detalhes.AttackCooldownSpells = {
-- [spellID] = true,
}
_detalhes.HelpfulSpells = {
-- [spellID] = true,
}
_detalhes.SpellOverwrite = {
--[124464] = {name = GetSpellInfo(124464) .. " (" .. Loc ["STRING_MASTERY"] .. ")"}, --shadow word: pain mastery proc (priest)
}
_detalhes.spells_school = {
[1] = {name = STRING_SCHOOL_PHYSICAL , formated = "|cFFFFFF00" .. STRING_SCHOOL_PHYSICAL .. "|r", hex = "FFFFFF00", rgb = {255, 255, 0}, decimals = {1.00, 1.00, 0.00}},
[2] = {name = STRING_SCHOOL_HOLY , formated = "|cFFFFE680" .. STRING_SCHOOL_HOLY .. "|r", hex = "FFFFE680", rgb = {255, 230, 128}, decimals = {1.00, 0.90, 0.50}},
[4] = {name = STRING_SCHOOL_FIRE , formated = "|cFFFF8000" .. STRING_SCHOOL_FIRE .. "|r", hex = "FFFF8000", rgb = {255, 128, 0}, decimals = {1.00, 0.50, 0.00}},
[8] = {name = STRING_SCHOOL_NATURE , formated = "|cFFbeffbe" .. STRING_SCHOOL_NATURE .. "|r", hex = "FFbeffbe", rgb = {190, 190, 190}, decimals = {0.7451, 1.0000, 0.7451}},
[16] = {name = STRING_SCHOOL_FROST, formated = "|cFF80FFFF" .. STRING_SCHOOL_FROST .. "|r", hex = "FF80FFFF", rgb = {128, 255, 255}, decimals = {0.50, 1.00, 1.00}},
[32] = {name = STRING_SCHOOL_SHADOW, formated = "|cFF8080FF" .. STRING_SCHOOL_SHADOW .. "|r", hex = "FF8080FF", rgb = {128, 128, 255}, decimals = {0.50, 0.50, 1.00}},
[64] = {name = STRING_SCHOOL_ARCANE, formated = "|cFFFF80FF" .. STRING_SCHOOL_ARCANE .. "|r", hex = "FFFF80FF", rgb = {255, 128, 255}, decimals = {1.00, 0.50, 1.00}},
[3] = {name = STRING_SCHOOL_HOLYSTRIKE , formated = "|cFFFFF240" .. STRING_SCHOOL_HOLYSTRIKE .. "|r", hex = "FFFFF240", rgb = {255, 64, 64}, decimals = {1.0000, 0.9490, 0.2510}}, --#FFF240
[5] = {name = STRING_SCHOOL_FLAMESTRIKE, formated = "|cFFFFB900" .. STRING_SCHOOL_FLAMESTRIKE .. "|r", hex = "FFFFB900", rgb = {255, 0, 0}, decimals = {1.0000, 0.7255, 0.0000}}, --#FFB900
[6] = {name = STRING_SCHOOL_HOLYFIRE , formated = "|cFFFFD266" .. STRING_SCHOOL_HOLYFIRE .. "|r", hex = "FFFFD266", rgb = {255, 102, 102}, decimals = {1.0000, 0.8235, 0.4000}}, --#FFD266
[9] = {name = STRING_SCHOOL_STORMSTRIKE, formated = "|cFFAFFF23" .. STRING_SCHOOL_STORMSTRIKE .. "|r", hex = "FFAFFF23", rgb = {175, 35, 35}, decimals = {0.6863, 1.0000, 0.1373}}, --#AFFF23
[10] = {name = STRING_SCHOOL_HOLYSTORM , formated = "|cFFC1EF6E" .. STRING_SCHOOL_HOLYSTORM .. "|r", hex = "FFC1EF6E", rgb = {193, 110, 110}, decimals = {0.7569, 0.9373, 0.4314}}, --#C1EF6E
[12] = {name = STRING_SCHOOL_FIRESTORM, formated = "|cFFAFB923" .. STRING_SCHOOL_FIRESTORM .. "|r", hex = "FFAFB923", rgb = {175, 35, 35}, decimals = {0.6863, 0.7255, 0.1373}}, --#AFB923
[17] = {name = STRING_SCHOOL_FROSTSTRIKE , formated = "|cFFB3FF99" .. STRING_SCHOOL_FROSTSTRIKE .. "|r", hex = "FFB3FF99", rgb = {179, 153, 153}, decimals = {0.7020, 1.0000, 0.6000}},--#B3FF99
[18] = {name = STRING_SCHOOL_HOLYFROST , formated = "|cFFCCF0B3" .. STRING_SCHOOL_HOLYFROST .. "|r", hex = "FFCCF0B3", rgb = {204, 179, 179}, decimals = {0.8000, 0.9412, 0.7020}},--#CCF0B3
[20] = {name = STRING_SCHOOL_FROSTFIRE, formated = "|cFFC0C080" .. STRING_SCHOOL_FROSTFIRE .. "|r", hex = "FFC0C080", rgb = {192, 128, 128}, decimals = {0.7529, 0.7529, 0.5020}}, --#C0C080
[24] = {name = STRING_SCHOOL_FROSTSTORM, formated = "|cFF69FFAF" .. STRING_SCHOOL_FROSTSTORM .. "|r", hex = "FF69FFAF", rgb = {105, 175, 175}, decimals = {0.4118, 1.0000, 0.6863}}, --#69FFAF
[33] = {name = STRING_SCHOOL_SHADOWSTRIKE , formated = "|cFFC6C673" .. STRING_SCHOOL_SHADOWSTRIKE .. "|r", hex = "FFC6C673", rgb = {198, 115, 115}, decimals = {0.7765, 0.7765, 0.4510}},--#C6C673
[34] = {name = STRING_SCHOOL_SHADOWHOLY, formated = "|cFFD3C2AC" .. STRING_SCHOOL_SHADOWHOLY .. "|r", hex = "FFD3C2AC", rgb = {211, 172, 172}, decimals = {0.8275, 0.7608, 0.6745}},--#D3C2AC
[36] = {name = STRING_SCHOOL_SHADOWFLAME , formated = "|cFFB38099" .. STRING_SCHOOL_SHADOWFLAME .. "|r", hex = "FFB38099", rgb = {179, 153, 153}, decimals = {0.7020, 0.5020, 0.6000}}, -- #B38099
[40] = {name = STRING_SCHOOL_SHADOWSTORM, formated = "|cFF6CB3B8" .. STRING_SCHOOL_SHADOWSTORM .. "|r", hex = "FF6CB3B8", rgb = {108, 184, 184}, decimals = {0.4235, 0.7020, 0.7216}}, --#6CB3B8
[48] = {name = STRING_SCHOOL_SHADOWFROST , formated = "|cFF80C6FF" .. STRING_SCHOOL_SHADOWFROST .. "|r", hex = "FF80C6FF", rgb = {128, 255, 255}, decimals = {0.5020, 0.7765, 1.0000}},--#80C6FF
[65] = {name = STRING_SCHOOL_SPELLSTRIKE, formated = "|cFFFFCC66" .. STRING_SCHOOL_SPELLSTRIKE .. "|r", hex = "FFFFCC66", rgb = {255, 102, 102}, decimals = {1.0000, 0.8000, 0.4000}},--#FFCC66
[66] = {name = STRING_SCHOOL_DIVINE, formated = "|cFFFFBDB3" .. STRING_SCHOOL_DIVINE .. "|r", hex = "FFFFBDB3", rgb = {255, 179, 179}, decimals = {1.0000, 0.7412, 0.7020}},--#FFBDB3
[68] = {name = STRING_SCHOOL_SPELLFIRE, formated = "|cFFFF808C" .. STRING_SCHOOL_SPELLFIRE .. "|r", hex = "FFFF808C", rgb = {255, 140, 140}, decimals = {1.0000, 0.5020, 0.5490}}, --#FF808C
[72] = {name = STRING_SCHOOL_SPELLSTORM, formated = "|cFFAFB9AF" .. STRING_SCHOOL_SPELLSTORM .. "|r", hex = "FFAFB9AF", rgb = {175, 175, 175}, decimals = {0.6863, 0.7255, 0.6863}}, --#AFB9AF
[80] = {name = STRING_SCHOOL_SPELLFROST , formated = "|cFFC0C0FF" .. STRING_SCHOOL_SPELLFROST .. "|r", hex = "FFC0C0FF", rgb = {192, 255, 255}, decimals = {0.7529, 0.7529, 1.0000}},--#C0C0FF
[96] = {name = STRING_SCHOOL_SPELLSHADOW, formated = "|cFFB980FF" .. STRING_SCHOOL_SPELLSHADOW .. "|r", hex = "FFB980FF", rgb = {185, 255, 255}, decimals = {0.7255, 0.5020, 1.0000}},--#B980FF
[28] = {name = STRING_SCHOOL_ELEMENTAL, formated = "|cFF0070DE" .. STRING_SCHOOL_ELEMENTAL .. "|r", hex = "FF0070DE", rgb = {0, 222, 222}, decimals = {0.0000, 0.4392, 0.8706}},
[124] = {name = STRING_SCHOOL_CHROMATIC, formated = "|cFFC0C0C0" .. STRING_SCHOOL_CHROMATIC .. "|r", hex = "FFC0C0C0", rgb = {192, 192, 192}, decimals = {0.7529, 0.7529, 0.7529}},
[126] = {name = STRING_SCHOOL_MAGIC , formated = "|cFF1111FF" .. STRING_SCHOOL_MAGIC .. "|r", hex = "FF1111FF", rgb = {17, 255, 255}, decimals = {0.0667, 0.0667, 1.0000}},
[127] = {name = STRING_SCHOOL_CHAOS, formated = "|cFFFF1111" .. STRING_SCHOOL_CHAOS .. "|r", hex = "FFFF1111", rgb = {255, 17, 17}, decimals = {1.0000, 0.0667, 0.0667}},
--[[custom]] [1024] = {name = "Reflection", formated = "|cFFFFFFFF" .. "Reflection" .. "|r", hex = "FFFFFFFF", rgb = {255, 255, 255}, decimals = {1, 1, 1}},
}
---return the school of a spell, this value is gotten from a cache
---@param spellID spellid|spellname
---@return spellschool
function Details:GetSpellSchool(spellID)
if (spellID == "number") then
spellID = GetSpellInfo(spellID)
end
local school = Details.spell_school_cache[spellID] or 1
return school
end
---return the name of a spell school
---@param school spellschool
---@return string
function Details:GetSpellSchoolName(school)
return Details.spells_school [school] and Details.spells_school [school].name or ""
end
---return the name of a spell school containing the scape code to color the name by the school color
---@param school spellschool
---@return string
function Details:GetSpellSchoolFormatedName(school)
return Details.spells_school[school] and Details.spells_school[school].formated or ""
end
local default_school_color = {145/255, 180/255, 228/255}
---return the color of a spell school
---@param school spellschool
---@return red, green, blue
function Details:GetSpellSchoolColor(school)
return unpack(Details.spells_school[school] and Details.spells_school[school].decimals or default_school_color)
end
function Details:GetCooldownList(class)
class = class or select(2, UnitClass("player"))
return Details.DefensiveCooldownSpells[class]
end
end
--save spells of a segment
local SplitLoadFrame = CreateFrame("frame")
local MiscContainerNames = {
"dispell_spells",
"cooldowns_defensive_spells",
"debuff_uptime_spells",
"buff_uptime_spells",
"interrupt_spells",
"cc_done_spells",
"cc_break_spells",
"ress_spells",
}
local SplitLoadFunc = function(self, deltaTime)
--which container it will iterate on this tick
local container = Details.tabela_vigente and Details.tabela_vigente [SplitLoadFrame.NextActorContainer] and Details.tabela_vigente [SplitLoadFrame.NextActorContainer]._ActorTable
if (not container) then
if (Details.debug) then
--Details:Msg("(debug) finished index spells.")
end
SplitLoadFrame:SetScript("OnUpdate", nil)
return
end
local inInstance = IsInInstance()
local isEncounter = Details.tabela_vigente and Details.tabela_vigente.is_boss
local encounterID = isEncounter and isEncounter.id
--get the actor
local actorToIndex = container [SplitLoadFrame.NextActorIndex]
--no actor? go to the next container
if (not actorToIndex) then
SplitLoadFrame.NextActorIndex = 1
SplitLoadFrame.NextActorContainer = SplitLoadFrame.NextActorContainer + 1
--finished all the 4 container? kill the process
if (SplitLoadFrame.NextActorContainer == 5) then
SplitLoadFrame:SetScript("OnUpdate", nil)
if (Details.debug) then
--Details:Msg("(debug) finished index spells.")
end
return
end
else
--++
SplitLoadFrame.NextActorIndex = SplitLoadFrame.NextActorIndex + 1
--get the class name or the actor name in case the actor isn't a player
local source
if (inInstance) then
source = RAID_CLASS_COLORS [actorToIndex.classe] and Details.classstring_to_classid [actorToIndex.classe] or actorToIndex.nome
else
source = RAID_CLASS_COLORS [actorToIndex.classe] and Details.classstring_to_classid [actorToIndex.classe]
end
--if found a valid actor
if (source) then
--if is damage, heal or energy
if (SplitLoadFrame.NextActorContainer == 1 or SplitLoadFrame.NextActorContainer == 2 or SplitLoadFrame.NextActorContainer == 3) then
--get the spell list in the spells container
local spellList = actorToIndex.spells and actorToIndex.spells._ActorTable
if (spellList) then
local SpellPool = Details.spell_pool
local EncounterSpellPool = Details.encounter_spell_pool
for spellID, _ in pairs(spellList) do
if (not SpellPool [spellID]) then
SpellPool [spellID] = source
end
if (encounterID and not EncounterSpellPool [spellID]) then
if (actorToIndex:IsEnemy()) then
EncounterSpellPool [spellID] = {encounterID, source}
end
end
end
end
--if is a misc container
elseif (SplitLoadFrame.NextActorContainer == 4) then
for _, containerName in ipairs(MiscContainerNames) do
--check if the actor have this container
if (actorToIndex [containerName]) then
local spellList = actorToIndex [containerName]._ActorTable
if (spellList) then
local spellPool = Details.spell_pool
local encounterSpellPool = Details.encounter_spell_pool
for spellId, _ in pairs(spellList) do
if (not spellPool[spellId]) then
spellPool[spellId] = source
end
if (encounterID and not encounterSpellPool[spellId]) then
if (actorToIndex:IsEnemy()) then
encounterSpellPool[spellId] = {encounterID, source}
end
end
end
end
end
end
--[=[ .spell_cast is deprecated
--spells the actor casted
if (actorToIndex.spell_cast) then
local spellPool = Details.spell_pool
local encounterSpellPool = Details.encounter_spell_pool
for spellName, _ in pairs(actorToIndex.spell_cast) do
local _, _, _, _, _, _, spellId = GetSpellInfo(spellName)
if (spellId) then
if (not spellPool[spellId]) then
spellPool[spellId] = source
end
if (encounterID and not encounterSpellPool[spellId]) then
if (actorToIndex:IsEnemy()) then
encounterSpellPool[spellId] = {encounterID, source}
end
end
end
end
end
--]=]
end
end
end
end
function Details.StoreSpells()
if (Details.debug) then
--Details:Msg("(debug) started to index spells.")
end
SplitLoadFrame:SetScript("OnUpdate", SplitLoadFunc)
SplitLoadFrame.NextActorContainer = 1
SplitLoadFrame.NextActorIndex = 1
end
+243
View File
@@ -0,0 +1,243 @@
local Details = _G.Details
local addonName, Details222 = ...
function Details:TestBarsUpdate()
local current_combat = Details:GetCombat("current")
for index, actor in current_combat[1]:ListActors() do
actor.total = actor.total + (actor.total / 100 * math.random(1, 10))
actor.total = actor.total - (actor.total / 100 * math.random(1, 10))
end
for index, actor in current_combat[2]:ListActors() do
actor.total = actor.total + (actor.total / 100 * math.random(1, 10))
actor.total = actor.total - (actor.total / 100 * math.random(1, 10))
end
current_combat[1].need_refresh = true
current_combat[2].need_refresh = true
end
function Details:StartTestBarUpdate()
if (Details.test_bar_update) then
Details:CancelTimer(Details.test_bar_update)
end
Details.test_bar_update = Details:ScheduleRepeatingTimer ("TestBarsUpdate", 0.1)
end
function Details:StopTestBarUpdate()
if (Details.test_bar_update) then
Details:CancelTimer(Details.test_bar_update)
end
Details.test_bar_update = nil
end
function Details:CreateTestBars (alphabet, isArena)
local current_combat = Details:GetCombat("current")
local pclass = select(2, UnitClass("player"))
local actors_name = {
{"Ragnaros", "MAGE", 86},
{"The Lich King", "DEATHKNIGHT", },
{"Antonidas", "MAGE"},
{"King Djoffrey", "PALADIN", },
{UnitName ("player") .. " Snow", pclass, },
{"Helvis Phresley", "DEATHKNIGHT", },
{"Stormwind Guard", "WARRIOR", },
{"Bolvar Fordragon", "PALADIN", },
{"Malygos", "MAGE", },
{"Akama", "ROGUE", },
{"Nozdormu", "MAGE", },
{"Lady Blaumeux", "DEATHKNIGHT", },
{"Cairne Bloodhoof", "WARRIOR", },
{"Borivar", "ROGUE", 75},
{"C'Thun", "WARLOCK", },
{"Drek'Thar", "DEATHKNIGHT", },
{"Durotan", "WARRIOR", },
{"Eonar", "DRUID", },
{"Malfurion Stormrage", "DRUID", },
{"Footman Malakai", "WARRIOR", },
{"Bolvar Fordragon", "PALADIN", },
{"Fritz Fizzlesprocket", "HUNTER", },
{"Lisa Gallywix", "ROGUE", },
{"M'uru", "WARLOCK", },
{"Elune", "PRIEST", },
{"Nazgrel", "WARRIOR", },
{"Ner'zhul", "WARLOCK", },
{"Saria Nightwatcher", "PALADIN", },
{"Kael'thas Sunstrider", "MAGE", 86},
{"Velen", "PRIEST"},
{"Tyrande Whisperwind", "PRIEST", 77},
{"Sargeras", "WARLOCK", 89},
{"Arthas", "PALADIN", },
{"Orman of Stromgarde", "WARRIOR", },
{"General Rajaxx", "WARRIOR", },
{"Baron Rivendare", "DEATHKNIGHT", },
{"Roland", "MAGE", },
{"Archmage Trelane", "MAGE", },
{"Lilian Voss", "ROGUE", },
{"Dutch", "HERO", },
}
local russian_actors_name = { --arial narrow
{"Экспортировать", "MAGE", 86},
{"Готово", "DEATHKNIGHT", },
{"Создать", "SHAMAN", },
{"Текущий", "MONK", },
{"список команд", "HUNTER", },
{"центр", "SHAMAN", },
{"Разное", "WARRIOR", },
}
local tw_actor_name = { --GBK
{"造成傷害目標", "ROGUE", },
{"怒氣生", "DEATHKNIGHT", },
{"承受治療", "WARLOCK", },
{"格檔", "PRIEST", },
{"中央", "MAGE", },
{"傷害", "SHAMAN", },
{"建立", "MONK", },
{"編輯", "WARRIOR", },
{"儲存變更", "ROGUE", },
{"刪除", "DEATHKNIGHT", },
{"", "WARLOCK", },
{"吸收", "PRIEST", },
{"加到書籤", "MAGE", },
{"最大化", "SHAMAN", },
{"未命中", "MONK", },
{"進階", "WARRIOR", },
}
local cn_actor_name = { --GBK
{"打断", "PRIEST"},
{"恢复", "PRIEST", 77},
{"自动射击", "WARLOCK", 89},
{"平均", "PALADIN", },
{"团队", "WARRIOR", },
{"当前", "WARRIOR", },
{"完毕", "DEATHKNIGHT", },
{"存储变更", "MAGE", },
{"闪避", "MAGE", },
{"空的片段", "ROGUE", },
{"删除", "ROGUE", },
{"治疗暴击", "ROGUE", },
}
local korean_actor_name = { --2002
{"적이 받은 피해", "ROGUE", },
{"초과 치유", "DEATHKNIGHT", },
{"자동 사격", "WARLOCK", },
{"시전", "PRIEST", },
{"현재", "MAGE", },
{"취소", "SHAMAN", },
{"내보내기", "MONK", },
{"(사용자 설정)", "WARRIOR", },
{"방어", "ROGUE", },
{"예제", "DEATHKNIGHT", },
{"특화", "WARLOCK", },
{"최소", "PRIEST", },
{"미러 이미지", "MAGE", },
{"가장자리", "SHAMAN", },
{"외형", "MONK", },
{"아바타 선택", "WARRIOR", },
}
if (not alphabet or alphabet == "en") then
actors_name = actors_name
elseif (alphabet == "ru") then
actors_name = russian_actors_name
elseif (alphabet == "cn") then
actors_name = cn_actor_name
elseif (alphabet == "ko") then
actors_name = korean_actor_name
elseif (alphabet == "tw") then
actors_name = tw_actor_name
end
local actors_classes = CLASS_SORT_ORDER
local total_damage = 0
local total_heal = 0
for i = 1, 10 do
local who = actors_name [math.random(1, #actors_name)]
local robot = current_combat[1]:PegarCombatente ("0x0000-0000-0000", who[1], 0x114, true)
robot.grupo = true
robot.classe = who [2]
robot.flag_original = "0x514"
if (isArena) then
if (math.random() > 0.5) then
robot.arena_ally = true
robot.arena_team = 0
else
robot.arena_enemy = true
robot.arena_team = 1
robot.enemy = true
end
end
robot.total = math.random(10000000, 20000000)
if robot.nome == "Dutch" then
robot.total = robot.total * 3 -- real
end
robot.damage_taken = math.random(10000000, 20000000)
robot.friendlyfire_total = math.random(10000000, 20000000)
total_damage = total_damage + robot.total
if (robot.nome == "King Djoffrey") then
local robot_death = current_combat[4]:PegarCombatente ("0x0000-0000-0000", robot.nome, 0x114, true)
robot_death.grupo = true
robot_death.classe = robot.classe
local esta_morte = {{true, 96648, 100000, time(), 0, "Lady Holenna"}, {true, 96648, 100000, time()-52, 100000, "Lady Holenna"}, {true, 96648, 100000, time()-86, 200000, "Lady Holenna"}, {true, 96648, 100000, time()-101, 300000, "Lady Holenna"}, {false, 55296, 400000, time()-54, 400000, "King Djoffrey"}, {true, 14185, 0, time()-59, 400000, "Lady Holenna"}, {false, 87351, 400000, time()-154, 400000, "King Djoffrey"}, {false, 56236, 400000, time()-158, 400000, "King Djoffrey"} }
local t = {esta_morte, time(), robot.nome, robot.classe, 400000, "52m 12s", ["dead"] = true}
table.insert(current_combat.last_events_tables, #current_combat.last_events_tables+1, t)
elseif (robot.nome == "Mr. President") then
rawset(Details.spellcache, 56488, {"Nuke", 56488, [[Interface\ICONS\inv_gizmo_supersappercharge]]})
robot.spells:PegaHabilidade (56488, true, "SPELL_DAMAGE")
robot.spells._ActorTable [56488].total = robot.total
end
local who = actors_name [math.random(1, #actors_name)]
local robot = current_combat[2]:PegarCombatente ("0x0000-0000-0000", who[1], 0x114, true)
robot.grupo = true
robot.classe = who[2]
robot.total = math.random(10000000, 20000000)
if robot.nome == "Dutch" then
robot.total = robot.total * 3 -- real
end
robot.totalover = math.random(10000000, 20000000)
robot.totalabsorb = math.random(10000000, 20000000)
robot.healing_taken = math.random(10000000, 20000000)
total_heal = total_heal + robot.total
end
--current_combat.start_time = time()-360
current_combat.start_time = GetTime() - 360
--current_combat.end_time = time()
current_combat.end_time = GetTime()
current_combat.totals_grupo [1] = total_damage
current_combat.totals_grupo [2] = total_heal
current_combat.totals [1] = total_damage
current_combat.totals [2] = total_heal
for _, instance in ipairs(Details.tabela_instancias) do
if (instance:IsEnabled()) then
instance:InstanceReset()
end
end
current_combat.enemy = "Illidan Stormrage"
end
+109
View File
@@ -0,0 +1,109 @@
local Details = _G.Details
local detailsFramework = _G.DetailsFramework
local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0", true)
local addonName, Details222 = ...
--frame to create textures
local frame33 = CreateFrame("frame")
--store portrait textures for enemy actors
local portraitPool = {
inUse = {},
available = {},
npcIdToTexture = {},
}
local getTextureForPortraitPool = function()
local texture = tremove(portraitPool.available, 1)
if (not texture) then
texture = frame33:CreateTexture(nil, "overlay")
end
table.insert(portraitPool.inUse, texture)
return texture
end
local releaseTextureForPortraitPool = function(texture)
pcall(function() table.remove(portraitPool.inUse, detailsFramework.table.find(portraitPool.inUse, texture)) end)
table.insert(portraitPool.available, texture)
end
local savePortraitTextureForNpcId = function(texture, npcId)
portraitPool.npcIdToTexture[npcId] = texture
end
--get a portrait texture and set all its attributes to mimic some other texture
--@texture: the texture from GetPortraitTextureForNpcID()
--@fromTexture: any other texture
function Details222.Textures.FormatPortraitAsTexture(texture, fromTexture)
texture:SetDrawLayer("overlay", 7)
texture:SetParent(fromTexture:GetParent())
texture:SetSize(fromTexture:GetSize())
texture:ClearAllPoints()
texture:Show()
for i = 1, fromTexture:GetNumPoints() do
local anchor1, anchorFrame, anchor2, x, y = fromTexture:GetPoint(i)
texture:SetPoint(anchor1, anchorFrame, anchor2, x, y)
end
fromTexture:SetTexture(0.0156, 0.047, 0.1215, 1)
end
function Details222.Textures.SavePortraitTextureForUnitID(unitId)
if true then return end --portrait saving disabled atm
local npcId = detailsFramework:GetNpcIdFromGuid(UnitGUID(unitId) or "")
if (npcId and not Details222.Textures.GetPortraitTextureForNpcID(npcId)) then
local texture = getTextureForPortraitPool()
SetPortraitTexture(texture, unitId)
savePortraitTextureForNpcId(texture, npcId)
end
end
--value
function Details222.Textures.GetPortraitTextureForNpcID(npcId)
return portraitPool.npcIdToTexture[npcId]
end
local eventListener = Details:CreateEventListener()
eventListener:RegisterEvent("DETAILS_DATA_RESET", function()
--> on reset data, release all textures:
for i = 1, #portraitPool.inUse do
local texture = portraitPool.inUse[i]
releaseTextureForPortraitPool(texture)
end
Details:Destroy(portraitPool.npcIdToTexture)
end)
eventListener:RegisterEvent("COMBAT_ENCOUNTER_START", function()
--> save a portrait texture for each boss in the boss list
for i = 1, 9 do
local unitId = "boss" .. i
if (UnitExists(unitId)) then
Details222.Textures.SavePortraitTextureForUnitID(unitId)
end
end
end)
eventListener:RegisterEvent("COMBAT_PLAYER_ENTER", function()
if (UnitExists("target")) then
Details222.Textures.SavePortraitTextureForUnitID("target")
end
end)
eventListener:RegisterEvent("PLAYER_TARGET", function()
if (Details.in_combat) then
if (UnitExists("target")) then
Details222.Textures.SavePortraitTextureForUnitID("target")
end
if (UnitExists("focus")) then
Details222.Textures.SavePortraitTextureForUnitID("focus")
end
end
end)
+637
View File
@@ -0,0 +1,637 @@
local _
local Details = _G.Details
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local addonName, Details222 = ...
--create a namespace
Details222.TimeCapture = {}
---@class timedataexec : table
---@field func function
---@field data table
---@field attributes table
---@field is_user boolean
---mantain the enabled time captures
---@class timedatacontainer : table
---@field Exec timedataexec[]
---@class timedatasaved : {key1: string, key2: function, key3: table, key4: string, key5: string, key6: string, key7: boolean}
---@field do_not_save boolean
do
---@type timedatacontainer
local timeContainer = {}
Details.timeContainer = timeContainer
Details.timeContainer.Exec = {}
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--local pointers
local ipairs = ipairs
local pcall = pcall
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--constants
local INDEX_NAME = 1
local INDEX_FUNCTION = 2
local INDEX_MATRIX = 3
local INDEX_AUTHOR = 4
local INDEX_VERSION = 5
local INDEX_ICON = 6
local INDEX_ENABLED = 7
local DEFAULT_USER_MATRIX = {
max_value = 0,
last_value = 0
}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--register and unregister captures
function Details:TimeDataUpdate (index_or_name, name, func, matrix, author, version, icon, is_enabled)
local thisCapture
if (type(index_or_name) == "number") then
thisCapture = Details.savedTimeCaptures[index_or_name]
else
for index, timeDataSaved in ipairs(Details.savedTimeCaptures) do
---@cast timeDataSaved timedatasaved
if (timeDataSaved [INDEX_NAME] == index_or_name) then
thisCapture = timeDataSaved
end
end
end
if (not thisCapture) then
return false
end
if (thisCapture.do_not_save) then
return Details:Msg("This capture belongs to a plugin and cannot be edited.")
end
thisCapture[INDEX_NAME] = name or thisCapture[INDEX_NAME]
thisCapture[INDEX_FUNCTION] = func or thisCapture[INDEX_FUNCTION]
thisCapture[INDEX_MATRIX] = matrix or thisCapture[INDEX_MATRIX]
thisCapture[INDEX_AUTHOR] = author or thisCapture[INDEX_AUTHOR]
thisCapture[INDEX_VERSION] = version or thisCapture[INDEX_VERSION]
thisCapture[INDEX_ICON] = icon or thisCapture[INDEX_ICON]
if (is_enabled ~= nil) then
thisCapture[INDEX_ENABLED] = is_enabled
else
thisCapture[INDEX_ENABLED] = thisCapture[INDEX_ENABLED]
end
if (_G.DetailsOptionsWindow and _G.DetailsOptionsWindow:IsShown()) then
DetailsOptionsWindowTab17UserTimeCapturesFillPanel.MyObject:Refresh()
end
return true
end
--matrix = table containing {max_value = 0, last_value = 0}
function Details:TimeDataRegister(timeDataName, callbackFunc, matrix, author, version, icon, bIsEnabled, bForceNoSave)
--check name
if (not timeDataName) then
return "Couldn't register the time capture, name was nil."
end
--check if the name already exists
for index, t in ipairs(Details.savedTimeCaptures) do
if (t [INDEX_NAME] == timeDataName) then
return "Couldn't register the time capture, name already registred."
end
end
--check function
if (not callbackFunc) then
return "Couldn't register the time capture, invalid function."
end
local no_save = nil
--passed a function means that this isn't came from a user
--so the plugin register the capture every time it loads.
if (type(callbackFunc) == "function") then
no_save = true
--this a custom capture from a user, so we register a default user table for matrix
elseif (type(callbackFunc) == "string") then
matrix = DEFAULT_USER_MATRIX
end
if (not no_save and bForceNoSave) then
no_save = true
end
--check matrix
if (not matrix or type(matrix) ~= "table") then
return "Couldn't register the time capture, matrix was invalid."
end
author = author or "Unknown"
version = version or "v1.0"
icon = icon or [[Interface\InventoryItems\WoWUnknownItem01]]
table.insert(Details.savedTimeCaptures, {timeDataName, callbackFunc, matrix, author, version, icon, bIsEnabled, do_not_save = no_save})
if (_G.DetailsOptionsWindow and _G.DetailsOptionsWindow:IsShown()) then
DetailsOptionsWindowTab17UserTimeCapturesFillPanel.MyObject:Refresh()
end
return true
end
--unregister
function Details:TimeDataUnregister (name)
if (type(name) == "number") then
table.remove(Details.savedTimeCaptures, name)
if (_G.DetailsOptionsWindow and _G.DetailsOptionsWindow:IsShown()) then
DetailsOptionsWindowTab17UserTimeCapturesFillPanel.MyObject:Refresh()
end
else
for index, t in ipairs(Details.savedTimeCaptures) do
if (t [INDEX_NAME] == name) then
table.remove(Details.savedTimeCaptures, index)
if (_G.DetailsOptionsWindow and _G.DetailsOptionsWindow:IsShown()) then
DetailsOptionsWindowTab17UserTimeCapturesFillPanel.MyObject:Refresh()
end
return true
end
end
return false
end
end
--cleanup when logout
function Details:TimeDataCleanUpTemporary()
---@type timedatasaved[]
local newData = {}
for index, timeDataSaved in ipairs(Details.savedTimeCaptures) do
if (not timeDataSaved.do_not_save) then
table.insert(newData, timeDataSaved)
end
end
Details.savedTimeCaptures = newData
end
local tickTime = 0
--starting a combat
function Details:TimeDataCreateChartTables()
--create capture table
local chartTables = {}
--drop the last capture exec table without wiping
---@type timedataexec
local exec = {}
Details.timeContainer.Exec = exec
Details:SendEvent("COMBAT_CHARTTABLES_CREATING")
--build the exec table
for index, chartData in ipairs(Details.savedTimeCaptures) do
if (chartData[INDEX_ENABLED]) then
local data = {}
chartTables[chartData[INDEX_NAME]] = data
if (type(chartData[INDEX_FUNCTION]) == "string") then
--user
local func, errortext = loadstring(chartData[INDEX_FUNCTION])
if (func) then
DetailsFramework:SetEnvironment(func)
---@type timedataexec
local timeDataTable = {func = func, data = data, attributes = Details.CopyTable(chartData[INDEX_MATRIX]), is_user = true}
table.insert(exec, timeDataTable)
else
Details:Msg("|cFFFF9900error compiling script for time data (charts)|r: ", errortext)
end
else
--plugin
local func = chartData[INDEX_FUNCTION]
DetailsFramework:SetEnvironment(func)
---@type timedataexec
local timeDataTable = {func = func, data = data, attributes = Details.CopyTable(chartData[INDEX_MATRIX])}
table.insert(exec, timeDataTable)
end
end
end
Details:SendEvent("COMBAT_CHARTTABLES_CREATED")
tickTime = 0
--return the capture table the to combat object
--the return value goes into combatObject.TimeData = @chartTables
return chartTables
end
local execUserFunc = function(func, attributes, data, thisSecond)
local okey, result = pcall(func, attributes)
if (not okey) then
Details:Msg("|cFFFF9900error on chart script function|r:", result)
result = 0
end
local current = result - attributes.last_value
data[thisSecond] = current
if (current > attributes.max_value) then
attributes.max_value = current
data.max_value = current
end
attributes.last_value = result
end
function Details:TimeDataTick()
tickTime = tickTime + 1
for index, timeDataTable in ipairs(Details.timeContainer.Exec) do
---@cast timeDataTable timedataexec
if (timeDataTable.is_user) then
--by a user
execUserFunc(timeDataTable.func, timeDataTable.attributes, timeDataTable.data, tickTime)
else
--by a plugin
timeDataTable.func(timeDataTable.attributes, timeDataTable.data, tickTime)
end
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--broker dps stuff
local ToKFunctions = Details.ToKFunctions
local broker_functions = {
-- raid dps [1]
function()
local combat = Details.tabela_vigente
local combatTime = combat:GetCombatTime()
if (not combatTime or combatTime == 0) then
return 0
else
return ToKFunctions[Details.minimap.text_format](_, combat.totals_grupo[1] / combatTime)
end
end,
-- raid hps [2]
function()
local combat = Details.tabela_vigente
local combatTime = combat:GetCombatTime()
if (not combatTime or combatTime == 0) then
return 0
else
return ToKFunctions[Details.minimap.text_format](_, combat.totals_grupo[2] / combatTime)
end
end
}
local get_combat_time = function()
local combat_time = Details.tabela_vigente:GetCombatTime()
local minutos, segundos = math.floor(combat_time / 60), math.floor(combat_time % 60)
if (segundos < 10) then
segundos = "0" .. segundos
end
return minutos .. "m " .. segundos .. "s"
end
local get_damage_position = function()
local damage_container = Details.tabela_vigente[1]
damage_container:SortByKey("total")
local pos = 1
for index, actor in ipairs(damage_container._ActorTable) do
if (actor.grupo) then
if (actor.nome == Details.playername) then
return pos
end
pos = pos + 1
end
end
return 0
end
local get_heal_position = function()
local heal_container = Details.tabela_vigente[2]
heal_container:SortByKey("total")
local pos = 1
for index, actor in ipairs(heal_container._ActorTable) do
if (actor.grupo) then
if (actor.nome == Details.playername) then
return pos
end
pos = pos + 1
end
end
return 0
end
local get_damage_diff = function()
local damage_container = Details.tabela_vigente[1]
damage_container:SortByKey("total")
local first
local first_index
for index, actor in ipairs(damage_container._ActorTable) do
if (actor.grupo) then
first = actor
first_index = index
break
end
end
if (first) then
if (first.nome == Details.playername) then
local second
local container = damage_container._ActorTable
for i = first_index+1, #container do
if (container[i].grupo) then
second = container[i]
break
end
end
if (second) then
local diff = first.total - second.total
return "+" .. ToKFunctions[Details.minimap.text_format] (_, diff)
else
return "0"
end
else
local player = damage_container._NameIndexTable[Details.playername]
if (player) then
player = damage_container._ActorTable[player]
local diff = first.total - player.total
return "-" .. ToKFunctions[Details.minimap.text_format](_, diff)
else
return ToKFunctions[Details.minimap.text_format](_, first.total)
end
end
else
return "0"
end
end
local get_heal_diff = function()
local heal_container = Details.tabela_vigente [2]
heal_container:SortByKey ("total")
local first
local first_index
for index, actor in ipairs(heal_container._ActorTable) do
if (actor.grupo) then
first = actor
first_index = index
break
end
end
if (first) then
if (first.nome == Details.playername) then
local second
local container = heal_container._ActorTable
for i = first_index+1, #container do
if (container[i].grupo) then
second = container[i]
break
end
end
if (second) then
local diff = first.total - second.total
return "+" .. ToKFunctions [Details.minimap.text_format] (_, diff)
else
return "0"
end
else
local player = heal_container._NameIndexTable [Details.playername]
if (player) then
player = heal_container._ActorTable [player]
local diff = first.total - player.total
return "-" .. ToKFunctions [Details.minimap.text_format] (_, diff)
else
return ToKFunctions [Details.minimap.text_format] (_, first.total)
end
end
else
return "0"
end
end
local get_player_dps = function()
local damage_player = Details.tabela_vigente(1, Details.playername)
if (damage_player) then
if (Details.time_type == 1) then --activity time
local combat_time = damage_player:Tempo()
if (combat_time > 0) then
return ToKFunctions [Details.minimap.text_format] (_, damage_player.total / combat_time)
else
return 0
end
else --effective time
local combat_time = Details.tabela_vigente:GetCombatTime()
if (combat_time > 0) then
return ToKFunctions [Details.minimap.text_format] (_, damage_player.total / combat_time)
else
return 0
end
end
return 0
else
return 0
end
end
local get_player_hps = function()
local heal_player = Details.tabela_vigente(2, Details.playername)
if (heal_player) then
if (Details.time_type == 1) then --activity time
local combat_time = heal_player:Tempo()
if (combat_time > 0) then
return ToKFunctions [Details.minimap.text_format] (_, heal_player.total / combat_time)
else
return 0
end
else --effective time
local combat_time = Details.tabela_vigente:GetCombatTime()
if (combat_time > 0) then
return ToKFunctions [Details.minimap.text_format] (_, heal_player.total / combat_time)
else
return 0
end
end
return 0
else
return 0
end
end
local get_raid_dps = function()
local damage_raid = Details.tabela_vigente and Details.tabela_vigente.totals [1]
if (damage_raid ) then
return ToKFunctions [Details.minimap.text_format] (_, damage_raid / Details.tabela_vigente:GetCombatTime())
else
return 0
end
end
local get_raid_hps = function()
local healing_raid = Details.tabela_vigente and Details.tabela_vigente.totals [2]
if (healing_raid ) then
return ToKFunctions [Details.minimap.text_format] (_, healing_raid / Details.tabela_vigente:GetCombatTime())
else
return 0
end
end
local get_player_damage = function()
local damage_player = Details.tabela_vigente(1, Details.playername)
if (damage_player) then
return ToKFunctions [Details.minimap.text_format] (_, damage_player.total)
else
return 0
end
end
local get_player_heal = function()
local heal_player = Details.tabela_vigente(2, Details.playername)
if (heal_player) then
return ToKFunctions [Details.minimap.text_format] (_, heal_player.total)
else
return 0
end
end
local parse_broker_text = function()
local text = Details.data_broker_text
if (text == "") then
return
end
text = text:gsub("{dmg}", get_player_damage)
text = text:gsub("{rdps}", get_raid_dps)
text = text:gsub("{rhps}", get_raid_hps)
text = text:gsub("{dps}", get_player_dps)
text = text:gsub("{heal}", get_player_heal)
text = text:gsub("{hps}", get_player_hps)
text = text:gsub("{time}", get_combat_time)
text = text:gsub("{dpos}", get_damage_position)
text = text:gsub("{hpos}", get_heal_position)
text = text:gsub("{ddiff}", get_damage_diff)
text = text:gsub("{hdiff}", get_heal_diff)
return text
end
function Details:BrokerTick()
if Details.databroker then
Details.databroker.text = parse_broker_text()
end
end
function Details:SetDataBrokerText (text)
if (type(text) == "string") then
Details.data_broker_text = text
Details:BrokerTick()
elseif (text == nil or (type(text) == "boolean" and not text)) then
Details.data_broker_text = ""
Details:BrokerTick()
end
end
------------------------------------------------------------------------------------------------------
--regular spell timers
Details222.TimeCapture.Timers = {}
local damageContainer
local healingContainer
local timeElapsed = 0
local combatTimeTicker = function()
timeElapsed = timeElapsed + 1
end
local damageCapture = function(tickerObject)
local actorObject = tickerObject.ActorObject
if (not actorObject) then
tickerObject.ActorObject = damageContainer:GetActor(tickerObject.unitName)
if (not actorObject) then
return
end
end
for spellId, spellTable in pairs(actorObject.spells._ActorTable) do
local totalDamage = spellTable.total
if (totalDamage) then
if (not spellTable.ChartData) then
spellTable.ChartData = {}
end
spellTable.ChartData[timeElapsed] = totalDamage
end
end
end
function Details222.TimeCapture.StartCombatTimer(combatObject)
timeElapsed = 0
damageContainer = combatObject[1]
healingContainer = combatObject[2]
Details222.TimeCapture.CombatObject = combatObject
Details222.TimeCapture.CombatTimeTicker = C_Timer.NewTicker(1, combatTimeTicker)
--debug: starting only for the player
Details222.TimeCapture.Start(Details.playername, DETAILS_ATTRIBUTE_DAMAGE)
end
--combat ended on Details! end
function Details222.TimeCapture.StopCombat()
local combatTimeTickerObject = Details222.TimeCapture.CombatTimeTicker
if (combatTimeTickerObject and not combatTimeTickerObject:IsCancelled()) then
combatTimeTickerObject:Cancel()
Details222.TimeCapture.CombatTimeTicker = nil
end
Details222.TimeCapture.StopAllUnitTimers()
end
--start a capture for a specific unit
function Details222.TimeCapture.Start(unitName, attribute)
local tickerObject = C_Timer.NewTicker(3, damageCapture)
tickerObject.unitName = unitName
Details222.TimeCapture.Timers[unitName] = tickerObject
end
function Details222.TimeCapture.StopAllUnitTimers()
for unitName, tickerObject in pairs(Details222.TimeCapture.Timers) do
if (not tickerObject:IsCancelled()) then --why do I need to stop here, it's stopping in the unit itself right below
tickerObject:Cancel()
end
Details222.TimeCapture.Stop(unitName)
end
Details:Destroy(Details222.TimeCapture.Timers)
end
--can be a manual stop or from the stop all unit frames (function above)
function Details222.TimeCapture.Stop(unitName)
local tickerObject = Details222.TimeCapture.Timers[unitName]
if (tickerObject and not tickerObject:IsCancelled()) then
tickerObject:Cancel()
Details222.TimeCapture.Timers[unitName] = nil
end
end
function Details222.TimeCapture.GetChartDataFromSpell(spellTable)
return spellTable.ChartData
end