From fe3deb1965b7562648b93d167a4f96d92abfb81b Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 13 Jan 2021 15:12:12 +0100 Subject: [PATCH] Initial push --- .gitmodules | 3 + DataStore.lua | 201 ++++++++++++++++++++ GuidInfo.lua | 335 +++++++++++++++++++++++++++++++++ Libs/LibCombatlog | 1 + Quicklog.lua | 470 ++++++++++++++++++++++++++++++++++++++++++++++ Quicklog.toc | 12 ++ Segments.lua | 104 ++++++++++ Statusbars.lua | 146 ++++++++++++++ Window.lua | 411 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1683 insertions(+) create mode 100644 .gitmodules create mode 100644 DataStore.lua create mode 100644 GuidInfo.lua create mode 160000 Libs/LibCombatlog create mode 100644 Quicklog.lua create mode 100644 Quicklog.toc create mode 100644 Segments.lua create mode 100644 Statusbars.lua create mode 100644 Window.lua diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..272b905 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Libs/LibCombatlog"] + path = Libs/LibCombatlog + url = https://git.grml.de/rilgamon/LibCombatlog.git diff --git a/DataStore.lua b/DataStore.lua new file mode 100644 index 0000000..23c6321 --- /dev/null +++ b/DataStore.lua @@ -0,0 +1,201 @@ +local name, addon = ... +local stores = {} +local methods_store = {} +local methods_source = {} +local methods_dest = {} +local testUnits = { 'TARGET' } +local UnitGUID, UnitClass, CopyTable = UnitGUID, UnitClass, CopyTable +local function findClass(guid) + for i = 1, #testUnits do + if(UnitGUID(testUnits[i])==guid) then + local _, class = UnitClass(testUnits[i]) + return class + end + end +end +local function setClass(src, unitName, unitGuid) + if(unitName and not src['class']) then + local _, class = UnitClass(unitName) + if(not class) then + class = findClass(unitGuid) + end + if(class) then + src['class'] = class + end + end +end +local defaultSpell = { + ['count'] = 0, + ['crits'] = 0, + ['total'] = 0, + ['totalHeal'] = 0, + ['totalAbsorbed'] = 0, + ['totalOverheal'] = 0, + ['min'] = 0, + ['max'] = 0 +} +local defaultSource = { + ['dest'] = {}, + ['total'] = 0, + ['totalHeal'] = 0, + ['totalOverheal'] = 0, + ['totalAbsorbed'] = 0, +} +local function GetDefaultSource(data, offset) + local offset = offset or 5 + local src = CopyTable(defaultSource) + if(data) then + src['guid'] = data[offset] + src['name'] = data[offset + 1] + src['flags'] = data[offset + 2] + setClass(src, data[offset + 1], data[offset]) + if(not src['name']) then + print("no src name", offset) + print(unpack(data)) + end + end + return src +end +local defaultDestination = { + ['spells'] = {}, + ['spellsHeal'] = {}, + ['spellsAbsorbed'] = {}, + ['total'] = 0, + ['totalHeal'] = 0, + ['totalAbsorbed'] = 0, +} +local function GetDefaultDestination(data) + local dest = CopyTable(defaultDestination) + dest['guid'] = data[9] + dest['name'] = data[10] + dest['flags'] = data[11] + setClass(dest, data[10], data[9]) + return dest +end +--dest2:addAbsorbSpell(extraSpellID, extraSpellName, extraSchool, amount) + +local function addSpell(data) +-- spellId[4], spellName, spellSchool, amount, overhealing, absorbed, critical +-- spellId[4], spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand + local unit = data[1] + local total = data[2] + local spells + if(total == 'totalAbsorbed') then + spells = unit['spellsAbsorbed'] + elseif(total == 'totalHeal') then + spells = unit['spellsHeal'] + else + spells = unit['spells'] + end + local isHeal = data[3] +-- local data = {...} + if(data[4]) then + local amount = data[7] +-- if(not spells[data[4]]) then print('new spell', data[5]) end + local spell = spells[data[4]] or CopyTable(defaultSpell) + spell['spellName'] = data[5] + spell['spellSchool'] = data[6] + spell[total] = spell[total] + amount + spell['count'] = spell['count'] + 1 + if(isHeal) then +--overhealing, absorbed, critical + if(data[8]) then + spell['totalOverheal'] = spell['totalOverheal'] + data[8] + end + if(data[10]) then + spell['crits'] = spell['crits'] + 1 + end + else +--overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand + if(data[13]) then + spell['crits'] = spell['crits'] + 1 + end + end + if(amount > spell['max']) then + spell['max'] = amount + end + if(amount < spell['min'] or spell['min'] == 0) then + spell['min'] = amount + end +-- spell['eventinfo'] = spell['eventinfo'] or {} +-- spell['eventinfo'][#spell['eventinfo'] + 1] = data + spells[data[4]] = spell + unit:addTotal(total, amount) + + else + print('no spellId') + end + return spells +end +function methods_source:addTotal(total, amount) + self[total] = self[total] + amount +end +function methods_dest:addTotal(total, amount) + self[total] = self[total] + amount +end +function methods_dest:addAbsorbSpell(...) + local data = {self, 'totalAbsorbed', false, ...} +-- self['spellsAbsorbed'] = + addSpell(data) +end +function methods_dest:addSpell(...) + local data = {self, 'total', false, ...} +-- self['spells'] = + addSpell(data) +end +function methods_dest:addHealSpell(...) + local data = {self, 'totalHeal', true, ...} +-- self['spellsHeal'] = + addSpell(data) +end +function methods_source:GetDestination(destGuid) + if(destGuid) then + local dest + for k, v in pairs(self['dest']) do + if(k == destGuid) then + dest = v + break + end + end + dest = dest or GetDefaultDestination(self['data']) + for k, v in pairs(methods_dest) do + dest[k] = v + end + dest['sourceGuid'] = self['guid'] + self['dest'][destGuid] = dest + return dest + end +end +function methods_store:GetSourceEmpty(data) + local guid = "Unknown" + self['source'][guid] = self['source'][guid] or GetDefaultSource(data) + local src = self['source'][guid] + src['name'] = "" + src['guid'] = "Unknown" + src['data'] = data + for k, v in pairs(methods_source) do + src[k] = v + end + return src +end +function methods_store:GetSource(guid, data, offset, ignore) + if(guid and (ignore or data[4] == false)) then + self['source'][guid] = self['source'][guid] or GetDefaultSource(data, offset) + local src = self['source'][guid] + src['data'] = data + for k, v in pairs(methods_source) do + src[k] = v + end + return src + end +end +function addon:GetStore(seg) + if(not stores[seg]) then +-- print('new store') + stores[seg] = seg + for k, v in pairs(methods_store) do + stores[seg][k] = v + end + end + return stores[seg] +end \ No newline at end of file diff --git a/GuidInfo.lua b/GuidInfo.lua new file mode 100644 index 0000000..89bd314 --- /dev/null +++ b/GuidInfo.lua @@ -0,0 +1,335 @@ +local name, addon = ... +local GetPlayerInfoByGUID, UnitFullName, UnitClassification, UnitGUID, GetNormalizedRealmName, UnitClass = GetPlayerInfoByGUID, UnitFullName, UnitClassification, UnitGUID, GetNormalizedRealmName, UnitClass +local MAX_RAID_MEMBERS, COMBATLOG_OBJECT_TYPE_PLAYER = MAX_RAID_MEMBERS, COMBATLOG_OBJECT_TYPE_PLAYER +local guidInfo = {} +--[[ +UNITNAME_SUMMON_TITLE1 = "Begleiter von %s"; +UNITNAME_SUMMON_TITLE10 = "Reittier von %s"; +UNITNAME_SUMMON_TITLE11 = "Lichtbrunnen von %s"; +UNITNAME_SUMMON_TITLE12 = "Butler von %s"; +UNITNAME_SUMMON_TITLE13 = "%s'aka"; +UNITNAME_SUMMON_TITLE14 = "Portal von %s"; +UNITNAME_SUMMON_TITLE15 = "Hass von %s"; +UNITNAME_SUMMON_TITLE16 = "Statue von %s"; +UNITNAME_SUMMON_TITLE17 = "Geist von %s"; +UNITNAME_SUMMON_TITLE18 = "Kriegsbanner von %s"; +UNITNAME_SUMMON_TITLE19 = "Herzerwärmer von %s"; +UNITNAME_SUMMON_TITLE2 = "Wächter von %s"; +UNITNAME_SUMMON_TITLE20 = "Angeheuert von %s"; +UNITNAME_SUMMON_TITLE21 = "Gekauft von %s"; +UNITNAME_SUMMON_TITLE22 = "Stolz von %s"; +UNITNAME_SUMMON_TITLE23 = "Verzerrtes Abbild von %s"; +UNITNAME_SUMMON_TITLE24 = "Nudelwagen von %s"; +UNITNAME_SUMMON_TITLE25 = "Innerer Dämon von %s"; +UNITNAME_SUMMON_TITLE26 = "Leibwächter von %s"; +UNITNAME_SUMMON_TITLE27 = "%s"; +UNITNAME_SUMMON_TITLE28 = "Knappe von %s"; +UNITNAME_SUMMON_TITLE29 = "Champion von %s"; +UNITNAME_SUMMON_TITLE3 = "Diener von %s"; +UNITNAME_SUMMON_TITLE30 = "Der Verräter"; +UNITNAME_SUMMON_TITLE31 = "Eruptive Reflexion von %s"; +UNITNAME_SUMMON_TITLE32 = "Hoffnungslose Reflexion von %s"; +UNITNAME_SUMMON_TITLE33 = "Bösartige Reflexion von %s"; +UNITNAME_SUMMON_TITLE34 = "Klagende Reflexion von %s"; +UNITNAME_SUMMON_TITLE35 = "Assistentin von %s"; +UNITNAME_SUMMON_TITLE36 = "Vollstrecker von %s"; +UNITNAME_SUMMON_TITLE37 = "Rekrut von %s"; +UNITNAME_SUMMON_TITLE38 = "Fan von %s"; +UNITNAME_SUMMON_TITLE39 = "Böser Zwilling von %s"; +UNITNAME_SUMMON_TITLE4 = "Totem von %s"; +UNITNAME_SUMMON_TITLE40 = "Gier von %s"; +UNITNAME_SUMMON_TITLE41 = "Verlorener Verstand von %s"; +UNITNAME_SUMMON_TITLE42 = "Paktgebundener Beschützer von %s"; +UNITNAME_SUMMON_TITLE43 = "Mechanoid von %s"; +UNITNAME_SUMMON_TITLE44 = "%s, Diener von N'Zoth"; +UNITNAME_SUMMON_TITLE45 = "Assassine von %s"; +UNITNAME_SUMMON_TITLE46 = "Wildsamen von %s"; +UNITNAME_SUMMON_TITLE47 = "Provost von %s"; +UNITNAME_SUMMON_TITLE48 = "Repariert von %s"; +UNITNAME_SUMMON_TITLE5 = "Gefährte von %s"; +UNITNAME_SUMMON_TITLE6 = "Runenklinge von %s"; +UNITNAME_SUMMON_TITLE7 = "Konstrukt von %s"; +UNITNAME_SUMMON_TITLE8 = "Gegner von %s"; +UNITNAME_SUMMON_TITLE9 = "Fahrzeug von %s"; +]] +local methods_guid = {} +local scanIds = { 1, 2, 3, 7, 9, 26, 29} +local scanTip = CreateFrame("GameTooltip", format("%s_ScanTooltip",name), nil, "GameTooltipTemplate") +scanTip:SetOwner( WorldFrame, "ANCHOR_NONE" ) +function methods_guid:scanTooltip() + if(self:HasOwner()) then return end + local guid = self['guid'] + local text = _G[format("%s_ScanTooltipTextLeft2",name)] + scanTip:SetOwner(WorldFrame, 'ANCHOR_NONE') + scanTip:SetHyperlink('unit:' .. guid or '') + + if(text) then + local text2 = text:GetText() + if(text2) then + for i = 1, #scanIds do + local check = _G[format("UNITNAME_SUMMON_TITLE%i",scanIds[i])]:gsub("%%s", "(.+)"):gsub("[%[%]]", "%%%1") + self['tooltip'] = text2 + if(string.find(text2, check)) then + self['tooltip'] = text2 + local a,b,c = string.find(text2, check) + if(c) then + local g = UnitGUID(c) + if(g) then + local gio = addon:GetGuidInfo(g) + -- local gip = addon:GetGuidInfo(guid) + self:SetOwner(gio) + + return + -- print('found',c,gio['name'], gip['name']) + end + end + end + -- print(text:GetText(), check, guid) + end + end + end +end +local function GetGuid(unit) + if(unit) then + local guid = UnitGUID(unit) + if(guid and guid ~= "") then + return guid + end + end +end +function methods_guid:IsParty() + return self['isParty'] +end +function methods_guid:SetName(newName) + self['name'] = newName + local nameSplit, serverSplit = strsplit('-',self['name'],2) + if(not serverSplit) then + serverSplit = GetNormalizedRealmName() + end + self['shortName'] = nameSplit + self['shortRealm'] = serverSplit +end +function methods_guid:IsPet() + return self['isPet'] +end +function methods_guid:HasOwner() + return self['ownerName'] or false +end +function methods_guid:SetOwner(gi) +-- print("set owner", self['name'], gi['name']) + + self['isPet'] = true + if(gi) then + self['ownerName'] = gi['name'] + self['ownerGuid'] = gi['guid'] + if(gi:IsParty())then + self['isParty'] = true + end + end +end +local function checkPet(unit) + local guid = GetGuid(unit) + if(guid) then + local unitName, unitRealm = UnitFullName(unit) + local _, unitClass = UnitClass(unit) + local o = addon:GetGuidInfo(guid, nil, unitRealm and format("%s-%s", unitName, unitRealm) or unitName, unitClass) + o['isParty'] = true + local pet = format("%sPET", unit) + local pguid = GetGuid(pet) + if(pguid) then + local p = addon:GetGuidInfo(pguid) + p['isParty'] = true + p:SetOwner(o) + end + return guid + end +end +local function assocOwner(guid, petguid) + local gio = addon:GetGuidInfo(guid) + local gip = addon:GetGuidInfo(petguid) + gip:SetOwner(gio) +end +local function group_helper(guids, endcount, unitbase) + for i = 1, endcount do + local guid = UnitGUID(format("%s%i", unitbase, i)) + if(guid) then + guids[guid] = true + local petguid = UnitGUID(format("%s%ipet", unitbase, i)) + if(petguid) then + assocOwner(guid, petguid) + guids[petguid] = true + end + end + end + return guids +end +function addon:GetGroupGuid(guids) + guids = guids or {} + if(IsInRaid()) then + guids = group_helper(guids, MAX_RAID_MEMBERS, 'raid') + elseif(IsInGroup()) then + guids = group_helper(guids, 4, 'party') + local guid = UnitGUID('player') + if(guid) then + guids[guid] = true + local petguid = UnitGUID('pet') + if(petguid) then + assocOwner(guid, petguid) + guids[petguid] = true + end + end + else + local guid = UnitGUID('player') + if(guid) then + guids[guid] = true + local petguid = UnitGUID('pet') + if(petguid) then + assocOwner(guid, petguid) + guids[petguid] = true + end + end + end + return guids +end +function addon:CheckUnit(unit) + if(unit) then + return checkPet(unit) + end +end +function addon:UNIT_TARGET(event, unit) + local trg = format("%starget", unit) + local guid = GetGuid(trg) + if(guid) then + local _, class = UnitClass(trg) + local fullName, realm = UnitFullName(trg) + local gi = addon:GetGuidInfo(guid, nil, realm and format("%s-%s", fullName, realm) or fullName, class) + local clf = UnitClassification(trg) +-- print(trg, clf, UnitLevel(trg)) + if(clf == 'elite') then + if(UnitLevel(trg) == -1) then + + gi['isBoss'] = true + end + end +-- print('gi',gi['class']) + if(not gi['class']) then +-- print('find class') + local _, class = UnitClass(trg) + if(class) then + print('found class',class) + gi['class'] = class + end + end + end +end +local function purgeGroupMember() +-- if(true) then return end -- dont purge to keep data intact + local guids = addon:GetGroupGuid() + guids = addon:GetSegmentsGuid(guids) + for guid, v in pairs(guidInfo) do + if(not guids[k]) then + guidInfo[guid]['isParty'] = nil + end + end +end +function addon:RAID_ROSTER_UPDATE() + purgeGroupMember() + for i = 1, MAX_RAID_MEMBERS do + checkPet(format("raid%i", i)) + end +end +function addon:GROUP_ROSTER_UPDATE() + purgeGroupMember() + for i = 1, 4 do + checkPet(format('party%i', i)) + end + checkPet('player') +end +local purgeTime = { + ['Player'] = 3600 * 24, + ['Pet'] = 3600, + ['Creature'] = 3600 +} +function addon:PurgeGuidInfo() + local guids = addon:GetGroupGuid() +--[[ for k, v in pairs(guids) do + local gi = addon:GetGuidInfo(k) + if(gi) then + local localizedClass, englishClass, localizedRace, englishRace, sexGuid, nameGuid, realmGuid = GetPlayerInfoByGUID(k) +-- print(nameGuid, englishClass, k, gi:IsPet()) + end + end]] + guids = addon:GetSegmentsGuid(guids) + for k, v in pairs(guidInfo) do + if(not guids[k]) then +-- print(guidInfo[k]['name'],'purged') + guidInfo[k] = nil + end + end + return guidInfo +end +function addon:GetGuidInfoDB() + return guidInfo +end +function addon:SetGuidInfoDB(db) + if(db and type(db) == 'table') then + guidInfo = db + end + return guidInfo +end +function addon:GetGuidInfo(guid, unitFlag, unitName, unitClass) + if(guid and guid ~= '') then + if(not guidInfo[guid] or not guidInfo[guid]['name']) then + if(guid == 'Unknown') then + guidInfo[guid] = { + ['added'] = time(), + ['isParty'] = false, + ['isPet'] = false, + ['name'] = "", + ['guid'] = guid, + ['class'] = 'WARRIOR', + } + else --if(UnitFlag and bit.band(UnitFlag, COMBATLOG_OBJECT_TYPE_PLAYER) ~= 0) then + local localizedClass, englishClass, localizedRace, englishRace, sexGuid, nameGuid, realmGuid = GetPlayerInfoByGUID(guid) +-- print("gg",guid, unitFlag, unitName, " = ", nameGuid, realmGuid) +-- print(UnitName, nameGuid, realmGuid,format("%s-%s", nameGuid, realmGuid),format("%s-%s", nameGuid, realmGuid) ~= UnitName) + if(nameGuid) then + if(not realmGuid or realmGuid=='') then + realmGuid = GetNormalizedRealmName() + end + guidInfo[guid] = { + ['added'] = time(), + ['name'] = realmGuid and format("%s-%s", nameGuid, realmGuid) or nameGuid, + ['guid'] = guid, + ['class'] = englishClass, + ['classLocal'] = localizedClass, + ['race'] = englishRace, + ['raceLocal'] = localizedRace, + } + end + end + if(not guidInfo[guid]) then + guidInfo[guid] = { + ['added'] = time(), + ['name'] = unitName, + ['guid'] = guid, + ['flags'] = unitFlag + } + end + end + for k, v in pairs(methods_guid) do + guidInfo[guid][k] = v + end + if(unitClass and not guidInfo[guid]['class']) then + guidInfo[guid]['class'] = unitClass + end + if(guidInfo[guid]['name'] and not guidInfo[guid]['shortName']) then + guidInfo[guid]:SetName(guidInfo[guid]['name']) + end + if(not guidInfo[guid]['classColor']) then + guidInfo[guid]['classColor'] = addon:GetClassColors(guidInfo[guid]['class']) + end +-- guidInfo[guid]:scanTooltip() + return guidInfo[guid] + end +end \ No newline at end of file diff --git a/Libs/LibCombatlog b/Libs/LibCombatlog new file mode 160000 index 0000000..b20f7ec --- /dev/null +++ b/Libs/LibCombatlog @@ -0,0 +1 @@ +Subproject commit b20f7ecfb0b98006d158ed372d0025fb9ec887bc diff --git a/Quicklog.lua b/Quicklog.lua new file mode 100644 index 0000000..5073927 --- /dev/null +++ b/Quicklog.lua @@ -0,0 +1,470 @@ +local name, addon = ... +addon.f = CreateFrame("Frame") +local bit = bit +local segStarted = false +local COMBATLOG_OBJECT_AFFILIATION_OUTSIDER = COMBATLOG_OBJECT_AFFILIATION_OUTSIDER + +local function purgeDB(tbl) + for k, v in pairs(tbl) do + if(type(v) == 'table') then + tbl[k] = purgeDB(v) + elseif(type(v) == 'function') then + tbl[k] = nil + end + end + return tbl +end +local dbdefaults = { + ['maxSegments'] = 5, + ['maxTooltipSpells'] = 6, + ['maxTooltipUnits'] = 5, + ['currentSegment'] = 0, + ['dbversion'] = 1 +} +local dbfunc = {} +function dbfunc:Get(key, id) + if(key) then + if(key == 'segments') then + return self['segments'] + elseif(key == 'segment' and type(id) == 'number' and id > 0 and self['segments'][id]) then + return self['segments'][id] + else + if(type(self['settings'][key])~='nil') then + return self['settings'][key] + else + if(type(dbdefaults[key])~='nil') then + return dbdefaults[key] + end + end + end + end +end +function dbfunc:Set(key, value, id) + if(key) then + if(key == 'segments' and type(value) == 'table') then + self['segments'] = value + elseif(key == 'segment' and type(id) == 'number' and id > 0 and self['segments'][id] and type(value) == 'table') then + self['segments'][id] = value + else + if(value == dbdefaults[key]) then + self['settings'][key] = nil + else + self['settings'][key] = value + end + end + end +end +function addon:PLAYER_LOGOUT(event) + QuicklogDB['GuidDB'] = addon:PurgeGuidInfo() + QuicklogDB = purgeDB(QuicklogDB) +-- QuicklogDB['GuidDB'] = nil +end +local function processPet(UnitFlag, UnitName, guid, clot) + if(guid) then + local gi = addon:GetGuidInfo(guid, UnitFlag, UnitName) + if(gi['isPet'] ~= nil) then + return gi['isPet'] + end + if(UnitFlag and bit.band(UnitFlag, COMBATLOG_OBJECT_TYPE_PET) > 0) then + gi['isPet'] = true + return true + else + gi['isPet'] = false + end + end +end +local pet_clots = {COMBATLOG_OBJECT_TYPE_PET, COMBATLOG_OBJECT_TYPE_GUARDIAN, COMBATLOG_OBJECT_TYPE_OBJECT} +function addon:IsPet(UnitFlag, UnitName, guid) + local gi = addon:GetGuidInfo(guid, UnitFlag, UnitName) + for i = 1, #pet_clots do + processPet(UnitFlag, UnitName, guid, pet_clots[i]) + if(gi['isPet'] == true) then break end + end + return gi['isPet'] +end +function addon:IsInParty(UnitFlag, UnitName, guid) +--print("UF", UnitFlag, guid, data) + if(guid) then + local gi = addon:GetGuidInfo(guid, UnitFlag, UnitName) + if(gi) then + if(gi['isParty'] ~= nil) then + return gi['isParty'] + end + if(UnitFlag and bit.band(UnitFlag, COMBATLOG_OBJECT_AFFILIATION_OUTSIDER) == 0) then + gi['isParty'] = true + return true + else + gi['isParty'] = false + end + end + end +end +local function init(seg, data) + print('initSeg', data and #data) + if(data) then +-- print(unpack(data)) + end + seg['startevent'] = data + return seg +end + + +local computeAllowed = { + 'DAMAGE','HEAL','ABSORBED', +} +local function canCompute(typ) + for i = 1, #computeAllowed do + if(computeAllowed[i] == typ) then + return true + end + end +end +local function NoSource(data) + if(data[3] == "ENVIRONMENTAL_DAMAGE" or data[3] == "ENVIRONMENTAL_HEAL") then +-- print('aoe',data[3]) + local store = addon:GetStore(addon:GetSegment()) + return store:GetSource(data[9], data, 9, true) + end +end +local function computeEvent(data, prefix, suffix) +-- local prefix, suffix = unpack(addon:SplitCLEvent(data[3])) + if(canCompute(suffix)) then + local res = addon:GetSegment() + local store = addon:GetStore(res) + local source = store:GetSource(data[5], data) + if(not source) then + source = NoSource(data) + if(not source) then + source = store:GetSourceEmpty(data) + end + end + local dest = source:GetDestination(data[9]) + if(suffix == 'DAMAGE') then + local spellId, spellName, spellSchool, environmentalType + local amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand + if(prefix == 'ENVIRONMENTAL') then + environmentalType, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(13, unpack(data)) + spellId, spellName, spellSchool = 'ENVIRONMENTAL', format('ENVIRONMENTAL (%s)', environmentalType), environmentalType + elseif(prefix == 'SWING') then + amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(13, unpack(data)) + spellId, spellName, spellSchool = 'SWING', ACTION_SWING, school + else + spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand = select(13, unpack(data)) + end + if(overkill > -1) then + amount = amount - overkill + end + if(resisted or blocked) then + local destSource = store:GetSource(dest['guid'], data) + if(resisted and resisted > 0) then + destSource:addTotal('totalAbsorbed', resisted) + end + if(blocked and blocked > 0) then + destSource:addTotal('totalAbsorbed', blocked) + end + end + if(amount > 0) then + source:addTotal('total', amount) + if(spellId) then + dest:addSpell(spellId, spellName, spellSchool, amount, overkill, school, resisted, blocked, absorbed, critical, glancing, crushing, isOffHand) + if(resisted or blocked) then + local destSource = store:GetSource(dest['guid'], data) + local destSourceDest = destSource:GetDestination(dest['guid']) + if(resisted and resisted > 0) then + destSourceDest:addAbsorbSpell('RESISTED', 'RESISTED', 1, resisted) + end + if(blocked and blocked > 0) then + destSourceDest:addAbsorbSpell('BLOCKED', 'BLOCKED', 1, blocked) + end + end + end + else + print("no amount") + print(unpack(data)) + end + elseif(suffix == 'ABSORBED') then + local spellId, spellName, spellSchool, extraGUID, extraName, extraFlags, extraRaidFlags, extraSpellID, extraSpellName, extraSchool, amount + if(#data == 24) then + spellId, spellName, spellSchool, extraGUID, extraName, extraFlags, extraRaidFlags, extraSpellID, extraSpellName, extraSchool, amount = select(13, unpack(data)) + else + extraGUID, extraName, extraFlags, extraRaidFlags, extraSpellID, extraSpellName, extraSchool, amount = select(13, unpack(data)) + spellId, spellName, spellSchool = 'SWING', ACTION_SWING, nil + end + + if(amount > 0 and extraGUID) then + + if(type(data[13]) ~= 'number') then + local extraSource = store:GetSource(extraGUID, data, 13, true) + local dest2 = extraSource:GetDestination(data[9]) + extraSource:addTotal('totalAbsorbed', amount) + -- dest2:addTotal('totalAbsorbed', amount) + if(extraSpellID) then + dest2:addAbsorbSpell(extraSpellID, extraSpellName, extraSchool, amount) + end + else +-- print("es", data[13], data[14], data[15]) + end + end + elseif(suffix == 'HEAL') then + local spellId, spellName, spellSchool, amount, overhealing, absorbed, critical = select(13, unpack(data)) + if(overhealing > 0) then + amount = amount - overhealing + source:addTotal('totalOverheal', overhealing) + end + if(absorbed > 0) then + amount = amount - absorbed + end + if(amount > 0) then + source:addTotal('totalHeal', amount) + if(spellId) then + dest:addHealSpell(spellId, spellName, spellSchool, amount, overhealing, absorbed, critical) +-- dest:addTotal('totalHeal', amount) + else + print("no sid") + end + end + end +-- store:Store() + else + print('skip', data[3], suffix) + end + data = nil +end +local function startSegment(data) + segStarted = true + addon:NewSegment(init, data) + addon:UpdateWindows() +end +local function stopSegment(...) + if(segStarted) then + addon:UpdateWindows() + segStarted = false + addon:PurgeGuidInfo() + addon:GetSegment():stop() + else + print("stop not", ...) + end +end +local throttleUpdate,trottleDelay = time(), 0.4 +function addon:summon(data, prefix, suffix) + local summon = addon:GetGuidInfo(data[9], data[11], data[10]) + if(not summon:HasOwner()) then + summon:scanTooltip() + local owner = addon:GetGuidInfo(data[5], data[7], data[6]) + summon:SetOwner(owner) + end +-- summon['isPet'] = true +-- summon['ownerFlags'] = data[7] +-- summon['ownerName'] = data[6] +-- summon['ownerGuid'] = data[5] + +-- addon:IsInParty(data[11], data[10], data[9]) +end +local function prepData(data, offset) + local offset = offset or 5 + if(data[offset + 1]) then + local gis = addon:GetGuidInfo(data[offset], data[offset + 2], data[offset + 1]) + if(segStarted and gis['isBoss']) then + QuicklogDB['segments'][#QuicklogDB['segments']]['hasBoss'] = true + QuicklogDB['segments'][#QuicklogDB['segments']]['BossNames'][data[offset]] = data[offset + 1] + end +-- print('petc') + if(gis['isPet'] == nil) then +-- print('petc not yet',data[offset + 2], data[offset + 1], data[offset]) + addon:IsPet(data[offset + 2], data[offset + 1], data[offset]) + end + if(gis:IsPet()) then +-- print('pet found', gis['name'], gis['ownerName']) + if(not gis['ownerGuid']) then +-- print('pet found, no owner', gis['name'], gis['ownerName']) + gis:scanTooltip() + end + if(gis['ownerGuid']) then +-- print('rewrite', gis['name'], gis['ownerName']) + data[offset + 2] = gis['ownerFlags'] + data[offset + 1] = gis['ownerName'] + data[offset] = gis['ownerGuid'] + end + else +-- print("no pet", gis['name']) + end + end + return data +end +function addon:dmg(data, prefix, suffix) + --[[ [1] = event, + [2] = time, + [3] = type, + [4] = hideCaster, + [5] = sourceGUID, + [6] = sourceName, + [7] = sourceFlags, + [8] = sourceRaidFlags, + [9] = destGUID, + [10] = destName, + [11] = destFlags, + [12] = destRaidFlags, + ]] + if(not segStarted and not data[6]) then +-- print('environmental/aoe?', data[3], data[10]) + return + end + +-- if(addon:IsInParty(data[7],data[5]) or addon:IsInParty(data[11], data[9])) then + data = prepData(data, 5) + data = prepData(data, 9) + if(not segStarted) then + local gis = addon:GetGuidInfo(data[5], data[7], data[6]) + + if(gis and not gis['died'] and not (suffix == 'HEAL') and not(prefix == 'ENVIRONMENTAL')) then + local gid = addon:GetGuidInfo(data[9], data[11], data[10]) +-- if(addon:IsInParty(data[7], data[6], data[5])) then + if(gis:IsParty()) then + if(gis['guid'] and gid['guid'] and gis['guid'] ~= gid['guid']) then + startSegment(data) + end + end + end + end + if(segStarted) then + computeEvent(data, prefix, suffix) + if(time()>throttleUpdate) then + throttleUpdate = data[2] + trottleDelay + addon:UpdateWindows() + end + end +end +function addon:BOSS_KILL(event, encounterID, encounterName) + print("boss kill", encounterID, encounterName) +end +function addon:ENCOUNTER_END(event, encounterID, encounterName, difficultyID, groupSize, success) +print(event, encounterID, encounterName, difficultyID, groupSize) + addon:UpdateWindows() + stopSegment(event, encounterID, encounterName, difficultyID, groupSize, success) + self:UnregisterEvent(event) + self:RegisterEvent('ENCOUNTER_START') +end +function addon:ENCOUNTER_START(event, encounterID, encounterName, difficultyID, groupSize) +print(event, encounterID, encounterName, difficultyID, groupSize) + if(not segStarted) then + startSegment() + end + local seg = addon:GetSegment() + seg['isEncounter'] = true + seg['encounter'] = {encounterID, encounterName, difficultyID, groupSize} + self:RegisterEvent("ENCOUNTER_END") + self:UnregisterEvent(event) +end +function addon:SCENARIO_COMPLETED(event, questID, xp, money) + print("scenario end", event, questID, xp, money) + stopSegment(event, questID, xp, money) +end +function addon:UNIT_EXITED_VEHICLE(event, unit) +-- print("uev", event, unit) + if(unit=='player') then + stopSegment(event, unit) + end +end +function addon:UNIT_NAME_UPDATE(event, unit) + local gi = addon:GetGuidInfo(UnitGUID(unit)) + if(gi) then + gi:SetName(UnitName(unit)) + end +end +function addon:PLAYER_ENTERING_WORLD(event, isInitialLogin, isReloadingUi) +print("PEW") + if(not isInitialLogin and not isReloadingUi) then + stopSegment(event, isInitialLogin, isReloadingUi) + end +end + +function addon:cmbStatus(event) +-- print(event) + if(event=='PLAYER_REGEN_ENABLED') then + if(segStarted and not addon:GetSegment()['isEncounter']) then + if(not UnitIsDeadOrGhost('PLAYER')) then + stopSegment(event) + end + end + elseif(event=='PLAYER_REGEN_DISABLED') then + if(not segStarted) then + startSegment() + end + end +end +function addon:died(data) +-- print(unpack(data)) + if(data[9] and data[10]) then + local gi = addon:GetGuidInfo(data[9], data[11], data[10]) + gi['died'] = gi['died'] or {} + gi['died'][#gi['died'] + 1] = data + gi['dead'] = true + end +end +function addon:resurrect(data) +-- local gis = addon:GetGuidInfo(data[5], data[7], data[6]) +-- print('res', gis['name']) + if(data[9] and data[10]) then + local gi = addon:GetGuidInfo(data[9], data[11], data[10]) +-- print('res dest', gi['name']) + gi['resurrect'] = gi['resurrect'] or {} + gi['resurrect'][#gi['resurrect'] + 1] = data + gi['dead'] = nil + end +end +addon['f']:SetScript("OnEvent", function(self, event, arg1, ...) + if(event == "ADDON_LOADED" and arg1 == name) then + QuicklogDB = QuicklogDB or { + ['segments'] = {}, + ['settings'] = {}, + } + for k,v in pairs(dbfunc) do + QuicklogDB[k] = v + end + if(QuicklogDB['GuidDB']) then + addon:SetGuidInfoDB(QuicklogDB['GuidDB']) + end + self:UnregisterEvent(event) + local seg = addon:GetSegment() +-- seg['end'] = time() + if(not seg['end']) then + print('seg is running') + segStarted = true + else + print('no seg running') + addon:PurgeGuidInfo() + end + + LibStub("LibCombatLog"):Embed(addon) + addon:RegisterCLEvent({'UNIT_DIED', 'UNIT_DESTROYED', 'UNIT_DISSIPATES'}, 'died') + addon:RegisterCLSuffix({'DAMAGE','HEAL','ABSORBED'}, 'dmg') + addon:RegisterCLSuffix({'SUMMON', 'CREATE'}, 'summon') + addon:RegisterCLSuffix({'RESURRECT'}, 'resurrect') + addon:RegisterCLStatus('cmbStatus') + + addon:UpdateWindows() + elseif(type(addon[event]) == 'function') then + addon[event](self, event, arg1, ...) + else + print('else',event, arg1, ...) + end +end) +addon['f']:RegisterEvent("ADDON_LOADED") +addon['f']:RegisterEvent("PLAYER_LOGOUT") +addon['f']:RegisterEvent("RAID_ROSTER_UPDATE") +addon['f']:RegisterEvent("GROUP_ROSTER_UPDATE") +addon['f']:RegisterEvent("UNIT_TARGET") +addon['f']:RegisterEvent("ENCOUNTER_START") +addon['f']:RegisterEvent("UNIT_NAME_UPDATE") +--[[addon['f']:RegisterEvent("PLAYER_GAINS_VEHICLE_DATA") -- ? +addon['f']:RegisterEvent("PLAYER_LOSES_VEHICLE_DATA") -- ? +addon['f']:RegisterEvent("SCENARIO_COMPLETED") -- ? +addon['f']:RegisterEvent("PLAYER_ENTERING_WORLD") -- ? +addon['f']:RegisterEvent("WORLD_QUEST_COMPLETED_BY_SPELL") -- ? +addon['f']:RegisterEvent("QUEST_COMPLETE") -- ? +addon['f']:RegisterEvent("PLAYER_CONTROL_GAINED") -- ? +addon['f']:RegisterEvent("TASK_PROGRESS_UPDATE") -- ? +addon['f']:RegisterEvent("INSTANCE_ENCOUNTER_OBJECTIVE_COMPLETE") -- ? +addon['f']:RegisterEvent("SCENARIO_UPDATE") -- ? +]] +addon['f']:RegisterEvent("UNIT_EXITED_VEHICLE") -- Marasius diff --git a/Quicklog.toc b/Quicklog.toc new file mode 100644 index 0000000..861b346 --- /dev/null +++ b/Quicklog.toc @@ -0,0 +1,12 @@ +## Interface: 90002 +## Name: Quicklog +## SavedVariables: QuicklogDB +## Description: Combatlog display +Libs/LibCombatlog/Libstub/Libstub.lua +Libs/LibCombatlog/LibCombatlog.lua +GuidInfo.lua +Quicklog.lua +DataStore.lua +Segments.lua +Statusbars.lua +Window.lua \ No newline at end of file diff --git a/Segments.lua b/Segments.lua new file mode 100644 index 0000000..88761be --- /dev/null +++ b/Segments.lua @@ -0,0 +1,104 @@ +local name, addon = ... +function addon:GetSegment(sid) + if(not sid or not QuicklogDB['segments'][sid]) then + sid = #QuicklogDB['segments'] + end + return QuicklogDB['segments'][sid] +end +local segfunc = {} +function segfunc:add(data) + self['events'][#self['events']+1] = data +end +function segfunc:refresh(purge) + QuicklogDB['segments'][#QuicklogDB['segments']] = self + if(purge) then + for i = 1, #QuicklogDB['segments'] do + + for k, v in pairs(QuicklogDB['segments'][i]) do + if(type(v) == 'function') then +-- QuicklogDB['segments'][i][k] = nil + end + end + for a, b in pairs(QuicklogDB['segments'][i]['source']) do + QuicklogDB['segments'][i]['source'][a]['store'] = nil + for c, d in pairs(b) do + if(type(d) == 'function' or c == 'data') then + QuicklogDB['segments'][i]['source'][a][c] = nil + end + end + for e, f in pairs(b['dest']) do + QuicklogDB['segments'][i]['source'][a]['dest'][e]['source'] = nil + for g, h in pairs(f) do + if(type(h) == 'function') then +-- QuicklogDB['segments'][i]['source'][a]['dest'][e][g] = nil + end + end + end + end + end + +-- for k, v in pairs(segfunc) do +-- QuicklogDB['segments'][#QuicklogDB['segments']][k] = nil +-- end + end +end +function segfunc:stop() + print('stop seg') + self['end'] = time() + self:refresh(true) +end +function segfunc:GetBoss() + for k, v in pairs(self['BossNames']) do + return {k, self['BossNames'][k]} + end +end + +function segfunc:GetSegmentGuid(guids) + guids = guids or {} + for c, d in pairs(self['source']) do + if(addon:IsInParty(d['flags'], d['name'], d['guid'])) then + for k, v in pairs(d['dest']) do + guids[k] = true + end + guids[c] = true + end + end + return guids +end +function addon:GetSegmentsGuid(guids) + guids = guids or {} + for i = 1, #QuicklogDB['segments'] do + local seg = QuicklogDB['segments'][i] + for k, v in pairs(segfunc) do + seg[k] = v + end + guids = seg:GetSegmentGuid(guids) + end + return guids +end +local function Renumerate() + for i = 1, #QuicklogDB['segments'] do + QuicklogDB['segments'][i]['num'] = i + end +end +function addon:NewSegment(init, data) + QuicklogDB['segments'][#QuicklogDB['segments'] + 1] = { + ['begin'] = time(), + ['source'] = {}, + ['BossNames'] = {} + } + +-- QuicklogDB['segments'][#QuicklogDB['segments']]['res'] = CopyTable(defaultEvent) + while(#QuicklogDB['segments'] > QuicklogDB:Get('maxSegments')) do +-- print('rm segment') + tremove(QuicklogDB['segments'], 1) + end + for k, v in pairs(segfunc) do + QuicklogDB['segments'][#QuicklogDB['segments']][k] = v + end + Renumerate() + if(type(init) == 'function') then + return init(self:GetSegment(#QuicklogDB['segments']), data) + end + return self:GetSegment(#QuicklogDB['segments']) +end \ No newline at end of file diff --git a/Statusbars.lua b/Statusbars.lua new file mode 100644 index 0000000..7c86f21 --- /dev/null +++ b/Statusbars.lua @@ -0,0 +1,146 @@ +local name, addon = ... +local f = CreateFrame('FRAME', nil, UIParent) +local bars = {} +local function layoutProto(self) +-- print('val', self:GetValue(), self:GetMinMaxValues()) + if(tonumber(self:GetValue())==0) then + self:Hide() + else + self:Show() + end +end +local layouts = { + ['defaultLayout'] = { + ['texture'] = "Interface\\TargetingFrame\\UI-StatusBar", + ['tile'] = false, + ['width'] = 160, + ['height'] = 16, + ['value'] = 100, + ['color'] = { 1, 1, 1, 1 }, + ['script'] = layoutProto, + } +} +local tip = CreateFrame("GameTooltip", format("%s_tooltip", name), nil, "GameTooltipTemplate") +tip:SetScale(0.75) +local function OnEnter(self) + tip:ClearLines() + tip:SetOwner(self, "ANCHOR_CURSOR"); + self.tooltip = tip + local p = self:GetParent() + if(p.StatusBarOnEnter and type(p.StatusBarOnEnter) == 'function') then + p:StatusBarOnEnter(self) + end + tip:Show() +end +local function OnLeave(self) + local p = self:GetParent() + if(p.StatusBarOnLeave and type(p.StatusBarOnLeave) == 'function') then + p:StatusBarOnLeave(self) + end + self.tooltip:Hide() + self.tooltip = nil +end +local bar_methods = {} +function bar_methods:Release() + self['active'] = false + self['class'] = nil + self:Hide() +end +function bar_methods:SetText(text, text2) + if(text) then + self['fontstring']:SetText(text) + end + if(text2) then + self['fontstring2']:SetText(text2) + end +end +function addon:GetClassColors(class, undef) + if(class) then + if(RAID_CLASS_COLORS[class]) then + local col = RAID_CLASS_COLORS[class] + return {col['r'], col['g'], col['b'], col['a'] or 1, undef} + end + end + return self:GetClassColors('WARRIOR', true) +end +function bar_methods:SetColorByClass(class) + local r, g, b, a, u = unpack(addon:GetClassColors(class)) + if(not u) then + self['class'] = class + else + self['class'] = nil + end + self:SetStatusBarColor(r, g, b, a) +end +function bar_methods:GetText() + return self['fontstring']:GetText() +end +function bar_methods:GetText2() + return self['fontstring2']:GetText() +end +function bar_methods:SetLayout(layout) + if(not layout or not layouts[layout]) then + layout = 'defaultLayout' + end + local d = layouts['defaultLayout'] + local l = layouts[layout] + self:SetScript("OnEnter", OnEnter) + self:SetScript("OnLeave", OnLeave) + self:SetStatusBarTexture(l['texture'] or d['texture']) + self:GetStatusBarTexture():SetHorizTile(l['tile'] or d['tile']) + self:SetValue(l['value'] or d['value']) + self:SetWidth(l['width'] or d['width']) + self:SetHeight(l['height'] or d['height']) + self:SetScript("OnValueChanged", l['script'] or d['script']) + self:SetPoint("CENTER") + self:SetStatusBarColor((l['color'] and unpack(l['color']) or unpack(d['color']))) + self['fontstring'] = self:CreateFontString(nil, "OVERLAY", "TextStatusBarText") + self['fontstring']:ClearAllPoints() + self['fontstring']:SetAllPoints(self) + self['fontstring']:SetText(" *.*") + self['fontstring']:SetJustifyH('LEFT') + self['fontstring2'] = self:CreateFontString(nil, "OVERLAY", "TextStatusBarText") + self['fontstring2']:ClearAllPoints() + self['fontstring2']:SetAllPoints(self) + self['fontstring2']:SetText(" 0") + self['fontstring2']:SetJustifyH('RIGHT') + self:Show() + return self +end + +function addon:IsStatusBarActive(bid) + if(bars[bid] and bars[bid]['active']) then + return true + end +end +function addon:CreateStatusBar(layout) +-- print("Create bar",format("%s_Statusbar_%i",name,#bars + 1)) + local bar = CreateFrame("StatusBar",format("%s_Statusbar_%i",name,#bars + 1),f) + for method, func in pairs(bar_methods) do + bar[method] = func + end + return bar:SetLayout(layout) +end +function addon:GetStatusBar(bid) + if(not bid or type(bid) ~= 'number' or bid > #bars) then + for i = 1, #bars do + if(not bars[i]['active']) then + bid = i + break + end + end + if(not bid) then + bid = #bars + 1 + end + end +-- if(addon:IsStatusBarActive(bid)) then + if(not bars[bid]) then + bars[bid] = addon:CreateStatusBar() + end + bars[bid]['active'] = true +-- else +-- print("Bar not active") +-- end +-- print("Bar", bid, bars[bid]) + return bars[bid] +end \ No newline at end of file diff --git a/Window.lua b/Window.lua new file mode 100644 index 0000000..bd9ad91 --- /dev/null +++ b/Window.lua @@ -0,0 +1,411 @@ +local name, addon = ... +local f = CreateFrame("Frame", nil, UIParent) +local window_methods = {} +local displays = { 'DAMAGE', 'HEAL', 'ABSORBED' } +local display_methods = {} +local defaultWindow = { + ['active'] = true, + ['show'] = true, + ['bars'] = {}, + ['display'] = 'DAMAGE', +} +local windows = {} + +local function sort_total(a, b) + return a['total'] > b['total'] +end +local function sort_totalHeal(a, b) + return a['totalHeal'] > b['totalHeal'] +end +local function sort_totalAbsorbed(a, b) + return a['totalAbsorbed'] > b['totalAbsorbed'] +end +local sorts = { + ['total'] = sort_total, + ['totalHeal'] = sort_totalHeal, + ['totalAbsorbed'] = sort_totalAbsorbed, +} +local function releaseBars(bars, num) + while(num < #bars) do + local bar = bars[num + 1] + tremove(bars, num + 1) + bar['lastPos'] = nil + bar['class'] = nil + bar:Release() + end + return bars +end +local function GetSegmentId(seg) + for i = 1, #QuicklogDB['segments'] do + if(QuicklogDB['segments'][i] == seg) then + return i + end + end + return #QuicklogDB['segments'] +end +local function defaultDisplay(win, total) + local seg = win['seg'] + + if(seg) then + local store = addon:GetStore(seg) + +-- print('s',seg) + local src = {} + local maxtotal = 0 + for c, d in pairs(seg['source']) do + if(d[total] > 0 and addon:IsInParty(d['flags'], d['name'], d['guid'])) then + if(d[total]>maxtotal) then + maxtotal = d[total] + end + src[#src + 1] = d + end + end +-- print('src', #src) + sort(src, sorts[total]) + local last = win + local h1 = win:GetHeight() + local maxBars = win:GetMaxBars() +-- print("bars",#src, maxBars) + for i = 1, #src do + local d = src[i] + local bar = win:GetBar(i) + local guid = d['guid'] + bar['guid'] = guid + local gib = addon:GetGuidInfo(guid) + if(bar['class'] ~= gib['class']) then + bar:SetColorByClass(gib['class']) + end + if(bar['lastPos'] ~= i) then + bar['lastPos'] = i + bar:ClearAllPoints() + bar:SetPoint("TOPLEFT", win, "TOPLEFT", 14, (bar:GetHeight() * i * - 1) + 2) + end + bar:SetMinMaxValues(0, maxtotal) + bar:SetValue(d[total]) + bar:SetText(gib['shortName'] and gib['shortName'] or gib['name'], d[total]) + last = bar + if(i >= maxBars) then + break + end + end + win['settings']['bars'] = releaseBars(win['settings']['bars'], #src) + end +end +function display_methods:DAMAGE(win) + defaultDisplay(win, 'total') +end +function display_methods:HEAL(win) + defaultDisplay(win, 'totalHeal') +end +function display_methods:ABSORBED(win) + defaultDisplay(win, 'totalAbsorbed') +end +function window_methods:GetMaxBars() + local h1 = self:GetHeight() + local bar = self:GetBar(1) + local h2 = bar:GetHeight() + local e = h1 - 16 - (h2/2) + local n = (e) / (h2) + local m = floor(n) -- 8 = Insets, 2 y-offset +-- print('heights',h1, h2, 'e', e, 'h', h2*m, 'max',m , n, m*16) + return m +end +function window_methods:GetBar(bid) + if(self['settings']['bars'][bid]) then + return self['settings']['bars'][bid] + else + self['settings']['bars'][#self['settings']['bars'] + 1] = addon:GetStatusBar() + self['settings']['bars'][#self['settings']['bars']]:SetParent(self) + return self['settings']['bars'][#self['settings']['bars']] + end +end +local function defaultTooltip(tooltip, src, total, spells) + local dip = {} + tooltip:AddDoubleLine("Total", src[total], 1, 1, 1, 1, 1, 1) + for guid, dest in pairs(src['dest']) do + if(dest[total]>0) then + dip[#dip + 1] = dest + end + end + if(#dip > 0) then + sort(dip, sorts[total]) + for i = 1, #dip do + local dest = dip[i] + local gib = addon:GetGuidInfo(dest['guid']) + if(gib) then + local r, g, b, a, u = unpack(gib['classColor']) + tooltip:AddDoubleLine(gib['name'] and gib['name'] or '', dest[total], r, g, b, 1, 1, 1) + + if(dest[spells]) then + local tip = {} + for _, spell in pairs(dest[spells]) do + if(spell[total]>0) then + tip[#tip + 1] = spell + end + end + if(#tip > 0) then + sort(tip, sorts[total]) + for i = 1, #tip do + local spell = tip[i] + tooltip:AddDoubleLine(format(" %s",spell['spellName']), spell[total]) + if(i >= QuicklogDB:Get('maxTooltipSpells')) then + break + end + end + end + tip = nil + end + end + if(i >= QuicklogDB:Get('maxTooltipUnits')) then + break + end + end + end + dip = nil +end +function window_methods:StatusBarOnEnter(bar) + local seg = self['seg'] + local store = addon:GetStore(seg) + local src = store:GetSource(bar['guid'], nil, nil, true) + if(not src or not src['name']) then bar:Release() return end + + local display = self['settings']['display'] + bar['tooltip']:AddLine(format("%s - %s", display, src['name']),1,1,1) + if(display == 'DAMAGE') then + defaultTooltip(bar['tooltip'], src, 'total', 'spells') + elseif(display == 'HEAL') then + defaultTooltip(bar['tooltip'], src, 'totalHeal', 'spellsHeal') + elseif(display == 'ABSORBED') then + defaultTooltip(bar['tooltip'], src, 'totalAbsorbed', 'spellsAbsorbed') + end +end +local function OnResize(self) + local maxBars = self:GetMaxBars() + for i = 1, #self['settings']['bars'] do + local bar = self['settings']['bars'][i] + if(i>maxBars) then + bar:Hide() + else + bar:Show() + end + end +end +function window_methods:StatusBarOnLeave(bar) +-- +end +function window_methods:Refresh() + local display = self['settings']['display'] + self['title']['text']:SetText(display) + self['footer']['text']:SetText(format("# %i/%i",GetSegmentId(self['seg']),#QuicklogDB['segments'])) + if(type(display_methods[display]) == 'function') then + self['seg'] = self['seg'] or addon:GetSegment() + display_methods[display](nil, self) + end + OnResize(self) +-- print(self:GetName(), 'refresh', display) +end +function window_methods:SetSegment(seg) + self['seg'] = seg + self:Refresh() +end +function window_methods:UpdateWindow() + if(not self['seg']) then + self:SetSegment(addon:GetSegment()) + else + self:Refresh() + end +-- print("update",self:GetName()) +end +function window_methods:Clear() + for i = 1, #self['settings']['bars'] do + local bar = self['settings']['bars'][i] + if(bar) then + bar['src'] = nil + bar:Release() + end + tremove(self['settings']['bars'], i) + end +end +local function StartResize(self, button) + local p = self:GetParent() + p:StartSizing() + self['isMoving'] = true + self['hasMoved'] = false +end +local function StopResize(self) + if(self['isMoving']) then + local p = self:GetParent() + p:StopMovingOrSizing() + self['isMoving'] = false + self['hasMoved'] = true + end +end +local function GetDisplayId(display) + for i=1, #displays do + if(displays[i] == display) then + return i + end + end + return 1 +end + +local function TitleNavClick(self, button) + local win = self:GetParent():GetParent() + local display = win['settings']['display'] + local cur = GetDisplayId(display) + win:Clear() + if(self['nav'] == "LEFT") then + cur = cur - 1 + if(cur < 1) then + cur = #displays + end + else + cur = cur + 1 + if(cur > #displays) then + cur = 1 + end + end + win['settings']['display'] = displays[cur] + win:Refresh() +end +local function FooterNavClick(self, button) + local win = self:GetParent():GetParent() + local cur = GetSegmentId(win['seg']) + win:Clear() + if(self['nav'] == "LEFT") then + cur = cur - 1 + if(cur < 1) then + cur = #QuicklogDB['segments'] + end + else + cur = cur + 1 + if(cur > #QuicklogDB['segments']) then + cur = 1 + end + end + win:SetSegment(QuicklogDB['segments'][cur]) + win:Refresh() +end +local function titleDragStart(self) + self:GetParent():StartMoving() +end +local function titleDragStop(self) + self:GetParent():StopMovingOrSizing() +end +local function GetWinBar(self, barName, drag, clickFunc) + local title = CreateFrame("Frame", "$parent_"..barName, self, "BackdropTemplate") + self:EnableMouse(true) + title:SetSize(200, 20) + title:SetBackdrop({ + bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", +-- edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", + tile = true, + tileSize = 32, + edgeSize = 32, + insets = { left = 2, right = 2, top = 2, bottom = 2 } + }) + title:EnableMouse(true) + if(drag) then + title:RegisterForDrag("LeftButton") + title:SetScript("OnDragStart", titleDragStart) + title:SetScript("OnDragStop", titleDragStop) + end + title:SetBackdropBorderColor(1, 1, 1, 1) + local b = CreateFrame("BUTTON", "$parent_Left", title, "BackdropTemplate") + b:SetSize(20,20) + b:SetPoint("TOPLEFT", title, "TOPLEFT") + b:SetNormalTexture("Interface\\BUTTONS\\UI-SpellbookIcon-PrevPage-Up") + b:SetPushedTexture("Interface\\BUTTONS\\UI-SpellbookIcon-PrevPage-Down") + b:SetDisabledTexture("Interface\\BUTTONS\\UI-SpellbookIcon-PrevPage-Disabled") + b:SetScript("OnClick", clickFunc) + b['nav'] = "LEFT" + local d = CreateFrame("BUTTON", "$parent_Right", title, "BackdropTemplate") + d:SetSize(20,20) + d:SetPoint("TOPLEFT", b, "TOPRIGHT",-8) + d:SetNormalTexture("Interface\\BUTTONS\\UI-SpellbookIcon-NextPage-Up") + d:SetPushedTexture("Interface\\BUTTONS\\UI-SpellbookIcon-NextPage-Down") + d:SetDisabledTexture("Interface\\BUTTONS\\UI-SpellbookIcon-NextPage-Disabled") + d:SetScript("OnClick", clickFunc) + d['nav'] = "RIGHT" + local c = title:CreateFontString(nil,"OVERLAY","CombatLogFont") + c:ClearAllPoints() + c:SetAllPoints(title) + c:SetPoint("TOPLEFT", d, "TOPRIGHT") + c:SetJustifyH("LEFT") + c:SetText(name) + title['text'] = c + return title +end +function window_methods:AddFooterBar() + self['footer'] = GetWinBar(self, 'Footer', false, FooterNavClick) + self['footer']:SetPoint("TOPLEFT", self, "BOTTOMLEFT") +end +function window_methods:AddTitleBar() + self['title'] = GetWinBar(self, 'Title', true, TitleNavClick) + self['title']:SetPoint("BOTTOMLEFT", self, "TOPLEFT") +end +function addon:GetWindow(windowname) + for i = 1, #windows do + if(windows[i] and windows[i]:GetName() == windowname) then +-- print("Window", windowname) + return windows[i] + end + end + windows[#windows + 1] = CreateFrame("Frame", format("%s_Window_%i", name, #windows + 1),f,"BackdropTemplate") + + local win = windows[#windows] + for fname, func in pairs(window_methods) do + win[fname] = func + end + win:AddTitleBar() + win:AddFooterBar() + self['resize'] = CreateFrame("Frame", nil, win) + local btn = self['resize'] + btn:SetSize(20,20) + btn:SetPoint("BOTTOMRIGHT",win,"BOTTOMRIGHT") + btn:SetScript("OnMouseDown", StartResize) + btn:SetScript("OnMouseUp", StopResize) + win:SetBackdrop({ + bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", +-- edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", + tile = true, + tileSize = 32, + edgeSize = 32, + insets = { left = 8, right = 8, top = 8, bottom = 8 } + }) + win:SetBackdropColor(0, 0, 0, 1) + win['settings'] = CopyTable(defaultWindow) + win:HookScript("OnSizeChanged", OnResize) + win:SetPoint("CENTER",UIParent,"CENTER") + win:SetSize(200, 400) + win:SetMovable(true) + + win:SetResizable(true) + + win:SetMinResize(200,200) + win:SetMaxResize(200,800) + win:SetClampedToScreen(true) + win:Show() + win:UpdateWindow() + return win +end +function addon:CreateWindow(windowname) + return self:GetWindow(windowname) +end +function addon:UpdateWindows(seg) + if(#windows == 0) then + self:CreateWindow('DAMAGE') +-- self:CreateWindow('HEAL') + end + for i = 1, #windows do + local win = windows[i] + if(win['settings']['active'] and win:IsVisible()) then + if(seg) then + win:SetSegment(seg) + else + win:SetSegment(addon:GetSegment()) + end + win:UpdateWindow() + end + end +end \ No newline at end of file