From 0f60a50e732e64c2a63e7dfaf1e95b761929a1c8 Mon Sep 17 00:00:00 2001 From: Tercio Date: Wed, 27 May 2015 18:19:41 -0300 Subject: [PATCH] - TimeLine (plugin): now also shows marks symbolizing the player death. - Added raid history panel. Open it through bookmark or /details history. - Added support for skins for Player Detail Window. - Added report history on report button. - Added key bindings settings for report what is shown on window #1 or #2. --- API General.txt | 51 ++- API UI.txt | 27 ++ API.txt | 8 +- boot.lua | 6 +- core/control.lua | 3 + core/gears.lua | 202 +++++++++- core/parser.lua | 16 +- core/windows.lua | 464 +++++++++++++++++++++- framework/panel.lua | 886 ++++++++++++++++++++++++++++++++---------- functions/boss.lua | 34 ++ functions/events.lua | 4 + functions/slash.lua | 3 + gumps/janela_news.lua | 4 + gumps/switch.lua | 56 ++- images/icons.tga | Bin 388124 -> 399879 bytes images/icons2.tga | Bin 305355 -> 319281 bytes 16 files changed, 1532 insertions(+), 232 deletions(-) diff --git a/API General.txt b/API General.txt index 16475e5d..50570182 100644 --- a/API General.txt +++ b/API General.txt @@ -1,3 +1,15 @@ + +======================================= +Background Tasks +======================================= + +Details:RegisterBackgroundTask (name, func, priority, ...) +register a function to be called while the player isn't in: combat, group, raid instances. +priority determines the interval time and support "LOW", "MEDIUM", "HIGH" values. + +Details:UnregisterBackgroundTask (name) +unregister a background task. + ======================================= Item Level ======================================= @@ -19,6 +31,43 @@ Details.ilevel:GetInOrder() return a new numeric table with sorted in decreasing order: {{name-realm, item level, time()}, {name-realm, item level, time()}} +======================================= +Raid History ======================================= -======================================= \ No newline at end of file +_detalhes.storage:OpenRaidStorage() +get the table containing all stored data. + +_detalhes.storage:ListDiffs() +return a indexed table with dificulty numbers. + +_detalhes.storage:ListEncounters (diff) +return a indexed table with all encounters stored for the dificulty. + +_detalhes.storage:GetEncounterData (diff, encounterId, guildname) +return a indexed table with encounter tables playd by the guild. + +_detalhes.storage:GetPlayerData (diff, encounterId, playername) +return a indexed table with player tables for the player. + +_detalhes.storage:GetBestFromPlayer (diff, encounterId, role, playername) +return the best result from the player. + +Structure: +DB = hash{ + [difficulty index] = hash{ + [encounter id] = indexed{ + { + --encounter table + damage = hash{ + [playername] = indexed{} --player table + } + ... + }--[1] + } + } +} + +encounter table = hash {["time"] = time(), ["guild"] = guild name, ["date"] = formated date, ["healing"] = {[playername] = playertable}, ["elapsed"] = combat time, ["damage"] = {[playername] = playertable}} +player table = indexed {total done, item level, class index} + diff --git a/API UI.txt b/API UI.txt index b478c1f6..801f2e55 100644 --- a/API UI.txt +++ b/API UI.txt @@ -1,4 +1,8 @@ +local instance = Details:GetInstance (number) +returns the window object. + +============================================================= instance:DesaturateMenu (enabled) enabled = boolean, if true, buttons on the window's title bar became black and white. @@ -189,3 +193,26 @@ returns true with the 'self instance' is groupped with the passed instance objec instance:GetInstanceGroup() returns a table with instances objects of all instances inside the group. +-------------------- + +Details:SetWindowUpdateSpeed (interval, nosave) +set the update speed of all windows, if nosave is true, it won't save this change (apply only). + +Details:SetUseAnimations (enabled, nosave) +set on off bar animations on all windows, if nosave is true, it won't save this change (apply only). + +-------------------- + +_detalhes:OpenForge() +Open Forge Window. + +_detalhes:OpenRaidHistoryWindow() +Open Raid History Window. + +_detalhes.switch:ShowMe (instance object) +Open the bookmark panel on the top of the desired window. + +_detalhes.switch:CloseMe() +Closes the bookmark panel. + + diff --git a/API.txt b/API.txt index e4b7e8b5..d83d924c 100644 --- a/API.txt +++ b/API.txt @@ -43,6 +43,7 @@ DETAILS_SUBATTRIBUTE_DEBUFFUPTIME = 8 TL;DR ======================================= A history segment container is the higher table in the hierarchy, it holds combat objects. +Current and overall combat objects are directly attached to the core. When a combat finishes, it detach and is sent to history container. A Combat Object has 4 tables: damage on index 1, healing on index 2, energy on index 3, misc on index 4. Those tables are called 'containers', these containers holds 'Actor Objects'. Actor Objects hold a target table and a spell table. @@ -316,8 +317,11 @@ cc_break_spells = cc_break_oque = - - +Other API Calls: +======================================= + +Details:SetDeathLogLimit (limit) +Set the amount of lines to store on death log. diff --git a/boot.lua b/boot.lua index 71d89fe5..9aff14e9 100644 --- a/boot.lua +++ b/boot.lua @@ -4,7 +4,7 @@ _ = nil _detalhes = LibStub("AceAddon-3.0"):NewAddon("_detalhes", "AceTimer-3.0", "AceComm-3.0", "AceSerializer-3.0", "NickTag-1.0") _detalhes.build_counter = 896 --it's 896 for release - _detalhes.userversion = "v3.14.3" + _detalhes.userversion = "v3.14.4" _detalhes.realversion = 69 --core version _detalhes.version = _detalhes.userversion .. " (core " .. _detalhes.realversion .. ")" Details = _detalhes @@ -24,11 +24,13 @@ do |cFFFFFF00-|r .\n\n |cFFFFFF00v3.14.1 (|cFFFFCC00May 15, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added support for skins for Player Detail Window.\n\n |cFFFFFF00-|r Added report history on report button.\n\n|cFFFFFF00-|r Added key bindings settings for report what is shown on window #1 or #2.\n\n +|cFFFFFF00-|r Added raid history panel. Open it through bookmark or /details history.\n\n +|cFFFFFF00-|r TimeLine (plugin): now also shows marks symbolizing the player death.\n\n --]] -- - Loc ["STRING_VERSION_LOG"] = "|cFFFFFF00v3.14.3 (|cFFFFCC00May 22, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added support for skins for Player Detail Window.\n\n|cFFFFFF00-|r Added report history on report button.\n\n|cFFFFFF00-|r Added key bindings settings for report what is shown on window #1 or #2.\n\n|cFFFFFF00v3.14.0b (|cFFFFCC00May 13, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Several texture changes for a smaller download size.\n\n|cFFFFFF00v3.13.4a (|cFFFFCC00May 06, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Created 'Details! Forge' but is still under development: /details forge.\n\n|cFFFFFF00-|r Several improvements on combatlog reader and custom displays.\n\n|cFFFFFF00-|r Improvements on enemies damage taken tooltip.\n\n|cFFFFFF00v3.13.1 (|cFFFFCC00April 27, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added casted amount for healing and damage spells. The result is shown on Player Detail Window and Comparison Panel.\n\n|cFFFFFF00-|r Added uptime amount for healing spells on Player Detail Window.\n\n|cFFFFFF00-|r Added an option to disable lock/resize/ungroup buttons. It's under miscellaneous bracket on Options Panel.\n\n|cFFFFFF00-|r Wallpaper for menus on title bar got a customization option under tooltips bracket on Options Panel.\n\n|cFFFFFF00-|r Updated spell list for Crowd Control and class detection.\n\n|cFFFFFF00-|r Improved Weakaura creation tool under Encounter Details Plugin.\n\n|cFFFFFF00v3.12.10 (|cFFFFCC00April 18, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added the spell uptime on damage player detail window.\n\n|cFFFFFF00-|r Added Rune Tap as cooldown.\n\n|cFFFFFF00-|r Report lines for deaths is now inverted. No need to scroll up the chat to see the cause of death anymore.\n\n|cFFFFFF00-|r Fixed some annoyances with auto current feature where it was changing the segments even when the user were using the window.\n\n|cFFFFFF00v3.12.7 (|cFFFFCC00April 09, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added slash command /details profile .\n\n|cFFFFFF00v3.12.6 (|cFFFFCC00May 06, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Encounter Details (plugin): added a report button on Emotes tab.\n\n|cFFFFFF00-|r Encounter Details (plugin): improved Weakauras creation.\n\n|cFFFFFF00-|r Encounter Details (plugin): fixed the report text for interrupts and dispells.\n\n|cFFFFFF00-|r Crowd control by Pets now is merged with its owner.\n\n|cFFFFFF00-|r Truncated left text on bars now shows the entire text when hover over the bar.\n\n|cFFFFFF00-|r Added an extra option on minimap menu to disable the minimap icon.\n\n|cFFFFFF00-|r Fixed a problem when disabling the minimap icon through options panel.\n\n|cFFFFFF00-|r Fixed an issue with item level tracker feature.\n\n|cFFFFFF00v3.12.2 (|cFFFFCC00Mar 30, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Raid Check (plugin): added item level.\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): bug fixes, it's important to update it.\n\n|cFFFFFF00-|r Time Line (plugin): bug fixes.\n\n|cFFFFFF00-|r Added option to disabled th eitem level tracker.\n\n|cFFFFFF00-|r Added item level on Raid Check plugin panel.\n\n|cFFFFFF00-|r Added Spirit Link Totem damage and healing on death log.\n\n|cFFFFFF00-|r Added the total of life exchanged by the Spirit Link Totem on player detail window.\n\n|cFFFFFF00-|r Added more spells for crowd control.\n\n|cFFFFFF00-|r Added scroll on bookmark panel.\n\n|cFFFFFF00-|r Fixed issue with Dps/Hps data broker which wasn't respecting the type of time (effective/activity) selected.\n\n|cFFFFFF00-|r Fixed few bugs when using the key bind to toggle windows.\n\n|cFFFFFF00v3.11.5 (|cFFFFCC00Mar 18, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Fixed an issue with overall data where max/min amount for spells was being calculated wrong.\n\n|cFFFFFF00-|r Fixed a problem while reporting enemy damage taken.\n\n|cFFFFFF00-|r Fixed an issue with damage taken by spell where some spells wasn't shown.\n\n|cFFFFFF00-|r Few improvements on tooltips.\n\n|cFFFFFF00-|r Fixed a accuracy problem with shaman's spirit link toten where its life exchange was considered healing done.\n\n|cFFFFFF00-|r Boss segments now need to have at least 30 seconds to be added on the overall data.\n\n|cFFFFFF00-|r When the segment limit is reach, segments with less combat time will be erased instead of the olders.\n\n|cFFFFFF00-|r Added item level tracker.\n\n|cFFFFFF00-|r Fixed window positioning when changing from Solo mode to Group mode.\n\n|cFFFFFF00-|r Added an option for change the amount of lines on death log.\n\n|cFFFFFF00-|r Added custom display for CC done.\n\n|cFFFFFF00v3.10.10 (|cFFFFCC00Mar 10, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): done a calibration on endurance calculation.\n\n|cFFFFFF00-|r Raid Check (plugin): now also show information for 50+ stats runes.\n\n|cFFFFFF00-|r Fixed cooldowns spell targets on overall data.\n\n|cFFFFFF00-|r Fixed report custom displays where sometimes it didn't report spell links.\n\n|cFFFFFF00-|r Replaced old report line saying 'for the last X segments' with 'overall data'.\n\n|cFFFFFF00-|r Mini-displays on statusbar are now more responsible to right button click.\n\n|cFFFFFF00-|r Added Mage's Greater Invisibility as cooldown.\n\n|cFFFFFF00v3.10.8 (|cFFFFCC00Mar 02, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Improved aura creation.\n\n|cFFFFFF00-|r Custom display 'My Spells' now also show your pets.\n\n|cFFFFFF00-|r Fixed 'Raid Check' plugin food detection.\n\n|cFFFFFF00v3.10.6 (|cFFFFCC00Feb 24, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Encounter Details (plugin): several bug fixes and improvements.\n\n|cFFFFFF00-|r Encounter Details (plugin): added new tab showing enemy spells from the encounter with an aura creation panel.\n\n|cFFFFFF00-|r Encounter Details (plugin): added phase indicators on graphic tab, hovering over shows the damage and heal for that specific phase.\n\n|cFFFFFF00-|r Segments menu now also show the encounter try number plus the elapsed combat time.\n\n|cFFFFFF00-|r Several improvements on Overall Data, also, now it has an option to not save it when the character logoff.\n\n|cFFFFFF00-|r Player Details window now closes with right click on any bar.\n\n|cFFFFFF00-|r Fixed Nature's Vigil cooldown.\n\n|cFFFFFF00v3.9.12a (|cFFFFCC00Feb 16, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Roll back on combat detection after reset data, might be causing problems.\n\n|cFFFFFF00v3.9.12 (|cFFFFCC00Feb 13, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): got report buttons for Endurance and Deaths Overall Damage.\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): the Deaths and Endurance limits can now be changed under the plugin options.\n\n|cFFFFFF00-|r Chart Viewer (plugin): has been revamped and now it's less complicated to use." + Loc ["STRING_VERSION_LOG"] = "|cFFFFFF00v3.14.4 (|cFFFFCC00May 27, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r TimeLine (plugin): now also shows marks symbolizing the player death.\n\n|cFFFFFF00-|r Added raid history panel. Open it through bookmark or /details history.\n\n|cFFFFFF00-|r Added support for skins for Player Detail Window.\n\n|cFFFFFF00-|r Added report history on report button.\n\n|cFFFFFF00-|r Added key bindings settings for report what is shown on window #1 or #2.\n\n|cFFFFFF00v3.14.0b (|cFFFFCC00May 13, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Several texture changes for a smaller download size.\n\n|cFFFFFF00v3.13.4a (|cFFFFCC00May 06, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Created 'Details! Forge' but is still under development: /details forge.\n\n|cFFFFFF00-|r Several improvements on combatlog reader and custom displays.\n\n|cFFFFFF00-|r Improvements on enemies damage taken tooltip.\n\n|cFFFFFF00v3.13.1 (|cFFFFCC00April 27, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added casted amount for healing and damage spells. The result is shown on Player Detail Window and Comparison Panel.\n\n|cFFFFFF00-|r Added uptime amount for healing spells on Player Detail Window.\n\n|cFFFFFF00-|r Added an option to disable lock/resize/ungroup buttons. It's under miscellaneous bracket on Options Panel.\n\n|cFFFFFF00-|r Wallpaper for menus on title bar got a customization option under tooltips bracket on Options Panel.\n\n|cFFFFFF00-|r Updated spell list for Crowd Control and class detection.\n\n|cFFFFFF00-|r Improved Weakaura creation tool under Encounter Details Plugin.\n\n|cFFFFFF00v3.12.10 (|cFFFFCC00April 18, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added the spell uptime on damage player detail window.\n\n|cFFFFFF00-|r Added Rune Tap as cooldown.\n\n|cFFFFFF00-|r Report lines for deaths is now inverted. No need to scroll up the chat to see the cause of death anymore.\n\n|cFFFFFF00-|r Fixed some annoyances with auto current feature where it was changing the segments even when the user were using the window.\n\n|cFFFFFF00v3.12.7 (|cFFFFCC00April 09, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Added slash command /details profile .\n\n|cFFFFFF00v3.12.6 (|cFFFFCC00May 06, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Encounter Details (plugin): added a report button on Emotes tab.\n\n|cFFFFFF00-|r Encounter Details (plugin): improved Weakauras creation.\n\n|cFFFFFF00-|r Encounter Details (plugin): fixed the report text for interrupts and dispells.\n\n|cFFFFFF00-|r Crowd control by Pets now is merged with its owner.\n\n|cFFFFFF00-|r Truncated left text on bars now shows the entire text when hover over the bar.\n\n|cFFFFFF00-|r Added an extra option on minimap menu to disable the minimap icon.\n\n|cFFFFFF00-|r Fixed a problem when disabling the minimap icon through options panel.\n\n|cFFFFFF00-|r Fixed an issue with item level tracker feature.\n\n|cFFFFFF00v3.12.2 (|cFFFFCC00Mar 30, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Raid Check (plugin): added item level.\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): bug fixes, it's important to update it.\n\n|cFFFFFF00-|r Time Line (plugin): bug fixes.\n\n|cFFFFFF00-|r Added option to disabled th eitem level tracker.\n\n|cFFFFFF00-|r Added item level on Raid Check plugin panel.\n\n|cFFFFFF00-|r Added Spirit Link Totem damage and healing on death log.\n\n|cFFFFFF00-|r Added the total of life exchanged by the Spirit Link Totem on player detail window.\n\n|cFFFFFF00-|r Added more spells for crowd control.\n\n|cFFFFFF00-|r Added scroll on bookmark panel.\n\n|cFFFFFF00-|r Fixed issue with Dps/Hps data broker which wasn't respecting the type of time (effective/activity) selected.\n\n|cFFFFFF00-|r Fixed few bugs when using the key bind to toggle windows.\n\n|cFFFFFF00v3.11.5 (|cFFFFCC00Mar 18, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Fixed an issue with overall data where max/min amount for spells was being calculated wrong.\n\n|cFFFFFF00-|r Fixed a problem while reporting enemy damage taken.\n\n|cFFFFFF00-|r Fixed an issue with damage taken by spell where some spells wasn't shown.\n\n|cFFFFFF00-|r Few improvements on tooltips.\n\n|cFFFFFF00-|r Fixed a accuracy problem with shaman's spirit link toten where its life exchange was considered healing done.\n\n|cFFFFFF00-|r Boss segments now need to have at least 30 seconds to be added on the overall data.\n\n|cFFFFFF00-|r When the segment limit is reach, segments with less combat time will be erased instead of the olders.\n\n|cFFFFFF00-|r Added item level tracker.\n\n|cFFFFFF00-|r Fixed window positioning when changing from Solo mode to Group mode.\n\n|cFFFFFF00-|r Added an option for change the amount of lines on death log.\n\n|cFFFFFF00-|r Added custom display for CC done.\n\n|cFFFFFF00v3.10.10 (|cFFFFCC00Mar 10, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): done a calibration on endurance calculation.\n\n|cFFFFFF00-|r Raid Check (plugin): now also show information for 50+ stats runes.\n\n|cFFFFFF00-|r Fixed cooldowns spell targets on overall data.\n\n|cFFFFFF00-|r Fixed report custom displays where sometimes it didn't report spell links.\n\n|cFFFFFF00-|r Replaced old report line saying 'for the last X segments' with 'overall data'.\n\n|cFFFFFF00-|r Mini-displays on statusbar are now more responsible to right button click.\n\n|cFFFFFF00-|r Added Mage's Greater Invisibility as cooldown.\n\n|cFFFFFF00v3.10.8 (|cFFFFCC00Mar 02, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Improved aura creation.\n\n|cFFFFFF00-|r Custom display 'My Spells' now also show your pets.\n\n|cFFFFFF00-|r Fixed 'Raid Check' plugin food detection.\n\n|cFFFFFF00v3.10.6 (|cFFFFCC00Feb 24, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Encounter Details (plugin): several bug fixes and improvements.\n\n|cFFFFFF00-|r Encounter Details (plugin): added new tab showing enemy spells from the encounter with an aura creation panel.\n\n|cFFFFFF00-|r Encounter Details (plugin): added phase indicators on graphic tab, hovering over shows the damage and heal for that specific phase.\n\n|cFFFFFF00-|r Segments menu now also show the encounter try number plus the elapsed combat time.\n\n|cFFFFFF00-|r Several improvements on Overall Data, also, now it has an option to not save it when the character logoff.\n\n|cFFFFFF00-|r Player Details window now closes with right click on any bar.\n\n|cFFFFFF00-|r Fixed Nature's Vigil cooldown.\n\n|cFFFFFF00v3.9.12a (|cFFFFCC00Feb 16, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Roll back on combat detection after reset data, might be causing problems.\n\n|cFFFFFF00v3.9.12 (|cFFFFCC00Feb 13, 2015|r|cFFFFFF00)|r:\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): got report buttons for Endurance and Deaths Overall Damage.\n\n|cFFFFFF00-|r Advanced Death Logs (plugin): the Deaths and Endurance limits can now be changed under the plugin options.\n\n|cFFFFFF00-|r Chart Viewer (plugin): has been revamped and now it's less complicated to use." Loc ["STRING_DETAILS1"] = "|cffffaeaeDetails!:|r " diff --git a/core/control.lua b/core/control.lua index 1e3bd8a8..a25eb61b 100644 --- a/core/control.lua +++ b/core/control.lua @@ -466,6 +466,9 @@ _detalhes.schedule_store_boss_encounter = true end + _detalhes:SendEvent ("COMBAT_BOSS_DEFEATED", nil, _detalhes.tabela_vigente) + else + _detalhes:SendEvent ("COMBAT_BOSS_WIPE", nil, _detalhes.tabela_vigente) end --if (_detalhes:GetBossDetails (_detalhes.tabela_vigente.is_boss.mapid, _detalhes.tabela_vigente.is_boss.index) or ) then diff --git a/core/gears.lua b/core/gears.lua index a5c81efd..2f494aa1 100644 --- a/core/gears.lua +++ b/core/gears.lua @@ -17,8 +17,6 @@ function _detalhes:UpdateGears() end - - ------------------------------------------------------------------------------------------------------------ function _detalhes:SetDeathLogLimit (limit) @@ -366,7 +364,9 @@ _detalhes.background_tasks_loop = _detalhes:ScheduleRepeatingTimer ("DoBackgroun --> storage stuff ~storage --global database -function _detalhes:OpenRaidStorage() +_detalhes.storage = {} + +function _detalhes.storage:OpenRaidStorage() --> check if the storage is already loaded if (not IsAddOnLoaded ("Details_DataStorage")) then local loaded, reason = LoadAddOn ("Details_DataStorage") @@ -386,14 +386,178 @@ function _detalhes:OpenRaidStorage() elseif (not db) then return end - - --GlobalDatabase = {} - --UserChrStorage = {} - + return db end +function _detalhes.storage:GetBestFromPlayer (diff, encounter_id, role, playername) + local db = _detalhes.storage:OpenRaidStorage() + + local best + local onencounter + + if (not role) then + role = "damage" + end + role = string.lower (role) + if (role == "damager") then + role = "damage" + elseif (role == "healer") then + role = "healing" + end + + local table = db [diff] + if (table) then + local encounters = table [encounter_id] + if (encounters) then + for index, encounter in ipairs (encounters) do + local player = encounter [role] and encounter [role] [playername] + if (player) then + if (best) then + if (player[1] > best[1]) then + onencounter = encounter + best = player + end + else + onencounter = encounter + best = player + end + end + end + end + end + + return best, onencounter +end +function _detalhes.storage:ListDiffs() + local db = _detalhes.storage:OpenRaidStorage() + local t = {} + for diff, _ in pairs (db) do + tinsert (t, diff) + end + return t +end + +function _detalhes.storage:ListEncounters (diff) + local db = _detalhes.storage:OpenRaidStorage() + + local t = {} + if (diff) then + local table = db [diff] + if (table) then + for encounter_id, _ in pairs (table) do + tinsert (t, {diff, encounter_id}) + end + end + else + for diff, table in pairs (db) do + for encounter_id, _ in pairs (table) do + tinsert (t, {diff, encounter_id}) + end + end + end + + return t +end + +function _detalhes.storage:GetPlayerData (diff, encounter_id, playername) + local db = _detalhes.storage:OpenRaidStorage() + + local t = {} + assert (type (playername) == "string", "PlayerName must be a string.") + + + if (not diff) then + for diff, table in pairs (db) do + if (encounter_id) then + local encounters = table [encounter_id] + if (encounters) then + for i = 1, #encounters do + local encounter = encounters [i] + local player = encounter.healing [playername] or encounter.damage [playername] + if (player) then + tinsert (t, player) + end + end + end + else + for encounter_id, encounters in pairs (table) do + for i = 1, #encounters do + local encounter = encounters [i] + local player = encounter.healing [playername] or encounter.damage [playername] + if (player) then + tinsert (t, player) + end + end + end + end + end + else + local table = db [diff] + if (table) then + if (encounter_id) then + local encounters = table [encounter_id] + if (encounters) then + for i = 1, #encounters do + local encounter = encounters [i] + local player = encounter.healing [playername] or encounter.damage [playername] + if (player) then + tinsert (t, player) + end + end + end + else + for encounter_id, encounters in pairs (table) do + for i = 1, #encounters do + local encounter = encounters [i] + local player = encounter.healing [playername] or encounter.damage [playername] + if (player) then + tinsert (t, player) + end + end + end + end + end + end + + return t +end + +function _detalhes.storage:GetEncounterData (diff, encounter_id, guild) + local db = _detalhes.storage:OpenRaidStorage() + + if (not diff) then + return db + end + + local data = db [diff] + + assert (data, "Difficulty not found. Use: 14, 15 or 16.") + assert (type (encounter_id) == "number", "EncounterId must be a number.") + + data = data [encounter_id] + + local t = {} + + if (not data) then + return t + end + + for i = 1, #data do + local encounter = data [i] + + if (guild) then + if (encounter.guild == guild) then + tinsert (t, encounter) + end + else + tinsert (t, encounter) + end + end + + return t +end local store_instances = { [1205] = true, --Blackrock Foundry @@ -498,7 +662,7 @@ function _detalhes:StoreEncounter (combat) local role = UnitGroupRolesAssigned ("raid" .. i) - if (role == "DAMAGER") then + if (role == "DAMAGER" or role == "TANK") then local player_name, player_realm = UnitName ("raid" .. i) if (player_realm and player_realm ~= "") then player_name = player_name .. "-" .. player_realm @@ -532,6 +696,26 @@ function _detalhes:StoreEncounter (combat) print ("|cFFFFFF00Details! Storage|r: encounter saved!") + local myrole = UnitGroupRolesAssigned ("player") + local mybest, onencounter = _detalhes.storage:GetBestFromPlayer (diff, encounter_id, myrole, _detalhes.playername) + + print (myrole, mybest and mybest[1], mybest and mybest[2], mybest and mybest[3], onencounter and onencounter.date) + + if (mybest) then + local done = 0 + if (myrole == "DAMAGER" or myrole == "TANK") then + done = combat (1, _detalhes.playername) and combat (1, _detalhes.playername).total + elseif (myrole == "HEALER") then + done = combat (2, _detalhes.playername) and combat (2, _detalhes.playername).total + end + + if (mybest[1] > done) then + print (Loc ["STRING_DETAILS1"] .. format (Loc ["STRING_SCORE_NOTBEST"], _detalhes:comma_value (done), mybest[1], onencounter.date, mybest[2])) + else + print (Loc ["STRING_DETAILS1"] .. format (Loc ["STRING_SCORE_BEST"], _detalhes:comma_value (done))) + end + end + end end @@ -660,7 +844,7 @@ function ilvl_core:InspectTimeOut (guid) end function ilvl_core:GetItemLevel (unitid, guid) - --> double check + --> ddouble check if (UnitAffectingCombat ("player") or InCombatLockdown()) then return end diff --git a/core/parser.lua b/core/parser.lua index 7ee27976..264278fc 100644 --- a/core/parser.lua +++ b/core/parser.lua @@ -84,6 +84,8 @@ local last_events_cache = {} --> placeholder --> pets local container_pets = {} --> place holder + --> ignore deaths + local ignore_death = {} ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --> constants @@ -1186,6 +1188,12 @@ if (tipo == "BUFF") then ------------------------------------------------------------------------------------------------ --> buff uptime + + if (spellid == 27827) then --> spirit of redemption (holy priest) + parser:dead ("UNIT_DIED", time, who_serial, who_name, who_flags, alvo_serial, alvo_name, alvo_flags) + ignore_death [who_name] = true + return + end if (_recording_buffs_and_debuffs) then if (who_name == alvo_name and raid_members_cache [who_serial] and _in_combat) then @@ -2547,6 +2555,11 @@ _in_combat ) then + if (ignore_death [alvo_name]) then + ignore_death [alvo_name] = nil + return + end + _current_misc_container.need_refresh = true --> combat totals @@ -3451,8 +3464,6 @@ end function _detalhes:ClearParserCache() - - --> clear cache | not sure if replacing the old table is the best approach _table_wipe (damage_cache) _table_wipe (damage_cache_pets) @@ -3460,6 +3471,7 @@ _table_wipe (healing_cache) _table_wipe (energy_cache) _table_wipe (misc_cache) + _table_wipe (ignore_death) damage_cache = setmetatable ({}, _detalhes.weaktable) damage_cache_pets = setmetatable ({}, _detalhes.weaktable) diff --git a/core/windows.lua b/core/windows.lua index c3345b02..24778a79 100644 --- a/core/windows.lua +++ b/core/windows.lua @@ -1,4 +1,3 @@ ---> this file controls the window position, size and others panels local _detalhes = _G._detalhes local Loc = LibStub ("AceLocale-3.0"):GetLocale ( "Details" ) @@ -1006,6 +1005,453 @@ function _detalhes:OpenTranslateWindow() end + +--> raid history window + function _detalhes:OpenRaidHistoryWindow() + + if (not _G.DetailsRaidHistoryWindow) then + + local db = _detalhes.storage:OpenRaidStorage() + if (not db) then + return _detalhes:Msg ("Fail to open raid storage, may be the addon is disabled.") + end + + local f = CreateFrame ("frame", "DetailsRaidHistoryWindow", UIParent, "ButtonFrameTemplate") + f:SetPoint ("center", UIParent, "center") + f:SetFrameStrata ("HIGH") + f:SetToplevel (true) + + f:SetMovable (true) + f:SetWidth (850) + f:SetHeight (500) + tinsert (UISpecialFrames, "DetailsRaidHistoryWindow") + + local background = f:CreateTexture (nil, "border") + background:SetAlpha (0.3) + background:SetPoint ("topleft", f, "topleft", 6, -65) + background:SetPoint ("bottomright", f, "bottomright", -10, 28) + + local div = f:CreateTexture (nil, "artwork") + div:SetTexture ([[Interface\ACHIEVEMENTFRAME\UI-Achievement-MetalBorder-Left]]) + div:SetAlpha (0.3) + div:SetPoint ("topleft", f, "topleft", 180, -64) + div:SetHeight (460) + + function f:SetBackgroundImage (encounterId) + local instanceId = _detalhes:GetInstanceIdFromEncounterId (encounterId) + if (instanceId) then + local file, L, R, T, B = _detalhes:GetRaidBackground (instanceId) + background:SetTexture (file) + background:SetTexCoord (L, R, T, B) + end + end + + f:SetScript ("OnMouseDown", function(self, button) + if (self.isMoving) then + return + end + if (button == "RightButton") then + self:Hide() + else + self:StartMoving() + self.isMoving = true + end + end) + f:SetScript ("OnMouseUp", function(self, button) + if (self.isMoving and button == "LeftButton") then + self:StopMovingOrSizing() + self.isMoving = nil + end + end) + + f.TitleText:SetText ("Raid History") + f.portrait:SetTexture ([[Interface\AddOns\Details\images\icons2]]) + f.portrait:SetTexCoord (192/512, 258/512, 322/512, 388/512) + + local dropdown_size = 160 + local icon = [[Interface\FriendsFrame\battlenet-status-offline]] + + local diff_list = {} + local raid_list = {} + local boss_list = {} + local guild_list = {} + + local sort_alphabetical = function(a,b) return a[1] < b[1] end + local sort_alphabetical2 = function(a,b) return a.value < b.value end + + local on_select = function() + if (f.Refresh) then + f:Refresh() + end + end + + --> select raid: + local on_raid_select = function (_, _, raid) + on_select() + end + local build_raid_list = function() + return raid_list + end + local raid_dropdown = gump:CreateDropDown (f, build_raid_list, 1, dropdown_size, 20, "select_raid") + local raid_string = gump:CreateLabel (f, "Raid:", _, _, "GameFontNormal", "select_raid_label") + + --> select boss: + local on_boss_select = function (_, _, boss) + on_select() + end + local build_boss_list = function() + return boss_list + end + local boss_dropdown = gump:CreateDropDown (f, build_boss_list, 1, dropdown_size, 20, "select_boss") + local boss_string = gump:CreateLabel (f, "Boss:", _, _, "GameFontNormal", "select_boss_label") + + --> select difficulty: + local on_diff_select = function (_, _, diff) + on_select() + end + + local build_diff_list = function() + return diff_list + end + local diff_dropdown = gump:CreateDropDown (f, build_diff_list, 1, dropdown_size, 20, "select_diff") + local diff_string = gump:CreateLabel (f, "Difficulty:", _, _, "GameFontNormal", "select_diff_label") + + --> select role: + local on_role_select = function (_, _, role) + on_select() + end + local build_role_list = function() + return { + {value = "damage", label = "Damager", icon = icon, onclick = on_role_select}, + {value = "healing", label = "Healer", icon = icon, onclick = on_role_select} + } + end + local role_dropdown = gump:CreateDropDown (f, build_role_list, 1, dropdown_size, 20, "select_role") + local role_string = gump:CreateLabel (f, "Role:", _, _, "GameFontNormal", "select_role_label") + + --> select guild: + local on_guild_select = function (_, _, guild) + on_select() + end + local build_guild_list = function() + return guild_list + end + local guild_dropdown = gump:CreateDropDown (f, build_guild_list, 1, dropdown_size, 20, "select_guild") + local guild_string = gump:CreateLabel (f, "Guild:", _, _, "GameFontNormal", "select_guild_label") + + --> select playerbase: + local on_player_select = function (_, _, player) + on_select() + end + local build_player_list = function() + return { + {value = 1, label = "Raid", icon = icon, onclick = on_player_select}, + {value = 2, label = "Individual", icon = icon, onclick = on_player_select}, + } + end + local player_dropdown = gump:CreateDropDown (f, build_player_list, 1, dropdown_size, 20, "select_player") + local player_string = gump:CreateLabel (f, "Player Base:", _, _, "GameFontNormal", "select_player_label") + + --> select player: + local on_player2_select = function (_, _, player) + f:BuildPlayerTable (player) + end + local build_player2_list = function() + local encounterTable, guild, role = unpack (f.build_player2_data or {}) + local t = {} + local already_listed = {} + if (encounterTable) then + for encounterIndex, encounter in ipairs (encounterTable) do + if (encounter.guild == guild) then + local roleTable = encounter [role] + for playerName, _ in pairs (roleTable) do + if (not already_listed [playerName]) then + tinsert (t, {value = playerName, label = playerName, icon = icon, onclick = on_player2_select}) + already_listed [playerName] = true + end + end + end + end + end + + table.sort (t, sort_alphabetical2) + + return t + end + local player2_dropdown = gump:CreateDropDown (f, build_player2_list, 1, dropdown_size, 20, "select_player2") + local player2_string = gump:CreateLabel (f, "Player:", _, _, "GameFontNormal", "select_player2_label") + + function f:UpdateDropdowns() + + --difficulty + wipe (diff_list) + wipe (boss_list) + wipe (raid_list) + wipe (guild_list) + + local boss_repeated = {} + local raid_repeated = {} + local guild_repeated = {} + + for difficulty, encounterIdTable in pairs (db) do + + if (type (difficulty) == "number") then + if (difficulty == 14) then + tinsert (diff_list, {value = 14, label = "Normal", icon = icon, onclick = on_diff_select}) + elseif (difficulty == 15) then + tinsert (diff_list, {value = 15, label = "Heroic", icon = icon, onclick = on_diff_select}) + elseif (difficulty == 16) then + tinsert (diff_list, {value = 16, label = "Mythic", icon = icon, onclick = on_diff_select}) + end + + for encounterId, encounterTable in pairs (encounterIdTable) do + if (not boss_repeated [encounterId]) then + local encounter, instance = _detalhes:GetBossEncounterDetailsFromEncounterId (_, encounterId) + if (encounter) then + tinsert (boss_list, {value = encounterId, label = encounter.boss, icon = icon, onclick = on_boss_select}) + boss_repeated [encounterId] = true + + if (not raid_repeated [instance.name]) then + tinsert (raid_list, {value = instance.id, label = instance.name, icon = icon, onclick = on_raid_select}) + raid_repeated [instance.name] = true + end + end + end + + for index, encounter in ipairs (encounterTable) do + local guild = encounter.guild + if (not guild_repeated [guild]) then + tinsert (guild_list, {value = guild, label = guild, icon = icon, onclick = on_raid_select}) + guild_repeated [guild] = true + end + end + end + end + end + + diff_dropdown:Refresh() + diff_dropdown:Select (1, true) + boss_dropdown:Refresh() + boss_dropdown:Select (1, true) + raid_dropdown:Refresh() + raid_dropdown:Select (1, true) + guild_dropdown:Refresh() + guild_dropdown:Select (1, true) + + end + + --> anchors: + raid_string:SetPoint ("topleft", f, "topleft", 10, -70) + raid_dropdown:SetPoint ("topleft", f, "topleft", 10, -85) + + boss_string:SetPoint ("topleft", f, "topleft", 10, -110) + boss_dropdown:SetPoint ("topleft", f, "topleft", 10, -125) + + diff_string:SetPoint ("topleft", f, "topleft", 10, -150) + diff_dropdown:SetPoint ("topleft", f, "topleft", 10, -165) + + role_string:SetPoint ("topleft", f, "topleft", 10, -190) + role_dropdown:SetPoint ("topleft", f, "topleft", 10, -205) + + guild_string:SetPoint ("topleft", f, "topleft", 10, -230) + guild_dropdown:SetPoint ("topleft", f, "topleft", 10, -245) + + player_string:SetPoint ("topleft", f, "topleft", 10, -270) + player_dropdown:SetPoint ("topleft", f, "topleft", 10, -285) + + player2_string:SetPoint ("topleft", f, "topleft", 10, -310) + player2_dropdown:SetPoint ("topleft", f, "topleft", 10, -325) + player2_string:Hide() + player2_dropdown:Hide() + + --> refresh the window: + + function f:BuildPlayerTable (playerName) + + local encounterTable, guild, role = unpack (f.build_player2_data or {}) + local data = {} + + if (type (playerName) == "string" and string.len (playerName) > 1) then + for encounterIndex, encounter in ipairs (encounterTable) do + + if (encounter.guild == guild) then + local roleTable = encounter [role] + + local date = encounter.date + date = date:gsub (".*%s", "") + date = date:sub (1, -4) + + local player = roleTable [playerName] + + if (player) then + tinsert (data, {text = date, value = player[1], data = player, fulldate = encounter.date}) + end + end + end + + --> update graphic + if (not f.gframe) then + + local cooltip_block_bg = {0, 0, 0, 1} + local menu_wallpaper_tex = {.6, 0.1, 0, 0.64453125} + local menu_wallpaper_color = {1, 1, 1, 0.1} + + local onenter = function (self) + GameCooltip:Reset() + + GameCooltip:AddLine ("Total Done:", _detalhes:ToK2 (self.data.value)) + GameCooltip:AddLine ("Item Level:", floor (self.data.data [2])) + GameCooltip:AddLine ("Date:", self.data.fulldate) + + GameCooltip:SetWallpaper (1, [[Interface\SPELLBOOK\Spellbook-Page-1]], menu_wallpaper_tex, menu_wallpaper_color, true) + GameCooltip:SetBackdrop (1, _detalhes.tooltip_backdrop, cooltip_block_bg, _detalhes.tooltip_border_color) + GameCooltip:SetOwner (self.ball.tooltip_anchor) + GameCooltip:Show() + end + local onleave = function (self) + GameCooltip:Hide() + end + f.gframe = gump:CreateGFrame (f, 650, 400, 35, onenter, onleave, "gframe", "$parentGF") + f.gframe:SetPoint ("topleft", f, "topleft", 190, -65) + end + + f.gframe:Reset() + f.gframe:UpdateLines (data) + + end + end + + local fillpanel = gump:NewFillPanel (f, {}, "$parentFP", "fillpanel", 630, 400, false, false, true, nil) + fillpanel:SetPoint ("topleft", f, "topleft", 200, -65) + + function f:BuildRaidTable (encounterTable, guild, role) + + local header = {{name = "Player Name", type = "text"}} -- , width = 90 + local players = {} + local players_index = {} + local amt_encounters = 0 + + for encounterIndex, encounter in ipairs (encounterTable) do + if (encounter.guild == guild) then + local roleTable = encounter [role] + + local date = encounter.date + date = date:gsub (".*%s", "") + date = date:sub (1, -4) + amt_encounters = amt_encounters + 1 + + tinsert (header, {name = date, type = "text"}) + + for playerName, playerTable in pairs (roleTable) do + local index = players_index [playerName] + local player + + if (not index) then + player = {playerName} + for i = 2, encounterIndex-1 do + tinsert (player, "") + end + tinsert (player, _detalhes:ToK2 (playerTable [1])) + tinsert (players, player) + players_index [playerName] = #players + else + player = players [index] + for i = #player+1, encounterIndex-1 do + tinsert (player, "") + end + tinsert (player, _detalhes:ToK2 (playerTable [1])) + end + + end + end + end + + for index, playerTable in ipairs (players) do + for i = #playerTable, amt_encounters do + tinsert (playerTable, "") + end + end + + --_detalhes:DumpTable (players, true) + + table.sort (players, sort_alphabetical) + + fillpanel:SetFillFunction (function (index) return players [index] end) + fillpanel:SetTotalFunction (function() return #players end) + + fillpanel:UpdateRows (header) + + fillpanel:Refresh() + + end + + function f:Refresh() + --> build the main table + local diff = diff_dropdown.value + local boss = boss_dropdown.value + local role = role_dropdown.value + local guild = guild_dropdown.value + local player = player_dropdown.value + + local diffTable = db [diff] + + f:SetBackgroundImage (boss) + + if (diffTable) then + local encounters = diffTable [boss] + if (encounters) then + if (player == 1) then --> raid + fillpanel:Show() + if (f.gframe) then + f.gframe:Hide() + end + player2_string:Hide() + player2_dropdown:Hide() + f:BuildRaidTable (encounters, guild, role) + elseif (player == 2) then --> only one player + fillpanel:Hide() + if (f.gframe) then + f.gframe:Show() + end + player2_string:Show() + player2_dropdown:Show() + f.build_player2_data = {encounters, guild, role} + player2_dropdown:Refresh() + player2_dropdown:Select (1, true) + f:BuildPlayerTable (player2_dropdown.value) + end + else + if (player == 1) then --> raid + fillpanel:Show() + if (f.gframe) then + f.gframe:Hide() + end + player2_string:Hide() + player2_dropdown:Hide() + f:BuildRaidTable ({}, guild, role) + elseif (player == 2) then --> only one player + fillpanel:Hide() + if (f.gframe) then + f.gframe:Show() + end + player2_string:Show() + player2_dropdown:Show() + f.build_player2_data = {{}, guild, role} + player2_dropdown:Refresh() + player2_dropdown:Select (1, true) + f:BuildPlayerTable (player2_dropdown.value) + end + end + end + end + + end + + _G.DetailsRaidHistoryWindow:UpdateDropdowns() + _G.DetailsRaidHistoryWindow:Refresh() + _G.DetailsRaidHistoryWindow:Show() + + end --> feedback window function _detalhes:OpenFeedbackWindow() @@ -1996,10 +2442,12 @@ --comma_button:SetPoint ("topright", panel, "topright", -100, -14) --tok_button:SetPoint ("topright", panel, "topright", -100, -36) - + + + local done = function() local text = panel.editbox:GetText() - text = text:gsub ("\n", "") + --text = text:gsub ("\n", "") local test = text @@ -2011,11 +2459,13 @@ code = code:gsub ("STR", test) local f = loadstring (code) - local err, two = xpcall (f, errorhandler) - - if (not err) then - return + if (not f) then + print (f) end + --local err, two = xpcall (f, errorhandler) + --if (not err) then + -- return + --end panel.callback (text) panel:Hide() diff --git a/framework/panel.lua b/framework/panel.lua index 9a72d81e..c172ec32 100644 --- a/framework/panel.lua +++ b/framework/panel.lua @@ -572,6 +572,306 @@ local button_on_leave = function (self) self.MyObject._icon:SetBlendMode ("BLEND") end +local add_row = function (self, t, need_update) + local index = #self.rows+1 + + local thisrow = gump:NewPanel (self, self, "$parentHeader_" .. self._name .. index, nil, 1, 20) + thisrow.backdrop = {bgFile = [[Interface\DialogFrame\UI-DialogBox-Gold-Background]]} + thisrow.color = "silver" + thisrow.type = t.type + thisrow.func = t.func + thisrow.name = t.name + thisrow.notext = t.notext + thisrow.icon = t.icon + thisrow.iconalign = t.iconalign + + thisrow.hidden = t.hidden or false + + local text = gump:NewLabel (thisrow, nil, self._name .. "$parentLabel" .. index, "text") + text:SetPoint ("left", thisrow, "left", 2, 0) + text:SetText (t.name) + + tinsert (self._raw_rows, t) + tinsert (self.rows, thisrow) + + if (need_update) then + self:AlignRows() + end +end + +local align_rows = function (self) + + local rows_shown = 0 + for index, row in ipairs (self.rows) do + if (not row.hidden) then + rows_shown = rows_shown + 1 + end + end + + local cur_width = 0 + local row_width = self._width / rows_shown + local sindex = 1 + + wipe (self._anchors) + + for index, row in ipairs (self.rows) do + if (not row.hidden) then + if (self._autowidth) then + --row:SetWidth (row_width) + if (self._raw_rows [index].width) then + row.width = self._raw_rows [index].width + else + row.width = row_width + end + row:SetPoint ("topleft", self, "topleft", cur_width, 0) + tinsert (self._anchors, cur_width) + cur_width = cur_width + row_width + 1 + else + row:SetPoint ("topleft", self, "topleft", cur_width, 0) + row.width = self._raw_rows [index].width + tinsert (self._anchors, cur_width) + cur_width = cur_width + self._raw_rows [index].width + 1 + end + row:Show() + + local type = row.type + + if (type == "text") then + for i = 1, #self.scrollframe.lines do + local line = self.scrollframe.lines [i] + local text = tremove (line.text_available) + if (not text) then + self:CreateRowText (line) + text = tremove (line.text_available) + end + tinsert (line.text_inuse, text) + text:SetPoint ("left", line, "left", self._anchors [#self._anchors], 0) + text:SetWidth (row.width) + end + elseif (type == "entry") then + for i = 1, #self.scrollframe.lines do + local line = self.scrollframe.lines [i] + local entry = tremove (line.entry_available) + if (not entry) then + self:CreateRowEntry (line) + entry = tremove (line.entry_available) + end + tinsert (line.entry_inuse, entry) + entry:SetPoint ("left", line, "left", self._anchors [#self._anchors], 0) + if (sindex == rows_shown) then + entry:SetWidth (row.width - 25) + else + entry:SetWidth (row.width) + end + entry.func = row.func + end + elseif (type == "button") then + for i = 1, #self.scrollframe.lines do + local line = self.scrollframe.lines [i] + local button = tremove (line.button_available) + if (not button) then + self:CreateRowButton (line) + button = tremove (line.button_available) + end + tinsert (line.button_inuse, button) + button:SetPoint ("left", line, "left", self._anchors [#self._anchors], 0) + if (sindex == rows_shown) then + button:SetWidth (row.width - 25) + else + button:SetWidth (row.width) + end + + if (row.icon) then + button._icon.texture = row.icon + button._icon:ClearAllPoints() + if (row.iconalign) then + if (row.iconalign == "center") then + button._icon:SetPoint ("center", button, "center") + elseif (row.iconalign == "right") then + button._icon:SetPoint ("right", button, "right") + end + else + button._icon:SetPoint ("left", button, "left") + end + end + + if (row.name and not row.notext) then + button._text:SetPoint ("left", button._icon, "right", 2, 0) + button._text.text = row.name + end + + end + elseif (type == "icon") then + for i = 1, #self.scrollframe.lines do + local line = self.scrollframe.lines [i] + local icon = tremove (line.icon_available) + if (not icon) then + self:CreateRowIcon (line) + icon = tremove (line.icon_available) + end + tinsert (line.icon_inuse, icon) + icon:SetPoint ("left", line, "left", self._anchors [#self._anchors] + ( ((row.width or 22) - 22) / 2), 0) + icon.func = row.func + end + end + + sindex = sindex + 1 + else + row:Hide() + end + end + + if (#self.rows > 0) then + if (self._autowidth) then + self.rows [#self.rows]:SetWidth (row_width - rows_shown + 1) + else + self.rows [#self.rows]:SetWidth (self._raw_rows [rows_shown].width - rows_shown + 1) + end + end + + self.showing_amt = rows_shown +end + +local update_rows = function (self, updated_rows) + for i = 1, #updated_rows do + local t = updated_rows [i] + local raw = self._raw_rows [i] + + if (not raw) then + self:AddRow (t) + else + raw.name = t.name + raw.hidden = t.hidden or false + + local widget = self.rows [i] + widget.name = t.name + widget.hidden = t.hidden or false + + widget.text:SetText (t.name) + end + end + + for i = #updated_rows+1, #self._raw_rows do + local raw = self._raw_rows [i] + local widget = self.rows [i] + raw.hidden = true + widget.hidden = true + end + + for index, row in ipairs (self.scrollframe.lines) do + for i = #row.text_inuse, 1, -1 do + tinsert (row.text_available, tremove (row.text_inuse, i)) + end + for i = 1, #row.text_available do + row.text_available[i]:Hide() + end + + for i = #row.entry_inuse, 1, -1 do + tinsert (row.entry_available, tremove (row.entry_inuse, i)) + end + for i = 1, #row.entry_available do + row.entry_available[i]:Hide() + end + + for i = #row.button_inuse, 1, -1 do + tinsert (row.button_available, tremove (row.button_inuse, i)) + end + for i = 1, #row.button_available do + row.button_available[i]:Hide() + end + + for i = #row.icon_inuse, 1, -1 do + tinsert (row.icon_available, tremove (row.icon_inuse, i)) + end + for i = 1, #row.icon_available do + row.icon_available[i]:Hide() + end + end + + self:AlignRows() + +end + +local create_panel_text = function (self, row) + row.text_total = row.text_total + 1 + local text = gump:NewLabel (row, nil, self._name .. "$parentLabel" .. row.text_total, "text" .. row.text_total) + tinsert (row.text_available, text) +end + +local create_panel_entry = function (self, row) + row.entry_total = row.entry_total + 1 + local editbox = gump:NewTextEntry (row, nil, "$parentEntry" .. row.entry_total, "entry", 120, 20) + editbox.align = "left" + + editbox:SetHook ("OnEnterPressed", function() + editbox.widget.focuslost = true + editbox:ClearFocus() + editbox.func (editbox.index, editbox.text) + return true + end) + + editbox:SetBackdrop ({bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]], edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1}) + editbox:SetBackdropColor (1, 1, 1, 0.1) + editbox:SetBackdropBorderColor (1, 1, 1, 0.1) + editbox.editbox.current_bordercolor = {1, 1, 1, 0.1} + + tinsert (row.entry_available, editbox) +end + +local create_panel_button = function (self, row) + row.button_total = row.button_total + 1 + local button = gump:NewButton (row, nil, "$parentButton" .. row.button_total, "button" .. row.button_total, 120, 20) + button:InstallCustomTexture() + + --> create icon and the text + local icon = gump:NewImage (button, nil, 20, 20) + local text = gump:NewLabel (button) + + button._icon = icon + button._text = text + + button:SetHook ("OnEnter", button_on_enter) + button:SetHook ("OnLeave", button_on_leave) + + tinsert (row.button_available, button) +end + +local icon_onclick = function (texture, iconbutton) + iconbutton._icon.texture = texture + iconbutton.func (iconbutton.index, texture) +end + +local create_panel_icon = function (self, row) + row.icon_total = row.icon_total + 1 + local iconbutton = gump:NewButton (row, nil, "$parentIconButton" .. row.icon_total, "iconbutton", 22, 20) + iconbutton:InstallCustomTexture() + + iconbutton:SetHook ("OnEnter", button_on_enter) + iconbutton:SetHook ("OnLeave", button_on_leave) + + iconbutton:SetHook ("OnMouseUp", function() + gump:IconPick (icon_onclick, true, iconbutton) + return true + end) + + local icon = gump:NewImage (iconbutton, nil, 20, 20, "artwork", nil, "_icon", "$parentIcon" .. row.icon_total) + iconbutton._icon = icon + + icon:SetPoint ("center", iconbutton, "center", 0, 0) + + tinsert (row.icon_available, iconbutton) +end + +local set_fill_function = function (self, func) + self._fillfunc = func +end +local set_total_function = function (self, func) + self._totalfunc = func +end +local drop_header_function = function (self) + wipe (self.rows) +end + -- ~fillpanel function gump:NewFillPanel (parent, rows, name, member, w, h, total_lines, fill_row, autowidth, options) local panel = gump:NewPanel (parent, parent, name, member, w, h) @@ -579,127 +879,120 @@ function gump:NewFillPanel (parent, rows, name, member, w, h, total_lines, fill_ options = options or {rowheight = 20} panel.rows = {} - + + panel.AddRow = add_row + panel.AlignRows = align_rows + panel.UpdateRows = update_rows + panel.CreateRowText = create_panel_text + panel.CreateRowEntry = create_panel_entry + panel.CreateRowButton = create_panel_button + panel.CreateRowIcon = create_panel_icon + panel.SetFillFunction = set_fill_function + panel.SetTotalFunction = set_total_function + panel.DropHeader = drop_header_function + + panel._name = name + panel._width = w + panel._height = h + panel._raw_rows = {} + panel._anchors = {} + panel._fillfunc = fill_row + panel._totalfunc = total_lines + panel._autowidth = autowidth + for index, t in ipairs (rows) do - local thisrow = gump:NewPanel (panel, panel, "$parentHeader_" .. name .. index, nil, 1, 20) - thisrow.backdrop = {bgFile = [[Interface\DialogFrame\UI-DialogBox-Gold-Background]]} - thisrow.color = "silver" - thisrow.type = t.type - thisrow.func = t.func - thisrow.name = t.name - thisrow.notext = t.notext - thisrow.icon = t.icon - thisrow.iconalign = t.iconalign - - local text = gump:NewLabel (thisrow, nil, name .. "$parentLabel", "text") - text:SetPoint ("left", thisrow, "left", 2, 0) - text:SetText (t.name) - - tinsert (panel.rows, thisrow) - end - - local cur_width = 0 - local row_width = w / #rows - - local anchors = {} - - for index, row in ipairs (panel.rows) do - if (autowidth) then - row:SetWidth (row_width) - row:SetPoint ("topleft", panel, "topleft", cur_width, 0) - tinsert (anchors, cur_width) - cur_width = cur_width + row_width + 1 - else - row:SetPoint ("topleft", panel, "topleft", cur_width, 0) - row.width = rows [index].width - tinsert (anchors, cur_width) - cur_width = cur_width + rows [index].width + 1 - end - end - - if (autowidth) then - panel.rows [#panel.rows]:SetWidth (row_width - #rows + 1) - else - panel.rows [#panel.rows]:SetWidth (rows [#rows].width - #rows + 1) + panel.AddRow (panel, t) end local refresh_fillbox = function (self) + local offset = FauxScrollFrame_GetOffset (self) - local filled_lines = total_lines (panel) - + local filled_lines = panel._totalfunc (panel) + for index = 1, #self.lines do - + local row = self.lines [index] if (index <= filled_lines) then - + local real_index = index + offset - local results = fill_row (real_index, panel) - - if (results [1]) then + local results = panel._fillfunc (real_index, panel) + if (results [1]) then row:Show() - - for i = 1, #row.row_widgets do - - row.row_widgets [i].index = real_index - - if (panel.rows [i].type == "icon") then - local result = results [i]:gsub (".-%\\", "") - row.row_widgets [i]._icon.texture = results [i] - - elseif (panel.rows [i].type == "button") then - - if (type (results [i]) == "table") then + local text, entry, button, icon = 1, 1, 1, 1 + + for index, t in ipairs (panel.rows) do + if (not t.hidden) then + if (t.type == "text") then + local fontstring = row.text_inuse [text] + text = text + 1 + fontstring:SetText (results [index]) + fontstring.index = real_index + fontstring:Show() + + elseif (t.type == "entry") then + local entrywidget = row.entry_inuse [entry] + entry = entry + 1 + entrywidget:SetText (results [index]) + entrywidget.index = real_index + entrywidget:Show() + + elseif (t.type == "button") then + local buttonwidget = row.button_inuse [button] + button = button + 1 + buttonwidget.index = real_index - if (results [i].text) then - row.row_widgets [i]:SetText (results [i].text) + local func = function() + t.func (real_index, index) + panel:Refresh() + end + buttonwidget:SetClickFunction (func) + + if (type (results [index]) == "table") then + if (results [index].text) then + buttonwidget:SetText (results [index].text) + end + + if (results [index].icon) then + buttonwidget._icon:SetTexture (results [index].icon) + end + + if (results [index].func) then + buttonwidget:SetClickFunction (results [index].func, real_index, results [index].value) + end + else + buttonwidget:SetText (results [index]) end - if (results [i].icon) then - row.row_widgets [i]._icon:SetTexture (results [i].icon) - end + buttonwidget:Show() - if (results [i].func) then - row.row_widgets [i]:SetClickFunction (results [i].func, real_index, results [i].value) - end - - else - row.row_widgets [i]:SetText (results [i]) - end - - else - --> text - row.row_widgets [i]:SetText (results [i]) - if (panel.rows [i].type == "entry") then - row.row_widgets [i]:SetCursorPosition (0) + elseif (t.type == "icon") then + local iconwidget = row.icon_inuse [icon] + icon = icon + 1 + + iconwidget.line = index + iconwidget.index = real_index + + local result = results [index]:gsub (".-%\\", "") + iconwidget._icon.texture = results [index] + + iconwidget:Show() end end end - + else row:Hide() - for i = 1, #row.row_widgets do - row.row_widgets [i]:SetText ("") - if (panel.rows [i].type == "icon") then - row.row_widgets [i]._icon.texture = "" - end - end end else row:Hide() - for i = 1, #row.row_widgets do - row.row_widgets [i]:SetText ("") - if (panel.rows [i].type == "icon") then - row.row_widgets [i]._icon.texture = "" - end - end end end end function panel:Refresh() - local filled_lines = total_lines (panel) + local filled_lines = panel._totalfunc (panel) local scroll_total_lines = #panel.scrollframe.lines local line_height = options.rowheight refresh_fillbox (panel.scrollframe) @@ -719,130 +1012,44 @@ function gump:NewFillPanel (parent, rows, name, member, w, h, total_lines, fill_ --create lines local size = options.rowheight local amount = math.floor (((h-21) / size)) - - for i = 1, amount do - local row = gump:NewPanel (panel, nil, "$parentRow_" .. i, nil, 1, size) - row.backdrop = {bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]]} + for i = 1, amount do + --local row = gump:NewPanel (panel, nil, , nil, 1, size) + local row = CreateFrame ("frame", panel:GetName() .. "Row_" .. i, panel.widget) + row:SetSize (1, size) row.color = {1, 1, 1, .2} + + row:SetBackdrop ({bgFile = [[Interface\Tooltips\UI-Tooltip-Background]]}) + + if (i%2 == 0) then + row:SetBackdropColor (.5, .5, .5, 0.2) + else + row:SetBackdropColor (1, 1, 1, 0.00) + end + row:SetPoint ("topleft", scrollframe, "topleft", 0, (i-1) * size * -1) row:SetPoint ("topright", scrollframe, "topright", 0, (i-1) * size * -1) tinsert (scrollframe.lines, row) - row.row_widgets = {} + row.text_available = {} + row.text_inuse = {} + row.text_total = 0 - for o = 1, #rows do + row.entry_available = {} + row.entry_inuse = {} + row.entry_total = 0 - local _type = panel.rows [o].type - - if (_type == "text") then - - --> create text - local text = gump:NewLabel (row, nil, name .. "$parentLabel" .. o, "text" .. o) - text:SetPoint ("left", row, "left", anchors [o], 0) - - --> insert in the table - tinsert (row.row_widgets, text) - - elseif (_type == "entry") then - - --> create editbox - local editbox = gump:NewTextEntry (row, nil, "$parentEntry" .. i .. o .. math.random(100), "entry", panel.rows [o].width, 20, panel.rows [o].func, i, o) - editbox.align = "left" - - editbox:SetHook ("OnEnterPressed", function() - editbox.widget.focuslost = true - editbox:ClearFocus() - editbox.func (editbox.index, editbox.text) - return true - end) - editbox:SetPoint ("left", row, "left", anchors [o], 0) - editbox:SetBackdrop ({bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]], edgeFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeSize = 1}) - editbox:SetBackdropColor (1, 1, 1, 0.1) - editbox:SetBackdropBorderColor (1, 1, 1, 0.1) - editbox.editbox.current_bordercolor = {1, 1, 1, 0.1} - --> insert in the table - tinsert (row.row_widgets, editbox) - - elseif (_type == "button") then - - --> create button - local button = gump:NewButton (row, nil, "$parentButton" .. o, "button", panel.rows [o].width, 20) - - local func = function() - panel.rows [o].func (button.index, o) - panel:Refresh() - end - button:SetClickFunction (func) - - button:SetPoint ("left", row, "left", anchors [o], 0) - - --> create icon and the text - local icon = gump:NewImage (button, nil, 20, 20) - local text = gump:NewLabel (button) - - button._icon = icon - button._text = text - - button:SetHook ("OnEnter", button_on_enter) - button:SetHook ("OnLeave", button_on_leave) - - if (panel.rows [o].icon) then - icon.texture = panel.rows [o].icon - if (panel.rows [o].iconalign) then - if (panel.rows [o].iconalign == "center") then - icon:SetPoint ("center", button, "center") - elseif (panel.rows [o].iconalign == "right") then - icon:SetPoint ("right", button, "right") - end - else - icon:SetPoint ("left", button, "left") - end - end - - if (panel.rows [o].name and not panel.rows [o].notext) then - text:SetPoint ("left", icon, "right", 2, 0) - text.text = panel.rows [o].name - end - - --> inser in the table - tinsert (row.row_widgets, button) - - elseif (_type == "icon") then - - --> create button and icon - local iconbutton = gump:NewButton (row, nil, "$parentIconButton" .. o, "iconbutton", 22, 20) - iconbutton:InstallCustomTexture() - - iconbutton:SetHook ("OnEnter", button_on_enter) - iconbutton:SetHook ("OnLeave", button_on_leave) - - --iconbutton:InstallCustomTexture() - local icon = gump:NewImage (iconbutton, nil, 20, 20, "artwork", nil, "_icon", "$parentIcon" .. o) - iconbutton._icon = icon - - iconbutton:SetPoint ("left", row, "left", anchors [o] + ( (panel.rows [o].width - 22) / 2), 0) - icon:SetPoint ("center", iconbutton, "center", 0, 0) - - --> set functions - local function iconcallback (texture) - iconbutton._icon.texture = texture - panel.rows [o].func (iconbutton.index, texture) - end - - iconbutton:SetClickFunction (function() - gump:IconPick (iconcallback, true) - return true - end) - - --> insert in the table - tinsert (row.row_widgets, iconbutton) - - end - - end + row.button_available = {} + row.button_inuse = {} + row.button_total = 0 + + row.icon_available = {} + row.icon_inuse = {} + row.icon_total = 0 end + panel.AlignRows (panel) + return panel end @@ -884,7 +1091,7 @@ function gump:ColorPick (frame, r, g, b, alpha, callback) end ------------icon pick -function gump:IconPick (callback, close_when_select) +function gump:IconPick (callback, close_when_select, param1, param2) if (not gump.IconPickFrame) then @@ -995,7 +1202,7 @@ function gump:IconPick (callback, close_when_select) gump.IconPickFrame.buttons = {} local OnClickFunction = function (self) - gump.IconPickFrame.callback (self.icon:GetTexture()) + gump.IconPickFrame.callback (self.icon:GetTexture(), gump.IconPickFrame.param1, gump.IconPickFrame.param2) if (gump.IconPickFrame.click_close) then close_button:Click() end @@ -1217,6 +1424,8 @@ function gump:IconPick (callback, close_when_select) end + gump.IconPickFrame.param1, gump.IconPickFrame.param2 = param1, param2 + gump.IconPickFrame:Show() gump.IconPickFrameScroll.update (gump.IconPickFrameScroll) gump.IconPickFrame.callback = callback or gump.IconPickFrame.emptyFunction @@ -1959,5 +2168,266 @@ function gump:CreateChartPanel (parent, w, h, name) return f end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- ~gframe +local gframe_on_enter_line = function (self) + self:SetBackdropColor (0, 0, 0, 0) + local parent = self:GetParent() + local ball = self.ball + ball:SetBlendMode ("ADD") + + local on_enter = parent._onenter_line + if (on_enter) then + return on_enter (self, parent) + end +end +local gframe_on_leave_line = function (self) + self:SetBackdropColor (0, 0, 0, .6) + + local parent = self:GetParent() + local ball = self.ball + ball:SetBlendMode ("BLEND") + + local on_leave = parent._onleave_line + if (on_leave) then + return on_leave (self, parent) + end +end + +local gframe_create_line = function (self) + local index = #self._lines+1 + + local f = CreateFrame ("frame", nil, self) + self._lines [index] = f + f.id = index + f:SetScript ("OnEnter", gframe_on_enter_line) + f:SetScript ("OnLeave", gframe_on_leave_line) + + f:SetWidth (self._linewidth) + + if (index == 1) then + f:SetPoint ("topleft", self, "topleft") + f:SetPoint ("bottomleft", self, "bottomleft") + else + local previous_line = self._lines [index-1] + f:SetPoint ("topleft", previous_line, "topright") + f:SetPoint ("bottomleft", previous_line, "bottomright") + end + + local t = f:CreateTexture (nil, "background") + t:SetWidth (1) + t:SetPoint ("topright", f, "topright") + t:SetPoint ("bottomright", f, "bottomright") + t:SetTexture (1, 1, 1, .1) + f.grid = t + + local b = f:CreateTexture (nil, "overlay") + b:SetTexture ([[Interface\COMMON\Indicator-Yellow]]) + b:SetSize (16, 16) + f.ball = b + local anchor = CreateFrame ("frame", nil, f) + anchor:SetAllPoints (b) + b.tooltip_anchor = anchor + + local spellicon = f:CreateTexture (nil, "artwork") + spellicon:SetPoint ("bottom", b, "bottom", 0, 10) + spellicon:SetSize (16, 16) + f.spellicon = spellicon + + local timeline = f:CreateFontString (nil, "overlay", "GameFontNormal") + timeline:SetPoint ("bottomright", f, "bottomright", -2, 0) + _detalhes:SetFontSize (timeline, 8) + f.timeline = timeline + + return f +end + +local gframe_getline = function (self, index) + local line = self._lines [index] + if (not line) then + line = gframe_create_line (self) + end + return line +end + +local gframe_reset = function (self) + for i, line in ipairs (self._lines) do + line:Hide() + end + if (self.GraphLib_Lines_Used) then + for i = #self.GraphLib_Lines_Used, 1, -1 do + local line = tremove (self.GraphLib_Lines_Used) + tinsert (self.GraphLib_Lines, line) + line:Hide() + end + end +end + +local gframe_update = function (self, lines) + + local g = LibStub:GetLibrary ("LibGraph-2.0") + local h = self:GetHeight()/100 + local amtlines = #lines + local linewidth = self._linewidth + + local max_value = 0 + for i = 1, amtlines do + if (lines [i].value > max_value) then + max_value = lines [i].value + end + end + + local o = 1 + local lastvalue = self:GetHeight()/2 + + for i = 1, min (amtlines, self._maxlines) do + + local data = lines [i] + + local pvalue = data.value / max_value * 100 + if (pvalue > 98) then + pvalue = 98 + end + pvalue = pvalue * h + + g:DrawLine (self, (o-1)*linewidth, lastvalue, o*linewidth, pvalue, linewidth, {1, 1, 1, 1}, "overlay") + lastvalue = pvalue + + local line = self:GetLine (i) + line:Show() + line.ball:Show() + + line.ball:SetPoint ("bottomleft", self, "bottomleft", (o*linewidth)-8, pvalue-8) + line.spellicon:SetTexture (nil) + line.timeline:SetText (data.text) + line.timeline:Show() + + line.data = data + + o = o + 1 + end + +end + +function gump:CreateGFrame (parent, w, h, linewidth, onenter, onleave, member, name) + local f = CreateFrame ("frame", name, parent) + f:SetSize (w or 450, h or 150) + f.CustomLine = [[Interface\AddOns\Details\Libs\LibGraph-2.0\line]] + + if (member) then + parent [member] = f + end + + f.CreateLine = gframe_create_line + f.GetLine = gframe_getline + f.Reset = gframe_reset + f.UpdateLines = gframe_update + + f._lines = {} + + f._onenter_line = onenter + f._onleave_line = onleave + + f._linewidth = linewidth or 50 + f._maxlines = floor (f:GetWidth() / f._linewidth) + + return f +end + +--[=[ + +function gframe:Reset() + for i = #gframe.GraphLib_Lines_Used, 1, -1 do + local line = tremove (gframe.GraphLib_Lines_Used) + tinsert (gframe.GraphLib_Lines, line) + line:Hide() + end +end + +function DeathGraphs:ShowGraphicForDeath (data) + + gframe:Reset() + gframe:ShowGrid() + gframe:Show() + + if (not data) then + return + end + + local timeline = data [1] + local max_health = data[4] + + if (#timeline < 16) then + while (#timeline < 16) do + table.insert (timeline, 1, {false, 0, 0, data[6], max_health, "-1"}) + end + end + + log = timeline + + local h = gframe:GetHeight()/100 + + local o = 1 + local lastlife = 156 + + --for i = 16, 1, -1 do + for i = 1, 16, 1 do + local t = timeline [i] + if (type (t) == "table") then + + --> death parser + + local evtype = t [1] --event type + local spellid = t [2] --spellid + local amount = t [3] --amount healed or damaged + local time = t [4] --time + local life = t [5] --health + local source = t [6] --source + + local plife = life / max_health * 100 + if (plife > 98) then + plife = 98 + end + plife = plife*h + + local line + + line = g:DrawLine (gframe, (o-1)*29, lastlife, o*29, plife, 50, red, "overlay") + + local ball = gballs [o] + ball:SetPoint ("bottomleft", gframe, "bottomleft", (o*29)-8, plife-8) + if (type (evtype) == "boolean" and evtype) then --> damage + ball.spellicon:SetTexture (select (3, GetSpellInfo (spellid))) + ball.spellicon:SetTexCoord (4/64, 60/64, 4/64, 60/64) + else + ball.spellicon:SetTexture (nil) + end + + ball.line = line + + local clock = data[6] - time + if (type (evtype) == "number" and evtype == 2) then + if (clock <= 100) then + timeline_bg.labels [o]:SetText (math.floor (clock)) + else + timeline_bg.labels [o]:SetText (string.format ("%.1f", clock)) + end + else + timeline_bg.labels [o]:SetText ("-" .. string.format ("%.1f", clock)) + end + + local frame = gradeframes [o] + frame.data = t + + lastlife = plife + o = o + 1 + end + end + + DeathGraphs:UpdateOverall() + +end + +--]=] \ No newline at end of file diff --git a/functions/boss.lua b/functions/boss.lua index 83357cd4..c77d1448 100644 --- a/functions/boss.lua +++ b/functions/boss.lua @@ -23,8 +23,42 @@ do return _detalhes.EncounterInformation [mapid] and _detalhes.EncounterInformation [mapid].trash_ids end + function _detalhes:GetInstanceIdFromEncounterId (encounterid) + for id, instanceTable in pairs (_detalhes.EncounterInformation) do + local ids = instanceTable.encounter_ids2 + if (ids) then + if (ids [encounterid]) then + return id + end + end + end + end + --> return the boss table using a encounter id function _detalhes:GetBossEncounterDetailsFromEncounterId (mapid, encounterid) + if (not mapid) then + local bossIndex, instance + for id, instanceTable in pairs (_detalhes.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 = _detalhes.EncounterInformation [mapid] and _detalhes.EncounterInformation [mapid].encounter_ids and _detalhes.EncounterInformation [mapid].encounter_ids [encounterid] if (bossindex) then return _detalhes.EncounterInformation [mapid] and _detalhes.EncounterInformation [mapid].encounters [bossindex], bossindex diff --git a/functions/events.lua b/functions/events.lua index 684a55fc..69512ed8 100644 --- a/functions/events.lua +++ b/functions/events.lua @@ -31,6 +31,8 @@ ["COMBAT_PLAYER_ENTER"] = {}, ["COMBAT_PLAYER_LEAVE"] = {}, ["COMBAT_PLAYER_TIMESTARTED"] = {}, + ["COMBAT_BOSS_WIPE"] = {}, + ["COMBAT_BOSS_DEFEATED"] = {}, ["COMBAT_BOSS_FOUND"] = {}, ["COMBAT_INVALID"] = {}, ["COMBAT_PREPOTION_UPDATED"] = {}, @@ -86,6 +88,8 @@ local common_events = { ["COMBAT_PLAYER_ENTER"] = true, ["COMBAT_PLAYER_LEAVE"] = true, ["COMBAT_PLAYER_TIMESTARTED"] = true, + ["COMBAT_BOSS_WIPE"] = true, + ["COMBAT_BOSS_DEFEATED"] = true, ["COMBAT_BOSS_FOUND"] = true, ["COMBAT_INVALID"] = true, ["COMBAT_PREPOTION_UPDATED"] = true, diff --git a/functions/slash.lua b/functions/slash.lua index 9d53573f..efd35c7c 100644 --- a/functions/slash.lua +++ b/functions/slash.lua @@ -20,6 +20,9 @@ function SlashCmdList.DETAILS (msg, editbox) if (command == Loc ["STRING_SLASH_NEW"] or command == "new") then _detalhes:CriarInstancia (nil, true) + elseif (command == Loc ["STRING_SLASH_HISTORY"] or command == "history") then + _detalhes:OpenRaidHistoryWindow() + elseif (command == Loc ["STRING_SLASH_TOGGLE"] or command == "toggle") then local instance = rest:match ("^(%S*)%s*(.-)$") diff --git a/gumps/janela_news.lua b/gumps/janela_news.lua index ba50b0c0..4435a8b1 100644 --- a/gumps/janela_news.lua +++ b/gumps/janela_news.lua @@ -4,6 +4,10 @@ local Loc = LibStub ("AceLocale-3.0"):GetLocale ( "Details" ) local g = _detalhes.gump local _ +function _detalhes:DumpTable (text_to_show, dumpvalues, keeptext) + return _detalhes:OpenNewsWindow (text_to_show, dumpvalues, keeptext) +end + function _detalhes:OpenNewsWindow (text_to_show, dumpvalues, keeptext) local news_window = _detalhes:CreateOrOpenNewsWindow() diff --git a/gumps/switch.lua b/gumps/switch.lua index 983f3bb8..e0b0c7d4 100644 --- a/gumps/switch.lua +++ b/gumps/switch.lua @@ -135,7 +135,7 @@ do local options_button = gump:CreateButton (frame.topbg_frame, open_options, 14, 14, open_options) options_button:SetPoint ("right", window_color, "left", -2, 0) - local options_button_texture = gump:CreateImage (options_button, [[Interface\AddOns\Details\images\modo_icones]], 14, 14, "artwork", {0.5, 0.625, 0, 1}) + local options_button_texture = gump:CreateImage (options_button, [[Interface\AddOns\Details\images\icons]], 14, 14, "artwork", {396/512, 428/512, 277/512, 307/512}) options_button_texture:SetAlpha (0.35) options_button_texture:SetAllPoints() @@ -154,6 +154,60 @@ do GameCooltip:Hide() end) +--------------------------------------------------------------------------------------------------------------------------- + + local open_forge = function() + _detalhes:OpenForge() + end + local forge_button = gump:CreateButton (frame.topbg_frame, open_forge, 14, 14, open_forge) + forge_button:SetPoint ("right", options_button, "left", -2, 0) + + local forge_button_texture = gump:CreateImage (forge_button, [[Interface\AddOns\Details\images\icons]], 14, 14, "artwork", {396/512, 428/512, 243/512, 273/512}) + forge_button_texture:SetAlpha (0.35) + forge_button_texture:SetAllPoints() + + forge_button:SetHook ("OnEnter", function() + forge_button_texture:SetAlpha (1) + GameCooltip:Reset() + _detalhes:CooltipPreset (1) + GameCooltip:SetBackdrop (1, _detalhes.tooltip_backdrop, backgroundColor, _detalhes.tooltip_border_color) + GameCooltip:AddLine ("Open Forge") + GameCooltip:SetOwner (window_color.widget) + GameCooltip:SetType ("tooltip") + GameCooltip:Show() + end) + forge_button:SetHook ("OnLeave", function() + forge_button_texture:SetAlpha (0.35) + GameCooltip:Hide() + end) + +--------------------------------------------------------------------------------------------------------------------------- + + local open_history = function() + _detalhes:OpenRaidHistoryWindow() + end + local history_button = gump:CreateButton (frame.topbg_frame, open_history, 14, 14, open_history) + history_button:SetPoint ("right", forge_button, "left", -2, 0) + + local history_button_texture = gump:CreateImage (history_button, [[Interface\AddOns\Details\images\icons]], 14, 14, "artwork", {434/512, 466/512, 243/512, 273/512}) + history_button_texture:SetAlpha (0.35) + history_button_texture:SetAllPoints() + + history_button:SetHook ("OnEnter", function() + history_button_texture:SetAlpha (1) + GameCooltip:Reset() + _detalhes:CooltipPreset (1) + GameCooltip:SetBackdrop (1, _detalhes.tooltip_backdrop, backgroundColor, _detalhes.tooltip_border_color) + GameCooltip:AddLine ("Open History Panel") + GameCooltip:SetOwner (window_color.widget) + GameCooltip:SetType ("tooltip") + GameCooltip:Show() + end) + history_button:SetHook ("OnLeave", function() + history_button_texture:SetAlpha (0.35) + GameCooltip:Hide() + end) + --------------------------------------------------------------------------------------------------------------------------- function _detalhes.switch:CloseMe() diff --git a/images/icons.tga b/images/icons.tga index 44419d6e9969a647661699cd4122cdd1b0770b30..6bb140b26174d3edda8c697db5fbd51690f8582b 100644 GIT binary patch delta 13950 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 VTFDWPX7OuNBxwRaRU+T;e*iWLs0si8 delta 3267 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 UD!qB$?M^*jK8?3KeTP2yKaVotR{#J2 delta 3412 zcmZ{m32c;A5XZm&d`qDeJ~wIKyx+{5d2iml+1>uq z#CJZJ(7yxU#IezNDTK-H{x~)P8@!2QMG$cBZNfHK>ka-W{^b9?;+~koTd>yoaeWK+ zz`DhcC$!-RvWEP)sRJ!!-R#H9yO1I4CO>{UjYVXw_2W}}v5c%WejJ{Oi^*E;#}nt^ z3Jsoo46Y?>mESr!9|L5q^yAYPVn~CxEXM6*4f?J3Ex|gn2K@NArP!>&J5Iv}vT}aw z;4-Y&;M>l|TC%c!>jUTEHVwY&0^SL-&hZmz%5%%HhAi8^GJ6R&YVi2W#bJ^4{e8#* zbKUUOurS{5tMgjS1D3e`*YQ}S{or~m2aa`nZ$u@W$IEGoySyJ!S#Z0pTkvC!4Ou&S zP^VqC3RQ%0&h=}MDvI^uT2vCoI-lN*h9YI@EgG?n$#yLuy*o9R7JLC;qPXVFUHDdme|;IHnhG8D8e*E4F#Sz5 z%S-UeZsprzQ}pC^y^EO|9C%-F(Q{s~8@(F5>LWxoJ>juWF;9cP{{r2b#xn71v}$se?X6>PiFTbrRl0S{USLjYPN&FW0IydpZf<6ptz9-|3V2Be;a)H z4}O7ltG`|J9l&qJUn$RyqJ5h0pDfW2ur~O8d>5dPVQuo`_-J~aAB7*!FQsQ-ZT92Q zWpod$Eq?seI2wYr)sN>LPCamTR?sBZ8BcA);#U?m4U07sC|$OquCC6gt*tfc>+8Ky z<|5kysW1zLLPplZ;c(a>nMNWJqoJX}up1j2jhsliXf$dd7K<5)M8dF}nwktdnM@ia zk_f3(Do<>YN}8LSjTNn}twvj0n;~OMON%FWbaWWm_V#we7EOsvySloJ&dyFlWEs=x zw9(VkV+@L5imzqW!y0k%v`Ss=k>B4bvzMzS{% zggABsO$r36tE=6?Dk|gO5WG*tTE$0FHk&%LuB7t3#ja|a{J$0@;sZ4l)mdD2FIAW| zH8t+3wR8i_g4+_CnKIlz>*x|3hC=9rVIgaVLUhr=H5hOwMd&;P2H4!&9BlW_20HB! z5-8ydcFUu54FZyVF{?1S4BRJUbTv#t-j;&FpgTK3=Ri`GKPiO0+(hT|hB%XobEoWK zALSfsaf+_Xd+-ti@%n8uT?vyd^MSb3+`d*0UO?r@xn=E#fC@7d4!K)9=&BK$3Nx07 zyUktH2eWs^^t`8NJm$XIO=p5;%ywoipvk5jEYm|XVD@A(-j0!THfLymAj`WgfbG4s zuT&~c*~15`@K(M(ok}<*1?HqKrGWGM3|U=ZV&{yRMf=Q={0=xr&ZfOy*1SYU8X|7Z z9QI^(r_$2LYA)c~Jc8BWz~s&LMpOP`zROi%t8ta#{@qbZ+dFY%JKD~_Qw4o1m3 z?;I-|8Oe3l-7}BMM{+&YEkBN@<#JVujl3$Ak}2HIRQHSp{4*mkIK1(xZvR3`pi~~> zp`|RbKU_qMVRDThtb^XgG&hg(6)$yg_X(8ZeEI8j|A`bIsmOd`XHKG{xMJkha7R$K zxb~7I72JO^P37_|uoYERRm!fctW-8X8b#BlO;aLpM3oc45XsY(1*?M|ujSmfi%l=#nd KcP|Z`x%xlUiwaNx