Fixed Pet Health Addon by Goobsnake ... more than 2 pets, all classes ... Files Attached

Uazole2
Uazole2
✭✭✭
Since subclassing went live, every class can potentially have pets. The addon Pet Health is useful when you have pets, but in its present form it will only load for wardens or sorcerers, lets fix this.

The Pet Health addon by Scootworks/Goobsnake is easily modified to include the other classes, beyond warden and sorcerer. Open the PetHealth.lua file, found in the addon directory. At the very top of the file is a section that identifies the classes that the addon should load for. The original file only includes classes 2 & 4, the sorcerer and warden. Edit the file to include the other classes:

PetHealth.supportedClasses = {
[1] = true, -- Dragonknight
[2] = true, -- Sorcerer
[3] = true, -- Nightblade
[4] = true, -- Warden
[5] = true, -- Necromancer
[6] = true, -- Templar
[117] = true, -- Archanist
}

However, while this minor edit will allow all classes to have pets, you will still be limited to displaying health bars for only 2 pets. If you want to use 3 or more pets, there is a replacement PetHealth.lua file located below. It fixes both issues.
Edited by Uazole2 on July 1, 2025 12:16PM
I ALWAYS believe I know the answer. Turns out I rarely understand the question.

Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • MafiaCat115
    MafiaCat115
    ✭✭✭✭✭
    Glad to see someone is still thinking about this addon. I've also noticed that the tracking stops at two health bars, like if you have the Sorc pets and the warden bear. Is there any fix to this?
    Still waiting for answers about class set aura behavior, and also hoping that one day hair dyeing will be an option. We know it's canon thanks to a book in the Manor of Masques! (House of Reveries: The Troupe)
    Proud owner of a Morrowind Banner of the 6th House (back when it actually meant something)
  • Uazole2
    Uazole2
    ✭✭✭
    All of the DELETED stuff was bad code, that did not work properly. Corrected code that does work is found later in this thread. Very simple procedure....Copy code then paste and replace PetHealth.lua in the addon's folder with it.
    Edited by Uazole2 on June 15, 2025 1:10PM
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • MafiaCat115
    MafiaCat115
    ✭✭✭✭✭
    f1tnsitueao0.png
    Weird. Here's the message that pops up sometimes when using three summons at once, along with the health bars to the right of the compass. I've really got to try take up working on and making addons so I could understand what's going on behind the scenes.

    "UI Error: 21EFDA50

    user:AddOns/PetHealth/PetHealth.lua:401: attempt to index a nil value stack traceback:
    user:AddOns/PetHealth/PetHealth.lua:401: in function 'OnHealthUpdate'"
    Still waiting for answers about class set aura behavior, and also hoping that one day hair dyeing will be an option. We know it's canon thanks to a book in the Manor of Masques! (House of Reveries: The Troupe)
    Proud owner of a Morrowind Banner of the 6th House (back when it actually meant something)
  • Uazole2
    Uazole2
    ✭✭✭
    Further digging shows that there are only a petOne and a petTwo defined in the code. This causes the error that you are seeing. This code starts around line 385, find and substitute the below code and see if it works with 3 pets. Remember that I am not a proficient coder. This could be wrong.

    DELETED CODE
    Edited by Uazole2 on June 15, 2025 2:29AM
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • ZaiZah
    ZaiZah
    ✭✭✭
    Arcanist class ID is 117
    So it would be
    PetHealth.supportedClasses = {
    [1] = true, -- Dragonknight
    [2] = true, -- Sorcerer
    [3] = true, -- Nightblade
    [4] = true, -- Warden
    [5] = true, -- Necromancer
    [6] = true, -- Templar
    [117] = true, -- Arcanist 
    }
    

    That should hopefully work then for the classes atleast
    Player since BetaPC - EU

    ArcthulhuArcanist, DD
    ZaizahDragonknight, DD
    Hermora the ExplorerArcanist, Healer
    Step Vrol Im StuckDragonknight, Tank

    Addon Creator
    House & Wayshrine
    Coral Riptide Tracker
    Zai's Dungeon Finder Tools
    Leaderboard 2 Chat - Also for Console!
    Previously maintained: No, Thank You! and other personal/unfinished projects
  • Uazole2
    Uazole2
    ✭✭✭
    Many thanks to @ZaiZah for providing the correct classID for Archanist. If anyone has a 3 pet setup and can modify the LUA, let me know if the code above fixed the 3 pet issue
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • MafiaCat115
    MafiaCat115
    ✭✭✭✭✭
    Uazole2 wrote: »
    Further digging shows that there are only a petOne and a petTwo defined in the code. This causes the error that you are seeing. This code starts around line 385, find and substitute the below code and see if it works with 3 pets. Remember that I am not a proficient coder. This could be wrong.


    else
    local name = currentPets.unitName
    local petOne = currentPets[1].unitName
    local petTwo = currentPets[2].unitName
    local petThree = currentPets[3].unitName
    if lowHealthAlertPercentage > 1 and powerValue ~= 0 and powerValue < (powerMax*.01*lowHealthAlertPercentage) then
    if name == petOne and onScreenHealthAlertPetOne == 0 then
    OnScreenMessage(zo_strformat("|cff0000<<1>> <<2>>|r", petOne, GetString(SI_PET_HEALTH_LOW_HEALTH_WARNING_MSG)))
    onScreenHealthAlertPetOne = 1
    elseif name == petTwo and onScreenHealthAlertPetTwo == 0 then
    OnScreenMessage(zo_strformat("|cff0000<<1>> <<2>>|r", petTwo, GetString(SI_PET_HEALTH_LOW_HEALTH_WARNING_MSG)))
    onScreenHealthAlertPetTwo = 1
    elseif name == petThree and onScreenHealthAlertPetThree == 0 then
    OnScreenMessage(zo_strformat("|cff0000<<1>> <<2>>|r", petThree, GetString(SI_PET_HEALTH_LOW_HEALTH_WARNING_MSG)))
    onScreenHealthAlertPetThree = 1
    end
    else
    if name == petOne then
    onScreenHealthAlertPetOne = 0
    elseif name == petTwo then
    onScreenHealthAlertPetTwo = 0
    elseif name == petThree then
    onScreenHealthAlertPetThree = 0
    end
    end
    end
    Tried this and it gave a new error message
    7uufq9gq01bd.png
    Still waiting for answers about class set aura behavior, and also hoping that one day hair dyeing will be an option. We know it's canon thanks to a book in the Manor of Masques! (House of Reveries: The Troupe)
    Proud owner of a Morrowind Banner of the 6th House (back when it actually meant something)
  • MafiaCat115
    MafiaCat115
    ✭✭✭✭✭
    Quote from #11:
    Uazole2 wrote: »
    --this might work: Use CTRL-A to copy everything. Open Notepad and use CTRL-V to past the entire text. Save as
    --PetHealth.lua, in the pet health addon folder. Maybe rename the original first.


    PetHealth = PetHealth or {}
    --The supported classes for this addon (ClassId from function GetUnitClassId("player"))
    PetHealth.supportedClasses = {
    [1] = true, -- Dragonknight
    [2] = true, -- Sorcerer
    [3] = true, -- Nightblade
    [4] = true, -- Warden
    [5] = true, -- Necromancer
    [6] = true, -- Templar
    [117] = true, -- Archanist
    }
    local addon = {
    name = "PetHealth",
    displayName = "PetHealth",
    version = "1.12",
    savedVarName = "PetHealth_Save",
    savedVarVersion = 2,
    lamDisplayName = "PetHealth",
    lamAuthor = "Scootworks, Goobsnake",
    lamUrl = "https://www.esoui.com/downloads/info1884-PetHealth.html",
    }
    PetHealth.addonData = addon

    local default = {
    saveMode = 1, -- Default for each character setting
    point = TOPLEFT,
    relPoint = CENTER,
    x = 0,
    y = 0,
    onlyInCombat = false,
    showValues = true,
    showLabels = true,
    hideInDungeon = false,
    lockWindow = false,
    lowHealthAlertSlider = 0,
    lowShieldAlertSlider = 0,
    petUnsummonedAlerts = false,
    onlyInCombatHealthSlider = 0,
    showBackground = true,
    useZosStyle = false,
    debug = false,
    }

    local UNIT_PLAYER_PET = "playerpet"
    local UNIT_PLAYER_TAG = "player"

    local base, background, savedVars
    local currentPets = {}
    local PetHealthWarner
    local window = {}
    local inCombatAddon = false

    local AddOnManager = GetAddOnManager()
    local hideInDungeon = false
    local LAM
    local LSC
    local lockWindow = false
    local lowHealthAlertPercentage = 0
    local lowShieldAlertPercentage = 0
    local onlyInCombatHealthMax = 0
    local onlyInCombatHealthCurrent = 0
    local onlyInCombatHealthPercentage = 0
    local onScreenHealthAlerts = {} -- Dynamic table for health alerts
    local onScreenShieldAlerts = {} -- Dynamic table for shield alerts
    local unsummonedAlerts = false

    local WINDOW_MANAGER = GetWindowManager()
    local WINDOW_WIDTH = 250
    local WINDOW_HEIGHT_PER_PET = 40 -- Height per pet bar
    local WINDOW_HEIGHT_BASE = 36 -- Base height for no pets
    local PET_BAR_FRAGMENT

    -- UTIL --
    local function OnScreenMessage(message)
    local messageParams = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_CATEGORY_LARGE_TEXT)
    messageParams:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_COUNTDOWN)
    messageParams:SetText(message)
    CENTER_SCREEN_ANNOUNCE:AddMessageWithParams(messageParams)
    end

    local function ChatOutput(message)
    CHAT_SYSTEM:AddMessage(message)
    end

    local function CheckAddon(addonName)
    for i = 1, AddOnManager:GetNumAddOns() do
    local name, title, author, description, enabled, state, isOutOfDate = AddOnManager:GetAddOnInfo(i)
    if title == addonName and enabled == true and state == 2 then
    return true
    end
    end
    end

    local function GetPetNameLower(abilityId)
    local abilityName = GetAbilityName(abilityId)
    local petName
    if abilityName:match('Summon ') then
    if abilityName:match('Summon Unstable ') then
    petName = abilityName:gsub("Summon Unstable ","")
    else
    petName = abilityName:gsub("Summon ","")
    end
    else
    petName = abilityName
    end
    return zo_strformat("<<z:1>>", petName)
    end

    local validPets = {
    -- Familiar
    [GetPetNameLower(23304)] = true,
    ["begleiter"] = true, -- de
    ["familier"] = true, -- fr
    ["призванный слуга"] = true, -- ru
    -- Clannfear
    [GetPetNameLower(23319)] = true,
    ["clannbann"] = true, -- de
    ["faucheclan"] = true, -- fr
    ["кланфир"] = true, -- ru
    -- Volatile Familiar
    [GetPetNameLower(23316)] = true,
    ["explosiver begleiter"] = true, -- de
    ["familier explosif"] = true, -- fr
    ["взрывной призванный слуга"] = true, -- ru
    -- Winged Twilight
    [GetPetNameLower(24613)] = true,
    ["zwielichtschwinge"] = true, -- de
    ["crépuscule ailé"] = true, -- fr
    ["крылатый сумрак"] = true, -- ru
    -- Twilight Tormentor
    [GetPetNameLower(24636)] = true,
    ["zwielichtpeinigerin"] = true, -- de
    ["tourmenteur crépusculaire"] = true, -- fr
    ["сумрак-мучитель"] = true, -- ru
    -- Twilight Matriarch
    [GetPetNameLower(24639)] = true,
    ["zwielichtmatriarchin"] = true, -- de
    ["matriarche crépusculaire"] = true, -- fr
    ["сумрак-матриарх"] = true, -- ru
    -- Warden Pets
    [GetPetNameLower(85982)] = true,
    ["хищный страж"] = true, -- ru
    [GetPetNameLower(85986)] = true,
    ["вечный страж"] = true, -- ru
    [GetPetNameLower(85990)] = true,
    ["дикий защитник"] = true, -- ru
    }

    local function IsUnitValidPet(unitTag)
    local unitName = zo_strformat("<<z:1>>", GetUnitName(unitTag))
    return DoesUnitExist(unitTag) and validPets[unitName]
    end

    local function GetKeyWithData(unitTag)
    for k, v in pairs(currentPets) do
    if v.unitTag == unitTag then return k end
    end
    return nil
    end

    local function GetAlphaFromControl(savedVariable)
    return (not savedVariable and 0) or 1
    end

    local function GetCombatState()
    return not inCombatAddon and savedVars.onlyInCombat
    end

    local function SetPetWindowHidden(hidden, combatState)
    local setToHidden = hidden
    if combatState then
    setToHidden = true
    end
    PET_BAR_FRAGMENT:SetHiddenForReason("NoPetOrOnlyInCombat", setToHidden)
    end

    local function PetUnSummonedAlerts(unitTag)
    if unsummonedAlerts then
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local petName = currentPets.unitName
    local swimming = IsUnitSwimming("player")
    local inCombat = IsUnitInCombat("player")
    if swimming then
    OnScreenMessage(string.format(GetString(SI_PET_HEALTH_UNSUMMONED_SWIMMING_MSG)))
    elseif inCombat then
    OnScreenMessage(zo_strformat("<<1>> <<2>>", petName, GetString(SI_PET_HEALTH_UNSUMMONED_MSG)))
    end
    end
    end

    local function RefreshPetWindow()
    local countPets = #currentPets
    local combatState = GetCombatState()
    if PET_BAR_FRAGMENT:IsHidden() and countPets == 0 and combatState then
    return
    end
    local height = WINDOW_HEIGHT_BASE + (countPets * WINDOW_HEIGHT_PER_PET)
    local setToHidden = true
    if countPets > 0 then
    for i = 1, countPets do
    window:SetHidden(false)
    end
    for i = countPets + 1, #window do
    window:SetHidden(true)
    end
    setToHidden = false
    end
    if not combatState and savedVars.onlyInCombat == true then
    if onlyInCombatHealthPercentage == 0 then
    setToHidden = false
    elseif onlyInCombatHealthCurrent > (onlyInCombatHealthMax*.01*onlyInCombatHealthPercentage) then
    setToHidden = true
    end
    end
    if savedVars.hideInDungeon == true then
    local inDungeon = IsUnitInDungeon("player")
    local zoneDifficulty = GetCurrentZoneDungeonDifficulty()
    if inDungeon == true and zoneDifficulty > 0 then
    local currentZone = GetUnitZone("player")
    if currentZone ~= 'Maelstrom Arena' then
    setToHidden = true
    end
    end
    end
    base:SetHeight(height)
    background:SetHeight(height)
    SetPetWindowHidden(setToHidden, combatState)
    end


    -- SHIELD --
    local function OnShieldUpdate(handler, unitTag, value, maxValue, initial)
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local petName = currentPets.unitName
    if lowShieldAlertPercentage > 1 and value ~= 0 and value < (maxValue*.01*lowShieldAlertPercentage) then
    if not onScreenShieldAlerts then
    OnScreenMessage(zo_strformat("|c000099<<1>>\'s <<2>>|r", petName, GetString(SI_PET_HEALTH_LOW_SHIELD_WARNING_MSG)))
    onScreenShieldAlerts = true
    end
    else
    onScreenShieldAlerts = false
    end
    local ctrl, ctrlr
    if not savedVars.useZosStyle then
    ctrl = window.shield
    else
    ctrl = window.shieldleft
    ctrlr = window.shieldright
    end
    if handler ~= nil then
    if not ctrl:IsHidden() or value == 0 then
    ctrl:SetHidden(true)
    if savedVars.useZosStyle then
    ctrlr:SetHidden(true)
    end
    end
    else
    if ctrl:IsHidden() then
    ctrl:SetHidden(false)
    if savedVars.useZosStyle then
    ctrlr:SetHidden(false)
    end
    end
    end
    if maxValue > 0 then
    if savedVars.useZosStyle then
    value = value / 2
    maxValue = maxValue / 2
    ZO_StatusBar_SmoothTransition(window.shieldleft, value, maxValue, (initial == "true" and true or false))
    ZO_StatusBar_SmoothTransition(window.shieldright, value, maxValue, (initial == "true" and true or false))
    else
    ZO_StatusBar_SmoothTransition(window.shield, value, maxValue, (initial == "true" and true or false))
    end
    end
    end

    local function GetShield(unitTag)
    local value, maxValue = GetUnitAttributeVisualizerEffectInfo(unitTag, ATTRIBUTE_VISUAL_POWER_SHIELDING, STAT_MITIGATION, ATTRIBUTE_HEALTH, POWERTYPE_HEALTH)
    if value == nil then
    value = 0
    maxValue = 0
    end
    OnShieldUpdate(_, unitTag, value, maxValue, "true")
    end


    -- HEALTH --
    local function OnHealthUpdate(_, unitTag, _, _, powerValue, powerMax, initial)
    if onlyInCombatHealthPercentage > 1 and savedVars.onlyInCombat == true then
    onlyInCombatHealthMax = powerMax
    onlyInCombatHealthCurrent = powerValue
    RefreshPetWindow()
    end
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local petName = currentPets.unitName
    if lowHealthAlertPercentage > 1 and powerValue ~= 0 and powerValue < (powerMax*.01*lowHealthAlertPercentage) then
    if not onScreenHealthAlerts then
    OnScreenMessage(zo_strformat("|cff0000<<1>> <<2>>|r", petName, GetString(SI_PET_HEALTH_LOW_HEALTH_WARNING_MSG)))
    onScreenHealthAlerts = true
    end
    else
    onScreenHealthAlerts = false
    end
    window.values:SetText(ZO_FormatResourceBarCurrentAndMax(powerValue, powerMax))
    if savedVars.useZosStyle then
    local halfValue = powerValue / 2
    local halfMax = powerMax / 2
    ZO_StatusBar_SmoothTransition(window.barleft, halfValue, halfMax, (initial == "true" and true or false))
    ZO_StatusBar_SmoothTransition(window.barright, halfValue, halfMax, (initial == "true" and true or false))
    window.warner:OnHealthUpdate(powerValue, powerMax)
    else
    ZO_StatusBar_SmoothTransition(window.healthbar, powerValue, powerMax, (initial == "true" and true or false))
    end
    end

    local function GetHealth(unitTag)
    local powerValue, powerMax = GetUnitPower(unitTag, POWERTYPE_HEALTH)
    OnHealthUpdate(_, unitTag, _, _, powerValue, powerMax, "true")
    end


    -- STATS --
    local function GetControlText(control)
    local controlText = control:GetText()
    if controlText ~= nil then return controlText end
    return ""
    end

    local function UpdatePetStats(unitTag)
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local name = currentPets.unitName
    local control = window.label
    if GetControlText(control) ~= name then
    window.label:SetText(name)
    end
    GetHealth(unitTag)
    GetShield(unitTag)
    end

    local function GetActivePets()
    currentPets = {}
    for i=1,7 do
    local unitTag = UNIT_PLAYER_PET..i
    if IsUnitValidPet(unitTag) then
    table.insert(currentPets, { unitTag = unitTag, unitName = GetUnitName(unitTag) })
    UpdatePetStats(unitTag)
    end
    end
    zo_callLater(function() RefreshPetWindow() end, 300)
    end


    -- COMBAT --
    local function OnPlayerCombatState(_, inCombat)
    inCombatAddon = inCombat
    RefreshPetWindow()
    end

    local function CreateWarner()
    if savedVars.useZosStyle then
    local HEALTH_ALPHA_PULSE_THRESHOLD = 0.25
    local RESOURCE_WARNER_FLASH_TIME = 300

    PetHealthWarner = ZO_Object:Subclass()

    function PetHealthWarner:New(...)
    local warner = ZO_Object.New(self)
    warner:Initialize(...)
    return warner
    end

    function PetHealthWarner:Initialize(parent)
    self.warning = GetControl(parent, "Warner")
    self.OnPowerUpdate = function(_, unitTag, powerIndex, powerType, health, maxHealth)
    self:OnHealthUpdate(health, maxHealth)
    end
    local function OnPlayerActivated()
    local current, max = GetUnitPower(self.unitTag, POWERTYPE_HEALTH)
    self:OnHealthUpdate(current, max)
    end
    self.warning:RegisterForEvent(EVENT_PLAYER_ACTIVATED, OnPlayerActivated)
    self.warnAnimation = ZO_AlphaAnimation:New(self.warning)
    self.statusBar = parent
    self.paused = false
    end

    function PetHealthWarner:SetPaused(paused)
    if self.paused ~= paused then
    self.paused = paused
    if paused then
    if self.warnAnimation:IsPlaying() then
    self.warnAnimation:Stop()
    end
    else
    local current, max = GetUnitPower("player", POWERTYPE_HEALTH)
    self.warning:SetAlpha(0)
    self:UpdateAlphaPulse(current / max)
    end
    end
    end

    function PetHealthWarner:UpdateAlphaPulse(healthPerc)
    if healthPerc <= HEALTH_ALPHA_PULSE_THRESHOLD then
    if not self.warnAnimation:IsPlaying() then
    self.warnAnimation:PingPong(0, 1, RESOURCE_WARNER_FLASH_TIME)
    end
    else
    if self.warnAnimation:IsPlaying() then
    self.warnAnimation:Stop()
    self.warning:SetAlpha(0)
    end
    end
    end

    function PetHealthWarner:OnHealthUpdate(health, maxHealth)
    if not self.paused then
    local healthPerc = health / maxHealth
    self:UpdateAlphaPulse(healthPerc)
    end
    end
    end
    end

    -- CONTROLS --
    local function CreateControls()
    local function AddControl(parent, cType, level)
    local c = WINDOW_MANAGER:CreateControl(nil, parent, cType)
    c:SetDrawLayer(DL_OVERLAY)
    c:SetDrawLevel(level)
    return c, c
    end

    base = WINDOW_MANAGER:CreateTopLevelWindow(addon.name.."_TopLevel")
    base:SetDimensions(WINDOW_WIDTH, WINDOW_HEIGHT_BASE)
    base:SetAnchor(savedVars.point, GuiRoot, savedVars.relPoint, savedVars.x, savedVars.y)
    base:SetMouseEnabled(true)
    if savedVars.lockWindow == true then
    base:SetMovable(false)
    else
    base:SetMovable(true)
    end
    base:SetDrawLayer(DL_OVERLAY)
    base:SetDrawLevel(0)
    base:SetHandler("OnMouseUp", function()
    local a, b
    a, savedVars.point, b, savedVars.relPoint, savedVars.x, savedVars.y = base:GetAnchor(0)
    end)
    base:SetHidden(true)

    local INSET_BACKGROUND = 32
    local baseWidth = base:GetWidth()
    local baseHeight = base:GetHeight()
    local ctrl

    background, ctrl = AddControl(base, CT_BACKDROP, 1)
    ctrl:SetEdgeTexture("esoui/art/chatwindow/chat_bg_edge.dds", 256, 128, INSET_BACKGROUND)
    ctrl:SetCenterTexture("esoui/art/chatwindow/chat_bg_center.dds")
    ctrl:SetInsets(INSET_BACKGROUND, INSET_BACKGROUND, -INSET_BACKGROUND, -INSET_BACKGROUND)
    ctrl:SetCenterColor(1,1,1,0.8)
    ctrl:SetEdgeColor(1,1,1,0.8)
    ctrl:SetDimensions(baseWidth, baseHeight)
    ctrl:SetAnchor(TOPLEFT)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showBackground))

    if not savedVars.useZosStyle then
    for i=1,7 do
    window, ctrl = AddControl(base, CT_BACKDROP, 5)
    ctrl:SetDimensions(baseWidth*0.8, 36)
    ctrl:SetCenterColor(1,0,1,0)
    ctrl:SetEdgeColor(1,0,1,0)
    ctrl:SetAnchor(CENTER, base)

    local windowHeight = window:GetHeight()
    window.label, ctrl = AddControl(window, CT_LABEL, 10)
    ctrl:SetFont("$(BOLD_FONT)|$(KB_16)|soft-shadow-thin")
    ctrl:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_NORMAL))
    ctrl:SetDimensions(baseWidth, windowHeight*0.4)
    ctrl:SetAnchor(TOPLEFT, window)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showLabels))

    window.border, ctrl = AddControl(window, CT_BACKDROP, 20)
    ctrl:SetDimensions(window:GetWidth(), windowHeight*0.45)
    ctrl:SetCenterColor(0,0,0,.6)
    ctrl:SetEdgeColor(1,1,1,0.4)
    ctrl:SetEdgeTexture("", 1, 1, 1)
    ctrl:SetAnchor(BOTTOM, window)

    local borderWidth = window.border:GetWidth()
    local borderHeight = window.border:GetHeight()
    window.healthbar, ctrl = AddControl(window.border, CT_STATUSBAR, 30)
    ctrl:SetColor(1,1,1,0.5)
    ctrl:SetGradientColors(.45, .13, .13, 1, .85, .19, .19, 1)
    ctrl:SetDimensions(borderWidth-2, borderHeight-2)
    ctrl:SetAnchor(CENTER, window.border)

    window.shield, ctrl = AddControl(window.healthbar, CT_STATUSBAR, 40)
    ctrl:SetColor(1,1,1,0.5)
    ctrl:SetGradientColors(.5, .5, 1, .3, .25, .25, .5, .5)
    ctrl:SetDimensions(borderWidth-2, borderHeight-2)
    ctrl:SetAnchor(CENTER, window.healthbar)
    ctrl:SetValue(0)
    ctrl:SetMinMax(0,1)

    window.values, ctrl = AddControl(window.healthbar, CT_LABEL, 50)
    ctrl:SetFont("$(BOLD_FONT)|$(KB_14)|soft-shadow-thin")
    ctrl:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
    ctrl:SetAnchor(CENTER, window.healthbar)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showValues))

    window:ClearAnchors()
    if i == 1 then
    window:SetAnchor(TOP, base, TOP, 0, 18)
    else
    window:SetAnchor(TOP, window[i-1], BOTTOM, 0, 2)
    end
    end
    else
    local CHILD_DIRECTIONS = { "Left", "Right", "Center" }
    local function SetColors(self)
    local powerType = self.powerType
    local gradient = ZO_POWER_BAR_GRADIENT_COLORS[powerType]
    for i, control in ipairs(self.barControls) do
    ZO_StatusBar_SetGradientColor(control, gradient)
    control:SetFadeOutLossColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_POWER_FADE_OUT, powerType))
    control:SetFadeOutGainColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_POWER_FADE_IN, powerType))
    end
    end

    local PAB_TEMPLATES = {
    [POWERTYPE_HEALTH] = {
    background = {
    Left = "ZO_PlayerAttributeBgLeftArrow",
    Right = "ZO_PlayerAttributeBgRightArrow",
    Center = "ZO_PlayerAttributeBgCenter",
    },
    frame = {
    Left = "ZO_PlayerAttributeFrameLeftArrow",
    Right = "ZO_PlayerAttributeFrameRightArrow",
    Center = "ZO_PlayerAttributeFrameCenter",
    },
    warner = {
    texture = "ZO_PlayerAttributeHealthWarnerTexture",
    Left = "ZO_PlayerAttributeWarnerLeftArrow",
    Right = "ZO_PlayerAttributeWarnerRightArrow",
    Center = "ZO_PlayerAttributeWarnerCenter",
    },
    anchors = {
    "ZO_PlayerAttributeHealthBarAnchorLeft",
    "ZO_PlayerAttributeHealthBarAnchorRight",
    },
    },
    statusBar = "ZO_PlayerAttributeStatusBar",
    statusBarGloss = "ZO_PlayerAttributeStatusBarGloss",
    resourceNumbersLabel = "ZO_PlayerAttributeResourceNumbers",
    }

    local function ApplyStyle(bar)
    local powerTypeTemplates = PAB_TEMPLATES[bar.powerType]
    local backgroundTemplates = powerTypeTemplates.background
    local frameTemplates = powerTypeTemplates.frame
    local warnerControl = bar:GetNamedChild("Warner")
    local bgControl = bar:GetNamedChild("BgContainer")
    local warnerTemplates = powerTypeTemplates.warner
    for _, direction in pairs(CHILD_DIRECTIONS) do
    local bgChild = bgControl:GetNamedChild("Bg" .. direction)
    ApplyTemplateToControl(bgChild, ZO_GetPlatformTemplate(backgroundTemplates[direction]))
    local frameControl = bar:GetNamedChild("Frame" .. direction)
    ApplyTemplateToControl(frameControl, ZO_GetPlatformTemplate(frameTemplates[direction]))
    local warnerChild = warnerControl:GetNamedChild(direction)
    ApplyTemplateToControl(warnerChild, ZO_GetPlatformTemplate(warnerTemplates.texture))
    ApplyTemplateToControl(warnerChild, ZO_GetPlatformTemplate(warnerTemplates[direction]))
    end
    for i, subBar in pairs(bar.barControls) do
    ApplyTemplateToControl(subBar, ZO_GetPlatformTemplate(PAB_TEMPLATES.statusBar))
    local gloss = subBar:GetNamedChild("Gloss")
    ApplyTemplateToControl(gloss, ZO_GetPlatformTemplate(PAB_TEMPLATES.statusBarGloss))
    local anchorTemplates = powerTypeTemplates.anchors
    if anchorTemplates then
    subBar:ClearAnchors()
    ApplyTemplateToControl(subBar, ZO_GetPlatformTemplate(anchorTemplates))
    else
    ApplyTemplateToControl(subBar, ZO_GetPlatformTemplate(PAB_TEMPLATES.anchor))
    end
    end
    local resourceNumbersLabel = bar:GetNamedChild("ResourceNumbers")
    if resourceNumbersLabel then
    ApplyTemplateToControl(resourceNumbersLabel, ZO_GetPlatformTemplate(PAB_TEMPLATES.resourceNumbersLabel))
    end
    end

    for i=1,7 do
    window = WINDOW_MANAGER:CreateControlFromVirtual("PetHealth"..i, base, "PetHealth_ZOSStyleBar")
    local windowHeight = window:GetHeight()
    window.label, ctrl = AddControl(window, CT_LABEL, 10)
    ctrl:SetFont("$(BOLD_FONT)|$(KB_16)|soft-shadow-thin")
    ctrl:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_NORMAL))
    ctrl:SetDimensions(baseWidth, windowHeight*0.4)
    ctrl:SetAnchor(BOTTOMLEFT, window, TOPLEFT, 0, -10.5)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showLabels))
    window.barleft = window:GetNamedChild("BarLeft")
    window.barright = window:GetNamedChild("BarRight")
    window.barControls = { window.barleft, window.barright }
    window.powerType = POWERTYPE_HEALTH
    SetColors(window)
    ApplyStyle(window)
    window.shieldleft = window:GetNamedChild("ShieldLeft")
    window.shieldright = window:GetNamedChild("ShieldRight")
    window.values = window:GetNamedChild("ResourceNumbers")
    window.values:SetAlpha(GetAlphaFromControl(savedVars.showValues))
    window.warner = PetHealthWarner:New(window)
    if i == 1 then
    window:SetAnchor(TOP, base, TOP, 0, 18)
    else
    window:SetAnchor(TOP, window[i-1], BOTTOM, 0, 20)
    end
    end
    end

    PET_BAR_FRAGMENT = ZO_HUDFadeSceneFragment:New(base)
    HUD_SCENE:AddFragment(PET_BAR_FRAGMENT)
    HUD_UI_SCENE:AddFragment(PET_BAR_FRAGMENT)
    PET_BAR_FRAGMENT:SetHiddenForReason("NoPetOrOnlyInCombat", true)
    end


    -- INIT --
    local function LoadEvents()
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_POWER_UPDATE, OnHealthUpdate)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_CREATED, function(_, unitTag)
    if IsUnitValidPet(unitTag) then
    GetActivePets()
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_DESTROYED, function(_, unitTag)
    PetUnSummonedAlerts(unitTag)
    local key = GetKeyWithData(unitTag)
    if key ~= nil then
    table.remove(currentPets, key)
    local countPets = #currentPets
    if countPets > 0 then
    for i = 1, countPets do
    local name = currentPets.unitName
    local control = window.label
    unitTag = currentPets.unitTag
    if GetControlText(control) ~= name then
    window.label:SetText(name)
    end
    GetHealth(unitTag)
    GetShield(unitTag)
    end
    end
    RefreshPetWindow()
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_PLAYER_DEAD, GetActivePets)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_DEATH_STATE_CHANGE, GetActivePets)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_ACTION_SLOT_ABILITY_SLOTTED, GetActivePets)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_POWER_UPDATE, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_CREATED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_DESTROYED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_PLAYER_DEAD, REGISTER_FILTER_UNIT_TAG, UNIT_PLAYER_TAG)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_DEATH_STATE_CHANGE, REGISTER_FILTER_UNIT_TAG, UNIT_PLAYER_TAG)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_ADDED, function(_, unitTag, unitAttributeVisual, _, _, _, value, maxValue)
    if unitAttributeVisual == ATTRIBUTE_VISUAL_POWER_SHIELDING then
    OnShieldUpdate(nil, unitTag, value, maxValue, "true")
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_REMOVED, function(_, unitTag, unitAttributeVisual, _, _, _, value, maxValue)
    if unitAttributeVisual == ATTRIBUTE_VISUAL_POWER_SHIELDING then
    OnShieldUpdate("removed", unitTag, value, maxValue, "false")
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_UPDATED, function(_, unitTag, unitAttributeVisual, _, _, _, _, newValue, _, newMaxValue)
    if unitAttributeVisual == ATTRIBUTE_VISUAL_POWER_SHIELDING then
    OnShieldUpdate(nil, unitTag, newValue, newMaxValue, "false")
    end
    end)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_ADDED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_REMOVED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_UPDATED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_INTERFACE_SETTING_CHANGED, GetActivePets)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_INTERFACE_SETTING_CHANGED, REGISTER_FILTER_SETTING_SYSTEM_TYPE, SETTING_TYPE_UI)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_PLAYER_COMBAT_STATE, OnPlayerCombatState)
    OnPlayerCombatState(_, IsUnitInCombat(UNIT_PLAYER_TAG))
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_PLAYER_ACTIVATED, function() zo_callLater(function() GetActivePets() end, 75) end)
    end

    function PetHealth.changeCombatState()
    OnPlayerCombatState(_, IsUnitInCombat(UNIT_PLAYER_TAG))
    end

    function PetHealth.hideInDungeon(toValue)
    hideInDungeon = toValue
    RefreshPetWindow()
    end

    function PetHealth.changeBackground(toValue)
    background:SetAlpha(GetAlphaFromControl(toValue))
    end

    function PetHealth.changeValues(toValue)
    for i=1,7 do
    if window then
    window.values:SetAlpha(GetAlphaFromControl(toValue))
    end
    end
    end

    function PetHealth.changeLabels(toValue)
    for i=1,7 do
    if window then
    window.label:SetAlpha(GetAlphaFromControl(toValue))
    end
    end
    end

    function PetHealth.lockPetWindow(toValue)
    lockWindow = toValue
    end

    function PetHealth.lowHealthAlertPercentage(toValue)
    lowHealthAlertPercentage = toValue
    end

    function PetHealth.lowShieldAlertPercentage(toValue)
    lowShieldAlertPercentage = toValue
    end

    function PetHealth.unsummonedAlerts(toValue)
    unsummonedAlerts = toValue
    end

    function PetHealth.onlyInCombatHealthPercentage(toValue)
    onlyInCombatHealthPercentage = toValue
    end

    local function SlashCommands()
    LSC:Register("/pethealthcombat", function()
    savedVars.onlyInCombat = not savedVars.onlyInCombat
    if savedVars.onlyInCombat then
    ChatOutput(GetString(SI_PET_HEALTH_COMBAT_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_COMBAT_DEACTIVATED))
    end
    PetHealth.changeCombatState()
    end, GetString(SI_PET_HEALTH_LSC_COMBAT))

    LSC:Register("/pethealthhideindungeon", function()
    savedVars.hideInDungeon = not savedVars.hideInDungeon
    if savedVars.hideInDungeon then
    ChatOutput(GetString(SI_PET_HEALTH_HIDE_IN_DUNGEON_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_HIDE_IN_DUNGEON_DEACTIVATED))
    end
    PetHealth.hideInDungeon()
    end, GetString(SI_PET_HEALTH_LSC_DUNGEON))

    LSC:Register("/pethealthvalues", function()
    savedVars.showValues = not savedVars.showValues
    if savedVars.showValues then
    ChatOutput(GetString(SI_PET_HEALTH_VALUES_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_VALUES_DEACTIVATED))
    end
    PetHealth.changeValues(savedVars.showValues)
    end, GetString(SI_PET_HEALTH_LSC_VALUES))

    LSC:Register("/pethealthlabels", function()
    savedVars.showLabels = not savedVars.showLabels
    if savedVars.showLabels then
    ChatOutput(GetString(SI_PET_HEALTH_LABELS_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_LABELS_DEACTIVATED))
    end
    PetHealth.changeLabels(savedVars.showLabels)
    end, GetString(SI_PET_HEALTH_LSC_LABELS))

    if not savedVars.useZosStyle then
    LSC:Register("/pethealthbackground", function()
    savedVars.showBackground = not savedVars.showBackground
    if savedVars.showBackground then
    ChatOutput(GetString(SI_PET_HEALTH_BACKGROUND_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_BACKGROUND_DEACTIVATED))
    end
    PetHealth.changeBackground(savedVars.showBackground)
    end, GetString(SI_PET_HEALTH_LSC_BACKGROUND))
    else
    showBackground = false
    savedVars.showBackground = false
    end

    LSC:Register("/pethealthunsummonedalerts", function()
    savedVars.petUnsummonedAlerts = not savedVars.petUnsummonedAlerts
    if savedVars.petUnsummonedAlerts then
    ChatOutput(GetString(SI_PET_HEALTH_UNSUMMONEDALERTS_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_UNSUMMONEDALERTS_DEACTIVATED))
    end
    PetHealth.unsummonedAlerts(savedVars.petUnsummonedAlerts)
    end, GetString(SI_PET_HEALTH_LSC_UNSUMMONEDALERTS))

    LSC:Register("/pethealthwarnhealth", function(healthValuePercent)
    if healthValuePercent == nil or healthValuePercent == "" then
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_HEALTH_WARN) .. ": " .. tostring(savedVars.lowHealthAlertSlider))
    else
    local healthValuePercentNumber = tonumber(healthValuePercent)
    if type(healthValuePercentNumber) == "number" then
    if healthValuePercentNumber <= 0 then healthValuePercentNumber = 0 end
    if healthValuePercentNumber >= 100 then healthValuePercentNumber = 99 end
    savedVars.lowHealthAlertSlider = healthValuePercentNumber
    PetHealth.lowHealthAlertPercentage(healthValuePercentNumber)
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_HEALTH_WARN) .. ": " .. tostring(healthValuePercentNumber))
    end
    end
    end, GetString(SI_PET_HEALTH_LSC_WARN_HEALTH))

    LSC:Register("/pethealthwarnshield", function(shieldValuePercent)
    if shieldValuePercent == nil or shieldValuePercent == "" then
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_SHIELD_WARN) .. ": " .. tostring(savedVars.lowShieldAlertSlider))
    else
    local shieldValuePercentNumber = tonumber(shieldValuePercent)
    if type(shieldValuePercentNumber) == "number" then
    if shieldValuePercentNumber <= 0 then shieldValuePercentNumber = 0 end
    if shieldValuePercentNumber >= 100 then shieldValuePercentNumber = 99 end
    savedVars.lowShieldAlertSlider = shieldValuePercentNumber
    PetHealth.lowShieldAlertPercentage(shieldValuePercentNumber)
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_SHIELD_WARN) .. ": " .. tostring(shieldValuePercentNumber))
    end
    end
    end, GetString(SI_PET_HEALTH_LSC_WARN_SHIELD))

    LSC:Register("/pethealthcombathealth", function(combatHealthValuePercent)
    if combatHealthValuePercent == nil or combatHealthValuePercent == "" then
    ChatOutput(GetString(SI_PET_HEALTH_LAM_ONLY_IN_COMBAT_HEALTH) .. ": " .. tostring(savedVars.onlyInCombatHealthSlider))
    else
    local combatHealthPercentNumber = tonumber(combatHealthValuePercent)
    if type(combatHealthPercentNumber) == "number" then
    if combatHealthPercentNumber <= 0 then combatHealthPercentNumber = 0 end
    if combatHealthPercentNumber >= 100 then combatHealthPercentNumber = 99 end
    savedVars.onlyInCombatHealthSlider = combatHealthPercentNumber
    PetHealth.onlyInCombatHealthPercentage(combatHealthPercentNumber)
    ChatOutput(GetString(SI_PET_HEALTH_LAM_ONLY_IN_COMBAT_HEALTH) .. ": " .. tostring(combatHealthPercentNumber))
    end
    end
    end, GetString(SI_PET_HEALTH_LSC_COMBAT_HEALTH))
    end

    local function OnAddOnLoaded(_, addonName)
    if addonName ~= addon.name then return end
    EVENT_MANAGER:UnregisterForEvent(addon.name, EVENT_ADD_ON_LOADED)

    savedVars = ZO_SavedVars:NewAccountWide(addon.savedVarName, addon.savedVarVersion, nil, default, GetWorldName())
    if savedVars.saveMode == 1 then
    savedVars = ZO_SavedVars:NewCharacterIdSettings(addon.savedVarName, addon.savedVarVersion, nil, default, GetWorldName())
    end

    PetHealth.savedVars = savedVars
    PetHealth.savedVarsDefault = default
    lowHealthAlertPercentage = savedVars.lowHealthAlertSlider
    lowShieldAlertPercentage = savedVars.lowShieldAlertSlider
    unsummonedAlerts = savedVars.petUnsummonedAlerts
    onlyInCombatHealthPercentage = savedVars.onlyInCombatHealthSlider

    local getUnitClassId = GetUnitClassId(UNIT_PLAYER_TAG)
    local supportedClasses = PetHealth.supportedClasses
    local supportedClass = supportedClasses[getUnitClassId] or false
    if not supportedClass then
    ChatOutput("[PetHealth] " .. GetString(SI_PET_HEALTH_CLASS))
    return
    end

    local isLAMActive = CheckAddon('LibAddonMenu-2.0')
    local isLSCActive = CheckAddon('LibSlashCommander')

    if isLAMActive then
    LAM = LibAddonMenu2
    PetHealth.LAM = LAM
    PetHealth.buildLAMAddonMenu()
    end

    if isLSCActive then
    LSC = LibSlashCommander
    SlashCommands()
    end

    CreateWarner()
    CreateControls()
    LoadEvents()
    end

    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_ADD_ON_LOADED, OnAddOnLoaded)

    The error comes up with "20F3BB52

    user:/AddOns/PetHealth/PetHealth.lua:205: operator # is not supported for # userdata
    stack traceback:
    user:/AddOns/PetHealth/PetHealth.lua:205: in function 'RefreshPetWindow'
    user:/AddOns/PetHealth/PetHealth.lua:357: in function 'func'
    /EsoUI/Libraries/Globals/globalapi.lua:282: in function '(anonymous)'"

    To my untrained eye, it seems like this is getting close to having it figured out.
    Still waiting for answers about class set aura behavior, and also hoping that one day hair dyeing will be an option. We know it's canon thanks to a book in the Manor of Masques! (House of Reveries: The Troupe)
    Proud owner of a Morrowind Banner of the 6th House (back when it actually meant something)
  • Uazole2
    Uazole2
    ✭✭✭
    I built a sorcerer/warden and have 3 pets up and have 3 health bars. I did not experience those errors. Will see if I can fix.

    As an aside, I am not the author, nor the maintainer of this addon. I take no credit for its creation or function. I am simply a selfish hacker that wanted this fixed for me, so I am trying. I suggest that if the original ever gets fixed, use it.
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • MafiaCat115
    MafiaCat115
    ✭✭✭✭✭
    Uazole2 wrote: »
    I built a sorcerer/warden and have 3 pets up and have 3 health bars. I did not experience those errors. Will see if I can fix.

    As an aside, I am not the author, nor the maintainer of this addon. I take no credit for its creation or function. I am simply a selfish hacker that wanted this fixed for me, so I am trying. I suggest that if the original ever gets fixed, use it.

    I'll see what I can do as well. And your work has been greatly appreciated as a person who uses this addon often. If it matters, the character I have been testing on is a Dunmer Sorc with Animal Companions subclassed.
    Still waiting for answers about class set aura behavior, and also hoping that one day hair dyeing will be an option. We know it's canon thanks to a book in the Manor of Masques! (House of Reveries: The Troupe)
    Proud owner of a Morrowind Banner of the 6th House (back when it actually meant something)
  • Uazole2
    Uazole2
    ✭✭✭
    second one is broken, don't bother
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • Uazole2
    Uazole2
    ✭✭✭
    This update seems to work for me. LMK.


    PetHealth = PetHealth or {}
    --The supported classes for this addon (ClassId from function GetUnitClassId("player"))
    PetHealth.supportedClasses = {
    [1] = true, -- Dragonknight
    [2] = true, -- Sorcerer
    [3] = true, -- Nightblade
    [4] = true, -- Warden
    [5] = true, -- Necromancer
    [6] = true, -- Templar
    [117] = true, -- Archanist
    }
    local addon = {
    name = "PetHealth",
    displayName = "PetHealth",
    version = "1.12",
    savedVarName = "PetHealth_Save",
    savedVarVersion = 2,
    lamDisplayName = "PetHealth",
    lamAuthor = "Scootworks, Goobsnake, with unauthorized edits by Uazole",
    lamUrl = "https://www.esoui.com/downloads/info1884-PetHealth.html",
    }
    PetHealth.addonData = addon

    local default = {
    saveMode = 1, -- Default for each character setting
    point = TOPLEFT,
    relPoint = CENTER,
    x = 0,
    y = 0,
    onlyInCombat = false,
    showValues = true,
    showLabels = true,
    hideInDungeon = false,
    lockWindow = false,
    lowHealthAlertSlider = 0,
    lowShieldAlertSlider = 0,
    petUnsummonedAlerts = false,
    onlyInCombatHealthSlider = 0,
    showBackground = true,
    useZosStyle = false,
    debug = false,
    }

    local UNIT_PLAYER_PET = "playerpet"
    local UNIT_PLAYER_TAG = "player"

    local base, background, savedVars
    local currentPets = {}
    local PetHealthWarner
    local window = {}
    local inCombatAddon = false

    local AddOnManager = GetAddOnManager()
    local hideInDungeon = false
    local LAM
    local LSC
    local lockWindow = false
    local lowHealthAlertPercentage = 0
    local lowShieldAlertPercentage = 0
    local onlyInCombatHealthMax = 0
    local onlyInCombatHealthCurrent = 0
    local onlyInCombatHealthPercentage = 0
    local onScreenHealthAlerts = {} -- Dynamic table for health alerts
    local onScreenShieldAlerts = {} -- Dynamic table for shield alerts
    local unsummonedAlerts = false

    local WINDOW_MANAGER = GetWindowManager()
    local WINDOW_WIDTH = 250
    local WINDOW_HEIGHT_PER_PET = 40 -- Height per pet bar
    local WINDOW_HEIGHT_BASE = 36 -- Base height for no pets
    local PET_BAR_FRAGMENT

    -- UTIL --
    local function OnScreenMessage(message)
    local messageParams = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_CATEGORY_LARGE_TEXT)
    messageParams:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_COUNTDOWN)
    messageParams:SetText(message)
    CENTER_SCREEN_ANNOUNCE:AddMessageWithParams(messageParams)
    end

    local function ChatOutput(message)
    CHAT_SYSTEM:AddMessage(message)
    end

    local function CheckAddon(addonName)
    for i = 1, AddOnManager:GetNumAddOns() do
    local name, title, author, description, enabled, state, isOutOfDate = AddOnManager:GetAddOnInfo(i)
    if title == addonName and enabled == true and state == 2 then
    return true
    end
    end
    end

    local function GetPetNameLower(abilityId)
    local abilityName = GetAbilityName(abilityId)
    local petName
    if abilityName:match('Summon ') then
    if abilityName:match('Summon Unstable ') then
    petName = abilityName:gsub("Summon Unstable ","")
    else
    petName = abilityName:gsub("Summon ","")
    end
    else
    petName = abilityName
    end
    return zo_strformat("<<z:1>>", petName)
    end

    local validPets = {
    -- Familiar
    [GetPetNameLower(23304)] = true,
    ["begleiter"] = true, -- de
    ["familier"] = true, -- fr
    ["призванный слуга"] = true, -- ru
    -- Clannfear
    [GetPetNameLower(23319)] = true,
    ["clannbann"] = true, -- de
    ["faucheclan"] = true, -- fr
    ["кланфир"] = true, -- ru
    -- Volatile Familiar
    [GetPetNameLower(23316)] = true,
    ["explosiver begleiter"] = true, -- de
    ["familier explosif"] = true, -- fr
    ["взрывной призванный слуга"] = true, -- ru
    -- Winged Twilight
    [GetPetNameLower(24613)] = true,
    ["zwielichtschwinge"] = true, -- de
    ["crépuscule ailé"] = true, -- fr
    ["крылатый сумрак"] = true, -- ru
    -- Twilight Tormentor
    [GetPetNameLower(24636)] = true,
    ["zwielichtpeinigerin"] = true, -- de
    ["tourmenteur crépusculaire"] = true, -- fr
    ["сумрак-мучитель"] = true, -- ru
    -- Twilight Matriarch
    [GetPetNameLower(24639)] = true,
    ["zwielichtmatriarchin"] = true, -- de
    ["matriarche crépusculaire"] = true, -- fr
    ["сумрак-матриарх"] = true, -- ru
    -- Warden Pets
    [GetPetNameLower(85982)] = true,
    ["хищный страж"] = true, -- ru
    [GetPetNameLower(85986)] = true,
    ["вечный страж"] = true, -- ru
    [GetPetNameLower(85990)] = true,
    ["дикий защитник"] = true, -- ru
    }

    local function IsUnitValidPet(unitTag)
    local unitName = zo_strformat("<<z:1>>", GetUnitName(unitTag))
    return DoesUnitExist(unitTag) and validPets[unitName]
    end

    local function GetKeyWithData(unitTag)
    for k, v in pairs(currentPets) do
    if v.unitTag == unitTag then return k end
    end
    return nil
    end

    local function GetAlphaFromControl(savedVariable)
    return (not savedVariable and 0) or 1
    end

    local function GetCombatState()
    return not inCombatAddon and savedVars.onlyInCombat
    end

    local function SetPetWindowHidden(hidden, combatState)
    local setToHidden = hidden
    if combatState then
    setToHidden = true
    end
    PET_BAR_FRAGMENT:SetHiddenForReason("NoPetOrOnlyInCombat", setToHidden)
    end

    local function PetUnSummonedAlerts(unitTag)
    if unsummonedAlerts then
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local petName = currentPets.unitName
    local swimming = IsUnitSwimming("player")
    local inCombat = IsUnitInCombat("player")
    if swimming then
    OnScreenMessage(string.format(GetString(SI_PET_HEALTH_UNSUMMONED_SWIMMING_MSG)))
    elseif inCombat then
    OnScreenMessage(zo_strformat("<<1>> <<2>>", petName, GetString(SI_PET_HEALTH_UNSUMMONED_MSG)))
    end
    end
    end

    local function GetTableSize(tbl)
    local count = 0
    for _ in pairs(tbl) do
    count = count + 1
    end
    return count
    end

    local function RefreshPetWindow()
    local countPets = GetTableSize(currentPets)
    local combatState = GetCombatState()
    if PET_BAR_FRAGMENT:IsHidden() and countPets == 0 and combatState then
    return
    end
    local height = WINDOW_HEIGHT_BASE + (countPets * WINDOW_HEIGHT_PER_PET)
    local setToHidden = true
    if countPets > 0 then
    for i = 1, countPets do
    if window then
    window:SetHidden(false)
    end
    end
    for i = countPets + 1, 7 do
    if window then
    window:SetHidden(true)
    end
    end
    setToHidden = false
    end
    if not combatState and savedVars.onlyInCombat == true then
    if onlyInCombatHealthPercentage == 0 then
    setToHidden = false
    elseif onlyInCombatHealthCurrent > (onlyInCombatHealthMax*.01*onlyInCombatHealthPercentage) then
    setToHidden = true
    end
    end
    if savedVars.hideInDungeon == true then
    local inDungeon = IsUnitInDungeon("player")
    local zoneDifficulty = GetCurrentZoneDungeonDifficulty()
    if inDungeon == true and zoneDifficulty > 0 then
    local currentZone = GetUnitZone("player")
    if currentZone ~= 'Maelstrom Arena' then
    setToHidden = true
    end
    end
    end
    base:SetHeight(height)
    background:SetHeight(height)
    SetPetWindowHidden(setToHidden, combatState)
    end


    -- SHIELD --
    local function OnShieldUpdate(handler, unitTag, value, maxValue, initial)
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local petName = currentPets.unitName
    if lowShieldAlertPercentage > 1 and value ~= 0 and value < (maxValue*.01*lowShieldAlertPercentage) then
    if not onScreenShieldAlerts then
    OnScreenMessage(zo_strformat("|c000099<<1>>\'s <<2>>|r", petName, GetString(SI_PET_HEALTH_LOW_SHIELD_WARNING_MSG)))
    onScreenShieldAlerts = true
    end
    else
    onScreenShieldAlerts = false
    end
    local ctrl, ctrlr
    if not savedVars.useZosStyle then
    ctrl = window.shield
    else
    ctrl = window.shieldleft
    ctrlr = window.shieldright
    end
    if handler ~= nil then
    if not ctrl:IsHidden() or value == 0 then
    ctrl:SetHidden(true)
    if savedVars.useZosStyle then
    ctrlr:SetHidden(true)
    end
    end
    else
    if ctrl:IsHidden() then
    ctrl:SetHidden(false)
    if savedVars.useZosStyle then
    ctrlr:SetHidden(false)
    end
    end
    end
    if maxValue > 0 then
    if savedVars.useZosStyle then
    value = value / 2
    maxValue = maxValue / 2
    ZO_StatusBar_SmoothTransition(window.shieldleft, value, maxValue, (initial == "true" and true or false))
    ZO_StatusBar_SmoothTransition(window.shieldright, value, maxValue, (initial == "true" and true or false))
    else
    ZO_StatusBar_SmoothTransition(window.shield, value, maxValue, (initial == "true" and true or false))
    end
    end
    end

    local function GetShield(unitTag)
    local value, maxValue = GetUnitAttributeVisualizerEffectInfo(unitTag, ATTRIBUTE_VISUAL_POWER_SHIELDING, STAT_MITIGATION, ATTRIBUTE_HEALTH, POWERTYPE_HEALTH)
    if value == nil then
    value = 0
    maxValue = 0
    end
    OnShieldUpdate(_, unitTag, value, maxValue, "true")
    end


    -- HEALTH --
    local function OnHealthUpdate(_, unitTag, _, _, powerValue, powerMax, initial)
    if onlyInCombatHealthPercentage > 1 and savedVars.onlyInCombat == true then
    onlyInCombatHealthMax = powerMax
    onlyInCombatHealthCurrent = powerValue
    RefreshPetWindow()
    end
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local petName = currentPets.unitName
    if lowHealthAlertPercentage > 1 and powerValue ~= 0 and powerValue < (powerMax*.01*lowHealthAlertPercentage) then
    if not onScreenHealthAlerts then
    OnScreenMessage(zo_strformat("|cff0000<<1>> <<2>>|r", petName, GetString(SI_PET_HEALTH_LOW_HEALTH_WARNING_MSG)))
    onScreenHealthAlerts = true
    end
    else
    onScreenHealthAlerts = false
    end
    window.values:SetText(ZO_FormatResourceBarCurrentAndMax(powerValue, powerMax))
    if savedVars.useZosStyle then
    local halfValue = powerValue / 2
    local halfMax = powerMax / 2
    ZO_StatusBar_SmoothTransition(window.barleft, halfValue, halfMax, (initial == "true" and true or false))
    ZO_StatusBar_SmoothTransition(window.barright, halfValue, halfMax, (initial == "true" and true or false))
    window.warner:OnHealthUpdate(powerValue, powerMax)
    else
    ZO_StatusBar_SmoothTransition(window.healthbar, powerValue, powerMax, (initial == "true" and true or false))
    end
    end

    local function GetHealth(unitTag)
    local powerValue, powerMax = GetUnitPower(unitTag, POWERTYPE_HEALTH)
    OnHealthUpdate(_, unitTag, _, _, powerValue, powerMax, "true")
    end


    -- STATS --
    local function GetControlText(control)
    local controlText = control:GetText()
    if controlText ~= nil then return controlText end
    return ""
    end

    local function UpdatePetStats(unitTag)
    local i = GetKeyWithData(unitTag)
    if i == nil then
    return
    end
    local name = currentPets.unitName
    local control = window.label
    if GetControlText(control) ~= name then
    window.label:SetText(name)
    end
    GetHealth(unitTag)
    GetShield(unitTag)
    end

    local function GetActivePets()
    currentPets = {}
    for i=1,7 do
    local unitTag = UNIT_PLAYER_PET..i
    if IsUnitValidPet(unitTag) then
    table.insert(currentPets, { unitTag = unitTag, unitName = zo_strformat("<<z:1>>", GetUnitName(unitTag)) })
    UpdatePetStats(unitTag)
    end
    end
    zo_callLater(function() RefreshPetWindow() end, 300)
    end


    -- COMBAT --
    local function OnPlayerCombatState(_, inCombat)
    inCombatAddon = inCombat
    RefreshPetWindow()
    end

    local function CreateWarner()
    if savedVars.useZosStyle then
    local HEALTH_ALPHA_PULSE_THRESHOLD = 0.25
    local RESOURCE_WARNER_FLASH_TIME = 300

    PetHealthWarner = ZO_Object:Subclass()

    function PetHealthWarner:New(...)
    local warner = ZO_Object.New(self)
    warner:Initialize(...)
    return warner
    end

    function PetHealthWarner:Initialize(parent)
    self.warning = GetControl(parent, "Warner")
    self.OnPowerUpdate = function(_, unitTag, powerIndex, powerType, health, maxHealth)
    self:OnHealthUpdate(health, maxHealth)
    end
    local function OnPlayerActivated()
    local current, max = GetUnitPower(self.unitTag, POWERTYPE_HEALTH)
    self:OnHealthUpdate(current, max)
    end
    self.warning:RegisterForEvent(EVENT_PLAYER_ACTIVATED, OnPlayerActivated)
    self.warnAnimation = ZO_AlphaAnimation:New(self.warning)
    self.statusBar = parent
    self.paused = false
    end

    function PetHealthWarner:SetPaused(paused)
    if self.paused ~= paused then
    self.paused = paused
    if paused then
    if self.warnAnimation:IsPlaying() then
    self.warnAnimation:Stop()
    end
    else
    local current, max = GetUnitPower("player", POWERTYPE_HEALTH)
    self.warning:SetAlpha(0)
    self:UpdateAlphaPulse(current / max)
    end
    end
    end

    function PetHealthWarner:UpdateAlphaPulse(healthPerc)
    if healthPerc <= HEALTH_ALPHA_PULSE_THRESHOLD then
    if not self.warnAnimation:IsPlaying() then
    self.warnAnimation:PingPong(0, 1, RESOURCE_WARNER_FLASH_TIME)
    end
    else
    if self.warnAnimation:IsPlaying() then
    self.warnAnimation:Stop()
    self.warning:SetAlpha(0)
    end
    end
    end

    function PetHealthWarner:OnHealthUpdate(health, maxHealth)
    if not self.paused then
    local healthPerc = health / maxHealth
    self:UpdateAlphaPulse(healthPerc)
    end
    end
    end
    end

    -- CONTROLS --
    local function CreateControls()
    local function AddControl(parent, cType, level)
    local c = WINDOW_MANAGER:CreateControl(nil, parent, cType)
    c:SetDrawLayer(DL_OVERLAY)
    c:SetDrawLevel(level)
    return c, c
    end

    base = WINDOW_MANAGER:CreateTopLevelWindow(addon.name.."_TopLevel")
    base:SetDimensions(WINDOW_WIDTH, WINDOW_HEIGHT_BASE)
    base:SetAnchor(savedVars.point, GuiRoot, savedVars.relPoint, savedVars.x, savedVars.y)
    base:SetMouseEnabled(true)
    if savedVars.lockWindow == true then
    base:SetMovable(false)
    else
    base:SetMovable(true)
    end
    base:SetDrawLayer(DL_OVERLAY)
    base:SetDrawLevel(0)
    base:SetHandler("OnMouseUp", function()
    local a, b
    a, savedVars.point, b, savedVars.relPoint, savedVars.x, savedVars.y = base:GetAnchor(0)
    end)
    base:SetHidden(true)

    local INSET_BACKGROUND = 32
    local baseWidth = base:GetWidth()
    local baseHeight = base:GetHeight()
    local ctrl

    background, ctrl = AddControl(base, CT_BACKDROP, 1)
    ctrl:SetEdgeTexture("esoui/art/chatwindow/chat_bg_edge.dds", 256, 128, INSET_BACKGROUND)
    ctrl:SetCenterTexture("esoui/art/chatwindow/chat_bg_center.dds")
    ctrl:SetInsets(INSET_BACKGROUND, INSET_BACKGROUND, -INSET_BACKGROUND, -INSET_BACKGROUND)
    ctrl:SetCenterColor(1,1,1,0.8)
    ctrl:SetEdgeColor(1,1,1,0.8)
    ctrl:SetDimensions(baseWidth, baseHeight)
    ctrl:SetAnchor(TOPLEFT)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showBackground))

    if not savedVars.useZosStyle then
    for i=1,7 do
    window, ctrl = AddControl(base, CT_BACKDROP, 5)
    ctrl:SetDimensions(baseWidth*0.8, 36)
    ctrl:SetCenterColor(1,0,1,0)
    ctrl:SetEdgeColor(1,0,1,0)
    ctrl:SetAnchor(CENTER, base)

    local windowHeight = window:GetHeight()
    window.label, ctrl = AddControl(window, CT_LABEL, 10)
    ctrl:SetFont("$(BOLD_FONT)|$(KB_16)|soft-shadow-thin")
    ctrl:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_NORMAL))
    ctrl:SetDimensions(baseWidth, windowHeight*0.4)
    ctrl:SetAnchor(TOPLEFT, window)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showLabels))

    window.border, ctrl = AddControl(window, CT_BACKDROP, 20)
    ctrl:SetDimensions(window:GetWidth(), windowHeight*0.45)
    ctrl:SetCenterColor(0,0,0,.6)
    ctrl:SetEdgeColor(1,1,1,0.4)
    ctrl:SetEdgeTexture("", 1, 1, 1)
    ctrl:SetAnchor(BOTTOM, window)

    local borderWidth = window.border:GetWidth()
    local borderHeight = window.border:GetHeight()
    window.healthbar, ctrl = AddControl(window.border, CT_STATUSBAR, 30)
    ctrl:SetColor(1,1,1,0.5)
    ctrl:SetGradientColors(.45, .13, .13, 1, .85, .19, .19, 1)
    ctrl:SetDimensions(borderWidth-2, borderHeight-2)
    ctrl:SetAnchor(CENTER, window.border)

    window.shield, ctrl = AddControl(window.healthbar, CT_STATUSBAR, 40)
    ctrl:SetColor(1,1,1,0.5)
    ctrl:SetGradientColors(.5, .5, 1, .3, .25, .25, .5, .5)
    ctrl:SetDimensions(borderWidth-2, borderHeight-2)
    ctrl:SetAnchor(CENTER, window.healthbar)
    ctrl:SetValue(0)
    ctrl:SetMinMax(0,1)

    window.values, ctrl = AddControl(window.healthbar, CT_LABEL, 50)
    ctrl:SetFont("$(BOLD_FONT)|$(KB_14)|soft-shadow-thin")
    ctrl:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_SELECTED))
    ctrl:SetAnchor(CENTER, window.healthbar)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showValues))

    window:ClearAnchors()
    if i == 1 then
    window:SetAnchor(TOP, base, TOP, 0, 18)
    else
    window:SetAnchor(TOP, window[i-1], BOTTOM, 0, 2)
    end
    end
    else
    local CHILD_DIRECTIONS = { "Left", "Right", "Center" }
    local function SetColors(self)
    local powerType = self.powerType
    local gradient = ZO_POWER_BAR_GRADIENT_COLORS[powerType]
    for i, control in ipairs(self.barControls) do
    ZO_StatusBar_SetGradientColor(control, gradient)
    control:SetFadeOutLossColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_POWER_FADE_OUT, powerType))
    control:SetFadeOutGainColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_POWER_FADE_IN, powerType))
    end
    end

    local PAB_TEMPLATES = {
    [POWERTYPE_HEALTH] = {
    background = {
    Left = "ZO_PlayerAttributeBgLeftArrow",
    Right = "ZO_PlayerAttributeBgRightArrow",
    Center = "ZO_PlayerAttributeBgCenter",
    },
    frame = {
    Left = "ZO_PlayerAttributeFrameLeftArrow",
    Right = "ZO_PlayerAttributeFrameRightArrow",
    Center = "ZO_PlayerAttributeFrameCenter",
    },
    warner = {
    texture = "ZO_PlayerAttributeHealthWarnerTexture",
    Left = "ZO_PlayerAttributeWarnerLeftArrow",
    Right = "ZO_PlayerAttributeWarnerRightArrow",
    Center = "ZO_PlayerAttributeWarnerCenter",
    },
    anchors = {
    "ZO_PlayerAttributeHealthBarAnchorLeft",
    "ZO_PlayerAttributeHealthBarAnchorRight",
    },
    },
    statusBar = "ZO_PlayerAttributeStatusBar",
    statusBarGloss = "ZO_PlayerAttributeStatusBarGloss",
    resourceNumbersLabel = "ZO_PlayerAttributeResourceNumbers",
    }

    local function ApplyStyle(bar)
    local powerTypeTemplates = PAB_TEMPLATES[bar.powerType]
    local backgroundTemplates = powerTypeTemplates.background
    local frameTemplates = powerTypeTemplates.frame
    local warnerControl = bar:GetNamedChild("Warner")
    local bgControl = bar:GetNamedChild("BgContainer")
    local warnerTemplates = powerTypeTemplates.warner
    for _, direction in pairs(CHILD_DIRECTIONS) do
    local bgChild = bgControl:GetNamedChild("Bg" .. direction)
    ApplyTemplateToControl(bgChild, ZO_GetPlatformTemplate(backgroundTemplates[direction]))
    local frameControl = bar:GetNamedChild("Frame" .. direction)
    ApplyTemplateToControl(frameControl, ZO_GetPlatformTemplate(frameTemplates[direction]))
    local warnerChild = warnerControl:GetNamedChild(direction)
    ApplyTemplateToControl(warnerChild, ZO_GetPlatformTemplate(warnerTemplates.texture))
    ApplyTemplateToControl(warnerChild, ZO_GetPlatformTemplate(warnerTemplates[direction]))
    end
    for i, subBar in pairs(bar.barControls) do
    ApplyTemplateToControl(subBar, ZO_GetPlatformTemplate(PAB_TEMPLATES.statusBar))
    local gloss = subBar:GetNamedChild("Gloss")
    ApplyTemplateToControl(gloss, ZO_GetPlatformTemplate(PAB_TEMPLATES.statusBarGloss))
    local anchorTemplates = powerTypeTemplates.anchors
    if anchorTemplates then
    subBar:ClearAnchors()
    ApplyTemplateToControl(subBar, ZO_GetPlatformTemplate(anchorTemplates))
    else
    ApplyTemplateToControl(subBar, ZO_GetPlatformTemplate(PAB_TEMPLATES.anchor))
    end
    end
    local resourceNumbersLabel = bar:GetNamedChild("ResourceNumbers")
    if resourceNumbersLabel then
    ApplyTemplateToControl(resourceNumbersLabel, ZO_GetPlatformTemplate(PAB_TEMPLATES.resourceNumbersLabel))
    end
    end

    for i=1,7 do
    window = WINDOW_MANAGER:CreateControlFromVirtual("PetHealth"..i, base, "PetHealth_ZOSStyleBar")
    local windowHeight = window:GetHeight()
    window.label, ctrl = AddControl(window, CT_LABEL, 10)
    ctrl:SetFont("$(BOLD_FONT)|$(KB_16)|soft-shadow-thin")
    ctrl:SetColor(GetInterfaceColor(INTERFACE_COLOR_TYPE_TEXT_COLORS, INTERFACE_TEXT_COLOR_NORMAL))
    ctrl:SetDimensions(baseWidth, windowHeight*0.4)
    ctrl:SetAnchor(BOTTOMLEFT, window, TOPLEFT, 0, -10.5)
    ctrl:SetAlpha(GetAlphaFromControl(savedVars.showLabels))
    window.barleft = window:GetNamedChild("BarLeft")
    window.barright = window:GetNamedChild("BarRight")
    window.barControls = { window.barleft, window.barright }
    window.powerType = POWERTYPE_HEALTH
    SetColors(window)
    ApplyStyle(window)
    window.shieldleft = window:GetNamedChild("ShieldLeft")
    window.shieldright = window:GetNamedChild("ShieldRight")
    window.values = window:GetNamedChild("ResourceNumbers")
    window.values:SetAlpha(GetAlphaFromControl(savedVars.showValues))
    window.warner = PetHealthWarner:New(window)
    if i == 1 then
    window:SetAnchor(TOP, base, TOP, 0, 18)
    else
    window:SetAnchor(TOP, window[i-1], BOTTOM, 0, 20)
    end
    end
    end

    PET_BAR_FRAGMENT = ZO_HUDFadeSceneFragment:New(base)
    HUD_SCENE:AddFragment(PET_BAR_FRAGMENT)
    HUD_UI_SCENE:AddFragment(PET_BAR_FRAGMENT)
    PET_BAR_FRAGMENT:SetHiddenForReason("NoPetOrOnlyInCombat", true)
    end


    -- INIT --
    local function LoadEvents()
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_POWER_UPDATE, OnHealthUpdate)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_CREATED, function(_, unitTag)
    if IsUnitValidPet(unitTag) then
    GetActivePets()
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_DESTROYED, function(_, unitTag)
    PetUnSummonedAlerts(unitTag)
    local key = GetKeyWithData(unitTag)
    if key ~= nil then
    table.remove(currentPets, key)
    local countPets = GetTableSize(currentPets)
    if countPets > 0 then
    for i = 1, countPets do
    local name = currentPets.unitName
    local control = window.label
    unitTag = currentPets.unitTag
    if GetControlText(control) ~= name then
    window.label:SetText(name)
    end
    GetHealth(unitTag)
    GetShield(unitTag)
    end
    end
    RefreshPetWindow()
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_PLAYER_DEAD, GetActivePets)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_DEATH_STATE_CHANGE, GetActivePets)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_ACTION_SLOT_ABILITY_SLOTTED, GetActivePets)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_POWER_UPDATE, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_CREATED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_DESTROYED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_PLAYER_DEAD, REGISTER_FILTER_UNIT_TAG, UNIT_PLAYER_TAG)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_DEATH_STATE_CHANGE, REGISTER_FILTER_UNIT_TAG, UNIT_PLAYER_TAG)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_ADDED, function(_, unitTag, unitAttributeVisual, _, _, _, value, maxValue)
    if unitAttributeVisual == ATTRIBUTE_VISUAL_POWER_SHIELDING then
    OnShieldUpdate(nil, unitTag, value, maxValue, "true")
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_REMOVED, function(_, unitTag, unitAttributeVisual, _, _, _, value, maxValue)
    if unitAttributeVisual == ATTRIBUTE_VISUAL_POWER_SHIELDING then
    OnShieldUpdate("removed", unitTag, value, maxValue, "false")
    end
    end)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_UPDATED, function(_, unitTag, unitAttributeVisual, _, _, _, _, newValue, _, newMaxValue)
    if unitAttributeVisual == ATTRIBUTE_VISUAL_POWER_SHIELDING then
    OnShieldUpdate(nil, unitTag, newValue, newMaxValue, "false")
    end
    end)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_ADDED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_REMOVED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_UNIT_ATTRIBUTE_VISUAL_UPDATED, REGISTER_FILTER_UNIT_TAG_PREFIX, UNIT_PLAYER_PET)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_INTERFACE_SETTING_CHANGED, GetActivePets)
    EVENT_MANAGER:AddFilterForEvent(addon.name, EVENT_INTERFACE_SETTING_CHANGED, REGISTER_FILTER_SETTING_SYSTEM_TYPE, SETTING_TYPE_UI)
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_PLAYER_COMBAT_STATE, OnPlayerCombatState)
    OnPlayerCombatState(_, IsUnitInCombat(UNIT_PLAYER_TAG))
    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_PLAYER_ACTIVATED, function() zo_callLater(function() GetActivePets() end, 75) end)
    end

    function PetHealth.changeCombatState()
    OnPlayerCombatState(_, IsUnitInCombat(UNIT_PLAYER_TAG))
    end

    function PetHealth.hideInDungeon(toValue)
    hideInDungeon = toValue
    RefreshPetWindow()
    end

    function PetHealth.changeBackground(toValue)
    background:SetAlpha(GetAlphaFromControl(toValue))
    end

    function PetHealth.changeValues(toValue)
    for i=1,7 do
    if window then
    window.values:SetAlpha(GetAlphaFromControl(toValue))
    end
    end
    end

    function PetHealth.changeLabels(toValue)
    for i=1,7 do
    if window then
    window.label:SetAlpha(GetAlphaFromControl(toValue))
    end
    end
    end

    function PetHealth.lockPetWindow(toValue)
    lockWindow = toValue
    end

    function PetHealth.lowHealthAlertPercentage(toValue)
    lowHealthAlertPercentage = toValue
    end

    function PetHealth.lowShieldAlertPercentage(toValue)
    lowShieldAlertPercentage = toValue
    end

    function PetHealth.unsummonedAlerts(toValue)
    unsummonedAlerts = toValue
    end

    function PetHealth.onlyInCombatHealthPercentage(toValue)
    onlyInCombatHealthPercentage = toValue
    end

    local function SlashCommands()
    LSC:Register("/pethealthcombat", function()
    savedVars.onlyInCombat = not savedVars.onlyInCombat
    if savedVars.onlyInCombat then
    ChatOutput(GetString(SI_PET_HEALTH_COMBAT_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_COMBAT_DEACTIVATED))
    end
    PetHealth.changeCombatState()
    end, GetString(SI_PET_HEALTH_LSC_COMBAT))

    LSC:Register("/pethealthhideindungeon", function()
    savedVars.hideInDungeon = not savedVars.hideInDungeon
    if savedVars.hideInDungeon then
    ChatOutput(GetString(SI_PET_HEALTH_HIDE_IN_DUNGEON_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_HIDE_IN_DUNGEON_DEACTIVATED))
    end
    PetHealth.hideInDungeon()
    end, GetString(SI_PET_HEALTH_LSC_DUNGEON))

    LSC:Register("/pethealthvalues", function()
    savedVars.showValues = not savedVars.showValues
    if savedVars.showValues then
    ChatOutput(GetString(SI_PET_HEALTH_VALUES_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_VALUES_DEACTIVATED))
    end
    PetHealth.changeValues(savedVars.showValues)
    end, GetString(SI_PET_HEALTH_LSC_VALUES))

    LSC:Register("/pethealthlabels", function()
    savedVars.showLabels = not savedVars.showLabels
    if savedVars.showLabels then
    ChatOutput(GetString(SI_PET_HEALTH_LABELS_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_LABELS_DEACTIVATED))
    end
    PetHealth.changeLabels(savedVars.showLabels)
    end, GetString(SI_PET_HEALTH_LSC_LABELS))

    if not savedVars.useZosStyle then
    LSC:Register("/pethealthbackground", function()
    savedVars.showBackground = not savedVars.showBackground
    if savedVars.showBackground then
    ChatOutput(GetString(SI_PET_HEALTH_BACKGROUND_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_BACKGROUND_DEACTIVATED))
    end
    PetHealth.changeBackground(savedVars.showBackground)
    end, GetString(SI_PET_HEALTH_LSC_BACKGROUND))
    else
    showBackground = false
    savedVars.showBackground = false
    end

    LSC:Register("/pethealthunsummonedalerts", function()
    savedVars.petUnsummonedAlerts = not savedVars.petUnsummonedAlerts
    if savedVars.petUnsummonedAlerts then
    ChatOutput(GetString(SI_PET_HEALTH_UNSUMMONEDALERTS_ACTIVATED))
    else
    ChatOutput(GetString(SI_PET_HEALTH_UNSUMMONEDALERTS_DEACTIVATED))
    end
    PetHealth.unsummonedAlerts(savedVars.petUnsummonedAlerts)
    end, GetString(SI_PET_HEALTH_LSC_UNSUMMONEDALERTS))

    LSC:Register("/pethealthwarnhealth", function(healthValuePercent)
    if healthValuePercent == nil or healthValuePercent == "" then
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_HEALTH_WARN) .. ": " .. tostring(savedVars.lowHealthAlertSlider))
    else
    local healthValuePercentNumber = tonumber(healthValuePercent)
    if type(healthValuePercentNumber) == "number" then
    if healthValuePercentNumber <= 0 then healthValuePercentNumber = 0 end
    if healthValuePercentNumber >= 100 then healthValuePercentNumber = 99 end
    savedVars.lowHealthAlertSlider = healthValuePercentNumber
    PetHealth.lowHealthAlertPercentage(healthValuePercentNumber)
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_HEALTH_WARN) .. ": " .. tostring(healthValuePercentNumber))
    end
    end
    end, GetString(SI_PET_HEALTH_LSC_WARN_HEALTH))

    LSC:Register("/pethealthwarnshield", function(shieldValuePercent)
    if shieldValuePercent == nil or shieldValuePercent == "" then
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_SHIELD_WARN) .. ": " .. tostring(savedVars.lowShieldAlertSlider))
    else
    local shieldValuePercentNumber = tonumber(shieldValuePercent)
    if type(shieldValuePercentNumber) == "number" then
    if shieldValuePercentNumber <= 0 then shieldValuePercentNumber = 0 end
    if shieldValuePercentNumber >= 100 then shieldValuePercentNumber = 99 end
    savedVars.lowShieldAlertSlider = shieldValuePercentNumber
    PetHealth.lowShieldAlertPercentage(shieldValuePercentNumber)
    ChatOutput(GetString(SI_PET_HEALTH_LAM_LOW_SHIELD_WARN) .. ": " .. tostring(shieldValuePercentNumber))
    end
    end
    end, GetString(SI_PET_HEALTH_LSC_WARN_SHIELD))

    LSC:Register("/pethealthcombathealth", function(combatHealthValuePercent)
    if combatHealthValuePercent == nil or combatHealthValuePercent == "" then
    ChatOutput(GetString(SI_PET_HEALTH_LAM_ONLY_IN_COMBAT_HEALTH) .. ": " .. tostring(savedVars.onlyInCombatHealthSlider))
    else
    local combatHealthPercentNumber = tonumber(combatHealthValuePercent)
    if type(combatHealthPercentNumber) == "number" then
    if combatHealthPercentNumber <= 0 then combatHealthPercentNumber = 0 end
    if combatHealthPercentNumber >= 100 then combatHealthPercentNumber = 99 end
    savedVars.onlyInCombatHealthSlider = combatHealthPercentNumber
    PetHealth.onlyInCombatHealthPercentage(combatHealthPercentNumber)
    ChatOutput(GetString(SI_PET_HEALTH_LAM_ONLY_IN_COMBAT_HEALTH) .. ": " .. tostring(combatHealthPercentNumber))
    end
    end
    end, GetString(SI_PET_HEALTH_LSC_COMBAT_HEALTH))
    end

    local function OnAddOnLoaded(_, addonName)
    if addonName ~= addon.name then return end
    EVENT_MANAGER:UnregisterForEvent(addon.name, EVENT_ADD_ON_LOADED)

    savedVars = ZO_SavedVars:NewAccountWide(addon.savedVarName, addon.savedVarVersion, nil, default, GetWorldName())
    if savedVars.saveMode == 1 then
    savedVars = ZO_SavedVars:NewCharacterIdSettings(addon.savedVarName, addon.savedVarVersion, nil, default, GetWorldName())
    end

    PetHealth.savedVars = savedVars
    PetHealth.savedVarsDefault = default
    lowHealthAlertPercentage = savedVars.lowHealthAlertSlider
    lowShieldAlertPercentage = savedVars.lowShieldAlertSlider
    unsummonedAlerts = savedVars.petUnsummonedAlerts
    onlyInCombatHealthPercentage = savedVars.onlyInCombatHealthSlider

    local getUnitClassId = GetUnitClassId(UNIT_PLAYER_TAG)
    local supportedClasses = PetHealth.supportedClasses
    local supportedClass = supportedClasses[getUnitClassId] or false
    if not supportedClass then
    ChatOutput("[PetHealth] " .. GetString(SI_PET_HEALTH_CLASS))
    return
    end

    local isLAMActive = CheckAddon('LibAddonMenu-2.0')
    local isLSCActive = CheckAddon('LibSlashCommander')

    if isLAMActive then
    LAM = LibAddonMenu2
    PetHealth.LAM = LAM
    PetHealth.buildLAMAddonMenu()
    end

    if isLSCActive then
    LSC = LibSlashCommander
    SlashCommands()
    end

    CreateWarner()
    CreateControls()
    LoadEvents()
    end

    EVENT_MANAGER:RegisterForEvent(addon.name, EVENT_ADD_ON_LOADED, OnAddOnLoaded)
    Edited by Uazole2 on June 15, 2025 2:34AM
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • Uazole2
    Uazole2
    ✭✭✭
    The above code is a revised PetHealth.lua file. I have been using it successfully. It will allow the Pet Health addon to work with all classes and it will also allow health bars for more than 2 pets, follow the directions below to make it work

    1. Copy the entire code (it is long)
    2. Open notepad or notepad++ (preferred)
    3. Paste the code into the notepad program
    4. Open file browser (optional)
    5. Go to the PetHealth folder inside your addons folder (optional)
    6. rename PetHealth.lua to another name (optional)
    7. In notepad program "Save As " PetHealth.lua" inside the PetHealth addon folder
    8. done
    9. /reloadui if you are in the game
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • Uazole2
    Uazole2
    ✭✭✭
    Just realized that something is unclear. The above code is a modified PetHealth.lua file, it is not a complete addon.

    You must have the addon called PetHealth by goobsnake already installed.

    The above code is then saved and replaces the original PetHealth.lua file, inside the PetHealth addon's folder. The version number was not changed, so no other files will need to be modified
    Edited by Uazole2 on June 17, 2025 2:08PM
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • Uazole2
    Uazole2
    ✭✭✭
    I zipped the folder, with all files. Copy attached. Install normally
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
  • Uazole2
    Uazole2
    ✭✭✭
    If you can help get this uploaded to esoui, LMK.
    I ALWAYS believe I know the answer. Turns out I rarely understand the question.

    Player since BETA (February 2014) and have the monkey to prove it. Level 50 in all classes and have mastered all normal skill lines. Still working on mastering all subclassed skill lines.
Sign In or Register to comment.