Add a loss of control alert

A movable alert in the middle of the screen for the strongest active
effect from C_LossOfControl: icon, cooldown sweep, the effect's name and
the seconds left. A school lockout reads "<School> Locked". It opens on
LOSS_OF_CONTROL_ADDED / _UPDATE and rescans every 0.1s while shown,
because a refreshed debuff or a second kick changes the timer without an
event.

The labels are Classic Era's LOSS_OF_CONTROL_DISPLAY_* GlobalStrings,
added to each locale file. The README lists the alert and the dressing
room controls.
This commit is contained in:
Brues
2026-10-30 23:16:20 -05:00
parent 70cc994a97
commit f56720dccc
10 changed files with 216 additions and 7 deletions
+2
View File
@@ -37,6 +37,8 @@ Provides:
- Click-casting
- Loot Roll History (/loothistory)
- New item highlighting
- Loss of control alert - Stuns, fears, silences and school lockouts from C_LossOfControl
- Dressing room zoom, pan and per-slot undress (PlayerModel / DressUpModel methods)
- Plenty other functions
### [Nampower](https://github.com/brues-code/nampower)
+17 -1
View File
@@ -30,4 +30,20 @@ pfUI_locale["deDE"]["resurrections"] = {
-- custom entries not detected by DBC extractor
-- Classic Era GlobalStrings (DE) that the loss of control alert
-- reads and the 1.12 client lacks.
pfUI_locale["deDE"]["lossofcontrol"] = {
LOSS_OF_CONTROL_DISPLAY_CHARM = "Betört",
LOSS_OF_CONTROL_DISPLAY_CONFUSE = "Verwirrt",
LOSS_OF_CONTROL_DISPLAY_DISARM = "Entwaffnet",
LOSS_OF_CONTROL_DISPLAY_FEAR = "Verängstigt",
LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL = "%s gesperrt",
LOSS_OF_CONTROL_DISPLAY_PACIFY = "Befriedet",
LOSS_OF_CONTROL_DISPLAY_PACIFYSILENCE = "Kampfunfähig",
LOSS_OF_CONTROL_DISPLAY_POSSESS = "Besessen",
LOSS_OF_CONTROL_DISPLAY_ROOT = "Bewegungsunfähig",
LOSS_OF_CONTROL_DISPLAY_SCHOOL_INTERRUPT = "Unterbrochen",
LOSS_OF_CONTROL_DISPLAY_SILENCE = "Stille",
LOSS_OF_CONTROL_DISPLAY_STUN = "Betäubt",
LOSS_OF_CONTROL_SECONDS = "Sek.",
}
+17 -1
View File
@@ -30,4 +30,20 @@ pfUI_locale["enUS"]["resurrections"] = {
-- custom entries not detected by DBC extractor
-- Classic Era GlobalStrings (GB) that the loss of control alert
-- reads and the 1.12 client lacks.
pfUI_locale["enUS"]["lossofcontrol"] = {
LOSS_OF_CONTROL_DISPLAY_CHARM = "Charmed",
LOSS_OF_CONTROL_DISPLAY_CONFUSE = "Confused",
LOSS_OF_CONTROL_DISPLAY_DISARM = "Disarmed",
LOSS_OF_CONTROL_DISPLAY_FEAR = "Feared",
LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL = "%s Locked",
LOSS_OF_CONTROL_DISPLAY_PACIFY = "Pacified",
LOSS_OF_CONTROL_DISPLAY_PACIFYSILENCE = "Disabled",
LOSS_OF_CONTROL_DISPLAY_POSSESS = "Possessed",
LOSS_OF_CONTROL_DISPLAY_ROOT = "Rooted",
LOSS_OF_CONTROL_DISPLAY_SCHOOL_INTERRUPT = "Interrupted",
LOSS_OF_CONTROL_DISPLAY_SILENCE = "Silenced",
LOSS_OF_CONTROL_DISPLAY_STUN = "Stunned",
LOSS_OF_CONTROL_SECONDS = "seconds",
}
+17 -1
View File
@@ -30,4 +30,20 @@ pfUI_locale["esES"]["resurrections"] = {
-- custom entries not detected by DBC extractor
-- Classic Era GlobalStrings (ES) that the loss of control alert
-- reads and the 1.12 client lacks.
pfUI_locale["esES"]["lossofcontrol"] = {
LOSS_OF_CONTROL_DISPLAY_CHARM = "Embelesado",
LOSS_OF_CONTROL_DISPLAY_CONFUSE = "Confundido",
LOSS_OF_CONTROL_DISPLAY_DISARM = "Desarmado",
LOSS_OF_CONTROL_DISPLAY_FEAR = "Asustado",
LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL = "Bloqueo: %s",
LOSS_OF_CONTROL_DISPLAY_PACIFY = "Pacificado",
LOSS_OF_CONTROL_DISPLAY_PACIFYSILENCE = "Inutilizado",
LOSS_OF_CONTROL_DISPLAY_POSSESS = "Poseído",
LOSS_OF_CONTROL_DISPLAY_ROOT = "Enraizado",
LOSS_OF_CONTROL_DISPLAY_SCHOOL_INTERRUPT = "Interrumpido",
LOSS_OF_CONTROL_DISPLAY_SILENCE = "Silenciado",
LOSS_OF_CONTROL_DISPLAY_STUN = "Aturdido",
LOSS_OF_CONTROL_SECONDS = "segundos",
}
+17 -1
View File
@@ -30,4 +30,20 @@ pfUI_locale["frFR"]["resurrections"] = {
-- custom entries not detected by DBC extractor
-- Classic Era GlobalStrings (FR) that the loss of control alert
-- reads and the 1.12 client lacks.
pfUI_locale["frFR"]["lossofcontrol"] = {
LOSS_OF_CONTROL_DISPLAY_CHARM = "Charme",
LOSS_OF_CONTROL_DISPLAY_CONFUSE = "Confusion",
LOSS_OF_CONTROL_DISPLAY_DISARM = "Désarmement",
LOSS_OF_CONTROL_DISPLAY_FEAR = "Peur",
LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL = "%s verrouillé",
LOSS_OF_CONTROL_DISPLAY_PACIFY = "Pacification",
LOSS_OF_CONTROL_DISPLAY_PACIFYSILENCE = "Neutralisation",
LOSS_OF_CONTROL_DISPLAY_POSSESS = "$gPossédé:Possédée;",
LOSS_OF_CONTROL_DISPLAY_ROOT = "Immobilisation",
LOSS_OF_CONTROL_DISPLAY_SCHOOL_INTERRUPT = "Interruption",
LOSS_OF_CONTROL_DISPLAY_SILENCE = "Silence",
LOSS_OF_CONTROL_DISPLAY_STUN = "Étourdissement",
LOSS_OF_CONTROL_SECONDS = "secondes",
}
+17 -1
View File
@@ -30,4 +30,20 @@ pfUI_locale["koKR"]["resurrections"] = {
-- custom entries not detected by DBC extractor
-- Classic Era GlobalStrings (KR) that the loss of control alert
-- reads and the 1.12 client lacks.
pfUI_locale["koKR"]["lossofcontrol"] = {
LOSS_OF_CONTROL_DISPLAY_CHARM = "현혹",
LOSS_OF_CONTROL_DISPLAY_CONFUSE = "혼란",
LOSS_OF_CONTROL_DISPLAY_DISARM = "무장 해제",
LOSS_OF_CONTROL_DISPLAY_FEAR = "공포",
LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL = "%s 계열 차단됨",
LOSS_OF_CONTROL_DISPLAY_PACIFY = "평정",
LOSS_OF_CONTROL_DISPLAY_PACIFYSILENCE = "무력화",
LOSS_OF_CONTROL_DISPLAY_POSSESS = "빙의",
LOSS_OF_CONTROL_DISPLAY_ROOT = "이동 불가",
LOSS_OF_CONTROL_DISPLAY_SCHOOL_INTERRUPT = "차단",
LOSS_OF_CONTROL_DISPLAY_SILENCE = "침묵",
LOSS_OF_CONTROL_DISPLAY_STUN = "기절",
LOSS_OF_CONTROL_SECONDS = "초",
}
+17 -1
View File
@@ -30,4 +30,20 @@ pfUI_locale["ruRU"]["resurrections"] = {
-- custom entries not detected by DBC extractor
-- Classic Era GlobalStrings (RU) that the loss of control alert
-- reads and the 1.12 client lacks.
pfUI_locale["ruRU"]["lossofcontrol"] = {
LOSS_OF_CONTROL_DISPLAY_CHARM = "Подчинение",
LOSS_OF_CONTROL_DISPLAY_CONFUSE = "Растерянный",
LOSS_OF_CONTROL_DISPLAY_DISARM = "Без оружия",
LOSS_OF_CONTROL_DISPLAY_FEAR = "Страх",
LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL = "%s: способности недоступны",
LOSS_OF_CONTROL_DISPLAY_PACIFY = "Покой",
LOSS_OF_CONTROL_DISPLAY_PACIFYSILENCE = "Отключено",
LOSS_OF_CONTROL_DISPLAY_POSSESS = "Подчинен",
LOSS_OF_CONTROL_DISPLAY_ROOT = "Обездвиженность",
LOSS_OF_CONTROL_DISPLAY_SCHOOL_INTERRUPT = "Прерывание",
LOSS_OF_CONTROL_DISPLAY_SILENCE = "Немота.",
LOSS_OF_CONTROL_DISPLAY_STUN = "Оглушение",
LOSS_OF_CONTROL_SECONDS = "сек",
}
+17 -1
View File
@@ -30,4 +30,20 @@ pfUI_locale["zhCN"]["resurrections"] = {
-- custom entries not detected by DBC extractor
-- Classic Era GlobalStrings (CN) that the loss of control alert
-- reads and the 1.12 client lacks.
pfUI_locale["zhCN"]["lossofcontrol"] = {
LOSS_OF_CONTROL_DISPLAY_CHARM = "魅惑",
LOSS_OF_CONTROL_DISPLAY_CONFUSE = "迷惑",
LOSS_OF_CONTROL_DISPLAY_DISARM = "缴械",
LOSS_OF_CONTROL_DISPLAY_FEAR = "恐惧",
LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL = "%s系法术被锁定",
LOSS_OF_CONTROL_DISPLAY_PACIFY = "平静",
LOSS_OF_CONTROL_DISPLAY_PACIFYSILENCE = "禁用",
LOSS_OF_CONTROL_DISPLAY_POSSESS = "被占据",
LOSS_OF_CONTROL_DISPLAY_ROOT = "被定身",
LOSS_OF_CONTROL_DISPLAY_SCHOOL_INTERRUPT = "打断",
LOSS_OF_CONTROL_DISPLAY_SILENCE = "沉默",
LOSS_OF_CONTROL_DISPLAY_STUN = "昏迷",
LOSS_OF_CONTROL_SECONDS = "秒",
}
+1
View File
@@ -82,4 +82,5 @@
<Include file="..\modules\loothistory.lua"/>
<Include file="..\modules\newitem.lua"/>
<Include file="..\modules\friendnotes.lua"/>
<Include file="..\modules\lossofcontrol.lua"/>
</Ui>
+94
View File
@@ -0,0 +1,94 @@
pfUI:RegisterModule("lossofcontrol", function ()
-- 5.x's loss of control alert. ClassicAPI's C_LossOfControl builds the
-- effects from the player's debuffs and from the school lockout of an
-- interrupt.
local strings = L["lossofcontrol"]
-- how much each effect takes away; the alert shows the strongest one
local PRIORITY = {
STUN = 3, FEAR = 3, CONFUSE = 3, CHARM = 3, POSSESS = 3, PACIFYSILENCE = 3,
SILENCE = 2, SCHOOL_INTERRUPT = 2, PACIFY = 2,
DISARM = 1, ROOT = 1,
}
-- the lockout packet does not name the interrupting spell
local INTERRUPT_ICON = "Interface\\Icons\\Ability_Kick"
local function IsStronger(data, best)
local priority, bestPriority = PRIORITY[data.locType], PRIORITY[best.locType]
if priority ~= bestPriority then return priority > bestPriority end
return (data.timeRemaining or math.huge) > (best.timeRemaining or math.huge)
end
local frame = CreateFrame("Frame", "pfLossOfControl", UIParent)
frame:SetSize(220, 48)
frame:SetPoint("CENTER", 0, 120)
frame:SetFrameStrata("HIGH")
frame:Hide()
frame.icon = CreateFrame("Frame", nil, frame)
frame.icon:SetSize(48, 48)
frame.icon:SetPoint("LEFT", 0, 0)
CreateBackdrop(frame.icon, nil, true)
frame.icon.tex = frame.icon:CreateTexture(nil, "ARTWORK")
HandleIcon(frame.icon, frame.icon.tex)
frame.cd = CreateFrame("Cooldown", nil, frame.icon, "CooldownFrameTemplate")
frame.cd:SetAllPoints(frame.icon.tex)
frame.cd.pfCooldownStyleAnimation = 1
-- the alert prints the time itself
frame.cd.noCooldownCount = true
frame.label = frame:CreateFontString(nil, "OVERLAY")
frame.label:SetFont(pfUI.font_default, 20, "OUTLINE")
frame.label:SetTextColor(NORMAL_FONT_COLOR:GetRGB())
frame.label:SetPoint("TOPLEFT", frame.icon, "TOPRIGHT", 8, -4)
frame.time = frame:CreateFontString(nil, "OVERLAY")
frame.time:SetFont(pfUI.font_default, 14, "OUTLINE")
frame.time:SetPoint("BOTTOMLEFT", frame.icon, "BOTTOMRIGHT", 8, 4)
frame.Refresh = function(self)
local best
for i = 1, C_LossOfControl.GetActiveLossOfControlDataCount() do
local data = C_LossOfControl.GetActiveLossOfControlData(i)
if not best or IsStronger(data, best) then best = data end
end
if not best then
if pfUI.unlock and pfUI.unlock:IsShown() then return end
self:Hide()
return
end
if best.locType == "SCHOOL_INTERRUPT" then
self.label:SetText(format(strings.LOSS_OF_CONTROL_DISPLAY_INTERRUPT_SCHOOL, C_Spell.GetSchoolString(best.lockoutSchool)))
self.icon.tex:SetTexture(INTERRUPT_ICON)
else
self.label:SetText(strings["LOSS_OF_CONTROL_DISPLAY_" .. best.locType])
self.icon.tex:SetTexture(best.iconTexture)
end
-- a refreshed effect or a second kick on the same school changes the
-- timer without an event, so this runs again while the alert shows
if self.start ~= best.startTime or self.duration ~= best.duration then
self.start, self.duration = best.startTime, best.duration
CooldownFrame_SetTimer(self.cd, best.startTime or 0, best.duration or 0, best.startTime and 1 or 0)
end
self.time:SetText(best.timeRemaining and format("%.1f %s", best.timeRemaining, strings.LOSS_OF_CONTROL_SECONDS) or "")
self:Show()
end
frame:SetScript("OnUpdate", function()
if ( this.tick or 0) > GetTime() then return else this.tick = GetTime() + .1 end
this:Refresh()
end)
pfUI.events:RegisterFrameEventAndCallback("LOSS_OF_CONTROL_ADDED", function() frame:Refresh() end)
pfUI.events:RegisterFrameEventAndCallback("LOSS_OF_CONTROL_UPDATE", function() frame:Refresh() end)
UpdateMovable(frame)
pfUI.lossofcontrol = frame
end)