diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6c14d..2ea6318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,55 @@ # Changelog +## 1.4.2 (2026-09-05) + +### English + +**New: raid mode** (module `FBHealBox_Raid.lua`, on by default, switches in automatically from 11 raid members; *Raid view from N players* is adjustable) + +- Compact grid for 20 and 40 player raids: one cell per member in blocks of five per raid group, blocks arranged in rows (*Groups per row*). Empty groups collapse, so a 20-player raid is a single row. +- Each cell: name in class colour, health bar with shield and incoming-heal layers, 3 px mana strip, HP percent or deficit, dispel colouring, dead/ghost/offline text, range fading, line-of-sight eye, buff-watch border. +- Up to four mini buttons per cell, wired to Button 1 to N from the Buttons tab (right-click spells included). Click on a cell targets the unit (or unit menu / move / nothing, as configured for plates). +- Own options tab *Raid mode* with size, spacing, scale, headers, mana strip, HP text mode, title bar, hide-empty-groups and hide-party-plates switches. +- Raid test with 20 or 40 ghosts covering every visual state. `/fbp raid`, `/fbp raidtest 20|40|off`, raid line in `/fbp`. +- Grid position and scale are saved separately from the party plates. Party plates are hidden while the grid is up (switchable). + +**New: drag and drop.** Spells can be dragged from the spellbook onto any heal button, raid mini button or options field. Right mouse button fills the right-click side when that option is on. Spells outside the class list are accepted. + +**Fixed** + +- **Double loading.** The `.xml` included `FBHealBox.lua` a second time on top of the `.toc`. Every frame, hook and the options window existed twice. Visible effects: the raid tab was missing (added to the first, overwritten options window), Escape could crash the client (the second hook called itself), and learned absorb values were doubled (two prediction frames counted every absorb line). The `.xml` no longer loads scripts, and both Lua files refuse to run twice. +- Escape hook stores the original in a local upvalue, is installed at most once, and no longer uses a tail call. +- Learned absorb values above 1.5 times the tooltip are dropped on load; doubled values from earlier versions clean themselves up. + +**Changed** + +- Core gained a hook interface (`FBHealBox_RegisterHook`, `FBHealBox_AddOptionsTab`) so modules can attach without editing the core. No behaviour change for the party display. +- `.toc` now loads `FBHealBox_Raid.lua` after the core; removing that line disables raid mode entirely. + +### Deutsch + +**Neu: Raidmodus** (Modul `FBHealBox_Raid.lua`, standardmaessig an, schaltet ab 11 Raidmitgliedern automatisch um; *Raid-Ansicht ab N Spielern* ist einstellbar) + +- Kompaktes Raster fuer 20er- und 40er-Raids: eine Zelle je Mitglied in Fuenferbloecken je Gruppe, die Bloecke in Zeilen (*Gruppen je Zeile*). Leere Gruppen fallen weg, ein 20er-Raid ist also eine einzige Zeile. +- Jede Zelle: Name in Klassenfarbe, Lebensbalken mit Schild- und Vorhersage-Schicht, 3 px Manastreifen, HP-Prozent oder Defizit, Dispel-Faerbung, Tot/Geist/Offline, Reichweiten-Fading, Sichtlinien-Auge, Buff-Wache-Rahmen. +- Bis zu vier Mini-Buttons je Zelle, belegt wie Button 1 bis N aus dem Reiter Buttons (Rechtsklick-Zauber inklusive). Klick auf die Zelle visiert an (oder Einheitenmenue / Verschieben / Nichts, wie fuer Plaketten eingestellt). +- Eigener Options-Reiter *Raidmodus* mit Groesse, Abstaenden, Skalierung, Gruppenkoepfen, Manastreifen, HP-Text-Modus, Titelleiste, Leere-Gruppen- und Plaketten-Ausblenden-Schaltern. +- Raid-Test mit 20 oder 40 Geistern, die jeden Anzeigezustand abdecken. `/fbp raid`, `/fbp raidtest 20|40|off`, Raid-Zeile in `/fbp`. +- Position und Skalierung des Rasters werden getrennt von den Plaketten gespeichert. Die Gruppenplaketten sind ausgeblendet, solange das Raster steht (abschaltbar). + +**Neu: Drag & Drop.** Zauber lassen sich aus dem Zauberbuch auf jeden Heil-Button, Raid-Mini-Button oder jedes Optionsfeld ziehen. Die rechte Maustaste fuellt die Rechtsklick-Seite, wenn die Option an ist. Zauber ausserhalb der Klassenliste werden angenommen. + +**Behoben** + +- **Doppeltes Laden.** Die `.xml` band `FBHealBox.lua` zusaetzlich zur `.toc` ein zweites Mal ein. Jeder Frame, jeder Hook und das Optionsfenster existierten doppelt. Sichtbare Folgen: der Raid-Reiter fehlte (an das erste, ueberschriebene Optionsfenster gehaengt), Escape konnte den Client zum Absturz bringen (der zweite Hook rief sich selbst auf), und gelernte Absorb-Werte waren verdoppelt (zwei Vorhersage-Frames zaehlten jede Absorb-Zeile). Die `.xml` laedt keine Skripte mehr, beide Lua-Dateien verweigern einen zweiten Durchlauf. +- Der Escape-Hook haelt das Original in einem lokalen Upvalue, wird hoechstens einmal gesetzt und nutzt keinen Tail-Call mehr. +- Gelernte Absorb-Werte ueber dem 1,5-fachen des Tooltips werden beim Laden verworfen; verdoppelte Werte aus frueheren Versionen bereinigen sich selbst. + +**Geaendert** + +- Der Kern hat eine Hook-Schnittstelle bekommen (`FBHealBox_RegisterHook`, `FBHealBox_AddOptionsTab`), damit Module andocken koennen, ohne den Kern zu aendern. Kein Verhaltensunterschied fuer die Gruppenanzeige. +- Die `.toc` laedt `FBHealBox_Raid.lua` nach dem Kern; wer die Zeile entfernt, hat keinen Raidmodus. + ## 1.4.1 (2026-09-04) ### English diff --git a/FBHealBox.lua b/FBHealBox.lua index 7e178b0..ccd287d 100644 --- a/FBHealBox.lua +++ b/FBHealBox.lua @@ -15,11 +15,19 @@ -- Buff-Wache (oranger Rahmen bei fehlendem Buff), Reichweiten-Fading, -- Optionsfenster mit Tabs, Rechtsklick-Zweitzauber, Tot/Geist/Offline- -- Anzeige, Debuff-Icon mit Stackzahl +-- * v1.4.2: Hook-Schnittstelle fuer Module (FBHealBox_RegisterHook, +-- FBHealBox_AddOptionsTab). Der Raidmodus lebt in FBHealBox_Raid.lua. -- -- Ehre wem Ehre gebuehrt: Aufbau, Namensplaketten und Grundidee stammen -- aus dem Original. -- ========================================================================== +-- Schutz gegen doppeltes Laden (z. B. .toc und .xml binden dieselbe Datei +-- ein). Ein zweiter Durchlauf wuerde alle Frames, Hooks und das +-- Optionsfenster erneut anlegen. +if (FBHealBox_CoreLoaded) then return; end +FBHealBox_CoreLoaded = true; + FBHasSuperWoW = (SUPERWOW_VERSION ~= nil); FBClass = UnitClass("player"); @@ -53,7 +61,7 @@ HealBox = { -- feuert ADDON_LOADED fuer uns. FBADDON_NAME = "Heal Box Vanilla"; FBADDON_FOLDER = "FBHealBox"; -HealBoxVersion = "|cFFFFFF00v1.4.1|r"; +HealBoxVersion = "|cFFFFFF00v1.4.2|r"; -- ========================================================================== -- [ Lokalisierung / Localization ] @@ -93,6 +101,9 @@ FBLocale["enUS"] = { RIGHTCLICK = "Right-click spell", RIGHTCLICK_TIP = "Gives every button a second spell on right click (e.g. Flash Heal left, Greater Heal right). Off by default. When on, a second column appears above and a small icon in the corner of each button shows the right-click spell.", TT_RIGHT = "Right click", + DROP_SET = "Button %d: |cFFFFFFFF%s|r (dragged from the spellbook)", + DROP_SET_R = "Button %d, right click: |cFFFFFFFF%s|r (dragged from the spellbook)", + DROP_UNKNOWN = "Could not identify the dragged spell.", STATE_DEAD = "Dead", STATE_GHOST = "Ghost", STATE_OFFLINE = "Offline", @@ -195,6 +206,9 @@ FBLocale["deDE"] = { RIGHTCLICK = "Rechtsklick-Zauber", RIGHTCLICK_TIP = "Gibt jedem Button einen zweiten Zauber per Rechtsklick (z. B. Blitzheilung links, Grosse Heilung rechts). Standardmaessig aus. Eingeschaltet erscheint oben eine zweite Spalte, und ein kleines Icon in der Ecke jedes Buttons zeigt den Rechtsklick-Zauber.", TT_RIGHT = "Rechtsklick", + DROP_SET = "Button %d: |cFFFFFFFF%s|r (aus dem Zauberbuch gezogen)", + DROP_SET_R = "Button %d, Rechtsklick: |cFFFFFFFF%s|r (aus dem Zauberbuch gezogen)", + DROP_UNKNOWN = "Der gezogene Zauber liess sich nicht erkennen.", STATE_DEAD = "Tot", STATE_GHOST = "Geist", STATE_OFFLINE = "Offline", @@ -301,6 +315,34 @@ function FBSetLocale(code, apply) end FBL = FBLocale[FBDetectLocale()]; + +-- ========================================================================== +-- [ Hooks fuer Module ] +-- +-- Module (z. B. FBHealBox_Raid.lua) haengen sich hier ein, statt den +-- Kern zu aendern. Aufrufpunkte: Defaults, SyncOptions, ApplyLocale, +-- UpdateNames, RefreshAllBars, ButtonsChanged, ActiveToggle, Status, Loaded +-- und Slash (Slash-Hooks geben true zurueck, wenn sie den Befehl verarbeitet +-- haben). +-- ========================================================================== + +FBHookRegistry = {}; + +function FBHealBox_RegisterHook(name, fn) + if (not FBHookRegistry[name]) then FBHookRegistry[name] = {}; end + table.insert(FBHookRegistry[name], fn); +end + +function FBHealBox_RunHook(name, a1, a2, a3) + local list = FBHookRegistry[name]; + if (not list) then return false; end + local handled = false; + for _, fn in ipairs(list) do + if (fn(a1, a2, a3)) then handled = true; end + end + return handled; +end + LowHP = 0.6; VeryLowHP = 0.3; NamePlateWidth = 120; @@ -663,6 +705,7 @@ function FBHealBox_ApplyDefaults() if (HealBox.BuffWatchPets == nil) then HealBox.BuffWatchPets = 0; end if (not FBPlateActionName[HealBox.PlateLeft or ""]) then HealBox.PlateLeft = "target"; end if (not FBPlateActionName[HealBox.PlateRight or ""]) then HealBox.PlateRight = "target"; end + FBHealBox_RunHook("Defaults"); end -- Optionsfenster an die gespeicherten Werte angleichen @@ -685,6 +728,7 @@ function FBHealBox_SyncOptions() FBHealBox_UpdateBuffWatchLabel(); FBHealBox_UpdatePlateActionLabels(); FBHealBox_ApplyRightClickLayout(); + FBHealBox_RunHook("SyncOptions"); end function FBUnitGUID(unit) @@ -699,11 +743,22 @@ end function FBLoadSpellData() FBPlayerSpells = {}; FBBuffSpells = {}; + -- Zauber, die per Drag & Drop belegt wurden, koennen ausserhalb der + -- Klassenliste liegen: ihre Namen aus der gespeicherten Belegung mitnehmen + local extra = {}; + for _, tbl in ipairs({ HealBox.SpellChoice or {}, HealBox.SpellChoiceR or {} }) do + for _, cast in pairs(tbl) do + if (type(cast) == "string") then + local base = FBPredict_SplitCast(cast); + if (base) then extra[base] = true; end + end + end + end local i = 1; while true do local spellName, spellRank = GetSpellName(i, BOOKTYPE_SPELL); if not spellName then break; end - local isHealBoxSpell = false; + local isHealBoxSpell = (extra[spellName] == true); for _, v in ipairs(Spell.Name) do if v == spellName then isHealBoxSpell = true; @@ -730,6 +785,8 @@ function FBLoadSpellData() -- Tooltips auswerten: welche dieser Zauber sind HoTs bzw. Absorb-Schilde? FBPredict_BuildWatch(); + -- Doppelt gezaehlte Absorb-Lernwerte (Versionen vor 1.4.2) verwerfen + FBPredict_SanitizeMemory(); if (not HealBox.SpellChoiceR) then HealBox.SpellChoiceR = {}; end for _, side in ipairs({ "L", "R" }) do @@ -1236,6 +1293,7 @@ function FBHealBox_OnEvent(event, arg1) if ((event == "ADDON_LOADED") and (arg1 == FBADDON_FOLDER)) then FBHealBox_ApplyDefaults(); FBSetLocale(HealBox.Locale, 1); + FBHealBox_RunHook("Loaded"); -- Module melden sich im Chat FBHealBox_SyncOptions(); HealBoxAttachMode(HealBox.AttachMode); FBHealBox_ApplyButtonSpacing(); @@ -1540,6 +1598,91 @@ function FBHealBoxCreateFrame(FrameName,ParentFrame,FrameTexture,FrameWidth,Fram return f; end +-- ========================================================================== +-- [ Drag & Drop aus dem Zauberbuch ] +-- +-- 1.12 kennt kein GetCursorInfo(). Das Zauberbuch ruft beim Ziehen +-- PickupSpell(id, book) auf; ein Vor-Hook merkt sich diese beiden Werte. +-- Loslassen auf einem Heil-Button (OnReceiveDrag oder Klick mit Zauber am +-- Cursor) belegt den Button: Linksklick-Feld, bei aktiviertem Rechtsklick- +-- Zauber und rechter Maustaste das Rechtsklick-Feld. Auch die Felder im +-- Optionsfenster nehmen Zauber an. +-- ========================================================================== + +FBDragSpell = nil; -- { id, book } des zuletzt aufgenommenen Zaubers + +function FBHealBox_HookSpellPickup() + if (FBHealBox_PickupHooked) then return; end + FBHealBox_PickupHooked = true; + if (PickupSpell) then + local origPickup = PickupSpell; + PickupSpell = function(id, book) + FBDragSpell = { id = id, book = book or BOOKTYPE_SPELL }; + origPickup(id, book); + end + end + if (ClearCursor) then + local origClear = ClearCursor; + ClearCursor = function() + FBDragSpell = nil; + origClear(); + end + end +end + +-- Zauber am Cursor: name, rank, id, book oder nil +function FBHealBox_CursorSpell() + if (not FBDragSpell) then return nil; end + if (CursorHasSpell and not CursorHasSpell()) then + FBDragSpell = nil; + return nil; + end + local name, rank = GetSpellName(FBDragSpell.id, FBDragSpell.book); + if (not name) then return nil; end + return name, rank, FBDragSpell.id, FBDragSpell.book; +end + +-- Zauber vom Cursor auf Button btnIndex legen. side = "L" oder "R". +-- true, wenn etwas belegt wurde. +function FBHealBox_DropSpell(btnIndex, side) + if (not btnIndex) then return false; end + local name, rank, id, book = FBHealBox_CursorSpell(); + if (not name) then + if (FBDragSpell) then + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r "..FBT("DROP_UNKNOWN")); + end + return false; + end + side = side or "L"; + if (side == "R" and HealBox.RightClick ~= 1) then side = "L"; end + + -- Zauber in die Rangliste aufnehmen, falls er nicht aus der Klassenliste stammt + if (not FBPlayerSpells[name]) then FBPlayerSpells[name] = {}; end + local known = false; + for _, sd in ipairs(FBPlayerSpells[name]) do + if (sd.rank == rank) then known = true; break; end + end + if (not known) then + table.insert(FBPlayerSpells[name], { rank = rank, id = id, icon = GetSpellTexture(id, book) }); + end + + local castString = name; + if (rank and rank ~= "") then castString = name.."("..rank..")"; end + + local _, _, _, _, savedTable = FBChoiceTables(side); + savedTable[btnIndex] = castString; + FBApplySpellChoice(btnIndex, castString, side); + FBHealBoxButtonsChanged(); + + local key = "DROP_SET"; + if (side == "R") then key = "DROP_SET_R"; end + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r "..format(FBT(key), btnIndex, castString)); + + FBDragSpell = nil; + if (ClearCursor) then ClearCursor(); end + return true; +end + -- Zauber castString auf das Ziel des Buttons wirken (Links- oder Rechtsklick) function FBHealBox_CastOn(button, castString) if (not castString) then return; end @@ -1643,10 +1786,19 @@ function FBHealBoxCreateButton(FBButtonName, FBParentFrame, xoffset, yoffset, te button:RegisterForClicks("LeftButtonUp"); button:SetScript("OnClick", function() + -- Zauber am Cursor: belegen statt casten + if (FBDragSpell and button.btnIndex) then + local side = "L"; + if (arg1 == "RightButton") then side = "R"; end + if (FBHealBox_DropSpell(button.btnIndex, side)) then return; end + end local castString = button.spellName; if (arg1 == "RightButton") then castString = button.spellNameR; end FBHealBox_CastOn(button, castString); end); + button:SetScript("OnReceiveDrag", function() + if (button.btnIndex) then FBHealBox_DropSpell(button.btnIndex, "L"); end + end); button:RegisterEvent("SPELL_UPDATE_USABLE"); button:SetScript("OnEvent", HealBoxButton_OnEvent); @@ -1677,6 +1829,7 @@ function FBHealBox_RefreshAllBars() local unit = FBPartyUnit[p]; if (FBUnitExists(unit)) then FBHealBox_UpdateUnit(unit, FBPartyFrame[p]); end end + FBHealBox_RunHook("RefreshAllBars"); end -- Ist dieser Slot gerade anzuzeigen? (Begleiter nur mit ShowPets) @@ -1727,6 +1880,7 @@ function FBUpdateNames() FBHealBox_CheckRangeAll(); FBHealBox_CheckLOSAll(); FBHealBox_RefreshAllBars(); + FBHealBox_RunHook("UpdateNames"); end -- Name in Klassenfarbe (Spieler) bzw. Pet-Farbe (Begleiter) @@ -2003,8 +2157,9 @@ function FBHealBox_ApplyLocale() if (r and not FBDropDownButtonR[i]) then r.text:SetText(FBT("SELECT_SPELL")); end end if (panel.tabButtons) then - panel.tabButtons[1].text:SetText(FBT("TAB_BUTTONS")); - panel.tabButtons[2].text:SetText(FBT("TAB_GENERAL")); + for i, btn in ipairs(panel.tabButtons) do + btn.text:SetText(FBT(panel.tabLabelKeys[i] or "")); + end end if (panel.colLeft) then panel.colLeft:SetText(FBT("COL_LEFT")); @@ -2068,6 +2223,7 @@ function FBHealBox_ApplyLocale() if (FBLangBtn) then FBLangBtn.text:SetText(FBT("LANGUAGE") .. ": |cFFFFFFFF" .. FBT("LANG_NAME")); end + FBHealBox_RunHook("ApplyLocale"); end -- [ Options-Fenster ] -- @@ -2135,6 +2291,22 @@ function FBHealBox_CreateTabButton(name, parent, x, index) return t; end +-- Neuen Reiter anlegen (auch fuer Module). Liefert den Inhalts-Frame, der +-- die Panelgroesse hat; Koordinaten wie im Panel, Inhalt ab FBOPT_CONTENT_Y. +function FBHealBox_AddOptionsTab(labelKey) + if (not panel) or (not panel.tabs) then return nil; end + local i = table.getn(panel.tabs) + 1; + local tab = CreateFrame("Frame", "HealBoxOptionsTab"..i, panel); + tab:SetAllPoints(panel); + tab:Hide(); + panel.tabs[i] = tab; + panel.tabLabelKeys[i] = labelKey; + panel.tabButtons[i] = FBHealBox_CreateTabButton("HealBoxOptionsTabButton"..i, panel, 25 + (i - 1) * 116, i); + panel.tabButtons[i].text:SetText(FBT(labelKey)); + if (panel.activeTab) then FBHealBox_ShowTab(panel.activeTab); end + return tab; +end + function FBHealBox_ShowTab(index) if (not panel) or (not panel.tabs) then return; end FBMenu_CloseAll(); @@ -2196,6 +2368,7 @@ function FBHealBoxCreateAddonOptionFrame() table.insert(UISpecialFrames, "HealBoxOptionsFrame"); end FBHealBox_HookEscape(); + FBHealBox_HookSpellPickup(); panel.CloseButton = CreateFrame("Button", "HealBoxOptionsFrameClose", panel, "UIPanelCloseButton"); panel.CloseButton:SetPoint("TOPRIGHT", -5, -5); @@ -2226,15 +2399,9 @@ function FBHealBoxCreateAddonOptionFrame() -- [ Reiter ] ------------------------------------------------------------ panel.tabs = {}; panel.tabButtons = {}; - for i = 1, 2 do - local tab = CreateFrame("Frame", "HealBoxOptionsTab"..i, panel); - tab:SetAllPoints(panel); - tab:Hide(); - panel.tabs[i] = tab; - panel.tabButtons[i] = FBHealBox_CreateTabButton("HealBoxOptionsTabButton"..i, panel, 25 + (i - 1) * 116, i); - end - local tabButtons = panel.tabs[1]; - local tabGeneral = panel.tabs[2]; + panel.tabLabelKeys = {}; + local tabButtons = FBHealBox_AddOptionsTab("TAB_BUTTONS"); + local tabGeneral = FBHealBox_AddOptionsTab("TAB_GENERAL"); -- Trennlinie unter den Reitern panel.tabLine = panel:CreateTexture(nil, "ARTWORK"); @@ -2272,17 +2439,21 @@ function FBHealBoxCreateAddonOptionFrame() btn.label:SetJustifyH("RIGHT"); btn.label:SetText(FBT("BUTTON") .. " " .. i); btn:SetScript("OnClick", function() + if (FBDragSpell and FBHealBox_DropSpell(btnIndex, "L")) then return; end PlaySound("igMainMenuOptionCheckBoxOn"); FBMenu_OpenSpellMenu(btnIndex, btn, "L"); end); + btn:SetScript("OnReceiveDrag", function() FBHealBox_DropSpell(btnIndex, "L"); end); FBSpellBtns[i] = btn; local btnR = FBHealBox_CreatePickButton("FBHealBoxBtnR"..i, tabButtons, xRight, yPos, fieldW, 28, 20); btnR.text:SetText(FBT("SELECT_SPELL")); btnR:SetScript("OnClick", function() + if (FBDragSpell and FBHealBox_DropSpell(btnIndex, "R")) then return; end PlaySound("igMainMenuOptionCheckBoxOn"); FBMenu_OpenSpellMenu(btnIndex, btnR, "R"); end); + btnR:SetScript("OnReceiveDrag", function() FBHealBox_DropSpell(btnIndex, "R"); end); btnR:Hide(); FBSpellBtnsR[i] = btnR; end @@ -2501,17 +2672,21 @@ end -- ESC: ToggleGameMenu abfangen, solange das Optionsfenster offen ist. -- Ist das Spielmenue selbst offen (ESC darueber gedrueckt), bleibt alles -- beim Original; sonst schliesst ESC zuerst unser Fenster und verpufft. -FBHealBox_OrigToggleGameMenu = nil; +-- Das Original liegt in einem lokalen Upvalue (nie in einer globalen +-- Variablen, die ein zweiter Ladevorgang ueberschreiben koennte), der +-- Hook wird hoechstens einmal gesetzt, und der Aufruf ist bewusst kein +-- Tail-Call (return f()), weil Lua 5.0 im 1.12-Client damit Probleme hat. function FBHealBox_HookEscape() - if (FBHealBox_OrigToggleGameMenu) or (not ToggleGameMenu) then return; end - FBHealBox_OrigToggleGameMenu = ToggleGameMenu; + if (FBHealBox_EscHooked) or (not ToggleGameMenu) then return; end + FBHealBox_EscHooked = true; + local origToggleGameMenu = ToggleGameMenu; ToggleGameMenu = function(clicked) local gameMenuOpen = (GameMenuFrame and GameMenuFrame:IsVisible()); if (panel and panel:IsVisible() and not gameMenuOpen) then panel:Hide(); return; end - return FBHealBox_OrigToggleGameMenu(clicked); + origToggleGameMenu(clicked); end end @@ -2561,6 +2736,7 @@ function FBHealBoxButtons() local name = "FBHealBoxSlot"..p.."Btn"..i; FBPartyTable[p][i] = FBHealBoxCreateButton(name, anchor, HealBox.ButtonSpacing or 2, 0, FBDropDownButtonIcon[i], FBDropDownButton[i], unit, FBActiveSpellIDs[i]); + FBPartyTable[p][i].btnIndex = i; end prevButton = FBPartyTable[p][i]; end @@ -2608,6 +2784,7 @@ function FBHealBoxButtonsChanged() end end end + FBHealBox_RunHook("ButtonsChanged"); end -- Buttons neu verketten: erster Button haengt an der Plakette, jeder weitere @@ -2711,6 +2888,7 @@ function CreateMiniMapButton() if (IsShiftKeyDown()) then HealBox.Active = 1 - HealBox.Active; if (HealBox.Active == 1) then FBHealBox1:Show(); else FBHealBox1:Hide(); end + FBHealBox_RunHook("ActiveToggle"); else FBHealBox_ToggleOptions(); end @@ -3030,6 +3208,31 @@ end -- [ Lernspeicher (wandert in die SavedVariables) ] -------------------------- +FBPREDICT_ABSORB_MAX_FACTOR = 1.5; + +-- Unplausible Absorb-Lernwerte verwerfen (etwa doppelt gezaehlte aus +-- aelteren Versionen). Laeuft nach dem Einlesen der Zauber. +function FBPredict_SanitizeMemory() + if (not HealBox) or (not HealBox.PredictMemory) then return; end + local drop = {}; + for key, value in pairs(HealBox.PredictMemory) do + local _, _, spell, rank = string.find(key, "^absorb|([^|]+)|(.*)$"); + if (spell) then + local bookID = FBPredict_FindBookID(spell, rank); + local info = bookID and FBPredict_GetSpellInfo(bookID, spell); + if (info and info.shield and value > info.shield.amount * FBPREDICT_ABSORB_MAX_FACTOR) then + table.insert(drop, key); + end + end + end + for _, key in ipairs(drop) do + HealBox.PredictMemory[key] = nil; + if (FBPredictDebug) then + DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r memory dropped: "..key); + end + end +end + function FBPredict_MemKey(kind, spell, rank) return kind.."|"..spell.."|"..(rank or ""); end @@ -3093,11 +3296,17 @@ function FBPredict_StartShield(unitName, spellName, rank, bookID, rankKnown) local info = FBPredict_GetSpellInfo(bookID, spellName); if (not info) or (not info.shield) then return false; end - local amount = FBPredict_Remembered("absorb", spellName, rank) or info.shield.amount; + -- Gelernter Wert nur, wenn er plausibel ist (hoechstens das + -- FBPREDICT_ABSORB_MAX_FACTOR-fache des Tooltips); sonst Tooltip. + local amount = FBPredict_Remembered("absorb", spellName, rank); + if (not amount) or (amount > info.shield.amount * FBPREDICT_ABSORB_MAX_FACTOR) then + amount = info.shield.amount; + end FBShields[unitName] = { spell = spellName, rank = rank, rankKnown = rankKnown, + tooltip = info.shield.amount, max = amount, absorbed = 0, expires = GetTime() + info.shield.duration, @@ -3427,9 +3636,12 @@ function FBPredict_OnAbsorb(unitName, amount) s.absorbed = s.absorbed + amount; if (s.absorbed > s.max) then - -- mehr absorbiert als der Tooltip hergibt (+Heilung) -> anheben + -- mehr absorbiert als der Tooltip hergibt (+Heilung) -> anheben. + -- Dauerhaft gemerkt wird nur ein plausibler Wert: PW:S skaliert in + -- Vanilla mit rund 10 % der Heilkraft, mehr als das + -- FBPREDICT_ABSORB_MAX_FACTOR-fache des Tooltips ist ein Zaehlfehler. s.max = s.absorbed; - if (s.rankKnown) then + if (s.rankKnown and s.tooltip and s.max <= s.tooltip * FBPREDICT_ABSORB_MAX_FACTOR) then FBPredict_Remember("absorb", s.spell, s.rank, s.max); end end @@ -3822,6 +4034,9 @@ end SLASH_FBHEALPREDICT1 = "/fbp"; SlashCmdList["FBHEALPREDICT"] = function(msg) + -- Module zuerst (z. B. /fbp raid) + if (FBHealBox_RunHook("Slash", msg)) then return; end + if (msg == "debug") then FBPredictDebug = not FBPredictDebug; DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..FBT("FBP_DEBUG").." "..tostring(FBPredictDebug)); @@ -3907,6 +4122,7 @@ SlashCmdList["FBHEALPREDICT"] = function(msg) end end end + FBHealBox_RunHook("Status"); DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..FBT("FBP_COMMANDS")); end diff --git a/FBHealBox.toc b/FBHealBox.toc index ff32a6d..1820aaa 100644 --- a/FBHealBox.toc +++ b/FBHealBox.toc @@ -1,11 +1,12 @@ ## Interface: 11200 -## Title: Heal Box Vanilla |cFFFFFF00v1.4.1|r -## Notes: Party, pet and self heal display with quick-cast buttons for healers. Ported to client 1.12.1 (optional SuperWoW). Up to 10 buttons per row, mana bar inside the health bar, class colours, buff watch, range fading, adjustable spacing, test mode with ghost players. Heal prediction for direct heals, HoT ticks and absorb shields, self-correcting from the combat log. HealComm sync with Puppeteer, pfUI and others. German/English. Original by Dourd, ported to Vanilla and extended 09/2026 by Mquadrat. -## Notes-deDE: Party-, Begleiter- und Selbst-Heilanzeige mit Schnellzugriff-Buttons fuer Heiler. Portiert fuer Client 1.12.1 (optional SuperWoW). Bis zu 10 Buttons pro Reihe, Manabalken im Lebensbalken, Klassenfarben, Buff-Wache, Reichweiten-Fading, einstellbare Abstaende, Testmodus mit Geisterspielern. Heal-Prediction fuer Direktheilung, HoT-Ticks und Absorb-Schilde, selbstkorrigierend ueber den Combatlog. HealComm-Sync mit Puppeteer, pfUI und Co. Deutsch/Englisch. Original von Dourd, Vanilla-Portierung und Erweiterung 09/2026 von Mquadrat. +## Title: Heal Box Vanilla |cFFFFFF00v1.4.2|r +## Notes: Party, pet and self heal display with quick-cast buttons for healers. Ported to client 1.12.1 (optional SuperWoW). Up to 10 buttons per row, mana bar inside the health bar, class colours, buff watch, range fading, adjustable spacing, test mode with ghost players, optional raid mode for 20 and 40 players. Heal prediction for direct heals, HoT ticks and absorb shields, self-correcting from the combat log. HealComm sync with Puppeteer, pfUI and others. German/English. Original by Dourd, ported to Vanilla and extended 09/2026 by Mquadrat. +## Notes-deDE: Party-, Begleiter- und Selbst-Heilanzeige mit Schnellzugriff-Buttons fuer Heiler. Portiert fuer Client 1.12.1 (optional SuperWoW). Bis zu 10 Buttons pro Reihe, Manabalken im Lebensbalken, Klassenfarben, Buff-Wache, Reichweiten-Fading, einstellbare Abstaende, Testmodus mit Geisterspielern, optionaler Raidmodus fuer 20 und 40 Spieler. Heal-Prediction fuer Direktheilung, HoT-Ticks und Absorb-Schilde, selbstkorrigierend ueber den Combatlog. HealComm-Sync mit Puppeteer, pfUI und Co. Deutsch/Englisch. Original von Dourd, Vanilla-Portierung und Erweiterung 09/2026 von Mquadrat. ## Author: Dourd (Argent Dawn EU) | Vanilla-Port & Erweiterung 09/2026: Mquadrat -## Version: 1.4.1 +## Version: 1.4.2 ## SavedVariablesPerCharacter: HealBox ## OptionalDeps: SuperWoW FBHealBox.lua +FBHealBox_Raid.lua FBHealBox.xml diff --git a/FBHealBox.xml b/FBHealBox.xml index e0a50f0..444cb66 100644 --- a/FBHealBox.xml +++ b/FBHealBox.xml @@ -4,7 +4,9 @@ Original: "Heal Box" von Dourd (Argent Dawn EU) - UI Overhauled. Vanilla-Portierung und Funktionserweiterung 09/2026 von Mquadrat. --> -