commit 3eb87180a3a55f756adb249cdd21e3f623f57c14 Author: shaga Date: Sun Jun 28 21:34:44 2026 -0700 Fork of RavenOfSeven/LifeSafer_LowHealthWarning for OctoWoW Only change vs upstream: lowercase the toc Title color escapes (|C... / |R -> |c... / |r) so the OctoLauncher addon browser renders the gradient name instead of the raw escape codes. Addon code unchanged. Co-Authored-By: Claude Opus 4.8 diff --git a/LifeSafer_LowHealthWarning.toc b/LifeSafer_LowHealthWarning.toc new file mode 100644 index 0000000..50a37ef --- /dev/null +++ b/LifeSafer_LowHealthWarning.toc @@ -0,0 +1,11 @@ +## Interface: 11200 +## Title: |cFFB700B7Li|cFFFF00FFfe|cFFFF50FFSa|cFFFF99FFf|cFFFFC4FFe|cFFFFFFFFr|cFFFFC4FFLO|cFFFF99FFw|cFFFF50FFHea|cFFFF00FFlth |cFFB700B7Wa|cFFFFFFFFrn|cFF7FFF7Fing|r πŸ¦‡πŸ¦‡ +## Author: |cFF9482C9Silent|r ported and merged by tamara09πŸ¦‡πŸ¦‡ +## Version: 0.7a +## Notes: Enables a flashing red effect when you are low on life, and a flasing blue effect when you're low on mana. This warning system was implemented by Blizzard but is not in use.|r +## SavedVariablesPerCharacter: LifeSafer_LowHealth_Thresholds, LifeSafer_LowHealth_Enabled, LifeSafer_LowHealth_SoundCombatOnly, LifeSafer_LowHealth_HealthCombatOnly, LifeSafer_LowHealth_ManaCombatOnly, LifeSafer_LowHealth_SoundEnabled, LifeSafer_LowHealth_HealthEnabled, LifeSafer_LowHealth_ManaEnabled, LifeSafer_LowHealth_HealthSync, LifeSafer_LowHealth_ManaSync, LifeSafer_LowHealth_HealthEmote, LifeSafer_LowHealth_ManaEmote, LifeSafer_LowHealth_UseAltTextures, LifeSafer_Nightfall_Enabled, LifeSafer_Nightfall_ShardEnabled, LifeSafer_Nightfall_UseLargeEffect, +LowHealth.xml +LowHealthSettings.xml +LifeSafer_Nightfall_SoundEffects +Nightfall.xml +NightfallSettings.xml diff --git a/LowHealth.lua b/LowHealth.lua new file mode 100644 index 0000000..abe4f3b --- /dev/null +++ b/LowHealth.lua @@ -0,0 +1,285 @@ +function LifeSafer_LowHealth_OnLoad() + + local health, mana, sound = "UNIT_HEALTH", "UNIT_MANA", "SOUND" + + LifeSafer_LowHealth_Version = "2.2.2" + + -- these are the default values + LifeSafer_LowHealth_Default_Enabled = true + LifeSafer_LowHealth_Default_SoundEnabled = true + LifeSafer_LowHealth_Default_HealthEnabled = true + LifeSafer_LowHealth_Default_ManaEnabled = true + LifeSafer_LowHealth_Default_SoundCombatOnly = false + LifeSafer_LowHealth_Default_HealthCombatOnly = false + LifeSafer_LowHealth_Default_ManaCombatOnly = false + LifeSafer_LowHealth_Default_HealthSync = false + LifeSafer_LowHealth_Default_ManaSync = false + LifeSafer_LowHealth_Default_HealthEmote = false + LifeSafer_LowHealth_Default_ManaEmote = false + LifeSafer_LowHealth_Default_UseAltTextures = false + LifeSafer_LowHealth_Default_Thresholds = { + [health] = { [1] = 40, [2] = 20 }, -- regular and critical, in percent + [mana] = { [1] = 40, [2] = 20 }, + [sound] = { [1] = 50 }, + } + + -- set variables to their default values. these will be overriden when variables are loaded + LifeSafer_LowHealth_Enabled = LifeSafer_LowHealth_Default_Enabled + LifeSafer_LowHealth_SoundEnabled = LifeSafer_LowHealth_Default_SoundEnabled + LifeSafer_LowHealth_HealthEnabled = LifeSafer_LowHealth_Default_HealthEnabled + LifeSafer_LowHealth_ManaEnabled = LifeSafer_LowHealth_Default_ManaEnabled + LifeSafer_LowHealth_SoundCombatOnly = LifeSafer_LowHealth_Default_SoundCombatOnly + LifeSafer_LowHealth_HealthCombatOnly = LifeSafer_LowHealth_Default_HealthCombatOnly + LifeSafer_LowHealth_ManaCombatOnly = LifeSafer_LowHealth_Default_ManaCombatOnly + LifeSafer_LowHealth_HealthSync = LifeSafer_LowHealth_Default_HealthSync + LifeSafer_LowHealth_ManaSync = LifeSafer_LowHealth_Default_ManaSync + LifeSafer_LowHealth_HealthEmote = LifeSafer_LowHealth_Default_HealthEmote + LifeSafer_LowHealth_ManaEmote = LifeSafer_LowHealth_Default_ManaEmote + LifeSafer_LowHealth_Thresholds = LifeSafer_LowHealth_Default_Thresholds + LifeSafer_LowHealth_UseAltTextures = LifeSafer_LowHealth_Default_UseAltTextures + + LifeSafer_LowHealth_HealthFrame = LowHealthFrame + LifeSafer_LowHealth_ManaFrame = OutOfControlFrame + + -- these values keep track of animation + this.states = { [health] = 0, [mana] = 0 } + + -- change the blue frame to act as the red one + OutOfControlFrame:ClearAllPoints() + OutOfControlFrame:SetParent("WorldFrame") + OutOfControlFrame:SetAllPoints("WorldFrame") + + -- set both frames transparent to avoid onload flashing + LowHealthFrame:SetAlpha(0) + OutOfControlFrame:SetAlpha(0) + + -- command line + SlashCmdList["LOWHEALTHWARNINGCMD"] = LifeSafer_LowHealth_CMD + SLASH_LOWHEALTHWARNINGCMD1 = "/lowhealthwarning" + SLASH_LOWHEALTHWARNINGCMD2 = "/lowhealth" + SLASH_LOWHEALTHWARNINGCMD3 = "/lhw" + +end + + +function LifeSafer_LowHealth_ApplySettings() + + -- for compatibility with older versions of saved variables + if (not LifeSafer_LowHealth_Thresholds["SOUND"]) then + LifeSafer_LowHealth_Thresholds["SOUND"] = { [1] = 30 } + end + + local frame = getglobal("LowHealthWarningFrame") + + LifeSafer_LowHealth_FlashFrameStop(LifeSafer_LowHealth_HealthFrame, "UNIT_HEALTH") + LifeSafer_LowHealth_FlashFrameStop(LifeSafer_LowHealth_ManaFrame, "UNIT_MANA") + + -- register or unregister events + if (LifeSafer_LowHealth_Enabled) then + LifeSafer_LowHealth_HealthFrame = LifeSafer_LowHealth_UseAltTextures and LowHealthWarningFrameAltTexture1 or LowHealthFrame + frame:RegisterEvent("UNIT_DISPLAYPOWER") + if (LifeSafer_LowHealth_HealthEnabled or LifeSafer_LowHealth_SoundEnabled) then + frame:RegisterEvent("UNIT_HEALTH") + end + LifeSafer_LowHealth_ManaCheck() + LifeSafer_LowHealth_ManaFrame = LifeSafer_LowHealth_UseAltTextures and LowHealthWarningFrameAltTexture2 or OutOfControlFrame + else + frame:UnregisterEvent("UNIT_HEALTH") + frame:UnregisterEvent("UNIT_MANA") + frame:UnregisterEvent("UNIT_DISPLAYPOWER") + end + +end + + +function LifeSafer_LowHealth_FlashFrame(value, regular, critical, frame, state) + + local REGULAR_FLASH, CRITICAL_FLASH = 1, 2 + + -- disable + if (value > regular or LifeSafer_LowHealth_CombatCheck(state) or UnitIsDeadOrGhost("player")) then + if (UIFrameIsFlashing(frame) or UIFrameIsFading(frame)) then + LifeSafer_LowHealth_FlashFrameStop(frame, state) + end + -- regular flash + elseif (value > critical) then + if (not (LowHealthWarningFrame.states[state] == REGULAR_FLASH)) then + if (UIFrameIsFlashing(frame)) then + --frame.flashDuration = frame.flashDuration + 10 + frame.fadeInTime = 0.4 + frame.fadeOutTime = 0.6 + frame.flashInHoldTime = 1 + else + UIFrameFlash(frame, 0.4, 0.6, -1, nil, 1, 0) + end + LowHealthWarningFrame.states[state] = REGULAR_FLASH + end + -- critical flash + else + if (not (LowHealthWarningFrame.states[state] == CRITICAL_FLASH)) then + if (LifeSafer_LowHealth_HealthEmote and state == "UNIT_HEALTH") then DoEmote("healme") end + if (LifeSafer_LowHealth_ManaEmote and state == "UNIT_MANA") then DoEmote("oom") end + if (UIFrameIsFlashing(frame)) then + --frame.flashDuration = frame.flashDuration + 10 + frame.fadeInTime = 0.2 + frame.fadeOutTime = 0.8 + frame.flashInHoldTime = 0 + else + UIFrameFlash(frame, 0.2, 0.8, -1, nil, 0, 0) + end + LowHealthWarningFrame.states[state] = CRITICAL_FLASH + end + end + +end + +function LifeSafer_LowHealth_SmoothFlashFrame(value, threshold, frame, state) + + -- disable + if (value > threshold or LifeSafer_LowHealth_CombatCheck(state) or UnitIsDeadOrGhost("player")) then + LifeSafer_LowHealth_FlashFrameStop(frame, state) + -- smooth flash + else + local rate = LifeSafer_LowHealth_GetHeartRate(state) + local flashIn, flashOut = rate * 0.1, rate * 0.4 + if (UIFrameIsFlashing(frame)) then + frame.flashDuration = frame.flashDuration + rate + 1 + if (rate < 1) then + frame.fadeInTime = rate * 0.15 + frame.fadeOutTime = rate * 0.85 + frame.flashInHoldTime = 0 + else + frame.fadeInTime = 0.2 + frame.fadeOutTime = 0.8 + frame.flashInHoldTime = rate - 1 + end + else + UIFrameFlash(frame, 0.2, 0.8, 10, nil, rate -1, 0) + end + end + +end + + +function LifeSafer_LowHealth_FlashFrameStop(frame, state) + + UIFrameFlashRemoveFrame(frame) + UIFrameFadeRemoveFrame(frame) + UIFrameFadeOut(frame, 0.5, frame:GetAlpha(), 0) + LowHealthWarningFrame.states[state] = 0 + +end + + +function LifeSafer_LowHealth_OnEvent() + + if (event == "UNIT_DISPLAYPOWER") then + LifeSafer_LowHealth_ManaCheck() + return + end + + -- unpacks regular threshold, critical threshold, nil, nil, nil + local t1, t2, value, frame, smooth = unpack(LifeSafer_LowHealth_Thresholds[event]) + + -- pick frame based on event + if (event == "UNIT_HEALTH") then + value = UnitHealth("player") / UnitHealthMax("player") + LifeSafer_LowHealth_SoundCheck(this, value * 100, unpack(LifeSafer_LowHealth_Thresholds["SOUND"])) + if (not LifeSafer_LowHealth_HealthEnabled) then + return -- this hack is because both sound and health flash need this event + end + frame = LifeSafer_LowHealth_HealthFrame + smooth = LifeSafer_LowHealth_HealthSync + elseif (event == "UNIT_MANA") then + value = UnitMana("player") / UnitManaMax("player") + frame = LifeSafer_LowHealth_ManaFrame + smooth = LifeSafer_LowHealth_ManaSync + end + + if (smooth) then -- flash differently depending on settings + LifeSafer_LowHealth_SmoothFlashFrame(value * 100, t1, frame, event) + else + LifeSafer_LowHealth_FlashFrame(value * 100, t1, t2, frame, event) + end + +end + + +function LifeSafer_LowHealth_CombatCheck(state) + + local combat = UnitAffectingCombat("player") + if (not state or state == "UNIT_HEALTH") then + return LifeSafer_LowHealth_HealthCombatOnly and not combat + elseif (state == "UNIT_MANA") then + return LifeSafer_LowHealth_ManaCombatOnly and not combat + else + return LifeSafer_LowHealth_SoundCombatOnly and not combat + end + +end + + +function LifeSafer_LowHealth_SoundCheck(frame, value, threshold) + + -- disable + if (value > threshold or not LifeSafer_LowHealth_SoundEnabled or LifeSafer_LowHealth_CombatCheck("SOUND") or UnitIsDeadOrGhost("player")) then + if (frame.heartRate) then + frame:SetScript("OnUpdate", nil) + frame.heartRate = nil + end + -- regular + elseif (not frame.heartRate) then + frame:SetScript("OnUpdate", LifeSafer_LowHealth_OnUpdate) + frame.timer = 1 + frame.heartRate = 0 + end +end + + +function LifeSafer_LowHealth_ManaCheck() + + local mana, frame = "UNIT_MANA", getglobal("LowHealthWarningFrame") + if (LifeSafer_LowHealth_ManaEnabled and UnitPowerType("player") == 0) then + -- player is using mana + frame:RegisterEvent(mana) + else + frame:UnregisterEvent(mana) + LifeSafer_LowHealth_FlashFrameStop(LifeSafer_LowHealth_ManaFrame, mana) + end +end + + +function LifeSafer_LowHealth_OnUpdate() + + if (this.timer > this.heartRate) then + PlaySoundFile("Interface\\AddOns\\LifeSafer_LowHealthWarning\\Sounds\\LifeSafer_LifeAlert_Low.mp3") + this.heartRate = LifeSafer_LowHealth_GetHeartRate() + this.timer = 0 + else + this.timer = this.timer + arg1 + end + +end + + +function LifeSafer_LowHealth_GetHeartRate(state) + if (state and state == "UNIT_MANA") then + return 0.5 + 2.0 * UnitMana("player") / UnitManaMax("player") + else + return 0.5 + 2.0 * UnitHealth("player") / UnitHealthMax("player") + end +end + + +function LifeSafer_LowHealth_CMD(arg1) + + if (not arg1) then + return + end + + if (string.find(arg1, "^settings$") or arg1 == "") then + ShowUIPanel(LowHealthSettingsFrame) + return + end + +end diff --git a/LowHealth.xml b/LowHealth.xml new file mode 100644 index 0000000..653923d --- /dev/null +++ b/LowHealth.xml @@ -0,0 +1,49 @@ + + + +