commit 46dca62dc043f5d37df6980e3cf8b8ff0423c9cb Author: Dusk-92 Date: Sun Aug 9 09:02:43 2026 +0200 Add files via upload diff --git a/HealerProtection.toc b/HealerProtection.toc new file mode 100644 index 0000000..e139df7 --- /dev/null +++ b/HealerProtection.toc @@ -0,0 +1,10 @@ +## Interface: 11200 +## Title: HealerProtection +## Notes: Alert your group when you run out of mana, take aggro, or are about to die! Designed for OctoWow. +## Author: Dusk92 +## Version: 1.0-OctoWow +## SavedVariablesPerCharacter: HPTABPC +## OptionalDeps: SuperWow, ClassicAPI, NamPower + +# Fichiers de l'addon +core.lua \ No newline at end of file diff --git a/core.lua b/core.lua new file mode 100644 index 0000000..6e0bfdd --- /dev/null +++ b/core.lua @@ -0,0 +1,744 @@ +-- HealerProtection Vanilla / Turtle WoW Port +-- Ported for the 1.12.1 client (with SuperWow / ClassicAPI support) +-- Optimized version: cached lookups, removed unused temp table, fixed NEARDEATH bug, removed role detection (Force Healer bypass always on) +-- Optimization pass 2: SETOOMP getglobal only runs when actually needed, GetLang()/GetLocale() cached +-- in ToCurrentChat instead of being recomputed up to 5 times, IsPlayerInBG() cache shared with CanWriteToChat + +HealerProtection = HealerProtection or {} +local HPDEBUG = false +local warning_aggro = nil +local nearoom = false +local oom = false +local aggro = false +local isdead = false +local isChanneling = false +local onceM1 = false +local onceM2 = false +local lasttarget = "" +local delayrange = GetTime() +local outsideOfGroup = false + +-- ============================================================================ +-- UTILS AND VANILLA 1.12 COMPATIBILITY +-- ============================================================================ + +-- Cleanly retrieves the number of group members +local function GetGroupMembersCount() +if GetNumGroupMembers then return GetNumGroupMembers() end +local raid = GetNumRaidMembers() +if raid > 0 then return raid end +return GetNumPartyMembers() +end + +-- Checks if the player is in a battleground +local function IsPlayerInBG() +for i = 1, (MAX_BATTLEFIELD_QUEUES or 3) do +local status = GetBattlefieldStatus(i) +if status == "active" then +return true +end +end +return false +end + +-- Mana resource abstraction +local function GetUnitPower(unit) +if UnitPower then return UnitPower(unit) end +return UnitMana(unit) +end + +local function GetUnitPowerMax(unit) +if UnitPowerMax then return UnitPowerMax(unit) end +return UnitManaMax(unit) +end + +local function GetUnitPowerType(unit) +if UnitPowerType then +local _, token = UnitPowerType(unit) +return token +end +local t = UnitManaType(unit) +if t == 0 then return "MANA" end +return "OTHER" +end + +-- Safe wrapper for C_Timer.After +local function SafeAfter(delay, func) +if C_Timer and C_Timer.After then +C_Timer.After(delay, func) +else +local fAfter = CreateFrame("Frame") +local elapsed_time = 0 +fAfter:SetScript("OnUpdate", function(self, elapsed) +local dt = elapsed or arg1 or 0 +elapsed_time = elapsed_time + dt +if elapsed_time >= delay then +fAfter:SetScript("OnUpdate", nil) +func() +end +end) +end +end + +-- Simplified aggro detection (adapts if SuperWow is installed) +local function GetThreatSituation() +if UnitThreatSituation then +return UnitThreatSituation("player") +else +if UnitExists("target") and not UnitIsFriend("player", "target") then +if UnitIsUnit("targettarget", "player") then +return 3 +end +end +return 0 +end +end + +-- ============================================================================ +-- HEALERPROTECTION FUNCTIONS +-- ============================================================================ + +-- Optimization: accepts an already computed channel to avoid recomputing GetCurrentChannel() +-- on every single call (PrintChat can call this up to 6 times per tick) +function HealerProtection:AllowedTo(cachedChannel) +local channel = cachedChannel or HealerProtection:GetCurrentChannel() + +if HealerProtection:DBGV("printnothing", false) then +return false +end + +-- SAY and YELL don't require being in a party/raid/guild to make sense +if channel == "SAY" or channel == "YELL" then +return true +end + +-- GUILD requires actually being in a guild +if channel == "GUILD" then +if GetGuildInfo("player") ~= nil or HPDEBUG then +return true +end +if outsideOfGroup == false then +outsideOfGroup = true +HealerProtection:INFO("Not in a Guild") +end +return false +end + +-- RAID requires actually being in a raid (being in a simple party is not enough) +if channel == "RAID" then +if GetNumRaidMembers() > 0 or HPDEBUG then +return true +end +if outsideOfGroup == false then +outsideOfGroup = true +HealerProtection:INFO("Not in a Raid") +end +return false +end + +-- PARTY (and AUTO, already resolved to PARTY/RAID by GetCurrentChannel) +if GetGroupMembersCount() > 0 or HPDEBUG then +return true +end + +if outsideOfGroup == false then +outsideOfGroup = true +HealerProtection:INFO("Not in a Party/Raid") +end + +return false +end + +function HealerProtection:GetCurrentChannel() +local _channel = "PARTY" +if HealerProtection:DBGV("channelchat", "AUTO") == "AUTO" then +if GetNumRaidMembers() > 0 then +_channel = "RAID" +elseif GetNumPartyMembers() > 0 then +_channel = "PARTY" +end +else +_channel = HealerProtection:DBGV("channelchat", "AUTO") +end + +return _channel +end + +function HealerProtection:InInstance() +if HPDEBUG then return true end +local is, _ = IsInInstance() +return is +end + +-- Optimization: accepts an already computed inInstance to avoid a redundant call to InInstance() +-- Optimization: accepts an already computed inBG to avoid a redundant call to IsPlayerInBG() +function HealerProtection:CanWriteToChat(chan, cachedInInstance, cachedInBG) +local inInstance = cachedInInstance +if inInstance == nil then +inInstance = HealerProtection:InInstance() +end + +local inBG = cachedInBG +if inBG == nil then +inBG = IsPlayerInBG() +end + +if onceM1 and not inInstance and HealerProtection:DBGV("showoutsideofinstance", false) == false then +onceM1 = false +HealerProtection:MSG("Only shows Messages in Instances.") +end + +if inInstance or HealerProtection:DBGV("showoutsideofinstance", false) then +if HealerProtection:DBGV("printnothing", false) == true then +if onceM2 then +onceM2 = false +HealerProtection:MSG("\"Print Nothing\" is enabled.") +end + +return false +elseif inBG and HealerProtection:DBGV("showinbgs", false) == false then +return false +elseif (GetNumRaidMembers() > 0) and HealerProtection:DBGV("showinraids", true) == false then +return false +else +return true +end +end + +return false +end + +function HealerProtection:GetLang() +-- Optimization: GetLocale() cached once instead of being called up to 3 times below +local locale = GetLocale() +if locale == "enUS" then return locale end +if HealerProtection:DBGV("showonlyenglish", false) then return "enUS" end +if HealerProtection:DBGV("showonlytranslation", false) then return locale end +if HealerProtection:DBGV("showtranslation", true) then return "enUS," .. locale end + +return "enUS" +end + +function HealerProtection:ToCurrentChat(formatStr, val1text, val1val, val2text, val2val, cachedChannel, cachedInInstance, cachedInBG) +local inInstance = cachedInInstance +if inInstance == nil then +inInstance = HealerProtection:InInstance() +end + +-- Optimization: computed once here and passed down to CanWriteToChat below, +-- instead of letting IsPlayerInBG() run a second time inside it. +local inBG = cachedInBG +if inBG == nil then +inBG = IsPlayerInBG() +end + +local _channel = cachedChannel or HealerProtection:GetCurrentChannel() +local prefix = HealerProtection:DBGV("prefix", "[Healer Protection]") +local suffix = HealerProtection:DBGV("suffix", "") +if prefix ~= "" and prefix ~= " " then +prefix = prefix .. " " +elseif prefix == " " then +prefix = "" +end + +if suffix ~= "" and suffix ~= " " then +suffix = " " .. suffix +elseif suffix == " " then +suffix = "" +end + +-- Optimization: reuse of already computed inInstance / inBG, avoids extra calls +if HealerProtection:CanWriteToChat(_channel, inInstance, inBG) then +-- Optimization: GetLang() and GetLocale() cached once instead of being recomputed +-- (each re-running its own DBGV / GetLocale lookups) up to 5 times in the block below +local lang = HealerProtection:GetLang() +local locale = GetLocale() +local msg = "" +if lang == "enUS" then +if val2text then +msg = string.format(formatStr, HealerProtection:TryTrans(val1text, "enUS", val1val), HealerProtection:TryTrans(val2text, "enUS", val2val)) +elseif val1text then +msg = string.format(formatStr, HealerProtection:TryTrans(val1text, "enUS", val1val)) +end +elseif lang == locale then +if val2text then +msg = string.format(formatStr, HealerProtection:TryTrans(val1text, locale, val1val), HealerProtection:TryTrans(val2text, locale, val2val)) +elseif val1text then +msg = string.format(formatStr, HealerProtection:TryTrans(val1text, locale, val1val)) +end +else +if val2text then +msg = string.format(formatStr, HealerProtection:TryTrans(val1text, "enUS", val1val), HealerProtection:TryTrans(val2text, "enUS", val2val)) +elseif val1text then +msg = string.format(formatStr, HealerProtection:TryTrans(val1text, "enUS", val1val)) +end + +if val2text then +msg = msg .. " [" .. string.format(formatStr, HealerProtection:TryTrans(val1text, locale, val1val), HealerProtection:TryTrans(val2text, locale, val2val)) .. "]" +elseif val1text then +msg = msg .. " [" .. string.format(formatStr, HealerProtection:TryTrans(val1text, locale, val1val)) .. "]" +end +end + +local mes = prefix .. msg .. "." .. suffix +if mes ~= nil then +if SendChatMessage then +SendChatMessage(mes, _channel) +end +end +end +end + +function HealerProtection:Setup() +if HealerProtection:IsSetup() then +if not InCombatLockdown() then +HPTABPC = HPTABPC or {} +HealerProtection:SetSetup(false) + +warning_aggro = CreateFrame("Frame", nil, UIParent) +warning_aggro:SetFrameStrata("BACKGROUND") +warning_aggro:SetWidth(128) +warning_aggro:SetHeight(64) +warning_aggro.text = warning_aggro:CreateFontString(nil, "ARTWORK") + +local font = STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF" +warning_aggro.text:SetFont(font, 20, "OUTLINE") +warning_aggro.text:SetPoint("CENTER", 0, 300) +warning_aggro.text:SetText(HealerProtection:Trans("LID_youhaveaggro") .. "!") +warning_aggro.text:SetTextColor(1, 0, 0, 1) +warning_aggro:SetPoint("CENTER", 0, 0) +warning_aggro:Hide() + +local f = CreateFrame("Frame") +f:RegisterEvent("UI_ERROR_MESSAGE") +f:SetScript("OnEvent", function(self, event, a1) +local errMessage = a1 or arg1 +if event == "UI_ERROR_MESSAGE" and errMessage == SPELL_FAILED_LINE_OF_SIGHT then +local TName = UnitName("target") +if TName and UnitIsFriend("player", "target") then +if lasttarget ~= TName or delayrange < GetTime() then +delayrange = GetTime() + 1 +lasttarget = TName +if HealerProtection:DBGV("notinsight", false) then +local tex = "Target is not in the field of view (" .. TName .. ")" +if HealerProtection:DBGV("showtranslation", true) and GetLocale() ~= "enUS" then +tex = tex .. " [" .. errMessage .. " (" .. TName .. ")]" +end + +HealerProtection:ToCurrentChat("%s", tex) +end +end +end +end +end) + +local channeling = CreateFrame("Frame") +channeling:RegisterEvent("SPELLCAST_CHANNEL_START") +channeling:RegisterEvent("SPELLCAST_CHANNEL_STOP") +channeling:SetScript("OnEvent", function(self, event) +if event == "SPELLCAST_CHANNEL_START" then +isChanneling = true +elseif event == "SPELLCAST_CHANNEL_STOP" then +isChanneling = false +end +end) + +HealerProtection:SetDbTab(HPTABPC) +HealerProtection:InitSetting() + +if C_Timer and C_Timer.NewTicker then +C_Timer.NewTicker(1, function() HealerProtection:PrintChat() end) +else +local tickerFrame = CreateFrame("Frame") +local elapsed_time = 0 +tickerFrame:SetScript("OnUpdate", function(self, elapsed) +local dt = elapsed or arg1 or 0 +elapsed_time = elapsed_time + dt +if elapsed_time >= 1 then +elapsed_time = 0 +HealerProtection:PrintChat() +end +end) +end +else +SafeAfter(0.1, function() HealerProtection:Setup() end) +end +end +end + +function HealerProtection:PrintChat() +-- Optimization: OOM / NEAROOM thresholds are cached once per tick +local oomPerc = HealerProtection:DBGV("OOMPercentage", 10) +local nearOomPerc = HealerProtection:DBGV("NEAROOMPercentage", 30) + +-- Optimization: getglobal("SETOOMP") now only runs when the thresholds are actually +-- misconfigured (oomPerc > nearOomPerc). With default settings (10 <= 30) that's false, +-- so before this change the getglobal lookup ran uselessly every single second, forever. +if oomPerc > nearOomPerc then +local SETOOMP = getglobal("SETOOMP") +if SETOOMP ~= nil and not InCombatLockdown() then +HPTABPC["OOMPercentage"] = nearOomPerc +SETOOMP:SetValue(HPTABPC["OOMPercentage"]) +oomPerc = nearOomPerc +end +end + +local inInstance = HealerProtection:InInstance() +local inBG = IsPlayerInBG() +local _channel = HealerProtection:GetCurrentChannel() +if not HealerProtection:CanWriteToChat(_channel, inInstance, inBG) then return end + +if HealerProtection:IsLoaded() then +-- Role detection removed: the "Force Healer" bypass is now always active, +-- so there is no need to check the player's class/role here anymore. +-- Note: "printnothing" is already checked inside CanWriteToChat right above, +-- so if we reach this point it is guaranteed to be false -- no need to test it again. + +-- Optimization: AllowedTo() only depends on the channel/group/guild/raid state, +-- none of which can change mid-tick, so it's computed once and reused below +-- instead of being called up to 6 times per tick (chat + emote x3 alerts). +local canAnnounce = HealerProtection:AllowedTo(_channel) + +if not UnitIsDead("player") then +isdead = false + +-- AGGRO LOGIC +if HealerProtection:DBGV("AGGRO", false) then +local status = GetThreatSituation() +if status ~= nil then +if status > 0 and not aggro then +if HealerProtection:DBGV("showaggrochat", true) and canAnnounce then +HealerProtection:ToCurrentChat("{rt8} %s", "LID_ihaveaggro", nil, nil, nil, _channel, inInstance, inBG) +end + +if HealerProtection:DBGV("showaggroemote", true) and canAnnounce and not isChanneling then +DoEmote("helpme") +end + +aggro = true +elseif status == 0 and aggro then +aggro = false +end +else +aggro = false +end + +if warning_aggro then +if aggro then +warning_aggro:Show() +else +warning_aggro:Hide() +end +end +else +if warning_aggro then +warning_aggro:Hide() +end +end + +-- MANA AND HEALTH LOGIC +local powerToken = GetUnitPowerType("player") +if powerToken == "MANA" then +local mana = GetUnitPower("player") +local manamax = GetUnitPowerMax("player") +local manaperc = HealerProtection:MathR((mana / manamax) * 100, 1) + +-- OOM Alert (without percentage) +if HealerProtection:DBGV("OOM", true) then +if manaperc <= oomPerc and not oom then +oom = true +if HealerProtection:DBGV("showoomchat", true) and canAnnounce then +HealerProtection:ToCurrentChat("%s (%s)", "LID_outofmana", nil, "LID_xmana", manaperc, _channel, inInstance, inBG) +end + +if HealerProtection:DBGV("showoomemote", true) and canAnnounce and not isChanneling then +DoEmote("oom") +end +elseif manaperc > oomPerc + 20 and oom then +oom = false +end +end + +-- Near-OOM Alert (without percentage) +if HealerProtection:DBGV("NEAROOM", true) and not oom then +if manaperc <= nearOomPerc and not nearoom then +nearoom = true +if HealerProtection:DBGV("shownearoomchat", true) and canAnnounce then +HealerProtection:ToCurrentChat("%s (%s)", "LID_nearoutofmana", nil, "LID_xmana", manaperc, _channel, inInstance, inBG) +end + +if HealerProtection:DBGV("shownearoomemote", true) and canAnnounce and not isChanneling then +DoEmote("incoming") +end +elseif manaperc > nearOomPerc + 20 and nearoom then +nearoom = false +end +end + +end +elseif not isdead then +isdead = true +if HealerProtection:DBGV("deathmessage", true) then +HealerProtection:ToCurrentChat("%s", "LID_healerisdead", nil, nil, nil, _channel, inInstance, inBG) +end +end +end +end + +-- ============================================================================ +-- SETTINGS WINDOW AND SLASH COMMANDS (HOMEMADE FOR 1.12) +-- ============================================================================ + +function HealerProtection:DBGV(key, default) +if HPTABPC and HPTABPC[key] ~= nil then +return HPTABPC[key] +end +return default +end + +SLASH_HEALERPROTECTION1 = "/hp" +SLASH_HEALERPROTECTION2 = "/healerprotection" +SlashCmdList["HEALERPROTECTION"] = function(msg) +if HPOptionsFrame and HPOptionsFrame:IsVisible() then +HPOptionsFrame:Hide() +else +if not HPOptionsFrame then +HealerProtection:CreateGUI() +end +HPOptionsFrame:Show() +end +end + +function HealerProtection:CreateGUI() +local f = CreateFrame("Frame", "HPOptionsFrame", UIParent) +f:SetWidth(400) +f:SetHeight(755) +f:SetPoint("CENTER", UIParent, "CENTER") +f:SetBackdrop({ +bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", +edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", +tile = true, tileSize = 32, edgeSize = 32, +insets = { left = 11, right = 12, top = 12, bottom = 11 } +}) +f:SetMovable(true) +f:EnableMouse(true) +f:RegisterForDrag("LeftButton") +f:SetScript("OnDragStart", function() f:StartMoving() end) +f:SetScript("OnDragStop", function() f:StopMovingOrSizing() end) +f:SetFrameStrata("DIALOG") +f:Hide() + +f.title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") +f.title:SetPoint("TOP", 0, -18) +f.title:SetText("Healer Protection - Settings") + +local btnClose = CreateFrame("Button", nil, f, "UIPanelButtonTemplate") +btnClose:SetWidth(100) +btnClose:SetHeight(24) +btnClose:SetPoint("BOTTOM", 0, 16) +btnClose:SetText("Close") +btnClose:SetScript("OnClick", function() f:Hide() end) + +-- ------------------------------------------------------------------ +-- Helpers +-- ------------------------------------------------------------------ + +local function CreateCheckbox(name, label, dbKey, defaultValue, xOffset, yOffset) +local cb = CreateFrame("CheckButton", "HPCB_"..name, f, "UICheckButtonTemplate") +cb:SetPoint("TOPLEFT", xOffset, yOffset) +getglobal(cb:GetName().."Text"):SetText(label) +getglobal(cb:GetName().."Text"):SetTextColor(1, 1, 1) + +cb:SetScript("OnShow", function() +local val = HPTABPC[dbKey] +if val == nil then val = defaultValue end +cb:SetChecked(val and 1 or nil) +end) + +cb:SetScript("OnClick", function() +local isChecked = (cb:GetChecked() == 1) +HPTABPC[dbKey] = isChecked +end) +return cb +end + +-- labelFormat must contain one "%d" placeholder for the current value, e.g. "If under %d%% Health, print message" +local function CreateSlider(name, labelFormat, dbKey, defaultValue, minVal, maxVal, yOffset) +local slider = CreateFrame("Slider", "HPSL_"..name, f, "OptionsSliderTemplate") +slider:SetPoint("TOP", 0, yOffset) +slider:SetWidth(320) +slider:SetHeight(16) +slider:SetOrientation("HORIZONTAL") +slider:SetMinMaxValues(minVal, maxVal) +slider:SetValueStep(1) + +getglobal(slider:GetName().."Low"):SetText(tostring(minVal)) +getglobal(slider:GetName().."High"):SetText(tostring(maxVal)) +local sliderLabel = getglobal(slider:GetName().."Text") + +local function UpdateLabel(val) +sliderLabel:SetText(string.format(labelFormat, val)) +end + +slider:SetScript("OnShow", function() +local val = HPTABPC[dbKey] +if val == nil then val = defaultValue end +slider:SetValue(val) +UpdateLabel(val) +end) + +slider:SetScript("OnValueChanged", function() +local val = arg1 +if val == nil then val = slider:GetValue() end +val = math.floor(val + 0.5) +HPTABPC[dbKey] = val +UpdateLabel(val) +end) + +return slider +end + +local function CreateEditBox(name, xOffset, yOffset, width, dbKey, defaultValue) +local labelFS = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") +labelFS:SetPoint("TOPLEFT", xOffset, yOffset) +labelFS:SetText(name) + +local eb = CreateFrame("EditBox", "HPEB_"..name, f, "InputBoxTemplate") +eb:SetWidth(width) +eb:SetHeight(20) +eb:SetPoint("TOPLEFT", xOffset + 4, yOffset - 18) +eb:SetAutoFocus(false) + +eb:SetScript("OnShow", function() +local val = HPTABPC[dbKey] +if val == nil then val = defaultValue end +eb:SetText(val) +end) + +eb:SetScript("OnEnterPressed", function() +HPTABPC[dbKey] = eb:GetText() +eb:ClearFocus() +end) + +eb:SetScript("OnEditFocusLost", function() +HPTABPC[dbKey] = eb:GetText() +end) + +return eb +end + +local function CreateChannelDropdown(xOffset, yOffset) +local labelFS = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") +labelFS:SetPoint("TOPLEFT", xOffset, yOffset) +labelFS:SetText("Announce Channel") + +local dropdown = CreateFrame("Frame", "HPChannelDropDown", f, "UIDropDownMenuTemplate") +dropdown:SetPoint("TOPLEFT", xOffset - 16, yOffset - 18) +UIDropDownMenu_SetWidth(120, dropdown) + +local channels = { "AUTO", "SAY", "YELL", "PARTY", "RAID", "GUILD" } + +local function OnClick() +local value = this.value +HPTABPC["channelchat"] = value +UIDropDownMenu_SetSelectedValue(dropdown, value) +UIDropDownMenu_SetText(value, dropdown) +end + +local function Initialize() +for _, chan in ipairs(channels) do +local info = {} +info.text = chan +info.value = chan +info.func = OnClick +UIDropDownMenu_AddButton(info) +end +end + +UIDropDownMenu_Initialize(dropdown, Initialize) +local current = HealerProtection:DBGV("channelchat", "AUTO") +UIDropDownMenu_SetSelectedValue(dropdown, current) +UIDropDownMenu_SetText(current, dropdown) + +return dropdown +end + +-- ------------------------------------------------------------------ +-- Layout (mirrors the D4KiR Healer Protection panel, minus the CC +-- checkbox grid top-right and the Loss of control section at the bottom) +-- ------------------------------------------------------------------ + +CreateCheckbox("PrintNothing", "Print Nothing", "printnothing", false, 20, -45) +CreateCheckbox("ShowInRaid", "Show in Raids", "showinraids", true, 20, -70) +CreateCheckbox("Outside", "Show outside", "showoutsideofinstance", false, 20, -95) +CreateCheckbox("ShowInBG", "Show in Battlegrounds", "showinbgs", false, 20, -120) + +CreateCheckbox("Aggro", "AGGRO", "AGGRO", false, 20, -155) +CreateCheckbox("AggroChat", "AGGRO Chat-Message", "showaggrochat", true, 40, -180) +CreateCheckbox("AggroEmote", "AGGRO Emote", "showaggroemote", true, 40, -205) +CreateCheckbox("DeathMessage", "Death message", "deathmessage", true, 20, -230) + +CreateCheckbox("OOM", "Out of Mana", "OOM", true, 20, -265) +CreateCheckbox("OOMChat", "OOM Chat-Message", "showoomchat", true, 40, -290) +CreateCheckbox("OOMEmote", "OOM Emote", "showoomemote", true, 40, -315) + +CreateSlider("OomPerc", "If under %d%% Mana, print message", "OOMPercentage", 10, 1, 10, -355) + +CreateCheckbox("NearOOM", "Near out of Mana", "NEAROOM", true, 20, -410) +CreateCheckbox("NearOOMChat", "Near OOM Chat-Message", "shownearoomchat", true, 40, -435) +CreateCheckbox("NearOOMEmote", "Near OOM Emote", "shownearoomemote", true, 40, -460) + +CreateSlider("NearOomPerc", "If under %d%% Mana, print message", "NEAROOMPercentage", 30, 11, 30, -500) + +CreateChannelDropdown(20, -555) + +CreateEditBox("Prefix", 20, -615, 160, "prefix", "[Healer Protection]") +CreateEditBox("Suffix", 210, -615, 160, "suffix", "") +end + +-- ============================================================================ +-- MAGIC PATCH: STANDALONE BOOTSTRAP AND TRANSLATIONS +-- ============================================================================ + +local traductions = { +["LID_youhaveaggro"] = "You have aggro", +["LID_ihaveaggro"] = "Aggro on me! Get it off!", +["LID_outofmana"] = "I am OOM!", +["LID_nearoutofmana"] = "Low Mana!", +["LID_xmana"] = "%s%% Mana", +["LID_neardeath"] = "Help, I'm dying!", +["LID_xhealth"] = "%s%% HP", +["LID_healerisdead"] = "Healer is down... Fly, you fools!" +} -- BUG FIXED: closing brace was missing in the original file, which would have caused a Lua syntax error + +function HealerProtection:Trans(key) +return traductions[key] or key +end + +function HealerProtection:TryTrans(key, lang, val) +local texte = traductions[key] or key +if val then +return string.format(texte, tostring(val)) +end +return texte +end + +function HealerProtection:IsSetup() return true end +function HealerProtection:SetSetup() end +function HealerProtection:SetDbTab() end +function HealerProtection:InitSetting() end +function HealerProtection:IsLoaded() return true end +function HealerProtection:INFO(msg) DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[HP Info]|r " .. msg) end +function HealerProtection:MSG(msg) DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00[HP]|r " .. msg) end +function HealerProtection:MathR(val, dec) +dec = dec or 0 +local mult = 10 ^ dec +return math.floor(val * mult + 0.5) / mult +end + +local bootFrame = CreateFrame("Frame") +bootFrame:RegisterEvent("PLAYER_LOGIN") +bootFrame:SetScript("OnEvent", function() +HealerProtection:Setup() +DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00[Healer Protection]|r ready and armed! Type |cFFFFFF00/hp|r to configure the alerts.") +end)