You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
635 lines
20 KiB
635 lines
20 KiB
|
|
do
|
|
local Details = _G.Details
|
|
local _
|
|
local addonName, Details222 = ...
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local unpack = table.unpack or _G.unpack
|
|
local GetSpellInfo = Details222.GetSpellInfo
|
|
local UnitClass = UnitClass
|
|
local UnitGUID = UnitGUID
|
|
|
|
local CONST_UNKNOWN_CLASS_COORDS = {0.75, 1, 0.75, 1}
|
|
local CONST_DEFAULT_COLOR = {1, 1, 1, 1}
|
|
|
|
local roles = {
|
|
DAMAGER = {421/512, 466/512, 381/512, 427/512},
|
|
HEALER = {467/512, 512/512, 381/512, 427/512},
|
|
TANK = {373/512, 420/512, 381/512, 427/512},
|
|
NONE = {0, 50/512, 110/512, 150/512},
|
|
}
|
|
|
|
---return a table containing information about the texture to use for the actor icon
|
|
---@param actorObject actor
|
|
---@return texturetable
|
|
function Details:GetActorIcon(actorObject)
|
|
---@type instance
|
|
local instance = Details:GetInstance(1)
|
|
|
|
local spec = actorObject:Spec()
|
|
if (spec and spec > 0) then
|
|
---@type string
|
|
local fileName
|
|
|
|
--get the spec icon file currently in use
|
|
if (instance) then
|
|
fileName = instance.row_info.spec_file
|
|
else
|
|
fileName = Details.instance_defaults.row_info.spec_file
|
|
end
|
|
|
|
local left, right, top, bottom = unpack(Details.class_specs_coords[spec])
|
|
|
|
local textureTable = {
|
|
texture = fileName,
|
|
coords = {left = left, right = right, top = top, bottom = bottom},
|
|
size = {height = 16, width = 16},
|
|
}
|
|
|
|
return textureTable
|
|
end
|
|
|
|
local class = actorObject:Class() or "UNKNOW"
|
|
local left, right, top, bottom = unpack(Details.class_coords[class])
|
|
|
|
---@type string
|
|
local fileName
|
|
--get the spec icon file currently in use
|
|
if (instance) then
|
|
fileName = instance.row_info.icon_file
|
|
else
|
|
fileName = Details.instance_defaults.row_info.icon_file
|
|
end
|
|
|
|
local textureTable = {
|
|
texture = fileName,
|
|
coords = {left = left, right = right, top = top, bottom = bottom},
|
|
size = {height = 16, width = 16},
|
|
}
|
|
|
|
return textureTable
|
|
end
|
|
|
|
---return the path to a texture file and the texture coordinates
|
|
---@return string, number, number, number, number
|
|
function Details:GetUnknownClassIcon()
|
|
return [[Interface\AddOns\Details\images\classes_small]], unpack(CONST_UNKNOWN_CLASS_COORDS)
|
|
end
|
|
|
|
---return a path to a texture file
|
|
---@param iconType "spec"|"class"
|
|
---@param bWithAlpha boolean
|
|
---@return string texturePath
|
|
function Details:GetIconTexture(iconType, bWithAlpha)
|
|
iconType = string.lower(iconType)
|
|
|
|
if (iconType == "spec") then
|
|
if (bWithAlpha) then
|
|
return [[Interface\AddOns\Details\images\spec_icons_normal_alpha]]
|
|
else
|
|
return [[Interface\AddOns\Details\images\spec_icons_normal]]
|
|
end
|
|
else --if is class
|
|
if (bWithAlpha) then
|
|
return [[Interface\AddOns\Details\images\classes_small_alpha]]
|
|
else
|
|
return [[Interface\AddOns\Details\images\classes_small]]
|
|
end
|
|
end
|
|
end
|
|
|
|
---attempt to get the class of an actor by its name, if the actor isn't found, it searches the overall data for the actor
|
|
---@param actorName string
|
|
---@return string className, number left, number right, number top, number bottom, number red, number green, number blue, number alpha
|
|
function Details:GetClass(actorName)
|
|
local unitClass = Details:GetUnitClass(actorName)
|
|
|
|
if (unitClass) then
|
|
local left, right, top, bottom = unpack(Details.class_coords[unitClass])
|
|
local r, g, b = unpack(Details.class_colors[unitClass])
|
|
return unitClass, left, right, top, bottom, r or 1, g or 1, b or 1, 1
|
|
else
|
|
local overallCombatObject = Details:GetCombat(DETAILS_SEGMENTID_OVERALL)
|
|
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
|
|
local actorContainer = overallCombatObject:GetContainer(containerId)
|
|
local actorObject = actorContainer:GetActor(actorName)
|
|
|
|
if (actorObject) then
|
|
unitClass = actorObject:Class()
|
|
if (unitClass) then
|
|
--found the class of the actor
|
|
local left, right, top, bottom = unpack(Details.class_coords[unitClass] or CONST_UNKNOWN_CLASS_COORDS)
|
|
local r, g, b = unpack(Details.class_colors[unitClass])
|
|
return unitClass, left, right, top, bottom, r or 1, g or 1, b or 1, 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return "UNKNOW", 0.75, 1, 0.75, 1, 1, 1, 1, 1
|
|
end
|
|
end
|
|
|
|
|
|
--note: this could return the coords and color as well to match Details:GetClass()
|
|
---attempt to get the spec of an actor by its name, if the actor isn't found, it searches the overall data for the actor
|
|
---@param actorName string
|
|
---@return number|nil
|
|
function Details:GetSpecFromActorName(actorName)
|
|
local GUID = UnitGUID(actorName)
|
|
local spec = Details:GetSpecByGUID(GUID)
|
|
|
|
if (spec) then
|
|
return spec
|
|
end
|
|
|
|
local overallCombatObject = Details:GetCombat(DETAILS_SEGMENTID_OVERALL)
|
|
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
|
|
local actorContainer = overallCombatObject:GetContainer(containerId)
|
|
local actorObject = actorContainer:GetActor(actorName)
|
|
|
|
if (actorObject) then
|
|
spec = actorObject:Spec()
|
|
if (spec) then
|
|
return spec
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---return the path to a texture file and the texture coordinates
|
|
---@param role string
|
|
---@return string texturePath, number left, number right, number top, number bottom
|
|
function Details:GetRoleIcon(role)
|
|
return [[Interface\AddOns\Details\images\icons2]], unpack(roles[role])
|
|
end
|
|
|
|
---return the path to a texture file and the texture coordinates for the given class
|
|
---@param class string
|
|
---@return string texturePath, number left, number right, number top, number bottom
|
|
function Details:GetClassIcon(class)
|
|
if (self.classe) then
|
|
class = self.classe
|
|
|
|
elseif (type(class) == "table" and class.classe) then
|
|
class = class.classe
|
|
|
|
elseif (type(class) == "string") then
|
|
class = class
|
|
|
|
else
|
|
class = "UNKNOW"
|
|
end
|
|
|
|
if (class == "UNKNOW") then
|
|
return [[Interface\LFGFRAME\LFGROLE_BW]], 0.25, 0.5, 0, 1
|
|
|
|
elseif (class == "UNGROUPPLAYER") then
|
|
return [[Interface\ICONS\Achievement_Character_Orc_Male]], 0, 1, 0, 1
|
|
|
|
elseif (class == "PET") then
|
|
return [[Interface\AddOns\Details\images\classes_small]], 0.25, 0.49609375, 0.75, 1
|
|
|
|
else
|
|
return [[Interface\AddOns\Details\images\classes_small]], unpack(Details.class_coords[class])
|
|
end
|
|
end
|
|
|
|
---return the path to a texture file and the texture coordinates for the given spec
|
|
---@param spec number
|
|
---@param useAlpha boolean
|
|
---@return string texturePath, number left, number right, number top, number bottom
|
|
function Details:GetSpecIcon(spec, useAlpha)
|
|
if (not spec or spec == 0) then
|
|
--this returns the icon for "unknown" spec (gotten from the class icon file)
|
|
return [[Interface\AddOns\Details\images\classes_small]], unpack(Details.class_coords["UNKNOW"])
|
|
end
|
|
|
|
if (useAlpha) then
|
|
return [[Interface\AddOns\Details\images\spec_icons_normal_alpha]], unpack(Details.class_specs_coords [spec])
|
|
else
|
|
return [[Interface\AddOns\Details\images\spec_icons_normal]], unpack(Details.class_specs_coords[spec])
|
|
end
|
|
end
|
|
|
|
---return the red, green, blue and alpha values for the given class
|
|
---@param class string
|
|
---@return number red, number green, number blue, number alpha
|
|
function Details:GetClassColor(class)
|
|
if (self.classe) then
|
|
return unpack(Details.class_colors[self.classe] or CONST_DEFAULT_COLOR)
|
|
|
|
elseif (type(class) == "table" and class.classe) then
|
|
return unpack(Details.class_colors[class.classe] or CONST_DEFAULT_COLOR)
|
|
|
|
elseif (type(class) == "string") then
|
|
return unpack(Details.class_colors[class] or CONST_DEFAULT_COLOR)
|
|
|
|
elseif (self.color) then
|
|
return unpack(self.color)
|
|
else
|
|
return unpack(CONST_DEFAULT_COLOR)
|
|
end
|
|
end
|
|
|
|
---get the spec or class texture and coordinates for the given player name and combat object, if the actor isn't found return unknown icon
|
|
---@param playerName string
|
|
---@param combatObject combat
|
|
---@return string texturePath, number left, number right, number top, number bottom
|
|
function Details:GetPlayerIcon(playerName, combatObject)
|
|
combatObject = combatObject or Details:GetCurrentCombat()
|
|
|
|
local texturePath, left, right, top, bottom
|
|
|
|
---@type actor
|
|
local playerObject = combatObject:GetActor(DETAILS_ATTRIBUTE_DAMAGE, playerName)
|
|
if (not playerObject or not playerObject.spec) then
|
|
---@type actor
|
|
playerObject = combatObject:GetActor(DETAILS_ATTRIBUTE_HEAL, playerName)
|
|
end
|
|
|
|
if (playerObject) then
|
|
local spec = playerObject.spec
|
|
if (spec) then
|
|
texturePath = [[Interface\AddOns\Details\images\spec_icons_normal]]
|
|
left, right, top, bottom = unpack(Details.class_specs_coords[spec])
|
|
|
|
elseif (playerObject.classe) then
|
|
texturePath = [[Interface\AddOns\Details\images\classes_small]]
|
|
left, right, top, bottom = unpack(Details.class_coords[playerObject.classe or "UNKNOW"])
|
|
end
|
|
end
|
|
|
|
if (not texturePath) then
|
|
texturePath = [[Interface\AddOns\Details\images\classes_small]]
|
|
left, right, top, bottom = unpack(Details.class_coords["UNKNOW"])
|
|
end
|
|
|
|
return texturePath, left, right, top, bottom
|
|
end
|
|
|
|
---return specId if it exists in the spec cache
|
|
---@param unitSerial string this is also called GUID
|
|
---@return number|nil
|
|
function Details:GetSpecByGUID(unitSerial)
|
|
return Details.cached_specs[unitSerial]
|
|
end
|
|
|
|
local specNamesToId = {}
|
|
function Details:BuildSpecsNameCache()
|
|
if (DetailsFramework.IsDragonflightAndBeyond()) then
|
|
---@type table<class, table<specializationid, boolean>>
|
|
local classSpecList = DetailsFramework.ClassSpecs
|
|
---@number
|
|
local numClasses = GetNumClasses()
|
|
|
|
for i = 1, numClasses do
|
|
local classInfo = C_CreatureInfo.GetClassInfo(i)
|
|
local localizedClassName = classInfo.className
|
|
local classTag = classInfo.classFile
|
|
|
|
local specIdsList = classSpecList[classTag]
|
|
if (specIdsList) then
|
|
for specId in pairs(specIdsList) do
|
|
local specId2, specName = GetSpecializationInfoByID(specId)
|
|
if (specId2 and specName) then
|
|
specNamesToId[specName .. " " .. localizedClassName] = specId2
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
---comment
|
|
---@param payload table
|
|
---@return any
|
|
function Details:GuessClass(payload)
|
|
---@type actor, actorcontainer, number
|
|
local actorObject, actorContainer, attempts = payload[1], payload[2], payload[3]
|
|
|
|
if (not actorObject or actorObject.__destroyed) then
|
|
return false
|
|
end
|
|
|
|
local spellContainerNames = actorObject:GetSpellContainerNames() --1x Details/functions/playerclass.lua:293: attempt to call method 'GetSpellContainerNames' (a nil value)
|
|
for i = 1, #spellContainerNames do
|
|
local spellContainer = actorObject:GetSpellContainer(spellContainerNames[i])
|
|
if (spellContainer) then
|
|
for spellId in spellContainer:ListSpells() do
|
|
local class = Details.ClassSpellList[spellId]
|
|
if (class) then
|
|
actorObject.classe = class
|
|
actorObject.guessing_class = nil
|
|
|
|
if (actorContainer) then
|
|
actorContainer.need_refresh = true
|
|
end
|
|
|
|
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
|
|
actorObject.minha_barra.minha_tabela = nil
|
|
Details:ScheduleWindowUpdate(2, true)
|
|
end
|
|
|
|
return class
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local class = Details:GetClass(actorObject:Name())
|
|
if (class and class ~= "UNKNOW") then
|
|
actorObject.classe = class
|
|
actorObject.need_refresh = true
|
|
actorObject.guessing_class = nil
|
|
|
|
if (actorContainer) then
|
|
actorContainer.need_refresh = true
|
|
end
|
|
|
|
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
|
|
actorObject.minha_barra.minha_tabela = nil
|
|
Details:ScheduleWindowUpdate(2, true)
|
|
end
|
|
|
|
return class
|
|
end
|
|
|
|
if (attempts and attempts < 10) then
|
|
payload[3] = attempts + 1 --thanks @Farmbuyer on curseforge
|
|
--_detalhes:ScheduleTimer("GuessClass", 2, {Actor, container, tries+1})
|
|
Details:ScheduleTimer("GuessClass", 2, payload) --passing the same table instead of creating a new one
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
---comment
|
|
---@param payload table
|
|
---@return any
|
|
function Details:GuessSpec(payload)
|
|
---@type actor, actorcontainer, number
|
|
local actorObject, actorContainer, attempts = payload[1], payload[2], payload[3]
|
|
if (not actorObject or actorObject.__destroyed) then
|
|
return false
|
|
end
|
|
|
|
local actorSpec
|
|
|
|
--attempt the obvious
|
|
if (actorObject.spec) then
|
|
actorSpec = actorObject.spec
|
|
end
|
|
|
|
local specSpellList = Details.SpecSpellList
|
|
|
|
--attempt to get from OpenRaid
|
|
if (not actorSpec) then
|
|
local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0", true)
|
|
if (openRaidLib) then
|
|
local unitInfo = openRaidLib.GetUnitInfo(actorObject:Name()) --1x Details/functions/playerclass.lua:368: attempt to call method 'Name' (a nil value)
|
|
if (unitInfo and unitInfo.specId and unitInfo.specId ~= 0) then
|
|
actorSpec = unitInfo.specId
|
|
end
|
|
end
|
|
end
|
|
|
|
--attempt to get from the spec cache
|
|
if (not actorSpec) then
|
|
actorSpec = Details.cached_specs[actorObject.serial]
|
|
end
|
|
|
|
--attempt to get spec from tooltip
|
|
if (not actorSpec and DetailsFramework:IsDragonflightAndBeyond()) then
|
|
local tooltipData = C_TooltipInfo.GetHyperlink("unit:" .. actorObject.serial)
|
|
if (tooltipData and tooltipData.lines) then
|
|
for i = 1, #tooltipData.lines do
|
|
local thisLineData = tooltipData.lines[i]
|
|
local text = thisLineData.leftText
|
|
if (text and thisLineData.type == 0) then
|
|
local specId = specNamesToId[text]
|
|
if (specId and type(specId) == "number") then
|
|
actorSpec = specId
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--attempt to get from the spells the actor used in the current combat
|
|
if (not actorSpec) then
|
|
local currentCombatObject = Details:GetCurrentCombat()
|
|
|
|
if (currentCombatObject.__destroyed) then
|
|
--schedule made before a destroy combat call, but not cancelled
|
|
return
|
|
end
|
|
|
|
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
|
|
if (actorSpec) then
|
|
break
|
|
end
|
|
|
|
---@type actorcontainer
|
|
local currentActorContainer = currentCombatObject:GetContainer(containerId)
|
|
---@type actor
|
|
local currentActorObject = currentActorContainer:GetActor(actorObject:Name())
|
|
|
|
if (currentActorObject) then
|
|
--iterate among all spells the actor used
|
|
if (not actorSpec) then
|
|
local spellContainerNames = currentActorObject:GetSpellContainerNames()
|
|
for i = 1, #spellContainerNames do
|
|
local spellContainer = currentActorObject:GetSpellContainer(spellContainerNames[i])
|
|
if (spellContainer) then
|
|
for spellId in spellContainer:ListSpells() do
|
|
local spec = specSpellList[spellId]
|
|
if (spec) then
|
|
actorSpec = spec
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--attempt to get from overall combat object
|
|
if (not actorSpec) then
|
|
local overallCombatObject = Details:GetCombat(DETAILS_SEGMENTID_OVERALL)
|
|
for containerId = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do
|
|
if (actorSpec) then
|
|
break
|
|
end
|
|
|
|
local overallActorContainer = overallCombatObject:GetContainer(containerId)
|
|
local overallActorObject = overallActorContainer:GetActor(actorObject:Name())
|
|
if (overallActorObject) then
|
|
if (overallActorObject.spec and overallActorObject.spec ~= 0) then
|
|
actorSpec = overallActorObject.spec
|
|
break
|
|
end
|
|
|
|
--iterate among all spells the actor used
|
|
if (not actorSpec) then
|
|
local spellContainerNames = overallActorObject:GetSpellContainerNames()
|
|
for i = 1, #spellContainerNames do
|
|
local spellContainer = overallActorObject:GetSpellContainer(spellContainerNames[i])
|
|
if (spellContainer) then
|
|
for spellId in spellContainer:ListSpells() do
|
|
local spec = specSpellList[spellId]
|
|
if (spec) then
|
|
actorSpec = spec
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (actorSpec) then
|
|
Details.cached_specs[actorObject.serial] = actorSpec
|
|
actorObject:SetSpecId(actorSpec)
|
|
actorObject.classe = Details.SpecIDToClass[actorSpec] or actorObject.classe
|
|
actorObject.guessing_spec = nil
|
|
|
|
if (actorContainer) then
|
|
actorContainer.need_refresh = true
|
|
end
|
|
|
|
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
|
|
actorObject.minha_barra.minha_tabela = nil
|
|
Details:ScheduleWindowUpdate(2, true)
|
|
end
|
|
|
|
return actorSpec
|
|
end
|
|
|
|
if (Details.streamer_config.quick_detection) then
|
|
if (attempts and attempts < 30) then
|
|
payload[3] = attempts + 1
|
|
Details:ScheduleTimer("GuessSpec", 1, payload) --todo: replace schedule from ace3 and use our own
|
|
end
|
|
else
|
|
if (attempts and attempts < 4) then
|
|
payload[3] = attempts + 1
|
|
Details:ScheduleTimer("GuessSpec", 4, payload)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function Details:ReGuessSpec(t) --deprecated
|
|
local actorObject, container = t[1], t[2]
|
|
local SpecSpellList = Details.SpecSpellList
|
|
|
|
---@type combat
|
|
local combatObject = Details:GetCurrentCombat()
|
|
|
|
--get from the spell cast list
|
|
if (combatObject) then
|
|
local spellCastTable = combatObject:GetSpellCastTable(actorObject.nome)
|
|
|
|
for spellName in pairs(spellCastTable) do
|
|
local _, _, _, _, _, _, spellid = GetSpellInfo(spellName)
|
|
local spec = SpecSpellList[spellid]
|
|
if (spec) then
|
|
Details.cached_specs[actorObject.serial] = spec
|
|
|
|
actorObject:SetSpecId(spec)
|
|
actorObject.classe = Details.SpecIDToClass[spec] or actorObject.classe
|
|
actorObject.guessing_spec = nil
|
|
|
|
Details:SendEvent("UNIT_SPEC", nil, actorObject:GetUnitId(), spec, actorObject.serial)
|
|
|
|
if (container) then
|
|
container.need_refresh = true
|
|
end
|
|
|
|
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
|
|
actorObject.minha_barra.minha_tabela = nil
|
|
Details:ScheduleWindowUpdate (2, true)
|
|
end
|
|
|
|
return spec
|
|
end
|
|
end
|
|
|
|
else
|
|
if (actorObject.spells) then
|
|
for spellid, _ in pairs(actorObject.spells._ActorTable) do
|
|
local spec = SpecSpellList [spellid]
|
|
if (spec) then
|
|
if (spec ~= actorObject.spec) then
|
|
Details.cached_specs [actorObject.serial] = spec
|
|
|
|
actorObject:SetSpecId(spec)
|
|
actorObject.classe = Details.SpecIDToClass [spec] or actorObject.classe
|
|
|
|
Details:SendEvent("UNIT_SPEC", nil, actorObject:GetUnitId(), spec, actorObject.serial)
|
|
|
|
if (container) then
|
|
container.need_refresh = true
|
|
end
|
|
|
|
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
|
|
actorObject.minha_barra.minha_tabela = nil
|
|
Details:ScheduleWindowUpdate (2, true)
|
|
end
|
|
|
|
return spec
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if (actorObject.classe == "HUNTER") then
|
|
local container_misc = Details.tabela_vigente[4]
|
|
local index = container_misc._NameIndexTable [actorObject.nome]
|
|
if (index) then
|
|
local misc_actor = container_misc._ActorTable [index]
|
|
local buffs = misc_actor.buff_uptime_spells and misc_actor.buff_uptime_spells._ActorTable
|
|
if (buffs) then
|
|
for spellid, spell in pairs(buffs) do
|
|
local spec = SpecSpellList [spellid]
|
|
if (spec) then
|
|
if (spec ~= actorObject.spec) then
|
|
Details.cached_specs [actorObject.serial] = spec
|
|
|
|
actorObject:SetSpecId(spec)
|
|
actorObject.classe = Details.SpecIDToClass [spec] or actorObject.classe
|
|
|
|
Details:SendEvent("UNIT_SPEC", nil, actorObject:GetUnitId(), spec, actorObject.serial)
|
|
|
|
if (container) then
|
|
container.need_refresh = true
|
|
end
|
|
|
|
if (actorObject.minha_barra and type(actorObject.minha_barra) == "table") then
|
|
actorObject.minha_barra.minha_tabela = nil
|
|
Details:ScheduleWindowUpdate (2, true)
|
|
end
|
|
|
|
return spec
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|