From 7fb0ae469c75a93ee6f0ea7b0dab692e0c475f33 Mon Sep 17 00:00:00 2001 From: Tercio Jose Date: Sun, 14 May 2023 16:30:09 -0300 Subject: [PATCH] Added an option to nest pet spells within a bar showing the pet name --- Definitions.lua | 8 +- classes/class_damage.lua | 69 +++++++++++------- classes/container_spells.lua | 14 ++++ frames/window_playerbreakdown.lua | 12 +-- frames/window_playerbreakdown_spells.lua | 65 +++++++++++------ .../window_playerbreakdown_spells_options.lua | 44 ++++++++--- functions/profiles.lua | 8 +- images/pets/pet_icon_1.tga | Bin 0 -> 16428 bytes 8 files changed, 152 insertions(+), 68 deletions(-) create mode 100644 images/pets/pet_icon_1.tga diff --git a/Definitions.lua b/Definitions.lua index 103a52eb..ecd2145c 100644 --- a/Definitions.lua +++ b/Definitions.lua @@ -358,10 +358,11 @@ ---@field ListActors fun(container: actorcontainer) usage: for index, actorObject in container:ListActors() do ---@class spellcontainer : table +---@field _ActorTable table store [spellId] = spelltable ---@field GetSpell fun(container: spellcontainer, spellId: number) get a spell by its id ---@field ListActors fun(container: spellcontainer) : any, any usage: for spellId, spelltable in container:ListActors() do ---@field ListSpells fun(container: spellcontainer) : any, any usage: for spellId, spelltable in container:ListActors() do ----@field _ActorTable table +---@field HasTwoOrMoreSpells fun(container: spellcontainer) : boolean return true if the container has two or more spells ---@class friendlyfiretable : table ---@field total number total amount of friendly fire caused by the actor @@ -593,8 +594,11 @@ ---@field expandedIndex number ---@field bIsExpanded boolean ---@field statusBarValue number +---@field actorName string --when showing an actor header, this is the actor name +---@field bIsActorHeader boolean is this is true, the spellbar is an actor header, which is a bar with the actor name with the actor spells nested +---@field actorIcon texture ----@class bknesteddata : {spellId: number, spellTable: spelltable, petName: string, value: number} +---@class bknesteddata : {spellId: number, spellTable: spelltable, actorName: string, value: number, bIsActorHeader: boolean} fills .nestedData table in spelltableadv, used to store the nested spells data, 'value' is set when the breakdown sort the values by the selected header ---@class breakdowntargetframe : frame ---@field spellId number diff --git a/classes/class_damage.lua b/classes/class_damage.lua index 547ae744..2a0368df 100644 --- a/classes/class_damage.lua +++ b/classes/class_damage.lua @@ -4446,7 +4446,7 @@ end end --lock into a line after clicking on it ---[[exported]] function Details:FocusLock(row, spellId) +--[[exported]] function Details:FocusLock(row, spellId) --will be deprecated if (not info.mostrando_mouse_over) then if (spellId == self.detalhes) then --tabela [1] = spellid = spellid que esta na caixa da direita if (not row.on_focus) then --se a barra n�o tiver no foco @@ -4466,11 +4466,11 @@ end end end -local wipeSpellCache = function() +local wipeSpellCache = function() --deprecated table.wipe(Details222.PlayerBreakdown.DamageSpellsCache) end -local addToSpellCache = function(unitGUID, spellName, spellTable) +local addToSpellCache = function(unitGUID, spellName, spellTable) --deprecated local unitSpellCache = Details222.PlayerBreakdown.DamageSpellsCache[unitGUID] if (not unitSpellCache) then unitSpellCache = {} @@ -4486,7 +4486,7 @@ local addToSpellCache = function(unitGUID, spellName, spellTable) table.insert(spellCache, spellTable) end -local getSpellDetails = function(unitGUID, spellName) +local getSpellDetails = function(unitGUID, spellName) --deprecated local unitCachedSpells = Details222.PlayerBreakdown.DamageSpellsCache[unitGUID] local spellsTableForSpellName = unitCachedSpells and unitCachedSpells[spellName] @@ -4605,7 +4605,7 @@ function damageClass:MontaInfoDamageDone() --I guess this fills the list of spel ---@type table local alreadyAdded = {} - local bShouldMergePlayerSpells = Details.breakdown_spell_tab.merge_players_spells_with_same_name + local bShouldMergePlayerSpells = Details.breakdown_spell_tab.nest_players_spells_with_same_name ---@type number, spelltable for spellId, spellTable in pairs(actorSpells) do @@ -4623,7 +4623,7 @@ function damageClass:MontaInfoDamageDone() --I guess this fills the list of spel bkSpellData.spellTables[#bkSpellData.spellTables+1] = spellTable ---@type bknesteddata - local nestedData = {spellId = spellId, spellTable = spellTable, petName = "", value = 0} + local nestedData = {spellId = spellId, spellTable = spellTable, actorName = "", value = 0} bkSpellData.nestedData[#bkSpellData.nestedData+1] = nestedData bkSpellData.bCanExpand = true else @@ -4635,7 +4635,7 @@ function damageClass:MontaInfoDamageDone() --I guess this fills the list of spel bCanExpand = false, spellTables = {spellTable}, - nestedData = {{spellId = spellId, spellTable = spellTable, petName = "", value = 0}}, + nestedData = {{spellId = spellId, spellTable = spellTable, actorName = "", value = 0}}, } detailsFramework:Mixin(bkSpellData, Details.SpellTableMixin) @@ -4646,33 +4646,46 @@ function damageClass:MontaInfoDamageDone() --I guess this fills the list of spel end --pets spells - local bShouldMergeSpellsWithThePet = false - local bShouldMergePetSpells = Details.breakdown_spell_tab.merge_pet_spells_with_same_name + local bShouldMergeSpellsWithThePet = Details.breakdown_spell_tab.nest_pet_spells_by_caster + local bShouldMergePetSpells = Details.breakdown_spell_tab.nest_pet_spells_by_name local actorPets = actorObject:GetPets() for _, petName in ipairs(actorPets) do ---@type actor local petActor = combatObject(DETAILS_ATTRIBUTE_DAMAGE, petName) if (petActor) then --PET - if (bShouldMergeSpellsWithThePet) then - --so, this is the bar with the pet name, it'll have a sum of all pet damage and other stuff to show in a bar + --get the amount of spells the pet used, if the pet used only one there`s no reason to nest one spell with the pet + local petSpellContainer = petActor:GetSpellContainer("spell") + + if (bShouldMergeSpellsWithThePet and petSpellContainer:HasTwoOrMoreSpells()) then ---@type spelltableadv local bkSpellData = { + bIsActorHeader = true, --tag this spelltable as an actor header, when the actor is the header it will nest the spells use by this actor + actorName = petName, id = 0, - spellschool = spellTable.spellschool, - bIsExpanded = Details222.BreakdownWindow.IsSpellExpanded(spellId), - bCanExpand = false, - - spellTables = {spellTable}, - nestedData = {{spellId = spellId, spellTable = spellTable, petName = petName, value = 0}}, + spellschool = 0, + bIsExpanded = Details222.BreakdownWindow.IsSpellExpanded(petName), + spellTables = {}, --populated below with the spells the pet used + nestedData = {}, --there's none data here in the main bar as the first bar is the pet name + bCanExpand = true, + actorIcon = [[Interface\AddOns\Details\images\pets\pet_icon_1]], } - detailsFramework:Mixin(bkSpellData, Details.SpellTableMixin) + + --output breakdownSpellDataList[#breakdownSpellDataList+1] = bkSpellData - alreadyAdded[spellName] = #breakdownSpellDataList - - + --fill here the spellTables using the actor abilities + --all these spells belong to the current actor in the loop + for spellId, spellTable in petSpellContainer:ListSpells() do + local spellName, _, spellIcon = GetSpellInfo(spellId) + if (spellName) then + bkSpellData.spellTables[#bkSpellData.spellTables+1] = spellTable + ---@type bknesteddata + local nestedData = {spellId = spellId, spellTable = spellTable, actorName = petName, value = 0, bIsActorHeader = true} --value to be defined + bkSpellData.nestedData[#bkSpellData.nestedData+1] = nestedData + end + end else local spells = petActor:GetSpellList() --all these spells belong to the current pet in the loop @@ -4694,7 +4707,7 @@ function damageClass:MontaInfoDamageDone() --I guess this fills the list of spel bkSpellData.spellTables[#bkSpellData.spellTables+1] = spellTable ---@type bknesteddata - local nestedData = {spellId = spellId, spellTable = spellTable, petName = petName, value = 0} + local nestedData = {spellId = spellId, spellTable = spellTable, actorName = petName, value = 0} bkSpellData.nestedData[#bkSpellData.nestedData+1] = nestedData bkSpellData.bCanExpand = true else --PET @@ -4706,7 +4719,7 @@ function damageClass:MontaInfoDamageDone() --I guess this fills the list of spel bCanExpand = false, spellTables = {spellTable}, - nestedData = {{spellId = spellId, spellTable = spellTable, petName = petName, value = 0}}, + nestedData = {{spellId = spellId, spellTable = spellTable, actorName = petName, value = 0}}, } detailsFramework:Mixin(bkSpellData, Details.SpellTableMixin) @@ -5368,23 +5381,23 @@ function damageClass:BuildSpellDetails(spellBar, spellBlockContainer, blockIndex blockLine1.leftText:SetText("Spell Empower Average Level: " .. string.format("%.2f", empowerLevelSum / empowerAmount)) if (level1AverageDamage ~= "0") then - blockLine2.leftText:SetText("Level 1 Avg: " .. level1AverageDamage .. " (" .. (empowerAmountPerLevel[1] or 0) .. ")") + blockLine2.leftText:SetText("#1 Avg: " .. level1AverageDamage .. " (" .. (empowerAmountPerLevel[1] or 0) .. ")") end if (level2AverageDamage ~= "0") then - blockLine2.centerText:SetText("Level 2 Avg: " .. level2AverageDamage .. " (" .. (empowerAmountPerLevel[2] or 0) .. ")") + blockLine2.centerText:SetText("#2 Avg: " .. level2AverageDamage .. " (" .. (empowerAmountPerLevel[2] or 0) .. ")") end if (level3AverageDamage ~= "0") then - blockLine2.rightText:SetText("Level 3 Avg: " .. level3AverageDamage .. " (" .. (empowerAmountPerLevel[3] or 0) .. ")") + blockLine2.rightText:SetText("#3 Avg: " .. level3AverageDamage .. " (" .. (empowerAmountPerLevel[3] or 0) .. ")") end if (level4AverageDamage ~= "0") then - blockLine3.leftText:SetText("Level 4 Avg: " .. level4AverageDamage .. " (" .. (empowerAmountPerLevel[4] or 0) .. ")") + blockLine3.leftText:SetText("#4 Avg: " .. level4AverageDamage .. " (" .. (empowerAmountPerLevel[4] or 0) .. ")") end if (level5AverageDamage ~= "0") then - blockLine3.rightText:SetText("Level 5 Avg: " .. level5AverageDamage .. " (" .. (empowerAmountPerLevel[5] or 0) .. ")") + blockLine3.rightText:SetText("#5 Avg: " .. level5AverageDamage .. " (" .. (empowerAmountPerLevel[5] or 0) .. ")") end end diff --git a/classes/container_spells.lua b/classes/container_spells.lua index 69acca63..dce4dbe8 100644 --- a/classes/container_spells.lua +++ b/classes/container_spells.lua @@ -71,6 +71,20 @@ local addonName, Details222 = ... return self:PegaHabilidade (id, shouldCreate, token) end + ---return (boolean) if the container two or more spells within + ---@return boolean + function container_habilidades:HasTwoOrMoreSpells() + local count = 0 + for _ in pairs(self._ActorTable) do + count = count + 1 + if (count >= 2) then + return true + end + end + return false + end + + function container_habilidades:PegaHabilidade (id, criar, token) local esta_habilidade = self._ActorTable [id] diff --git a/frames/window_playerbreakdown.lua b/frames/window_playerbreakdown.lua index edb89670..8de2a0f3 100644 --- a/frames/window_playerbreakdown.lua +++ b/frames/window_playerbreakdown.lua @@ -418,17 +418,17 @@ end Details222.BreakdownWindow.ExpandedSpells = {} ---set a spell as expanded or not in the breakdown window ----@param spellID number +---@param key any ---@param bIsExpanded boolean -function Details222.BreakdownWindow.SetSpellAsExpanded(spellID, bIsExpanded) - Details222.BreakdownWindow.ExpandedSpells[spellID] = bIsExpanded +function Details222.BreakdownWindow.SetSpellAsExpanded(key, bIsExpanded) + Details222.BreakdownWindow.ExpandedSpells[key] = bIsExpanded end ---get the state of the expanded for a spell ----@param spellID number +---@param key any ---@return boolean -function Details222.BreakdownWindow.IsSpellExpanded(spellID) - return Details222.BreakdownWindow.ExpandedSpells[spellID] +function Details222.BreakdownWindow.IsSpellExpanded(key) + return Details222.BreakdownWindow.ExpandedSpells[key] end ---receives spell data to show in the summary tab diff --git a/frames/window_playerbreakdown_spells.lua b/frames/window_playerbreakdown_spells.lua index 6bc930ba..b80551e5 100644 --- a/frames/window_playerbreakdown_spells.lua +++ b/frames/window_playerbreakdown_spells.lua @@ -2238,7 +2238,7 @@ local onClickExpandButton = function(expandButton, button) --todo: check is any other bar has expanded state true, and close the expand (or not) --toggle this spell expand mode - Details222.BreakdownWindow.SetSpellAsExpanded(expandButton.spellId, not bIsSpellExpaded) + Details222.BreakdownWindow.SetSpellAsExpanded(expandButton.petName or expandButton.spellId, not bIsSpellExpaded) --call the refresh function of the window ---@type instance @@ -2291,20 +2291,34 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll ---@type spelltable local spellTable - local petName = "" - spellBar.bkSpellData = bkSpellData - if (bIsMainLine) then + local petName = "" + ---@type boolean @if true, this is the main line of an actor which has its spells nested in the bkSpellData.nestedData + local bIsActorHeader = bkSpellData.bIsActorHeader + + if (bIsMainLine and bIsActorHeader) then + spellTable = bkSpellData + value = bkSpellData.total + spellId = 0 + petName = actorName + + elseif (bIsMainLine) then spellTable = bkSpellData value = bkSpellData.total spellId = bkSpellData.id - petName = bkSpellData.nestedData[spellTableIndex].petName + petName = bkSpellData.nestedData[spellTableIndex].actorName + else spellTable = bkSpellData.nestedData[spellTableIndex].spellTable value = spellTable.total spellId = spellTable.id - petName = bkSpellData.nestedData[spellTableIndex].petName + + --if isn't a spell from a nested actor, then it can use the pet name in the spell name + if (not bkSpellData.nestedData[spellTableIndex].bIsActorHeader) then + petName = bkSpellData.nestedData[spellTableIndex].actorName + end + spellBar.bIsExpandedSpell = true end @@ -2315,6 +2329,10 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll ---@type string, number, string local spellName, _, spellIcon = Details.GetSpellInfo(spellId) + if (not spellName) then + spellName = actorName + spellIcon = bkSpellData.actorIcon or "" + end ---@type number local amtCasts = combatObject:GetSpellCastAmount(actorName, spellName) @@ -2335,9 +2353,9 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll end if (petName ~= "") then - --if is a pet spell and has more pets nested + --if is a pet spell and has more pets nested || nop, now is a pet with its spells nested if (spellTablesAmount > 1 and bIsMainLine) then - spellName = formatPetName("", spellName, "") + spellName = formatPetName("", spellName, "") --causing error as spellName is nil elseif (bIsMainLine) then spellName = formatPetName(petName, spellName, actorName) else @@ -2361,7 +2379,7 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll local text = spellBar.InLineTexts[textIndex] local header = headerTable[headerIndex] - if (header.name == "icon") then --ok + if (header.name == "icon") then spellBar.spellIcon:Show() spellBar.spellIcon:SetTexture(spellIcon) spellBar.spellIcon:SetAlpha(0.92) @@ -2377,7 +2395,7 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll targetsSquareFrame.bIsMainLine = bIsMainLine spellBar:AddFrameToHeaderAlignment(targetsSquareFrame) - elseif (header.name == "rank") then --ok + elseif (header.name == "rank") then text:SetText(index) spellBar:AddFrameToHeaderAlignment(text) spellBar.rank = index @@ -2390,9 +2408,8 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll if (bkSpellData.bCanExpand and bIsMainLine) then spellBar.expandButton:Show() - local bIsSpellExpaded = Details222.BreakdownWindow.IsSpellExpanded(spellId) - - spellBar.expandButton.spellId = spellId + local bIsSpellExpaded = Details222.BreakdownWindow.IsSpellExpanded(bIsActorHeader and actorName or spellId) + spellBar.expandButton.spellId = bIsActorHeader and actorName or spellId spellBar.expandButton.bIsSpellExpaded = bIsSpellExpaded spellBar.expandButton:SetScript("OnClick", onClickExpandButton) @@ -2409,18 +2426,18 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll spellBar.expandButton.texture:SetSize(16, 16) end - elseif (header.name == "name") then --ok - text:SetText(spellName) + elseif (header.name == "name") then + text:SetText(Details:RemoveOwnerName(spellName)) spellBar.name = spellName spellBar:AddFrameToHeaderAlignment(text) textIndex = textIndex + 1 - elseif (header.name == "amount") then --ok + elseif (header.name == "amount") then text:SetText(Details:Format(value)) spellBar:AddFrameToHeaderAlignment(text) textIndex = textIndex + 1 - elseif (header.name == "persecond") then --ok + elseif (header.name == "persecond") then spellBar.perSecond = value / combatTime ---@type string @@ -2430,7 +2447,7 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll spellBar:AddFrameToHeaderAlignment(text) textIndex = textIndex + 1 - elseif (header.name == "percent") then --ok + elseif (header.name == "percent") then spellBar.percent = value / totalValue * 100 ---@type string local percentFormatted = string.format("%.1f", spellBar.percent) .. "%" @@ -2562,12 +2579,18 @@ local refreshSpellsFunc = function(scrollFrame, scrollData, offset, totalLines) if (mainSpellBar) then lineIndex = lineIndex + 1 local bIsMainLine = true - updateSpellBar(mainSpellBar, index, actorName, combatObject, scrollFrame, headerTable, bkSpellData, 1, totalValue, topValue, bIsMainLine, keyToSort, spellTablesAmount) + + if (bkSpellData.bIsActorHeader) then + updateSpellBar(mainSpellBar, index, bkSpellData.actorName, combatObject, scrollFrame, headerTable, bkSpellData, 1, totalValue, topValue, bIsMainLine, keyToSort, spellTablesAmount) + else + updateSpellBar(mainSpellBar, index, actorName, combatObject, scrollFrame, headerTable, bkSpellData, 1, totalValue, topValue, bIsMainLine, keyToSort, spellTablesAmount) + end end end + --if the spell is expanded --then it adds the lines for each spell merged, but it cannot use the bkSpellData, it needs the spellTable, it's kinda using bkSpellData, need to debug - if (bkSpellData.bIsExpanded and spellTablesAmount > 1) then + if (bkSpellData.bIsExpanded and (spellTablesAmount > 1)) then --filling necessary information to sort the data by the selected header column for spellTableIndex = 1, spellTablesAmount do ---@type bknesteddata @@ -2599,7 +2622,7 @@ local refreshSpellsFunc = function(scrollFrame, scrollData, offset, totalLines) lineIndex = lineIndex + 1 ---@type string - local petName = nestedBkSpellData.petName + local petName = nestedBkSpellData.actorName ---@type string local nameToUse = petName ~= "" and petName or actorName local bIsMainLine = false diff --git a/frames/window_playerbreakdown_spells_options.lua b/frames/window_playerbreakdown_spells_options.lua index 24811d34..de4eb440 100644 --- a/frames/window_playerbreakdown_spells_options.lua +++ b/frames/window_playerbreakdown_spells_options.lua @@ -201,25 +201,51 @@ local createOptionsPanel = function() }, {type = "blank"}, - {type = "label", get = function() return "Merge Options" end, text_template = subSectionTitleTextTemplate}, - { --merge player spells + {type = "label", get = function() return "Group Player Spells:" end, text_template = subSectionTitleTextTemplate}, + { --nest player spells | merge player spells type = "toggle", - get = function() return Details.breakdown_spell_tab.merge_players_spells_with_same_name end, + get = function() return Details.breakdown_spell_tab.nest_players_spells_with_same_name end, set = function(self, fixedparam, value) - Details.breakdown_spell_tab.merge_players_spells_with_same_name = value + Details.breakdown_spell_tab.nest_players_spells_with_same_name = value end, name = "Group Player Spells With Same Name", - desc = "Group Player Spells With Same Name", + desc = "Group spells casted by players which has the same name", }, - { --merge pet spells with the same name + {type = "blank"}, + {type = "label", get = function() return "Group Pet Spells:" end, text_template = subSectionTitleTextTemplate}, + + { --nest pet spells with the same name type = "toggle", - get = function() return Details.breakdown_spell_tab.merge_pet_spells_with_same_name end, + get = function() return Details.breakdown_spell_tab.nest_pet_spells_by_name end, set = function(self, fixedparam, value) - Details.breakdown_spell_tab.merge_pet_spells_with_same_name = value + Details.breakdown_spell_tab.nest_pet_spells_by_name = value end, - name = "Group Pets By Spell", + name = "Group Pet Names Under a Pet Spell Bar", + desc = "Group Pets By Name", + hooks = {["OnSwitch"] = function() + if (Details.breakdown_spell_tab.nest_pet_spells_by_name) then + Details.breakdown_spell_tab.nest_pet_spells_by_caster = false + DetailsSpellBreakdownOptionsPanel:RefreshOptions() + end + end} + }, + + { --nest pet spells with the same name + type = "toggle", + get = function() return Details.breakdown_spell_tab.nest_pet_spells_by_caster end, + set = function(self, fixedparam, value) + Details.breakdown_spell_tab.nest_pet_spells_by_caster = value + + end, + name = "Group Pet Spells Under a Pet Name Bar", desc = "Group Pets By Spell", + hooks = {["OnSwitch"] = function() + if (Details.breakdown_spell_tab.nest_pet_spells_by_caster) then + Details.breakdown_spell_tab.nest_pet_spells_by_name = false + DetailsSpellBreakdownOptionsPanel:RefreshOptions() + end + end} }, } diff --git a/functions/profiles.lua b/functions/profiles.lua index d9c80161..90155bf3 100644 --- a/functions/profiles.lua +++ b/functions/profiles.lua @@ -1411,8 +1411,12 @@ local default_global_data = { --/run Details.breakdown_spell_tab.statusbar_alpha = 0.823 --breakdown spell tab breakdown_spell_tab = { - merge_players_spells_with_same_name = true, - merge_pet_spells_with_same_name = true, + --player spells + nest_players_spells_with_same_name = true, + --pet spells + nest_pet_spells_by_name = false, + nest_pet_spells_by_caster = true, + blockcontainer_width = 430, blockcontainer_height = 270, blockcontainer_islocked = true, diff --git a/images/pets/pet_icon_1.tga b/images/pets/pet_icon_1.tga new file mode 100644 index 0000000000000000000000000000000000000000..900439adf86ed2f38ea864644af4cf1d134d3e6e GIT binary patch literal 16428 zcmc(H2U}Iww)Rp`z=jcfG`84{EwRL2uz(dcV53+-kRqs9wjv55O0l9M2nZrosvupY z7r{-3J>QsPY|*I6Ik|t}jp=X}R_M_m|6>MSY$Es_>V z!^dqNEUo@e4bwr=nm^myY_PPZSN4<4jFh>bWXf`&Vam^Skb}$xMF#(A zoFCi&1MITy+w{Jkb;&N#0Pu}}0?5xYx1ElXd;NH5#K-$iOuy5men!e=3GT_f7gY7II zex85VK?duX1B|ufo}aq@zmkcvKk4QVdRMTsPM7zSwkUY#_mOsd`WZ8DTopI}RmMRD8NI^7$6<)|Q*#*m>%@#Jc$zVP2*(U2x&K=M7;q|{$4$9ge z>YmTqhTW90GaklH!+c>M$zm?)xNlb}YOs!Ut!rlqG(=A=DSt#a>CEI_k_$hxnD86Y zZ&`lq7ugP2t}N#dhvFMQyx(@N-^FC*%>Pq4DC@NCp~Uy~jGbc{+iwT#<}3r&JMn%u zUo&o<&QfC=ehtknJ+-CWm7nWcC|u#^cnw~Q*JNMFSlJGg(Z)9=w`jlr5t;l!4*wGw zbi}Xlp3lcN&zxoM$~Z`mtarZtT3_1RzpE6+dY18PNzFQ1QVWeH`mE`$B^A#duIs=u zVmnb}#%nPL+H8Qaelmw*jo!rIj)jf)l-ib>-pYFGyJT}{8PQpBv!Bpa5QA~%HtEL)Z_@5A=MpP3iR z;LkkxJAS5P?Bp|cu#ex#;a}x}f?Jt+y*Ay;alRw(JLq2K^TeLgq0hQVNrb(QbS)-M zo*=H>3=zlO&tQkOwJ=_1tfB83^tGk?OTN&tCG738Q~Us}zlrg&Z(%+t`2^dCGKeee zFXa4zpKasJM{`L9hcbUC2j%|9<__4`eT?^Qx@R43pGW>~h^};z`Ary`dh6)5T)uiq zm~Au1Qlr%{G%^-Wt_L)q_cv(hL4I)DV9A4HB>h(A4$G2lfN{&V)mJj(&&!BooHH5H z;A`dyJ-c-UWpz}4d7q$TlUWeCXZ0&x9 z@GhblA@9@56P7c_7v4{Pp5?(ZVmUBZKH$PfYpaep`MM3OGRtv?aktsK9OpaOyzKA! zOdar>lh3o~ILEpt-Zy)Ddun_yT@gEW?t$w86 zcZ*sv$-a~EFvl4m-&gjTt}i+AwURgS^@qP>Ik4>5em?XI9k}p^xXK(?c>Y1WY0v9f z?|iN{?zLiX(+P3dY6`{kUfNQoqWimX(PEeN9^ve?A7{OMpmIBjJ$oIn)!b5ed--VC z2lnYA#dAKa;85ZK>yq`(pBMGlvS7@#cE-|vT5`_FK30)~oI{WgResAd;B)eMnG1?< z$Lj-+RKdfOWbhitu3XJuPC5( zLGwd;&fK7j6wN-CuiO1koBi@9U$T)H6n437Pa);Mhe>l7Bhl7g>r_cFeAK_Pdx{Ga= zb`6vZJ{Z+g^57hxBYxh`aFAr)w~G|Tv4YoSn_?O8Z+PZGa)|x1yL6J}zY z6fU&ML5?ZRi4ON5?(F=Zt?N`-4s#AgHu9LT z*U54v8R&KCENE~JDd&;=9p%@zSrB$e168k{AFD-K4tJ{9{gQ9UaH^Zc+)}G z%35u>w{y&Ue{jYM!+=@86GJH4D8)P z@uiu*Yvk`24)4C7IAq1=;WH{jIXBPD2PJm0Oq8L-v5wCw*UCBy?l#WL`5OBmu4yUH z4JX}~@L6=VJB#G3JP~js5Pny#!S7-qY>E3jb~?b;=?E;X?Xky+e87HJ;dRct$#BJr znn60!_4Qx&u~PgU@6Bf$+g);N!{0b^$RM$kV#n5&9ofC+n47zZNJtZB z{rpMy-Z<@j0Vdmaz<8ZGR&Usj)oV9n_nred>3&Lhd3!W_c%G{#{-0YuKzn;f{H&)j z-K7(ZZRDVVjr$y�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 B9QgnM literal 0 HcmV?d00001