Files
coa-details/core/control.lua
T
Tercio Jose 1830359943 General Fixes, Backend Improvements, Library Updates.
Release Documentation:
- Classic now uses the same combat log reader as retail (Flamanis).
- Merged Rage of Fyr'alath spells (equara).
- Added Rogue Ambushes to merged spells (WillowGryph).
- The Remove Common Segments option now also removes segments trash between raid bosses.
- Fixed an issue where auras applied before combat start, such as Power Infusion and Prescience, which are counted towards the target, were not being accounted for.
- Added to Combat Class: classCombat:GetRunTimeNoDefault(). This returns the run time of the Mythic+ if available, nil otherwise.

Technical Notes:
- Classic now uses retail parser.
- Combat class now have the member: classCombat:GetRunTimeNoDefault(); Returns the run time of a M+ (after completed).
- The Utility class's buff scan at the start of combat has been improved, and the code has been cleaned. Also, the scan runs now on the next frame after combat start.
- Augmentation Evoker won't track auras from the combat start aura scan, if the player isn't in combat (example: a player in the group enters in combat).
- Remove tier bonus for Augmentation Evoker Ebon Might damage prediction and nerfed Close as Cluthmates to 10%.
- Segments Container's ResetDataByCombatType() now supports multiple combat types per classification.
- Code cleanup on Segments menu code to use the new Mythic+ functions added to Combat class.
- Mythic+ start detection produced errors if a WORLD_STATE_TIMER_START event triggered before the CHALLENGE_MODE_START event.
- Mythic+ finish code was bugging when 'time' returned by C_ChallengeMode.GetCompletionInfo() wasn't being checked again nil value.
- Rogue's Ambush ability and Rage of Fyr'alath spellIds added to override_spellId within the parser.
- Details! Framework updated.
- Open Raid Library updated.
2023-12-14 11:33:30 -03:00

1875 lines
64 KiB
Lua

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
local _detalhes = _G.Details
local Loc = LibStub("AceLocale-3.0"):GetLocale ( "Details" )
local SharedMedia = LibStub:GetLibrary("LibSharedMedia-3.0")
local _tempo = time()
local _
local addonName, Details222 = ...
local detailsFramework = DetailsFramework
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--local pointers
local _math_max = math.max --lua local
local ipairs = ipairs --lua local
local pairs = pairs --lua local
local bitBand = bit.band --lua local
local GetInstanceInfo = GetInstanceInfo --wow api local
local UnitExists = UnitExists --wow api local
local UnitGUID = UnitGUID --wow api local
local GetTime = GetTime
local IsAltKeyDown = IsAltKeyDown
local IsShiftKeyDown = IsShiftKeyDown
local IsControlKeyDown = IsControlKeyDown
local atributo_damage = Details.atributo_damage
local atributo_heal = Details.atributo_heal
local atributo_energy = Details.atributo_energy
local atributo_misc = Details.atributo_misc
local atributo_custom = Details.atributo_custom
local UnitGroupRolesAssigned = DetailsFramework.UnitGroupRolesAssigned
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--constants
local groupMode = Details.modos.group
local everythingMode = Details.modos.all
local attributeDamage = Details.atributos.dano
local OBJECT_TYPE_PETS = 0x00003000
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--details api functions
---for a number to the current selected abbreviation
---@param number number
---@return string
function Details:Format(number)
return Details.ToKFunctions[Details.ps_abbreviation](nil, number)
end
--try to find the opponent of last fight, can be called during a fight as well
function Details:FindEnemy()
local zoneName, instanceType = GetInstanceInfo()
local bIsInInstance = IsInInstance() --garrison returns party as instance type
if ((instanceType == "party" or instanceType == "raid") and bIsInInstance) then
if (instanceType == "party") then
if (Details:GetBossNames(Details.zone_id)) then
return Loc ["STRING_SEGMENT_TRASH"]
end
else
return Loc ["STRING_SEGMENT_TRASH"]
end
end
local currentCombat = Details:GetCurrentCombat()
for _, actor in ipairs(currentCombat[attributeDamage]._ActorTable) do
if (not actor.grupo and not actor.owner and not actor.nome:find("[*]") and bitBand(actor.flag_original, 0x00000060) ~= 0) then --0x20+0x40 neutral + enemy reaction
for name, _ in pairs(actor.targets) do
if (name == Details.playername) then
return actor.nome
else
local targetActor = currentCombat(attributeDamage, name)
if (targetActor and targetActor.grupo) then
return actor.nome
end
end
end
end
end
for _, actor in ipairs(currentCombat[attributeDamage]._ActorTable) do
if (actor.grupo and not actor.owner) then
for targetName, _ in pairs(actor.targets) do
return targetName
end
end
end
return Loc ["STRING_UNKNOW"]
end
--try get the current encounter name during the encounter
local createBossTable = function(t, zoneName, zoneMapID, difficultyID)
local bossTable = {
index = 0,
name = t[1],
encounter = t[1],
zone = zoneName,
mapid = zoneMapID,
diff = difficultyID,
diff_string = select(4, GetInstanceInfo()),
ej_instance_id = t[5],
id = t[2],
bossimage = t[4],
unixtime = time(),
}
local currentCombat = Details:GetCurrentCombat()
currentCombat.is_boss = bossTable
end
local foundEncounterInfo = function(index, name, zone, mapId, diff, encounterid)
local mapID = C_Map.GetBestMapForUnit("player")
local ejid
if true then return end --@@@disabled for science
if (mapID) then
ejid = DetailsFramework.EncounterJournal.EJ_GetInstanceForMap(mapID) --using the framework to prevent errors on classic versions of the game
end
if (not mapID) then
return
end
if (ejid == 0) then
ejid = Details:GetInstanceEJID()
end
local bossTable = {
index = index,
name = name,
encounter = name,
zone = zone,
mapid = mapId,
diff = diff,
diff_string = select(4, GetInstanceInfo()),
ej_instance_id = ejid,
id = encounterid,
unixtime = time(),
}
local currentCombat = Details:GetCurrentCombat()
if (not Details:IsRaidRegistered(mapId) and Details.zone_type == "raid") then
--[=[
local bossList = Details:GetCurrentDungeonBossListFromEJ() --function name miss match, filtering raid only, calling dungeon only function
if (bossList) then
local actorContainer = currentCombat[attributeDamage]._ActorTable
if (actorContainer) then
for index, actorObject in ipairs(actorContainer) do
if (not actorObject.grupo) then
if (bossList[actorObject.nome]) then
actorObject.boss = true
bossTable.bossimage = bossList[actorObject.nome][4]
break
end
end
end
end
end
--]=]
end
currentCombat.is_boss = bossTable
--we the boss was found during the combat table creation, we must postpone the event trigger
if (not currentCombat.IsBeingCreated) then
Details:SendEvent("COMBAT_BOSS_FOUND", nil, index, name)
Details:CheckFor_SuppressedWindowsOnEncounterFound()
end
return bossTable
end
function Details:ReadBossFrames()
local currentCombat = Details:GetCurrentCombat()
if (currentCombat.is_boss) then
return --no need to check
end
if (Details.encounter_table.name) then
local encounter_table = Details.encounter_table
return foundEncounterInfo(encounter_table.index, encounter_table.name, encounter_table.zone, encounter_table.mapid, encounter_table.diff, encounter_table.id)
end
for index = 1, 5 do
if (UnitExists("boss" .. index)) then
local bossGuid = UnitGUID("boss" .. index)
if (bossGuid) then
local serial = Details:GetNpcIdFromGuid(bossGuid)
if (serial) then
local zoneName, _, difficultyID, _, _, _, _, zoneMapID = GetInstanceInfo()
local bossIds = Details:GetBossIds(zoneMapID)
if (bossIds) then
local bossIndex = bossIds[serial]
if (bossIndex) then
if (Details.debug) then
Details:Msg("(debug) boss found:", Details:GetBossName(zoneMapID, bossIndex))
end
return foundEncounterInfo(bossIndex, Details:GetBossName(zoneMapID, bossIndex), zoneName, zoneMapID, difficultyID)
end
end
end
end
end
end
end
--try to get the encounter name after the encounter (can be called during the combat as well)
function Details:FindBoss(noJournalSearch)
if (Details.encounter_table.name) then
local encounter_table = Details.encounter_table
return foundEncounterInfo(encounter_table.index, encounter_table.name, encounter_table.zone, encounter_table.mapid, encounter_table.diff, encounter_table.id)
end
local currentCombat = Details:GetCurrentCombat()
local zoneName, instanceType, difficultyID, _, _, _, _, zoneMapID = GetInstanceInfo()
local bossIds = Details:GetBossIds(zoneMapID)
if (bossIds) then
local actorContainer = currentCombat[attributeDamage]._ActorTable
if (actorContainer) then
for index, actorObject in ipairs(actorContainer) do
if (not actorObject.grupo) then
local serial = Details:GetNpcIdFromGuid(actorObject.serial)
if (serial) then
local bossIndex = bossIds[serial]
if (bossIndex) then
actorObject.boss = true
return foundEncounterInfo(bossIndex, Details:GetBossName(zoneMapID, bossIndex), zoneName, zoneMapID, difficultyID)
end
end
end
end
end
end
noJournalSearch = true --disabling the scan on encounter journal
if (not noJournalSearch) then
local in_instance = IsInInstance() --garrison returns party as instance type.
if ((instanceType == "party" or instanceType == "raid") and in_instance) then
local boss_list = Details:GetCurrentDungeonBossListFromEJ()
if (boss_list) then
local ActorsContainer = currentCombat[attributeDamage]._ActorTable
if (ActorsContainer) then
for index, Actor in ipairs(ActorsContainer) do
if (not Actor.grupo) then
if (boss_list [Actor.nome]) then
Actor.boss = true
return createBossTable (boss_list [Actor.nome], zoneName, zoneMapID, difficultyID)
end
end
end
end
end
end
end
return false
end
local showTutorialForDiscardedSegment = function()
--tutorial about the combat time < then 'minimum_combat_time'
local hasSeenTutorial = Details:GetTutorialCVar("MIN_COMBAT_TIME")
if (not hasSeenTutorial) then
local lowerInstanceId = Details:GetLowerInstanceNumber()
if (lowerInstanceId) then
---@type instance
local lowerInstanceObject = Details:GetInstance(lowerInstanceId)
if (lowerInstanceObject) then
lowerInstanceObject:InstanceAlert("combat ignored: less than 5 seconds.", {[[Interface\BUTTONS\UI-GROUPLOOT-PASS-DOWN]], 18, 18, false, 0, 1, 0, 1}, 20, {function() Details:Msg("combat ignored: elapsed time less than 5 seconds."); Details:Msg("add '|cFFFFFF00Details.minimum_combat_time = 2;|r' on Auto Run Code to change the minimum time.") end})
Details:SetTutorialCVar("MIN_COMBAT_TIME", true)
end
end
end
end
---return an array of encounter Ids in order of the most recent to the oldest
function Details:GetEncounterIDInOrder()
--get the segments table
local segmentsTable = Details:GetCombatSegments()
--table which contains the encounter Ids in order of the most recent to the oldest
local resultTable = {}
--iterate over the segments table from the most recent to the oldest, check if the combatObject of the segment has is_boss and get the encounter Id from the member is_boss.id
for i = 1, #segmentsTable do
local combatObject = segmentsTable[i]
if (combatObject.is_boss) then
table.insert(resultTable, 1, combatObject.is_boss.id)
end
end
return resultTable
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--internal functions
-- Details.statistics = {container_calls = 0, container_pet_calls = 0, container_unknow_pet = 0, damage_calls = 0, heal_calls = 0, absorbs_calls = 0, energy_calls = 0, pets_summons = 0}
function Details:StartCombat(...)
return Details:EntrarEmCombate (...)
end
-- ~start ~inicio ~novo ovo
function Details:EntrarEmCombate (...)
if (Details.debug) then
Details:Msg("(debug) |cFFFFFF00started a new combat|r|cFFFF7700", Details.encounter_table and Details.encounter_table.name or "")
local from = debugstack(2, 1, 0)
print("from:", from)
end
local segmentsTable = Details:GetCombatSegments()
--check if there's a 'current segment in place', if not, re-create the overall data before creating the new segment
if (not segmentsTable[1]) then
Details.tabela_overall = Details.combate:NovaTabela()
Details:InstanceCallDetailsFunc(Details.ResetaGump, nil, -1) --reseta scrollbar, iterators, rodap, etc
Details:InstanceCallDetailsFunc(Details.InstanciaFadeBarras, -1) --esconde todas as barras
Details:InstanceCallDetailsFunc(Details.UpdateCombatObjectInUse) --atualiza o showing
end
--get the yet 'current' combat and lock the activity time on all actors
local pastCombatObject = Details:GetCurrentCombat()
if (not pastCombatObject.__destroyed) then
pastCombatObject:LockActivityTime()
end
---@type number increate the combat counter by 1
local combatCounter = Details:GetOrSetCombatId(1)
--create a new combat object and preplace the current one
local newCombatObject = Details.combate:NovaTabela(true, Details.tabela_overall, combatCounter, ...)
Details:SetCurrentCombat(newCombatObject)
--flag this combat as being created
newCombatObject.IsBeingCreated = true
--flag Details! as 'in combat'
Details.in_combat = true
newCombatObject:seta_data(Details._detalhes_props.DATA_TYPE_START) --seta na tabela do combate a data do inicio do combate -- setup time data
--set the combat id on the combat object
newCombatObject.combat_id = combatCounter
--clear cache
Details.last_combat_pre_pot_used = nil
--flags the new combat as pvp or arena match
Details:FlagNewCombat_PVPState()
--start the ticker to know if the player is in combat or not
Details:StartCombatTicker()
Details:ClearCCPetsBlackList()
Details:Destroy(Details.encounter_end_table)
Details:Destroy(Details.pets_ignored)
Details:Destroy(Details.pets_no_owner)
Details.container_pets:BuscarPets()
Details:Destroy(Details.cache_damage_group)
Details:Destroy(Details.cache_healing_group)
local bFromCombatStart = true
Details:UpdateParserGears(bFromCombatStart)
--get all buff already applied before the combat start
C_Timer.After(0.05, function()
--wait the initial aura wipe done by the client on certain situations
Details:CatchRaidBuffUptime("BUFF_UPTIME_IN")
end)
Details:CatchRaidDebuffUptime("DEBUFF_UPTIME_IN")
Details:UptadeRaidMembersCache()
--Details222.TimeCapture.StartCombatTimer(Details.tabela_vigente)
--we already have boss information? build .is_boss table
if (Details.encounter_table.id and Details.encounter_table ["start"] >= GetTime() - 3 and not Details.encounter_table ["end"]) then
local encounter_table = Details.encounter_table
--boss_found will trigger "COMBAT_BOSS_FOUND" event, but at this point of the combat creation is safe to send it
foundEncounterInfo (encounter_table.index, encounter_table.name, encounter_table.zone, encounter_table.mapid, encounter_table.diff, encounter_table.id)
else
--if we don't have this infor right now, lets check in few seconds dop
if (Details.EncounterInformation [Details.zone_id]) then
Details:ScheduleTimer("ReadBossFrames", 1)
Details:ScheduleTimer("ReadBossFrames", 30)
end
end
--if the window is showing current segment, switch it for the new combat
--also if the window has auto current, jump to current segment
Details:InstanceCallDetailsFunc(Details.TrocaSegmentoAtual, Details.tabela_vigente.is_boss and true)
--clear hosts and make the cloud capture stuff
Details.host_of = nil
Details.host_by = nil
if (Details.in_group and Details.cloud_capture) then
if (Details:IsInInstance() or Details.debug) then
if (not Details:CaptureIsAllEnabled()) then
Details:ScheduleSendCloudRequest()
--if (Details.debug) then
-- Details:Msg("(debug) requesting a cloud server.")
--end
end
else
--if (Details.debug) then
-- Details:Msg("(debug) isn't inside a registred instance", Details:IsInInstance())
--end
end
else
--if (Details.debug) then
-- Details:Msg("(debug) isn't in group or cloud is turned off", Details.in_group, Details.cloud_capture)
--end
end
--hide / alpha / switch in combat
for index, instancia in ipairs(Details.tabela_instancias) do
if (instancia.ativa) then
instancia:CheckSwitchOnCombatStart(true)
end
end
Details:InstanceCall(Details.CheckPsUpdate)
--combat creation is completed, remove the flag
Details.tabela_vigente.IsBeingCreated = nil
Details:SendEvent("COMBAT_PLAYER_ENTER", nil, Details.tabela_vigente, Details.encounter_table and Details.encounter_table.id)
if (Details.tabela_vigente.is_boss) then
--the encounter was found through encounter_start event
Details:SendEvent("COMBAT_BOSS_FOUND", nil, Details.tabela_vigente.is_boss.index, Details.tabela_vigente.is_boss.name)
end
Details:CheckSwitchToCurrent()
Details:CheckForTextTimeCounter(true)
--stop bar testing if any
Details:StopTestBarUpdate()
end
function Details:DelayedSyncAlert()
local lower_instance = Details:GetLowerInstanceNumber()
if (lower_instance) then
lower_instance = Details:GetInstance(lower_instance)
if (lower_instance) then
if (not lower_instance:HaveInstanceAlert()) then
lower_instance:InstanceAlert (Loc ["STRING_EQUILIZING"], {[[Interface\COMMON\StreamCircle]], 22, 22, true}, 5, {function() end})
end
end
end
end
function Details:ScheduleSyncPlayerActorData()
if ((IsInGroup() or IsInRaid()) and (Details.zone_type == "party" or Details.zone_type == "raid")) then
--do not sync if in battleground or arena
Details:SendCharacterData()
end
end
--alias
function Details:EndCombat(bossKilled, bIsFromEncounterEnd)
return Details:SairDoCombate(bossKilled, bIsFromEncounterEnd)
end
-- ~end ~leave
function Details:SairDoCombate(bossKilled, bIsFromEncounterEnd)
if (Details.debug) then
Details:Msg("(debug) |cFFFFFF00ended a combat|r|cFFFF7700", Details.encounter_table and Details.encounter_table.name or "")
end
---@type combat
local currentCombat = Details:GetCurrentCombat()
if (currentCombat.bIsClosed) then
return
end
currentCombat.bIsClosed = true
if (currentCombat.__destroyed) then
Details:Msg("a deleted combat was found during combat end, please report this bug on discord:")
Details:Msg("combat destroyed by:", currentCombat.__destroyedBy)
end
--flag the addon as 'leaving combat'
Details.leaving_combat = true
--save the unixtime of the latest combat end
Details.last_combat_time = _tempo
Details:CatchRaidBuffUptime("BUFF_UPTIME_OUT")
Details:CatchRaidDebuffUptime("DEBUFF_UPTIME_OUT")
Details:CloseEnemyDebuffsUptime()
Details222.GuessSpecSchedules.ClearSchedules()
--Details222.TimeCapture.StopCombat() --it did not start
--check if this isn't a boss and try to find a boss in the segment
if (not currentCombat.is_boss) then
--if this is a mythic+ dungeon, do not scan for encounter journal boss names in the actor list
Details:FindBoss()
--still didn't find the boss
if (not currentCombat.is_boss) then
local ZoneName, _, DifficultyID, _, _, _, _, ZoneMapID = GetInstanceInfo()
local findboss = Details:GetRaidBossFindFunction(ZoneMapID)
if (findboss) then
local BossIndex = findboss()
if (BossIndex) then
foundEncounterInfo(BossIndex, Details:GetBossName(ZoneMapID, BossIndex), ZoneName, ZoneMapID, DifficultyID)
end
end
end
end
Details:OnCombatPhaseChanged() --.PhaseData is nil here on alpha-32
if (currentCombat.bossFunction) then
Details:CancelTimer(currentCombat.bossFunction)
currentCombat.bossFunction = nil
end
--stop combat ticker
Details:StopCombatTicker()
--lock timers
currentCombat:LockActivityTime()
--get waste shields
if (Details.close_shields) then
Details:CloseShields(currentCombat)
end
--salva hora, minuto, segundo do fim da luta
currentCombat:seta_data(Details._detalhes_props.DATA_TYPE_END)
currentCombat:seta_tempo_decorrido()
--drop last events table to garbage collector
currentCombat.player_last_events = {}
--flag instance type
local _, InstanceType = GetInstanceInfo()
currentCombat.instance_type = InstanceType
if (not currentCombat.is_boss and bIsFromEncounterEnd and type(bIsFromEncounterEnd) == "table") then
local encounterID, encounterName, difficultyID, raidSize, endStatus = unpack(bIsFromEncounterEnd)
if (encounterID) then
local ZoneName, InstanceType, DifficultyID, DifficultyName, _, _, _, ZoneMapID = GetInstanceInfo()
local mapID = C_Map.GetBestMapForUnit("player")
if (not mapID) then
mapID = 0
end
--local ejid = DetailsFramework.EncounterJournal.EJ_GetInstanceForMap(mapID) --@@@disabled for science
--if (ejid == 0) then
-- ejid = Details:GetInstanceEJID()
--end
local _, boss_index = Details:GetBossEncounterDetailsFromEncounterId(ZoneMapID, encounterID)
currentCombat.is_boss = {
index = boss_index or 0,
name = encounterName,
encounter = encounterName,
zone = ZoneName,
mapid = ZoneMapID,
diff = DifficultyID,
diff_string = DifficultyName,
ej_instance_id = ejid or 0,
id = encounterID,
unixtime = time()
}
end
end
--tag as a mythic dungeon segment, can be any type of segment, this tag also avoid the segment to be tagged as trash
local mythicLevel = C_ChallengeMode and C_ChallengeMode.GetActiveKeystoneInfo()
if (mythicLevel and mythicLevel >= 2) then
currentCombat.is_mythic_dungeon_segment = true
currentCombat.is_mythic_dungeon_run_id = Details.mythic_dungeon_id
end
--send item level after a combat if is in raid or party group
C_Timer.After(1, Details.ScheduleSyncPlayerActorData)
--if this segment isn't a boss fight
if (not currentCombat.is_boss) then
if (currentCombat.is_pvp or currentCombat.is_arena) then
Details:FlagActorsOnPvPCombat()
end
if (currentCombat.is_arena) then
currentCombat.enemy = "[" .. ARENA .. "] " .. currentCombat.is_arena.name
end
local in_instance = IsInInstance() --garrison returns party as instance type.
if ((InstanceType == "party" or InstanceType == "raid") and in_instance) then
if (InstanceType == "party") then
if (currentCombat.is_mythic_dungeon_segment) then --setted just above
--is inside a mythic+ dungeon and this is not a boss segment, so tag it as a dungeon mythic+ trash segment
local zoneName, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceMapID, instanceGroupSize = GetInstanceInfo()
currentCombat.is_mythic_dungeon_trash = {
ZoneName = zoneName,
MapID = instanceMapID,
Level = Details.MythicPlus.Level,
EJID = Details.MythicPlus.ejID,
}
if (Details.debug) then
Details:Msg("segment tagged as mythic+ trash.")
end
else
--tag the combat as trash clean up
currentCombat.is_trash = true
end
else
currentCombat.is_trash = true
end
else
if (not in_instance) then
if (Details.world_combat_is_trash) then
currentCombat.is_world_trash_combat = true
end
end
end
if (not currentCombat.enemy) then
local enemy = Details:FindEnemy()
if (enemy and Details.debug) then
Details:Msg("(debug) enemy found", enemy)
end
currentCombat.enemy = enemy
end
Details:FlagActorsOnCommonFight() --fight_component
else
--calling here without checking for combat since the does not ran too long for scripts
Details:FlagActorsOnBossFight()
if (bossKilled) then
currentCombat.is_boss.killed = true
--add to storage
if (not InCombatLockdown() and not UnitAffectingCombat("player") and not Details.logoff_saving_data) then
local successful, errortext = pcall(Details.Database.StoreEncounter)
if (not successful) then
Details:Msg("error occurred on Details.Database.StoreEncounter():", errortext)
end
else
Details.schedule_store_boss_encounter = true
end
Details:SendEvent("COMBAT_BOSS_DEFEATED", nil, currentCombat)
Details:CheckFor_TrashSuppressionOnEncounterEnd()
else
Details:SendEvent("COMBAT_BOSS_WIPE", nil, currentCombat)
--add to storage
if (not InCombatLockdown() and not UnitAffectingCombat("player") and not Details.logoff_saving_data) then
local successful, errortext = pcall(Details.Database.StoreWipe)
if (not successful) then
Details:Msg("error occurred on Details.Database.StoreWipe():", errortext)
end
else
Details.schedule_store_boss_encounter_wipe = true
end
end
currentCombat.is_boss.index = currentCombat.is_boss.index or 1
currentCombat.enemy = currentCombat.is_boss.encounter
if (currentCombat.instance_type == "raid") then
Details.last_encounter2 = Details.last_encounter
Details.last_encounter = currentCombat.is_boss.name
if (Details.pre_pot_used) then
Details.last_combat_pre_pot_used = Details.CopyTable(Details.pre_pot_used)
end
if (Details.pre_pot_used and Details.announce_prepots.enabled) then
Details:Msg(Details.pre_pot_used or "")
Details.pre_pot_used = nil
end
end
if (bIsFromEncounterEnd) then
if (Details.encounter_table.start) then
currentCombat:SetStartTime(Details.encounter_table.start)
end
currentCombat:SetEndTime(Details.encounter_table["end"] or GetTime())
end
if (currentCombat.instance_type == "raid") 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)
if (Details.debug) then
Details:Msg("(debug) freezing parser for 15 seconds.")
end
end
--schedule sync
Details:EqualizeActorsSchedule(Details.host_of)
if (Details:GetEncounterEqualize(currentCombat.is_boss.mapid, currentCombat.is_boss.index)) then
Details:ScheduleTimer("DelayedSyncAlert", 3)
end
end
if (Details.solo) then
--debuffs need a checkup, not well functional right now
Details.CloseSoloDebuffs()
end
local tempo_do_combate = currentCombat:GetCombatTime()
---@type combat
local invalidCombat
local segmentsTable = Details:GetCombatSegments()
--to force discard, the segmentsTable must have at least on segment
local bShouldForceDiscard = Details222.discardSegment and segmentsTable[1] and true
local zoneName, zoneType = GetInstanceInfo()
if (not bShouldForceDiscard and (zoneType == "none" or tempo_do_combate >= Details.minimum_combat_time or not segmentsTable[1])) then
--combat accepted
Details.tabela_historico:AddCombat(currentCombat) --move a tabela atual para dentro do histrico
if (currentCombat.is_boss) then
if (IsInRaid()) then
local cleuID = currentCombat.is_boss.id
local diff = currentCombat.is_boss.diff
if (cleuID and diff == 16) then -- 16 mythic
local raidData = Details.raid_data
--get or build mythic raid data table
local mythicRaidData = raidData.mythic_raid_data
if (not mythicRaidData) then
mythicRaidData = {}
raidData.mythic_raid_data = mythicRaidData
end
--get or build a table for this cleuID
mythicRaidData[cleuID] = mythicRaidData[cleuID] or {wipes = 0, kills = 0, best_try = 1, longest = 0, try_history = {}}
local cleuIDData = mythicRaidData[cleuID]
--store encounter data for plugins and weakauras
if (currentCombat:GetCombatTime() > cleuIDData.longest) then
cleuIDData.longest = currentCombat:GetCombatTime()
end
if (currentCombat.is_boss.killed) then
cleuIDData.kills = cleuIDData.kills + 1
cleuIDData.best_try = 0
table.insert(cleuIDData.try_history, {0, currentCombat:GetCombatTime()})
--print("KILL", "best try", cleuIDData.best_try, "amt kills", cleuIDData.kills, "wipes", cleuIDData.wipes, "longest", cleuIDData.longest)
else
cleuIDData.wipes = cleuIDData.wipes + 1
if (Details.boss1_health_percent and Details.boss1_health_percent < cleuIDData.best_try) then
cleuIDData.best_try = Details.boss1_health_percent
table.insert(cleuIDData.try_history, {Details.boss1_health_percent, currentCombat:GetCombatTime()})
end
--print("WIPE", "best try", cleuIDData.best_try, "amt kills", cleuIDData.kills, "wipes", cleuIDData.wipes, "longest", cleuIDData.longest)
end
end
end
end
--the combat is valid, see if the user is sharing data with somebody
if (Details.shareData) then
local zipData = Details:CompressData(currentCombat, "comm")
if (zipData) then
print("has zip data")
end
end
else
--combat denied: combat did not pass the filter and cannot be added into the segment history
--rewind the data set to the first slot in the segments table
showTutorialForDiscardedSegment()
--change the current combat to the latest combat available in the segment table
invalidCombat = currentCombat
Details:SetCurrentCombat(segmentsTable[1])
currentCombat = Details:GetCurrentCombat()
--if it rewinds to an already erased combat, then create a new combat
if (currentCombat.__destroyed) then
Details:SetCurrentCombat(Details.combate:NovaTabela(nil, Details.tabela_overall))
currentCombat = Details:GetCurrentCombat()
end
if (currentCombat:GetStartTime() == 0) then
currentCombat:SetStartTime(GetTime())
currentCombat:SetEndTime(GetTime())
end
currentCombat.resincked = true
Details:InstanceCallDetailsFunc(Details.AtualizarJanela)
if (Details.solo) then --code to update "solo" plugins, there's no solo plugins for details! at the moment
if (Details.SoloTables.CombatID == Details:GetOrSetCombatId()) then --significa que o solo mode validou o combate, como matar um bixo muito low level com uma s porrada
if (Details.SoloTables.CombatIDLast and Details.SoloTables.CombatIDLast ~= 0) then --volta os dados da luta anterior
Details.SoloTables.CombatID = Details.SoloTables.CombatIDLast
else
if (Details.RefreshSolo) then
Details:RefreshSolo()
end
Details.SoloTables.CombatID = nil
end
end
end
Details:GetOrSetCombatId(-1)
end
Details222.discardSegment = nil
Details.host_of = nil
Details.host_by = nil
if (Details.cloud_process) then
Details:CancelTimer(Details.cloud_process)
end
Details.in_combat = false
Details.leaving_combat = false
Details:Destroy(currentCombat.PhaseData.damage_section)
Details:Destroy(currentCombat.PhaseData.heal_section)
Details:Destroy(Details.cache_damage_group)
Details:Destroy(Details.cache_healing_group)
Details:UpdateParserGears()
--hide / alpha in combat
for index, instance in ipairs(Details.tabela_instancias) do
if (instance.ativa) then
if (instance.auto_switch_to_old) then
instance:CheckSwitchOnCombatEnd()
end
end
end
Details.pre_pot_used = nil
--do not wipe the encounter table if is in the argus encounter ~REMOVE on 8.0
if (Details.encounter_table and Details.encounter_table.id ~= 2092) then
Details:Destroy(Details.encounter_table)
else
if (Details.debug) then
Details:Msg("(debug) in argus encounter, cannot wipe the encounter table.")
end
end
Details:InstanceCall(Details.CheckPsUpdate)
if (invalidCombat) then
Details:SendEvent("COMBAT_INVALID")
Details:SendEvent("COMBAT_PLAYER_LEAVE", nil, invalidCombat)
else
Details:SendEvent("COMBAT_PLAYER_LEAVE", nil, currentCombat)
end
Details:CheckForTextTimeCounter()
Details.StoreSpells()
Details:RunScheduledEventsAfterCombat()
--issue: invalidCombat will be just floating around in memory if not destroyed
end --end of leaving combat function
function Details:GetPlayersInArena() --ARENA_OPPONENT_UPDATE
local aliados = GetNumGroupMembers() -- LE_PARTY_CATEGORY_HOME
for i = 1, aliados-1 do
local role = UnitGroupRolesAssigned and UnitGroupRolesAssigned("party" .. i) or "DAMAGER"
if (role ~= "NONE" and UnitExists("party" .. i)) then
local unitName = Details:GetFullName("party" .. i)
Details.arena_table [unitName] = {role = role}
end
end
local role = UnitGroupRolesAssigned and UnitGroupRolesAssigned("player") or "DAMAGER"
if (role ~= "NONE") then
local playerName = Details:GetFullName("player")
Details.arena_table [playerName] = {role = role}
end
--enemies
local enemiesAmount = GetNumArenaOpponentSpecs and GetNumArenaOpponentSpecs() or 5
Details:Destroy(_detalhes.arena_enemies)
for i = 1, enemiesAmount do
local enemyName = Details:GetFullName("arena" .. i)
if (enemyName) then
_detalhes.arena_enemies[enemyName] = "arena" .. i
end
end
end
--attempt to get the arena unitId for an actor
--this function is called from containerActors while reading the actor flag and parser when managing deathlog
function Details:GuessArenaEnemyUnitId(unitName)
for i = 1, 5 do
local unitId = "arena" .. i
local enemyName = Details:GetFullName(unitId)
if (enemyName == unitName) then
_detalhes.arena_enemies[enemyName] = unitId
return unitId
end
end
end
local string_arena_enemyteam_damage = [[
local combat = Details:GetCombat("current")
local total = 0
for _, actor in combat[1]:ListActors() do
if (actor.arena_enemy) then
total = total + actor.total
end
end
return total
]]
local string_arena_myteam_damage = [[
local combat = Details:GetCombat("current")
local total = 0
for _, actor in combat[1]:ListActors() do
if (actor.arena_ally) then
total = total + actor.total
end
end
return total
]]
local string_arena_enemyteam_heal = [[
local combat = Details:GetCombat("current")
local total = 0
for _, actor in combat[2]:ListActors() do
if (actor.arena_enemy) then
total = total + actor.total
end
end
return total
]]
local string_arena_myteam_heal = [[
local combat = Details:GetCombat("current")
local total = 0
for _, actor in combat[2]:ListActors() do
if (actor.arena_ally) then
total = total + actor.total
end
end
return total
]]
function Details:CreateArenaSegment()
Details:GetPlayersInArena()
Details.arena_begun = true
Details.start_arena = nil
if (Details.in_combat) then
Details:SairDoCombate()
end
--registra os grficos
Details:TimeDataRegister ("Your Team Damage", string_arena_myteam_damage, nil, "Details!", "v1.0", [[Interface\ICONS\Ability_DualWield]], true, true)
Details:TimeDataRegister ("Enemy Team Damage", string_arena_enemyteam_damage, nil, "Details!", "v1.0", [[Interface\ICONS\Ability_DualWield]], true, true)
Details:TimeDataRegister ("Your Team Healing", string_arena_myteam_heal, nil, "Details!", "v1.0", [[Interface\ICONS\Ability_DualWield]], true, true)
Details:TimeDataRegister ("Enemy Team Healing", string_arena_enemyteam_heal, nil, "Details!", "v1.0", [[Interface\ICONS\Ability_DualWield]], true, true)
Details.lastArenaStartTime = GetTime()
--inicia um novo combate
Details:EntrarEmCombate()
--sinaliza que esse combate arena
Details.tabela_vigente.arena = true
Details.tabela_vigente.is_arena = {name = Details.zone_name, zone = Details.zone_name, mapid = Details.zone_id}
Details:SendEvent("COMBAT_ARENA_START")
local bOrderDpsByRealTime = Details.CurrentDps.CanSortByRealTimeDps()
if (bOrderDpsByRealTime) then
local bNoSave = true
local nTimeIntervalBetweenUpdates = 0.1
Details:SetWindowUpdateSpeed(nTimeIntervalBetweenUpdates, bNoSave)
end
end
--return the GetTime() of the current or latest arena match
function Details:GetArenaStartTime()
return Details.lastArenaStartTime
end
function Details:GetBattlegroundStartTime()
return Details.lastBattlegroundStartTime
end
function Details:StartArenaSegment(...)
if (Details.debug) then
Details:Msg("(debug) starting a new arena segment.")
end
local _, timeSeconds = select(1, ...)
if (Details.start_arena) then
Details:CancelTimer(Details.start_arena, true)
end
Details.start_arena = Details:ScheduleTimer("CreateArenaSegment", timeSeconds)
Details:GetPlayersInArena()
--CHAT_MSG_BG_SYSTEM_NEUTRAL - "The Arena battle has begun!""
end
function Details:EnteredInArena()
if (Details.debug) then
Details:Msg("(debug) the player EnteredInArena().")
end
Details.arena_begun = false
Details:GetPlayersInArena()
end
function Details:LeftArena()
if (Details.debug) then
Details:Msg("(debug) player LeftArena().")
end
Details.is_in_arena = false
Details.arena_begun = false
if (Details.start_arena) then
Details:CancelTimer(Details.start_arena, true)
end
Details:TimeDataUnregister ("Your Team Damage")
Details:TimeDataUnregister ("Enemy Team Damage")
Details:TimeDataUnregister ("Your Team Healing")
Details:TimeDataUnregister ("Enemy Team Healing")
Details:SendEvent("COMBAT_ARENA_END")
--reset the update speed, as it could have changed when the arena started.
Details:SetWindowUpdateSpeed(Details.update_speed)
end
local validSpells = {
[220893] = {class = "ROGUE", spec = 261, maxPercent = 0.075, container = 1, commID = "MISSDATA_ROGUE_SOULRIP"},
--[11366] = {class = "MAGE", spec = 63, maxPercent = 0.9, container = 1, commID = "MISSDATA_ROGUE_SOULRIP"},
}
function Details:CanSendMissData()
if (not IsInRaid() and not IsInGroup()) then
return
end
local _, playerClass = UnitClass("player")
local specIndex = DetailsFramework.GetSpecialization()
local playerSpecID
if (specIndex) then
playerSpecID = DetailsFramework.GetSpecializationInfo(specIndex)
end
if (playerSpecID and playerClass) then
for spellID, t in pairs(validSpells) do
if (playerClass == t.class and playerSpecID == t.spec) then
Details:SendMissData (spellID, t.container, Details.network.ids [t.commID])
end
end
end
return false
end
function Details:SendMissData (spellID, containerType, commID)
local combat = Details.tabela_vigente
if (combat) then
local damageActor = combat (containerType, Details.playername)
if (damageActor) then
local spell = damageActor.spells:GetSpell (spellID)
if (spell) then
local data = {
[1] = containerType,
[2] = spellID,
[3] = spell.total,
[4] = spell.counter
}
if (Details.debug) then
Details:Msg("(debug) sending miss data packet:", spellID, containerType, commID)
end
Details:SendRaidOrPartyData (commID, data)
end
end
end
end
function Details.HandleMissData (playerName, data)
local combat = Details.tabela_vigente
if (Details.debug) then
Details:Msg("(debug) miss data received from:", playerName, "spellID:", data [2], data [3], data [4])
end
if (combat) then
local containerType = data[1]
if (type(containerType) ~= "number" or containerType < 1 or containerType > 4) then
return
end
local damageActor = combat (containerType, playerName)
if (damageActor) then
local spellID = data[2] --a spellID has been passed?
if (not spellID or type(spellID) ~= "number") then
return
end
local validateSpell = validSpells [spellID]
if (not validateSpell) then --is a valid spell?
return
end
--does the target player fit in the spell requirement on OUR end?
local class, spec, maxPercent = validateSpell.class, validateSpell.spec, validateSpell.maxPercent
if (class ~= damageActor.classe or spec ~= damageActor.spec) then
return
end
local total, counter = data[3], data[4]
if (type(total) ~= "number" or type(counter) ~= "number") then
return
end
if (total > (damageActor.total * maxPercent)) then
return
end
local spellObject = damageActor.spells:PegaHabilidade (spellID, true)
if (spellObject) then
if (spellObject.total < total and total > 0 and damageActor.nome ~= Details.playername) then
local difference = total - spellObject.total
if (difference > 0) then
spellObject.total = total
spellObject.counter = counter
damageActor.total = damageActor.total + difference
combat [containerType].need_refresh = true
if (Details.debug) then
Details:Msg("(debug) miss data successful added from:", playerName, data [2], "difference:", difference)
end
end
end
end
end
end
end
function Details:MakeEqualizeOnActor (player, realm, receivedActor)
if (true) then --disabled for testing
return
end
end
function Details:EqualizePets()
--check for pets without owner
for _, actor in ipairs(Details.tabela_vigente[1]._ActorTable) do
--have flag and the flag tell us he is a pet
if (actor.flag_original and bit.band(actor.flag_original, OBJECT_TYPE_PETS) ~= 0) then
--do not have owner and he isn't on owner container
if (not actor.owner and not Details.tabela_pets.pets [actor.serial]) then
Details:SendPetOwnerRequest (actor.serial, actor.nome)
end
end
end
end
function Details:EqualizeActorsSchedule (host_of)
--store pets sent through 'needpetowner'
Details.sent_pets = Details.sent_pets or {n = time()}
if (Details.sent_pets.n+20 < time()) then
Details:Destroy(Details.sent_pets)
Details.sent_pets.n = time()
end
--pet equilize disabled on details 1.4.0
--Details:ScheduleTimer("EqualizePets", 1+math.random())
--do not equilize if there is any disabled capture
--if (Details:CaptureIsAllEnabled()) then
Details:ScheduleTimer("EqualizeActors", 2+math.random()+math.random() , host_of)
--end
end
function Details:EqualizeActors (host_of)
--Disabling the sync. Since WoD combatlog are sent between player on phased zones during encounters.
if (not host_of or true) then --full disabled for testing
return
end
if (Details.debug) then
Details:Msg("(debug) sending equilize actor data")
end
local damage, heal, energy, misc
if (host_of) then
damage, heal, energy, misc = Details:GetAllActors("current", host_of)
else
damage, heal, energy, misc = Details:GetAllActors("current", Details.playername)
end
if (damage) then
damage = {damage.total or 0, damage.damage_taken or 0, damage.friendlyfire_total or 0}
else
damage = {0, 0, 0}
end
if (heal) then
heal = {heal.total or 0, heal.totalover or 0, heal.healing_taken or 0}
else
heal = {0, 0, 0}
end
if (energy) then
energy = {energy.mana or 0, energy.e_rage or 0, energy.e_energy or 0, energy.runepower or 0}
else
energy = {0, 0, 0, 0}
end
if (misc) then
misc = {misc.interrupt or 0, misc.dispell or 0}
else
misc = {0, 0}
end
local data = {damage, heal, energy, misc}
--envia os dados do proprio host pra ele antes
if (host_of) then
Details:SendRaidDataAs (Details.network.ids.CLOUD_EQUALIZE, host_of, nil, data)
Details:EqualizeActors()
else
Details:SendRaidData (Details.network.ids.CLOUD_EQUALIZE, data)
end
end
function Details:FlagActorsOnPvPCombat()
for class_type, container in ipairs(Details.tabela_vigente) do
for _, actor in ipairs(container._ActorTable) do
actor.pvp_component = true
end
end
end
function Details:FlagActorsOnBossFight()
for class_type, container in ipairs(Details.tabela_vigente) do
for _, actor in ipairs(container._ActorTable) do
actor.boss_fight_component = true
end
end
end
local fight_component = function(energy_container, misc_container, name)
local on_energy = energy_container._ActorTable [energy_container._NameIndexTable [name]]
if (on_energy) then
on_energy.fight_component = true
end
local on_misc = misc_container._ActorTable [misc_container._NameIndexTable [name]]
if (on_misc) then
on_misc.fight_component = true
end
end
function Details:FlagActorsOnCommonFight()
local damage_container = Details.tabela_vigente [1]
local healing_container = Details.tabela_vigente [2]
local energy_container = Details.tabela_vigente [3]
local misc_container = Details.tabela_vigente [4]
local mythicDungeonRun = Details.tabela_vigente.is_mythic_dungeon_segment
for class_type, container in ipairs({damage_container, healing_container}) do
for _, actor in ipairs(container._ActorTable) do
if (mythicDungeonRun) then
actor.fight_component = true
end
if (actor.grupo) then
if (class_type == 1 or class_type == 2) then
for target_name, amount in pairs(actor.targets) do
local target_object = container._ActorTable [container._NameIndexTable [target_name]]
if (target_object) then
target_object.fight_component = true
fight_component (energy_container, misc_container, target_name)
end
end
if (class_type == 1) then
for damager_actor, _ in pairs(actor.damage_from) do
local target_object = container._ActorTable [container._NameIndexTable [damager_actor]]
if (target_object) then
target_object.fight_component = true
fight_component (energy_container, misc_container, damager_actor)
end
end
elseif (class_type == 2) then
for healer_actor, _ in pairs(actor.healing_from) do
local target_object = container._ActorTable [container._NameIndexTable [healer_actor]]
if (target_object) then
target_object.fight_component = true
fight_component (energy_container, misc_container, healer_actor)
end
end
end
end
end
end
end
end
function Details:AtualizarJanela (instancia, _segmento)
if (_segmento) then --apenas atualizar janelas que estejam mostrando o segmento solicitado
if (_segmento == instancia.segmento) then
instancia:TrocaTabela(instancia, instancia.segmento, instancia.atributo, instancia.sub_atributo, true)
end
else
if (instancia.modo == groupMode or instancia.modo == everythingMode) then
instancia:TrocaTabela(instancia, instancia.segmento, instancia.atributo, instancia.sub_atributo, true)
end
end
end
function Details:PostponeInstanceToCurrent (instance)
if (
not instance.last_interaction or
(
(instance.ativa) and
(instance.last_interaction+3 < _tempo) and
(not DetailsReportWindow or not DetailsReportWindow:IsShown()) and
(not Details.BreakdownWindowFrame:IsShown())
)
) then
instance._postponing_current = nil
if (instance.segmento == 0) then
return Details:TrocaSegmentoAtual (instance)
else
return
end
end
if (instance.is_interacting and instance.last_interaction < _tempo) then
instance.last_interaction = _tempo
end
instance._postponing_current = Details:ScheduleTimer("PostponeInstanceToCurrent", 1, instance)
end
function Details:TrocaSegmentoAtual (instancia, is_encounter)
if (instancia.segmento == 0 and instancia.baseframe and instancia.ativa) then
if (not is_encounter) then
if (instancia.is_interacting) then
if (not instancia.last_interaction or instancia.last_interaction < _tempo) then
instancia.last_interaction = _tempo or time()
end
end
if ((instancia.last_interaction and (instancia.last_interaction+3 > Details._tempo)) or (DetailsReportWindow and DetailsReportWindow:IsShown()) or (Details.BreakdownWindowFrame:IsShown())) then
--postpone
instancia._postponing_current = Details:ScheduleTimer("PostponeInstanceToCurrent", 1, instancia)
return
end
end
--print("==> Changing the Segment now! - control.lua 1220")
instancia.last_interaction = _tempo - 4 --pode setar, completou o ciclo
instancia._postponing_current = nil
instancia.showing = Details.tabela_vigente
instancia:ResetaGump()
Details.FadeHandler.Fader(instancia, "in", nil, "barras")
end
end
function Details:SetTrashSuppression (n)
assert(type(n) == "number", "SetTrashSuppression expects a number on index 1.")
if (n < 0) then
n = 0
end
Details.instances_suppress_trash = n
end
function Details:CheckFor_SuppressedWindowsOnEncounterFound()
for _, instance in Details:ListInstances() do
if (instance.ativa and instance.baseframe and (not instance.last_interaction or instance.last_interaction > _tempo) and instance.segmento == 0) then
Details:TrocaSegmentoAtual (instance, true)
end
end
end
function Details:CheckFor_EnabledTrashSuppression()
if (Details.HasTrashSuppression and Details.HasTrashSuppression > _tempo) then
self.last_interaction = Details.HasTrashSuppression
end
end
function Details:SetTrashSuppressionAfterEncounter()
Details:InstanceCall("CheckFor_EnabledTrashSuppression")
end
function Details:CheckFor_TrashSuppressionOnEncounterEnd()
if (Details.instances_suppress_trash > 0) then
Details.HasTrashSuppression = _tempo + Details.instances_suppress_trash
--delaying in 3 seconds for other stuff like auto open windows after combat.
Details:ScheduleTimer("SetTrashSuppressionAfterEncounter", 3)
end
end
---internal GetCombatId() version
---@param self details
---@param numberId number|nil if nil, return the current combat id, if 0 resets the id, if a number will add it to the current combat id
---@return number
function Details:GetOrSetCombatId(numberId)
if (numberId == 0) then
Details.combat_id = 0
elseif (numberId) then
Details.combat_id = Details.combat_id + numberId
end
return Details.combat_id
end
--tooltip fork / search key: ~tooltip
local avatarPoint = {"bottomleft", "topleft", -3, -4}
local backgroundPoint = {{"bottomleft", "topleft", 0, -3}, {"bottomright", "topright", 0, -3}}
local textPoint = {"left", "right", -11, -5}
local avatarTexCoord = {0, 1, 0, 1}
local backgroundColor = {0, 0, 0, 0.6}
local avatarTextColor = {1, 1, 1, 1}
function Details:AddTooltipReportLineText()
GameCooltip:AddLine (Loc ["STRING_CLICK_REPORT_LINE1"], Loc ["STRING_CLICK_REPORT_LINE2"])
GameCooltip:AddStatusBar (100, 1, 0, 0, 0, 0.8)
end
function Details:AddTooltipBackgroundStatusbar (side, value, useSpark, statusBarColor)
Details.tooltip.background [4] = 0.8
Details.tooltip.icon_size.W = Details.tooltip.line_height
Details.tooltip.icon_size.H = Details.tooltip.line_height
--[[spark options
["SparkTexture"] = true,
["SparkHeightOffset"] = true,
["SparkWidthOffset"] = true,
["SparkHeight"] = true,
["SparkWidth"] = true,
["SparkAlpha"] = true,
["SparkColor"] = true,
["SparkPositionXOffset"] = true,
["SparkPositionYOffset"] = true,
--]]
useSpark = true
--GameCooltip:SetOption("SparkHeightOffset", 6)
GameCooltip:SetOption("SparkTexture", [[Interface\Buttons\WHITE8X8]])
GameCooltip:SetOption("SparkWidth", 1)
GameCooltip:SetOption("SparkHeight", 20)
GameCooltip:SetOption("SparkColor", Details.tooltip.divisor_color)
GameCooltip:SetOption("SparkAlpha", 0.15)
GameCooltip:SetOption("SparkPositionXOffset", 5)
--GameCooltip:SetOption("SparkAlpha", 0.3)
--GameCooltip:SetOption("SparkPositionXOffset", -2)
value = value or 100
if (not side) then
local r, g, b, a = unpack(Details.tooltip.bar_color)
if (statusBarColor) then
r, g, b, a = detailsFramework:ParseColors(statusBarColor)
end
local rBG, gBG, bBG, aBG = unpack(Details.tooltip.background)
GameCooltip:AddStatusBar (value, 1, r, g, b, a, useSpark, {value = 100, color = {rBG, gBG, bBG, aBG}, texture = [[Interface\AddOns\Details\images\bar_serenity]]})
else
GameCooltip:AddStatusBar (value, 2, unpack(Details.tooltip.bar_color))
end
end
function Details:AddTooltipHeaderStatusbar (r, g, b, a)
local r, g, b, a, statusbarGlow, backgroundBar = unpack(Details.tooltip.header_statusbar)
GameCooltip:AddStatusBar (100, 1, r, g, b, a, statusbarGlow, backgroundBar, "Skyline")
end
-- /run local a,b=Details.tooltip.header_statusbar,0.3;a[1]=b;a[2]=b;a[3]=b;a[4]=0.8;
function Details:AddTooltipSpellHeaderText (headerText, headerColor, amount, iconTexture, L, R, T, B, separator, iconSize)
if (separator and separator == true) then
GameCooltip:AddLine ("", "", nil, nil, 1, 1, 1, 1, 8)
return
end
if (type(iconSize) ~= "number") then
iconSize = 14
end
if (Details.tooltip.show_amount) then
GameCooltip:AddLine (headerText, "x" .. amount .. "", nil, headerColor, 1, 1, 1, .4, Details.tooltip.fontsize_title)
else
GameCooltip:AddLine (headerText, nil, nil, headerColor, nil, Details.tooltip.fontsize_title)
end
if (iconTexture) then
GameCooltip:AddIcon (iconTexture, 1, 1, iconSize, iconSize, L or 0, R or 1, T or 0, B or 1)
end
end
local bgColor, borderColor = {0, 0, 0, 0.8}, {0, 0, 0, 0} --{0.37, 0.37, 0.37, .75}, {.30, .30, .30, .3}
function Details:FormatCooltipForSpells()
local GameCooltip = GameCooltip
GameCooltip:Reset()
GameCooltip:SetType ("tooltip")
GameCooltip:SetOption("StatusBarTexture", [[Interface\AddOns\Details\images\bar_background_dark_withline]])
GameCooltip:SetOption("TextSize", Details.tooltip.fontsize)
GameCooltip:SetOption("TextFont", Details.tooltip.fontface)
GameCooltip:SetOption("TextColor", Details.tooltip.fontcolor)
GameCooltip:SetOption("TextColorRight", Details.tooltip.fontcolor_right)
GameCooltip:SetOption("TextShadow", Details.tooltip.fontshadow and "OUTLINE")
GameCooltip:SetOption("LeftBorderSize", -5)
GameCooltip:SetOption("RightBorderSize", 5)
GameCooltip:SetOption("RightTextMargin", 0)
GameCooltip:SetOption("VerticalOffset", 9)
GameCooltip:SetOption("AlignAsBlizzTooltip", true)
GameCooltip:SetOption("AlignAsBlizzTooltipFrameHeightOffset", -8)
GameCooltip:SetOption("LineHeightSizeOffset", 4)
GameCooltip:SetOption("VerticalPadding", -4)
GameCooltip:SetBackdrop(1, Details.cooltip_preset2_backdrop, bgColor, borderColor)
end
function Details:BuildInstanceBarTooltip(frame)
local GameCooltip = GameCooltip
Details:FormatCooltipForSpells()
GameCooltip:SetOption("MinWidth", _math_max(230, self.baseframe:GetWidth()*0.98))
local myPoint = Details.tooltip.anchor_point
local anchorPoint = Details.tooltip.anchor_relative
local x_Offset = Details.tooltip.anchor_offset[1]
local y_Offset = Details.tooltip.anchor_offset[2]
if (Details.tooltip.anchored_to == 1) then
GameCooltip:SetHost(frame, myPoint, anchorPoint, x_Offset, y_Offset)
else
GameCooltip:SetHost(DetailsTooltipAnchor, myPoint, anchorPoint, x_Offset, y_Offset)
end
end
---@param self instance
---@param frame table
---@param whichRowLine number
---@param keydown string
function Details:MontaTooltip(frame, whichRowLine, keydown)
self:BuildInstanceBarTooltip(frame)
local GameCooltip = GameCooltip
local thisLine = self.barras[whichRowLine] --hoverovered line
local object = thisLine.minha_tabela --the object the line is showing
--check if the object is valid
if (not object) then
return false
end
--check for special tooltips
if (object.dead) then -- uma barra de dead
return Details:ToolTipDead(self, object, thisLine, keydown) --instncia, [morte], barra
elseif (object.byspell) then
return Details:ToolTipBySpell(self, object, thisLine, keydown)
elseif (object.frags) then
return Details:ToolTipFrags(self, object, thisLine, keydown)
elseif (object.boss_debuff) then
return Details:ToolTipVoidZones(self, object, thisLine, keydown)
end
if (not object.ToolTip) then
if (object.__destroyed) then
Details:Msg("object:ToolTip() is invalid.", object.__destroyedBy)
self:ResetWindow()
self:RefreshWindow(true)
return
end
end
local bTooltipBuilt = object:ToolTip(self, whichRowLine, thisLine, keydown) --instance, lineId, lineObject, keydown
if (bTooltipBuilt) then
if (object.serial and object.serial ~= "") then
local avatar = NickTag:GetNicknameTable(object:Name(), true)
if (avatar and not Details.ignore_nicktag) then
if (avatar[2] and avatar[4] and avatar[1]) then
GameCooltip:SetBannerImage(1, 1, avatar [2], 80, 40, avatarPoint, avatarTexCoord, nil) --overlay [2] avatar path
GameCooltip:SetBannerImage(1, 2, avatar [4], 200, 55, backgroundPoint, avatar [5], avatar [6]) --background
GameCooltip:SetBannerText(1, 1, (not Details.ignore_nicktag and avatar[1]) or object.nome, textPoint, avatarTextColor, 14, SharedMedia:Fetch("font", Details.tooltip.fontface)) --text [1] nickname
end
end
end
GameCooltip:ShowCooltip()
end
end
function Details.gump:UpdateTooltip(whichRowLine, esta_barra, instancia)
if (IsShiftKeyDown()) then
return instancia:MontaTooltip(esta_barra, whichRowLine, "shift")
elseif (IsControlKeyDown()) then
return instancia:MontaTooltip(esta_barra, whichRowLine, "ctrl")
elseif (IsAltKeyDown()) then
return instancia:MontaTooltip(esta_barra, whichRowLine, "alt")
else
return instancia:MontaTooltip(esta_barra, whichRowLine)
end
end
function Details:EndRefresh(instancia, total, combatTable, showing)
Details:HideBarsNotInUse(instancia, showing)
end
function Details:HideBarsNotInUse(instance, showing)
if (instance.v_barras) then
--print("mostrando", instancia.rows_showing, instancia.rows_created)
for barra_numero = instance.rows_showing+1, instance.rows_created do
Details.FadeHandler.Fader(instance.barras[barra_numero], "in")
end
instance.v_barras = false
if (instance.rows_showing == 0 and instance:GetSegment() == -1) then -- -1 overall data
if (not instance:IsShowingOverallDataWarning()) then
local tutorial = Details:GetTutorialCVar("OVERALLDATA_WARNING1") or 0
if ((type(tutorial) == "number") and (tutorial < 60)) then
Details:SetTutorialCVar ("OVERALLDATA_WARNING1", tutorial + 1)
instance:ShowOverallDataWarning (true)
end
end
else
if (instance:IsShowingOverallDataWarning()) then
instance:ShowOverallDataWarning (false)
end
end
end
return showing
end
--call update functions
function Details:RefreshAllMainWindows(bForceRefresh) --getting deprecated soon
local combatObject = self.showing
--the the segment does not have a valid combat, freeze the window
if (not combatObject) then
if (not self.freezed) then
return self:Freeze()
end
return
end
local needRefresh = combatObject[self.atributo].need_refresh --erro de index nil value
if (not needRefresh and not bForceRefresh) then
return
end
--measure the cpu time spent on this function
--local startTime = debugprofilestop()
if (self.atributo == 1) then --damage
--[[return]] atributo_damage:RefreshWindow(self, combatObject, bForceRefresh, nil, needRefresh)
elseif (self.atributo == 2) then --heal
--[[return]] atributo_heal:RefreshWindow(self, combatObject, bForceRefresh, nil, needRefresh)
elseif (self.atributo == 3) then --energy
--[[return]] atributo_energy:RefreshWindow(self, combatObject, bForceRefresh, nil, needRefresh)
elseif (self.atributo == 4) then --outros
--[[return]] atributo_misc:RefreshWindow(self, combatObject, bForceRefresh, nil, needRefresh)
elseif (self.atributo == 5) then --ocustom
--[[return]] atributo_custom:RefreshWindow(self, combatObject, bForceRefresh, nil, needRefresh)
end
--[[if (Details222.Perf.WindowUpdateC) then
local elapsedTime = debugprofilestop() - startTime
if (Details222.Perf.WindowUpdate) then
Details222.Perf.WindowUpdate = Details222.Perf.WindowUpdate + elapsedTime
end
end--]]
end
--["1"] = "WindowUpdate",
--["2"] = 308.6662000129,
function Details:DumpPerf()
local t = {}
for name, value in pairs(Details222.Perf) do
t[#t+1] = {name, value}
end
dumpt(t)
end
function Details:ForceRefresh() --getting deprecated soon
self:RefreshMainWindow(true)
end
function Details:RefreshMainWindow(instanceObject, bForceRefresh) --getting deprecated soon
if (not instanceObject or type(instanceObject) == "boolean") then
bForceRefresh = instanceObject
instanceObject = self
end
if (not bForceRefresh) then
Details.LastUpdateTick = Details._tempo
end
if (instanceObject == -1) then
--update
for index, thisInstance in ipairs(Details.tabela_instancias) do
---@cast thisInstance instance
if (thisInstance:IsEnabled()) then
if (thisInstance:GetMode() == DETAILS_MODE_GROUP or thisInstance:GetMode() == DETAILS_MODE_ALL) then
thisInstance:RefreshData(bForceRefresh)
end
end
end
--flag windows as no need update next tick
for index, thisInstance in ipairs(Details.tabela_instancias) do
if (thisInstance:IsEnabled() and thisInstance.showing) then
if (thisInstance:GetMode() == DETAILS_MODE_GROUP or thisInstance:GetMode() == DETAILS_MODE_ALL) then
if (thisInstance.atributo <= 4) then
thisInstance.showing[thisInstance.atributo].need_refresh = false
end
end
end
end
if (not bForceRefresh) then --update player details window if opened
if (Details.BreakdownWindowFrame:IsShown()) then
---@type actor
local actorObject = Details:GetActorObjectFromBreakdownWindow()
if (actorObject and not actorObject.__destroyed) then
return actorObject:MontaInfo() --MontaInfo a nil value
else
Details:Msg("Invalid actor object on breakdown window.")
if (actorObject.__destroyed) then
Details:Msg("Invalidation Reason:", actorObject.__destroyedBy)
end
end
end
end
return
else
if (not instanceObject.ativa) then
return
end
end
local currentMode = instanceObject:GetMode()
if (currentMode == DETAILS_MODE_ALL or currentMode == DETAILS_MODE_GROUP) then
return instanceObject:RefreshAllMainWindows(bForceRefresh)
end
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--core
function Details:AutoEraseConfirm()
local panel = _G.DetailsEraseDataConfirmation
if (not panel) then
panel = CreateFrame("frame", "DetailsEraseDataConfirmation", UIParent, "BackdropTemplate")
panel:SetSize(400, 85)
panel:SetBackdrop({bgFile = [[Interface\AddOns\Details\images\background]], tile = true, tileSize = 16,
edgeFile = [[Interface\AddOns\Details\images\border_2]], edgeSize = 12})
panel:SetPoint("center", UIParent)
panel:SetBackdropColor(0, 0, 0, 0.4)
DetailsFramework:ApplyStandardBackdrop(panel)
panel:SetScript("OnMouseDown", function(self, button)
if (button == "RightButton") then
panel:Hide()
end
end)
--[=[
create 3 options
- overall data only
- current data only
- both
--]=]
local text = Details.gump:CreateLabel(panel, Loc ["STRING_OPTIONS_CONFIRM_ERASE"], nil, nil, "GameFontNormal")
text:SetPoint("center", panel, "center")
text:SetPoint("top", panel, "top", 0, -10)
local no = Details.gump:CreateButton(panel, function() panel:Hide() end, 90, 20, Loc ["STRING_NO"])
no:SetPoint("bottomleft", panel, "bottomleft", 30, 10)
no:InstallCustomTexture(nil, nil, nil, nil, true)
local yes = Details.gump:CreateButton(panel, function() panel:Hide(); Details.tabela_historico:ResetAllCombatData() end, 90, 20, Loc ["STRING_YES"])
yes:SetPoint("bottomright", panel, "bottomright", -30, 10)
yes:InstallCustomTexture(nil, nil, nil, nil, true)
end
panel:Show()
end
function Details:CheckForAutoErase(mapId)
if (Details.last_instance_id ~= mapId) then
Details.tabela_historico:ResetOverallData()
if (Details.segments_auto_erase == 2) then --ask to erase
Details:ScheduleTimer("AutoEraseConfirm", 1)
elseif (Details.segments_auto_erase == 3) then
--erase
Details.tabela_historico:ResetAllCombatData()
end
else
if (_tempo > Details.last_instance_time + 21600) then --6 hours
if (Details.segments_auto_erase == 2) then
--ask
Details:ScheduleTimer("AutoEraseConfirm", 1)
elseif (Details.segments_auto_erase == 3) then
--erase
Details.tabela_historico:ResetAllCombatData()
end
end
end
Details.last_instance_id = mapId
Details.last_instance_time = _tempo
end
function Details:UpdateControl()
_tempo = Details._tempo
end