11 Commits
Author SHA1 Message Date
QbiXibQ 4b36163446 Update README.md 2026-09-06 12:26:23 +02:00
QbiXibQ cf9a766745 Add files via upload 2026-09-06 12:25:49 +02:00
QbiXibQ c3f89148df Update README.md 2026-09-06 09:58:59 +02:00
QbiXibQ edf9a689e1 Update CHANGELOG.md 2026-09-06 09:47:45 +02:00
QbiXibQ 1c0424a74f Update README.md 2026-09-06 09:46:29 +02:00
QbiXibQ 0ab8b1a36c Add files via upload 2026-09-06 09:45:25 +02:00
QbiXibQ 09d96c95d2 Update README.md 2026-09-05 21:04:00 +02:00
QbiXibQ de37986255 Add files via upload 2026-09-05 20:56:56 +02:00
QbiXibQ 423f61ac3e Add files via upload 2026-09-05 20:56:44 +02:00
QbiXibQ de4daab039 Add files via upload 2026-09-05 20:53:40 +02:00
QbiXibQ 6f5ad76d63 Add files via upload 2026-09-05 08:47:53 +02:00
7 changed files with 3881 additions and 300 deletions
+148 -2
View File
@@ -1,5 +1,151 @@
# Changelog
## 1.4.4.1 (2026-09-06)
Buff tracking & class spells update. Fixes buff clock icons (Divine Spirit, etc.) not appearing next to the health bar, adds group buff alternate matching, and completes the spell selection lists across all healer classes with buffs, utility and rez spells.
### English
**Fixed**
- **Buff tracking & Tooltip scanning.** In Vanilla 1.12.1, `ClearLines()` on the hidden scan tooltip (`FBHealBoxScanTip`) does not clear hidden text lines. Because `FBPredict_TooltipText` did not check `fs:IsShown()`, leftover lines from previously scanned heal spells (e.g. "Heals a friendly target...") lingered and were read when inspecting buff tooltips like *Divine Spirit*. This erroneously flagged `info.isHeal = true`, skipping buff duration detection (`info.buff`) and causing *Divine Spirit* not to display its clock icon and timer in the plate UI.
- **Group buff alternate detection.** `FBPredict_BuildWatch` and `FBPredict_ScanUnit` now track group buff variants (`altTex`). When a group buff is applied or present (*Prayer of Fortitude*, *Prayer of Spirit*, *Gift of the Wild*, *Greater Blessings*), the addon correctly recognises the aura, confirms pending casts, and displays the buff clock icon.
- **Buff watch textures.** `FBLoadSpellData` now flags all spells in `FBBuffWatchSpells` as addon spells (`isHealBoxSpell = true`), ensuring their textures and ranks are gathered and monitored even if they are not yet assigned to a heal button.
- **Drag & drop instant sync.** Dropping a spell onto a button now immediately calls `FBPredict_BuildWatch()`, updating tracked buffs and predictions without requiring a `/reload`.
**New & Changed**
- **Expanded class spell lists.** Greatly expanded `Spell.Name` for all classes so that all relevant buffs, utility spells, dispels, and resurrections appear in the cascading spell menus:
- **Priest:** Added *Power Word: Fortitude*, *Prayer of Fortitude*, *Divine Spirit*, *Prayer of Spirit*, *Shadow Protection*, *Prayer of Shadow Protection*, *Fear Ward*, *Power Infusion*, *Resurrection*.
- **Druid:** Added *Mark of the Wild*, *Gift of the Wild*, *Thorns*, *Tranquility*, *Innervate*, *Rebirth*, *Cure Poison*.
- **Paladin:** Added all normal and Greater Blessings (*Wisdom, Might, Kings, Salvation, Light, Sanctuary*), *Blessing of Freedom*, *Blessing of Sacrifice*, *Redemption*, *Divine Intervention*.
- **Shaman:** Added *Ancestral Spirit*, *Purge*, *Water Walking*, *Water Breathing*, *Water Shield*.
- Added optional support for **Mage** (*Remove Lesser Curse*, *Arcane Intellect*, *Arcane Brilliance*, *Dampen Magic*, *Amplify Magic*) and **Warlock** (*Unending Breath*, *Detect Invisibility*).
- **Fear Ward in Buff Watch.** Added *Fear Ward* to `FBBuffWatchSpells` for Priests.
### Deutsch
**Behoben**
- **Buff-Erkennung & Tooltip-Scan.** Im 1.12.1-Client leert `ClearLines()` auf dem versteckten Scan-Tooltip (`FBHealBoxScanTip`) ausgeblendete Textzeilen nicht. Da `FBPredict_TooltipText` nicht pruefte, ob eine Zeile sichtbar ist (`fs:IsShown()`), wurden Textzeilen zuvor gescannter Heilzauber (z. B. „Heals a friendly target...") faelschlicherweise auch bei Buff-Tooltips wie *Goettlicher Willen* (*Divine Spirit*) mitgelesen. Dadurch wurde fehlerhaft `isHeal = true` gesetzt, was die Erkennung der Buff-Laufzeit (`info.buff`) verhinderte und dazu fuehrte, dass das Buff-Icon links am Lebensbalken nicht erschien.
- **Erkennung von Gruppen-Buffs.** `FBPredict_BuildWatch` und `FBPredict_ScanUnit` unterstuetzen nun Gruppenversionen (`altTex`). Gruppen-Buffs (*Gebet der Seelenstaerke*, *Gebet der Willenskraft*, *Gabe der Wildnis*, *Grosse Segen*) werden nun zuverlaessig bestaetigt und mit Laufzeit/Icon dargestellt.
- **Buff-Wache Texturen.** `FBLoadSpellData` markiert nun alle Zauber aus `FBBuffWatchSpells` automatisch als Addon-Zauber (`isHealBoxSpell = true`), sodass ihre Texturen und Daten auch dann geladen und getrackt werden, wenn sie noch auf keinem Button liegen.
- **Drag & Drop Sofort-Synchronisation.** Das Ablegen eines Zaubers auf einen Button ruft nun direkt `FBPredict_BuildWatch()` auf, sodass neue Buffs und Vorhersagen ohne `/reload` sofort aktiv sind.
**Neu & Geaendert**
- **Vollstaendige Klassen-Zauberlisten.** `Spell.Name` fuer alle Heilerklassen stark erweitert, sodass saemtliche Heiler-Buffs, Gruppen-Buffs, Hilfszauber und Wiederbelebungen in den Auswahlmenues verfuegbar sind:
- **Priester:** *Machtwort: Seelenstaerke*, *Gebet der Seelenstaerke*, *Goettlicher Willen*, *Gebet der Willenskraft*, *Schattenschutz*, *Gebet des Schattenschutzes*, *Furchtzauberschutz*, *Seele der Macht*, *Auferstehung* hinzugefuegt.
- **Druide:** *Mal der Wildnis*, *Gabe der Wildnis*, *Dornen*, *Gelassenheit*, *Anregen*, *Wiedergeburt*, *Vergiftung heilen* hinzugefuegt.
- **Paladin:** Alle normalen und Grossen Segen (*Weisheit, Macht, Koenige, Rettung, Licht, Schutz*), *Segen der Freiheit*, *Segen der Opferung*, *Erloesung*, *Goettliches Eingreifen* hinzugefuegt.
- **Schamane:** *Geist der Ahnen*, *Reinigen*, *Wasserwandeln*, *Wasseratmung*, *Wasserschild* hinzugefuegt.
- Zusaetzliche Unterstuetzung fuer **Magier** (*Geringen Fluch aufheben*, *Arkane Intelligenz*, *Arkane Brillanz*, *Magie daempfen*, *Magie verstaerken*) und **Hexenmeister** (*Unendlicher Atem*, *Unsichtbarkeit entdecken*) ergaenzt.
- **Furchtzauberschutz in Buff-Wache.** *Fear Ward* fuer Priester in `FBBuffWatchSpells` aufgenommen.
## 1.4.4 (2026-09-06)
Performance pass. No feature was added or changed; every visible behaviour is meant to be identical. A simulated five-second raid fight (40 players, aura and health events, mana changes, combat log, 60 frames per second) went from about 63,900 API and widget calls to about 14,450, a reduction of 77 %.
### English
**Changed (internals)**
- **Button states centralised.** The up to 260 heal buttons (10 plates x 10, 40 raid cells x 4) no longer carry their own `SPELL_UPDATE_USABLE` / `SPELL_UPDATE_COOLDOWN` handlers. One pass over the *visible* buttons queries usability and cooldown once per spell id and range once per button, and sets colours and cooldown sweeps only when the state changes. Several such events within one frame are merged into one pass.
- **Display caches.** Health text, bar maxima, bar values, bar colour and mana strip visibility are only written when they change. Dispellable debuffs are searched on `UNIT_AURA` only; `UNIT_HEALTH` and the prediction tick reuse the last result.
- **Aura scans shared.** A unit's buff textures are read once per frame and shared between the heal prediction and the buff watch. Buff names resolved through tooltip scans are remembered per texture, so a unit missing the watched buff no longer costs up to 32 tooltip scans per aura event. Raid units are only scanned when something depends on them (own cast pending, tracked HoT or shield, or a visible cell).
- **Cheaper ticks.** Spell timers use precomputed base spell names instead of string parsing per button per tick and skip units without tracked HoTs or shields. Buff icons reuse their work tables, iterate a presorted list instead of sorting per tick, and update on change or once a second instead of five times a second. The attacked-member check skips entirely while there is no hostile target and nothing is marked, and compares names before calling `UnitIsUnit`.
- **Event bursts coalesced.** `PARTY_MEMBERS_CHANGED`, `UNIT_PET` and `RAID_ROSTER_UPDATE` set a flag that is processed once in the next frame instead of rebuilding plates and grid for every event of a burst.
- **Mana ticker.** Enabled/mana/full checks moved from every frame to mana events and option changes; the spark is re-anchored only when it has moved at least half a pixel; sizes and visibility are cached.
- **Combat log.** Heal patterns run only for buff events, the absorb pattern only for hit events that contain the word absorbed. Smart Damage parses damage lines only while enabled and while there is a target being measured or an own cast waiting; its live target line is refreshed only while the options window is open.
- Raid test ghost animation frame is hidden outside the raid test instead of running an empty update every frame.
- **Client API compatibility.** `IsSpellInRange` and `IsUsableSpell` do not exist in the 1.12 client (they came with 2.0). All range and usability checks now go through wrappers that use them when a client offers them and otherwise fall back to 1.12 means: spell range from the tooltip against `UnitXP("distanceBetween")` where available, else `CheckInteractDistance` (28 m, beyond that undecided rather than red); mana cost from the tooltip against your current mana. Because clients differ (2.0 style `(id, "spell", unit)` versus the Turtle client's `(name, unit)`), the addon probes both signatures once at login with `pcall` and uses the one the client understands; a function that keeps throwing is switched off in favour of the fallback. `/fbp` reports the detected form. Without this the new central button pass crashed at login.
**Fixed**
- The old per-button event handler declared `this`, `event` and `arg1` as parameters, which shadowed the 1.12 globals; range colouring and cooldown sweeps on the buttons never worked in the game. They do now.
### Deutsch
**Geaendert (intern)**
- **Button-Zustaende zentral.** Die bis zu 260 Heil-Buttons (10 Plaketten x 10, 40 Raid-Zellen x 4) tragen keine eigenen `SPELL_UPDATE_USABLE`/`SPELL_UPDATE_COOLDOWN`-Handler mehr. Ein Durchgang ueber die *sichtbaren* Buttons fragt Nutzbarkeit und Cooldown je Zauber-ID einmal und die Reichweite je Button ab und setzt Farben und Cooldown-Uhren nur bei Zustandswechsel. Mehrere solche Events in einem Frame werden zu einem Durchgang zusammengefasst.
- **Anzeige-Zwischenspeicher.** Lebenstext, Balkenmaxima, Balkenwerte, Balkenfarbe und Sichtbarkeit des Manastreifens werden nur geschrieben, wenn sie sich aendern. Entfernbare Debuffs werden nur bei `UNIT_AURA` gesucht; `UNIT_HEALTH` und der Vorhersage-Tick nutzen den letzten Befund.
- **Aura-Scans gemeinsam.** Die Buff-Texturen einer Einheit werden je Frame einmal gelesen und von Heilvorhersage und Buff-Wache geteilt. Per Tooltip ermittelte Buffnamen werden je Textur gemerkt, sodass eine Einheit ohne den ueberwachten Buff nicht mehr bis zu 32 Tooltip-Scans je Aura-Event kostet. Raid-Einheiten werden nur gescannt, wenn etwas davon abhaengt (eigener Cast wartet, HoT oder Schild verfolgt, sichtbare Zelle).
- **Guenstigere Ticks.** Zauber-Timer nutzen vorberechnete Basisnamen statt String-Parsing je Button und Tick und ueberspringen Einheiten ohne verfolgten HoT oder Schild. Buff-Icons verwenden ihre Arbeitstabellen wieder, laufen ueber eine vorsortierte Liste statt je Tick zu sortieren und aktualisieren bei Aenderung oder einmal je Sekunde statt fuenfmal. Der Angegriffenen-Abgleich entfaellt ganz, solange kein feindliches Ziel besteht und nichts markiert ist, und vergleicht Namen, bevor `UnitIsUnit` gerufen wird.
- **Event-Salven zusammengefasst.** `PARTY_MEMBERS_CHANGED`, `UNIT_PET` und `RAID_ROSTER_UPDATE` setzen ein Kennzeichen, das im naechsten Frame einmal abgearbeitet wird, statt Plaketten und Raster bei jedem Event einer Salve neu aufzubauen.
- **Mana-Ticker.** Die Pruefungen an/Mana/voll laufen bei Mana-Events und Optionswechseln statt jeden Frame; der Funke wird nur neu verankert, wenn er sich mindestens einen halben Pixel bewegt hat; Groessen und Sichtbarkeit sind zwischengespeichert.
- **Combatlog.** Heilmuster laufen nur bei Buff-Events, das Absorb-Muster nur bei Treffer-Events, die das Wort absorbed enthalten. Smart Damage liest Schadenszeilen nur, solange es an ist und ein Ziel vermessen wird oder ein eigener Cast wartet; die Live-Zielzeile wird nur bei offenem Optionsfenster aktualisiert.
- Der Animationsframe der Raid-Testgeister ist ausserhalb des Raid-Tests ausgeblendet, statt jeden Frame leer zu laufen.
- **Client-API-Kompatibilitaet.** `IsSpellInRange` und `IsUsableSpell` gibt es im 1.12-Client nicht (sie kamen mit 2.0). Alle Reichweiten- und Nutzbarkeitspruefungen laufen jetzt ueber Wrapper, die sie nutzen, wenn ein Client sie anbietet, und sonst auf 1.12-Mittel zurueckfallen: Zauberreichweite aus dem Tooltip gegen `UnitXP("distanceBetween")`, wo vorhanden, sonst `CheckInteractDistance` (28 m, jenseits davon unentschieden statt rot); Manapreis aus dem Tooltip gegen das eigene Mana. Weil Clients sich unterscheiden (2.0-Form `(id, "spell", unit)` gegenueber der Turtle-Form `(name, unit)`), probiert das Addon beide Signaturen beim Login einmal per `pcall` aus und nutzt die verstandene; eine Funktion, die weiter Fehler wirft, wird zugunsten des Rueckfalls abgeschaltet. `/fbp` meldet die erkannte Form. Ohne das brach der neue zentrale Button-Durchgang beim Login ab.
**Behoben**
- Der alte Button-Handler deklarierte `this`, `event` und `arg1` als Parameter und verdeckte damit die 1.12-Globals; Reichweitenfaerbung und Cooldown-Uhr auf den Buttons haben im Spiel nie gearbeitet. Jetzt tun sie es.
## 1.4.3 (2026-09-05)
### English
**New**
- **Languages.** Spanish, French and Italian added to all addon texts (English and German unchanged). The client locale picks the language automatically, the language button lists all five, missing keys fall back to English.
- **Mana ticker** (module `FBHealBox_Ticker.lua`, on by default). A spark travels across your own mana strip every 2 seconds in step with the server's regeneration tick; after spending mana it turns orange and runs the five-second rule down, extended to the first tick after the five seconds. Works on the party plate and on your own raid cell, hidden while mana is full. Detection via `UNIT_MANA` only: grid from observed ticks, re-sync within a tolerance, foreign pulses (totems, Innervate) ignored, potions and runes filtered by size. Own options tab *Ticker*: master switch, five-second rule, tick tolerance, tick offset, spark width. `/fbp ticker` toggles, `/fbp` reports sync state.
- **Smart Healing** (off by default). When on, a click casts the lowest rank of the assigned spell whose expected heal covers the target's missing health (minus incoming healing) plus a safety margin (default 20 %). Never above the assigned rank, direct heals only, always the assigned rank below 30 % health. Learned heal values are used where available. Downsides (no crits in the estimate, burst damage, deliberate overhealing) are explained in the tooltip. Options on the *Buttons* tab; `/fbp debug` logs each decision, `/fbp` shows the state.
- **Cooldown sweep on buttons**, global cooldown excluded. Option *Cooldowns on buttons*.
- **Red border for the attacked member**: the plate or raid cell of the unit your hostile target is targeting. Takes precedence over the buff-watch border. Option *Mark who is attacked*.
- **HoT and shield timers on buttons**: each spell's button shows the remaining seconds of your own HoT (green) or shield (blue) on that unit; after Power Word: Shield the button shows Weakened Soul in red. Option *HoT and shield timers*.
- **Smart Damage** (module `FBHealBox_Damage.lua`, off by default). Attack spells pressed on any action bar or key binding are cast in the lowest rank that still kills the target, never above the rank on the bar. Target health from the server (real values), MobHealth3, MobInfo-2, or the addon's own per-mob-type estimate learned from combat-log damage and percent drops (highest measurement kept). Minimum damage per rank from the tooltip, raised by observed full hits. Section on the *Extras* tab (shared with the ticker) with switch, safety margin, live health-source line and spell list; `/fbp damage`, decisions in `/fbp debug`.
- **Buff icons in the health bar**: buffs with a duration that sit on your buttons (Fortitude, Divine Spirit, Fear Ward, Inner Fire) appear as small 8 px icons on the outer left side of the plate. The icon is a clock: it turns black and white clockwise from twelve o'clock as the time runs out (half left = right half grey). Exact on yourself, counted from your own cast on others, fully coloured when cast by someone else. Raid cells show them outside their left edge; the grid reserves the room. Line-of-sight badge moved to the plate's top-left corner. Units are scanned once after login and group changes so existing buffs appear immediately. `/fbp buffs` for diagnostics. Option *Buff icons in the health bar*.
- Test-mode ghost Dorn demonstrates the red border and the timers.
**Fixed**
- Own buffs were only read at login and on group changes: Vanilla fires `PLAYER_AURAS_CHANGED` for the player instead of `UNIT_AURA`. The addon now listens to it and re-reads your remaining time on every change, and once a second in between (a refresh of a running buff fires no aura event at all), so recasting a buff on yourself resets its clock and cancelling it removes the icon. Refreshing a buff on someone else through a HealBox button is confirmed by the completed cast. Also fixes confirmation of your own HoTs and shields on yourself.
- Raid cell names and percentages were hidden behind the health bar since the bar levels became relative; the texts now live on their own layer above the bars.
- Buff icons stack two high left of the plate; elapsed clock quadrants are drawn dark on a layer above the icon so the split shows on every client. Raid cells use a 3 by 4 grid of 6 px icons (twelve slots); the new raid option *Show buffs* removes icons and strip together. `/fbp buffs` reports the grey count per icon.
- Test modes extended: party ghost Brynn is dead, pet Bramble carries a disease, Dorn carries six and raid ghost 12 twelve buffs, raid ghost 27 a disease. Up to six buff icons per plate.
- Plate names could sit too low on some rows (the name box grew to two lines internally). Name, paw icon and debuff icon are now vertically centred with a fixed one-line height.
- Tooltip parser took durations ("for 30 min", "again for 15 sec") for direct heal amounts, so buffs such as Power Word: Fortitude and the shield reported a tiny "heal" and Smart Healing downranked them. Durations are ignored now, and Smart Healing only touches spells whose tooltip describes a heal and that carry no absorb.
**Changed**
- Option tab buttons are 100 px wide (was 110) so that four tabs fit in the window; class icon shrunk so it no longer overlaps the tabs. Checkbox labels have a fixed width and end with an ellipsis when a translation is long; the tooltip carries the full text. The fourth tab is called *Extras* and holds the ticker and Smart Damage sections.
- Tooltip parser: only `hr`/`hour` count as hours; `Holy` no longer looks like a time unit.
- New core hooks `Loaded`, `Aggro`, `SpellTimers`, `Cooldowns` for modules.
### Deutsch
**Neu**
- **Sprachen.** Spanisch, Franzoesisch und Italienisch fuer alle Addon-Texte hinzugefuegt (Englisch und Deutsch unveraendert). Die Client-Sprache waehlt automatisch, der Sprachknopf listet alle fuenf, fehlende Schluessel fallen auf Englisch zurueck.
- **Mana-Ticker** (Modul `FBHealBox_Ticker.lua`, standardmaessig an). Ein Funke wandert alle 2 Sekunden im Takt des Regenerationsticks ueber deinen eigenen Manastreifen; nach Manaverbrauch wird er orange und laeuft die Fuenf-Sekunden-Regel herunter, verlaengert bis zum ersten Tick nach den fuenf Sekunden. Auf der Gruppenplakette und der eigenen Raid-Zelle, bei vollem Mana ausgeblendet. Erkennung nur ueber `UNIT_MANA`: Raster aus beobachteten Ticks, Neusynchronisation innerhalb einer Toleranz, fremde Pulse (Totems, Anregen) ignoriert, Traenke und Runen nach Groesse gefiltert. Eigener Options-Reiter *Ticker*: Hauptschalter, Fuenf-Sekunden-Regel, Tick-Toleranz, Tick-Vorlauf, Funkenbreite. `/fbp ticker` schaltet um, `/fbp` meldet den Synchronzustand.
- **Smart Healing** (standardmaessig aus). Eingeschaltet wirkt ein Klick den niedrigsten Rang des belegten Zaubers, dessen erwartete Heilung das fehlende Leben des Ziels (abzueglich eingehender Heilung) plus Sicherheitsaufschlag (Standard 20 %) deckt. Nie ueber dem belegten Rang, nur Direktheilungen, unter 30 % Leben immer der belegte Rang. Gelernte Heilwerte werden genutzt, wo vorhanden. Nachteile (keine Crits in der Schaetzung, Schadensspitzen, gewolltes Ueberheilen) erklaert der Tooltip. Optionen im Reiter *Buttons*; `/fbp debug` protokolliert jede Entscheidung, `/fbp` zeigt den Zustand.
- **Cooldown-Uhr auf den Buttons**, globaler Cooldown ausgenommen. Option *Cooldowns auf den Buttons*.
- **Roter Rahmen fuer den Angegriffenen**: Plakette oder Raid-Zelle der Einheit, die dein feindliches Ziel im Ziel hat. Hat Vorrang vor dem Buff-Wache-Rahmen. Option *Angegriffenen markieren*.
- **HoT- und Schild-Timer auf den Buttons**: Der Button jedes Zaubers zeigt die Restsekunden deines eigenen HoTs (gruen) oder Schilds (blau) auf dieser Einheit; nach Machtwort: Schild zeigt der Button rot die Geschwaechte Seele. Option *HoT- und Schild-Timer*.
- **Smart Damage** (Modul `FBHealBox_Damage.lua`, standardmaessig aus). Angriffszauber auf jeder Aktionsleiste oder Taste werden im niedrigsten Rang gewirkt, der das Ziel noch toetet, nie ueber dem Rang auf der Leiste. Ziel-Leben vom Server (echte Werte), MobHealth3, MobInfo-2 oder aus der eigenen Schaetzung je Mobtyp, gelernt aus Combatlog-Schaden und Prozentabfall (hoechste Messung zaehlt). Mindestschaden je Rang aus dem Tooltip, angehoben durch beobachtete Volltreffer. Abschnitt im Reiter *Extras* (gemeinsam mit dem Ticker) mit Schalter, Sicherheitsaufschlag, Live-Zeile zur Lebensquelle und Zauberliste; `/fbp damage`, Entscheidungen in `/fbp debug`.
- **Buff-Icons im Lebensbalken**: Buffs mit Laufzeit, die auf deinen Buttons liegen (Seelenstaerke, Goettlicher Willen, Furchtzauberschutz, Inneres Feuer), erscheinen als kleine 8-px-Icons aussen links neben der Plakette. Das Icon ist eine Uhr: Es wird im Uhrzeigersinn ab zwoelf Uhr schwarz-weiss, je weiter die Zeit ablaeuft (halbe Zeit = rechte Haelfte grau). Exakt bei dir selbst, ab deinem eigenen Cast bei anderen, ganz farbig bei fremdem Cast. Raid-Zellen zeigen sie aussen an ihrer linken Kante; das Raster haelt den Platz frei. Sichtlinien-Abzeichen in die linke obere Plattenecke verlegt. Nach Login und Gruppenwechsel werden alle Einheiten einmal gescannt, damit vorhandene Buffs sofort erscheinen. `/fbp buffs` zur Diagnose. Option *Buff-Icons im Lebensbalken*.
- Testmodus-Geist Dorn zeigt roten Rahmen und Timer.
**Behoben**
- Eigene Buffs wurden nur beim Login und bei Gruppenwechseln gelesen: Vanilla feuert fuer den Spieler `PLAYER_AURAS_CHANGED` statt `UNIT_AURA`. Das Addon hoert jetzt darauf und liest die eigene Restzeit bei jeder Aenderung neu, und dazwischen einmal je Sekunde (ein Refresh eines laufenden Buffs feuert gar kein Aura-Event), sodass ein Neucast auf dich selbst die Uhr zuruecksetzt und ein Wegklicken das Icon entfernt. Ein Refresh auf jemand anderen ueber einen HealBox-Button gilt mit dem abgeschlossenen Cast als bestaetigt. Behebt auch die Bestaetigung eigener HoTs und Schilde auf dir selbst.
- Namen und Prozente der Raid-Zellen lagen seit den relativen Balken-Ebenen hinter dem Lebensbalken; die Texte liegen jetzt auf einer eigenen Ebene ueber den Balken.
- Buff-Icons stapeln sich zu zweit links neben der Plakette; abgelaufene Uhr-Quadranten werden dunkel auf einer Ebene ueber dem Icon gezeichnet, damit die Teilung auf jedem Client sichtbar ist. Raid-Zellen nutzen ein 3-mal-4-Raster aus 6-px-Icons (zwoelf Plaetze); die neue Raid-Option *Buffs anzeigen* entfernt Icons und Streifen gemeinsam. `/fbp buffs` nennt je Icon die Zahl grauer Quadranten.
- Testmodi erweitert: Partygeist Brynn ist tot, Pet Bramble traegt eine Krankheit, Dorn traegt sechs und Raid-Geist 12 zwoelf Buffs, Raid-Geist 27 eine Krankheit. Bis zu sechs Buff-Icons je Plakette.
- Plakettennamen sassen in manchen Zeilen zu tief (die Namensbox wuchs intern auf zwei Zeilen). Name, Pfoten-Icon und Debuff-Icon sind jetzt mit fester Einzeilenhoehe vertikal zentriert.
- Der Tooltip-Leser hielt Zeitangaben ("for 30 min", "again for 15 sec") fuer Heilbetraege, wodurch Buffs wie Machtwort: Seelenstaerke und der Schild eine winzige "Heilung" meldeten und Smart Healing sie abgerangt hat. Zeitangaben werden jetzt uebersprungen, und Smart Healing fasst nur Zauber an, deren Tooltip eine Heilung beschreibt und die keinen Absorb haben.
**Geaendert**
- Reiterknoepfe der Optionen sind 100 px breit (vorher 110), damit vier Reiter ins Fenster passen; Klassen-Icon verkleinert, damit es die Reiter nicht mehr ueberlagert. Schalterbeschriftungen haben eine feste Breite und enden bei langen Uebersetzungen mit Auslassungspunkten; der Tooltip traegt den vollen Text. Der vierte Reiter heisst *Extras* und enthaelt Ticker und Smart Damage.
- Tooltip-Leser: Nur `hr`/`hour` zaehlen als Stunden; `Holy` sieht nicht mehr wie eine Zeiteinheit aus.
- Neue Kern-Hooks `Loaded`, `Aggro`, `SpellTimers`, `Cooldowns` fuer Module.
## 1.4.2 (2026-09-05)
### English
@@ -13,7 +159,7 @@
- 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.
**New: drag and drop.** Spells can be dragged from the spellbook onto any heal button, raid mini button or options field. Right mouse button, or Shift held while dropping, fills the right-click side when that option is on. Spells outside the class list are accepted.
**Fixed**
@@ -37,7 +183,7 @@
- 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.
**Neu: Drag & Drop.** Zauber lassen sich aus dem Zauberbuch auf jeden Heil-Button, Raid-Mini-Button oder jedes Optionsfeld ziehen. Die rechte Maustaste oder gehaltene Shift-Taste beim Ablegen fuellt die Rechtsklick-Seite, wenn die Option an ist. Zauber ausserhalb der Klassenliste werden angenommen.
**Behoben**
+1883 -199
View File
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -1,12 +1,14 @@
## Interface: 11200
## 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.
## Title: Heal Box Vanilla |cFFFFFF00v1.4.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, optional raid mode for 20 and 40 players, mana ticker with five-second rule, smart damage ranking for attack spells. 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, Mana-Ticker mit Fuenf-Sekunden-Regel, Smart Damage fuer Angriffszauber. 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.2
## Version: 1.4.4.1
## SavedVariablesPerCharacter: HealBox
## OptionalDeps: SuperWoW
FBHealBox.lua
FBHealBox_Raid.lua
FBHealBox_Ticker.lua
FBHealBox_Damage.lua
FBHealBox.xml
+630
View File
@@ -0,0 +1,630 @@
-- ==========================================================================
-- Heal Box Vanilla: Smart Damage (Modul, ab v1.4.3)
--
-- Rangwahl fuer Angriffszauber auf jeder Aktionsleiste. Bongos, Blizzard-
-- Leisten und Tastenkuerzel rufen UseAction(slot) auf; ein Vor-Hook erkennt
-- den Zauber im Slot per Tooltip-Scan und wirkt, wenn ein kleinerer Rang
-- den Gegner sicher toetet, diesen Rang per CastSpellByName. Sonst laeuft
-- der Aufruf unveraendert weiter. Nie ueber dem Rang auf der Leiste.
--
-- Leben des Gegners, vier Quellen mit Vorrang:
-- 1) Server sendet echte Werte (UnitHealthMax ~= 100)
-- 2) MobHealth3 MobHealth3:GetUnitHealth(unit)
-- 3) MobInfo-2 MobHealth_GetTargetCurHP()/MaxHP()
-- 4) eigene Schaetzung Lebenspunkte je Prozent aus beobachtetem Schaden
-- (eigener, Gruppe, Begleiter) und dem Prozentabfall des Ziels, erst ab
-- FBDMG_MIN_DROP Prozent Abfall, Maximum ueber alle Messungen (Fehler
-- nur nach oben, also in die sichere Richtung), gemerkt je Name:Stufe.
--
-- Schaden je Rang: Mindestschaden aus dem Tooltip ("86 to 98 Holy damage"),
-- nachgelernt aus eigenen Treffern ohne Teilwiderstand. Entscheidung:
-- Mindestschaden >= Ziel-Leben (bei Prozent: Obergrenze) x (1 + Aufschlag).
-- ==========================================================================
if (FBHealBox_DamageLoaded) then return; end
FBHealBox_DamageLoaded = true;
FBDMG_MIN_DROP = 3; -- Prozent Abfall, ab dem eine Messung zaehlt
FBDMG_LASTCAST_SEC = 4; -- Sek., wie lange ein eigener Cast fuers Lernen gilt
FBDamageDefaults = {
Enabled = 0, -- Smart Damage an/aus (bewusst aus)
Margin = 20, -- Sicherheitsaufschlag in Prozent
};
-- Angriffszauber je Klasse (englische Zaubernamen wie im ganzen Addon).
-- Kanalisierte, reine DoTs und Flaechenzauber bleiben draussen.
FBDamageSpells = {
Priest = { "Smite", "Holy Fire", "Mind Blast" },
Druid = { "Wrath", "Starfire", "Moonfire" },
Shaman = { "Lightning Bolt", "Chain Lightning", "Earth Shock", "Flame Shock", "Frost Shock" },
Paladin = { "Holy Shock", "Hammer of Wrath", "Exorcism", "Holy Wrath" },
Mage = { "Fireball", "Frostbolt", "Fire Blast", "Scorch", "Pyroblast" },
Warlock = { "Shadow Bolt", "Searing Pain", "Immolate", "Soul Fire", "Conflagrate" },
Hunter = { "Arcane Shot", "Aimed Shot" },
};
-- ==========================================================================
-- [ Texte ]
-- ==========================================================================
FBLocale["enUS"].DMG_HEADER = "Smart Damage";
FBLocale["enUS"].DMG_ENABLED = "Smart Damage";
FBLocale["enUS"].DMG_ENABLED_TIP = "What it does: when you press an attack spell on any action bar (Bongos, Blizzard bars, key bindings) and a lower rank of that spell would still kill the target, that rank is cast instead. Saves mana on nearly dead targets. Rules: never above the rank on the bar, minimum damage of the rank must cover the target's remaining health plus the safety margin. Needs the target's real health: from the server, from MobHealth3 or MobInfo-2, or from the addon's own estimate learned while fighting a mob type. Downsides: estimates can be wrong, partial resists lower the damage, and on unknown mobs nothing happens until enough has been learned. Off by default.";
FBLocale["enUS"].DMG_MARGIN = "Safety margin: |cFFFFFFFF%s %%";
FBLocale["enUS"].DMG_SRC = "Target health source: |cFFFFFFFF%s";
FBLocale["enUS"].DMG_SRC_REAL = "server sends real values";
FBLocale["enUS"].DMG_SRC_MH3 = "MobHealth3";
FBLocale["enUS"].DMG_SRC_MI2 = "MobInfo-2";
FBLocale["enUS"].DMG_SRC_EST = "own estimate (%d mob types learned)";
FBLocale["enUS"].DMG_SRC_NONE = "no target";
FBLocale["enUS"].DMG_SRC_UNKNOWN = "percent only, nothing learned yet";
FBLocale["enUS"].DMG_SPELLS = "Spells: |cFFFFFFFF%s";
FBLocale["enUS"].DMG_INFO = "The target line above updates with your current target. Learning happens automatically: every fight against a mob type improves the estimate for that type.";
FBLocale["enUS"].DMG_STATUS = "Smart Damage: %s, margin %d %%, %s";
FBLocale["enUS"].DBG_DMG = "Smart Damage: %s -> %s (target %d HP via %s, min damage %d)";
FBLocale["enUS"].DBG_DMG_KEEP = "Smart Damage: %s kept (target %d HP via %s)";
FBLocale["enUS"].DMG_LOADED = "Smart Damage module loaded: section in tab |cFFFFFFFFExtras|r, /fbp damage";
FBLocale["enUS"].DMG_ON = "Smart Damage |cFF00FF00on|r.";
FBLocale["enUS"].DMG_OFF = "Smart Damage |cFFFF0000off|r.";
FBLocale["deDE"].DMG_HEADER = "Smart Damage";
FBLocale["deDE"].DMG_ENABLED = "Smart Damage";
FBLocale["deDE"].DMG_ENABLED_TIP = "Was es tut: Drueckst du einen Angriffszauber auf einer beliebigen Aktionsleiste (Bongos, Blizzard-Leisten, Tastenkuerzel) und ein niedrigerer Rang wuerde das Ziel noch toeten, wird stattdessen dieser Rang gewirkt. Spart Mana bei fast toten Zielen. Regeln: nie ueber dem Rang auf der Leiste, der Mindestschaden des Rangs muss das Restleben des Ziels plus Sicherheitsaufschlag decken. Braucht das echte Leben des Ziels: vom Server, von MobHealth3 oder MobInfo-2, oder aus der eigenen Schaetzung, die das Addon im Kampf gegen einen Mobtyp lernt. Nachteile: Schaetzungen koennen danebenliegen, Teilwiderstaende senken den Schaden, und bei unbekannten Mobs passiert nichts, bis genug gelernt ist. Standardmaessig aus.";
FBLocale["deDE"].DMG_MARGIN = "Sicherheitsaufschlag: |cFFFFFFFF%s %%";
FBLocale["deDE"].DMG_SRC = "Lebensquelle des Ziels: |cFFFFFFFF%s";
FBLocale["deDE"].DMG_SRC_REAL = "Server sendet echte Werte";
FBLocale["deDE"].DMG_SRC_MH3 = "MobHealth3";
FBLocale["deDE"].DMG_SRC_MI2 = "MobInfo-2";
FBLocale["deDE"].DMG_SRC_EST = "eigene Schaetzung (%d Mobtypen gelernt)";
FBLocale["deDE"].DMG_SRC_NONE = "kein Ziel";
FBLocale["deDE"].DMG_SRC_UNKNOWN = "nur Prozent, noch nichts gelernt";
FBLocale["deDE"].DMG_SPELLS = "Zauber: |cFFFFFFFF%s";
FBLocale["deDE"].DMG_INFO = "Die Zielzeile oben folgt deinem aktuellen Ziel. Gelernt wird von selbst: Jeder Kampf gegen einen Mobtyp verbessert die Schaetzung fuer diesen Typ.";
FBLocale["deDE"].DMG_STATUS = "Smart Damage: %s, Aufschlag %d %%, %s";
FBLocale["deDE"].DBG_DMG = "Smart Damage: %s -> %s (Ziel %d LP ueber %s, Mindestschaden %d)";
FBLocale["deDE"].DBG_DMG_KEEP = "Smart Damage: %s bleibt (Ziel %d LP ueber %s)";
FBLocale["deDE"].DMG_LOADED = "Smart-Damage-Modul geladen: Abschnitt im Reiter |cFFFFFFFFExtras|r, /fbp damage";
FBLocale["deDE"].DMG_ON = "Smart Damage |cFF00FF00an|r.";
FBLocale["deDE"].DMG_OFF = "Smart Damage |cFFFF0000aus|r.";
-- Weitere Sprachen (Spanisch, Franzoesisch, Italienisch)
FBLocale["esES"].DMG_HEADER = "Smart Damage";
FBLocale["esES"].DMG_ENABLED = "Smart Damage";
FBLocale["esES"].DMG_ENABLED_TIP = "Qué hace: cuando pulsas un hechizo de ataque en cualquier barra de acción (Bongos, barras de Blizzard, atajos de teclado) y un rango inferior de ese hechizo aún mataría al objetivo, se lanza ese rango en su lugar. Ahorra maná con objetivos casi muertos. Reglas: nunca por encima del rango de la barra, el daño mínimo del rango debe cubrir la vida restante del objetivo más el margen de seguridad. Necesita la vida real del objetivo: del servidor, de MobHealth3 o MobInfo-2, o de la estimación propia del addon aprendida al combatir un tipo de enemigo. Desventajas: las estimaciones pueden fallar, las resistencias parciales reducen el daño y con enemigos desconocidos no ocurre nada hasta haber aprendido lo suficiente. Desactivado por defecto.";
FBLocale["esES"].DMG_MARGIN = "Margen de seguridad: |cFFFFFFFF%s %%";
FBLocale["esES"].DMG_SRC = "Fuente de vida del objetivo: |cFFFFFFFF%s";
FBLocale["esES"].DMG_SRC_REAL = "el servidor envía valores reales";
FBLocale["esES"].DMG_SRC_MH3 = "MobHealth3";
FBLocale["esES"].DMG_SRC_MI2 = "MobInfo-2";
FBLocale["esES"].DMG_SRC_EST = "estimación propia (%d tipos de enemigo aprendidos)";
FBLocale["esES"].DMG_SRC_NONE = "sin objetivo";
FBLocale["esES"].DMG_SRC_UNKNOWN = "solo porcentaje, aún nada aprendido";
FBLocale["esES"].DMG_SPELLS = "Hechizos: |cFFFFFFFF%s";
FBLocale["esES"].DMG_INFO = "La línea del objetivo se actualiza con tu objetivo actual. El aprendizaje es automático: cada combate contra un tipo de enemigo mejora la estimación de ese tipo.";
FBLocale["esES"].DMG_STATUS = "Smart Damage: %s, margen %d %%, %s";
FBLocale["esES"].DBG_DMG = "Smart Damage: %s -> %s (objetivo %d PV vía %s, daño mínimo %d)";
FBLocale["esES"].DBG_DMG_KEEP = "Smart Damage: %s mantenido (objetivo %d PV vía %s)";
FBLocale["esES"].DMG_LOADED = "Módulo Smart Damage cargado: sección en la pestaña |cFFFFFFFFExtras|r, /fbp damage";
FBLocale["esES"].DMG_ON = "Smart Damage |cFF00FF00activado|r.";
FBLocale["esES"].DMG_OFF = "Smart Damage |cFFFF0000desactivado|r.";
FBLocale["frFR"].DMG_HEADER = "Smart Damage";
FBLocale["frFR"].DMG_ENABLED = "Smart Damage";
FBLocale["frFR"].DMG_ENABLED_TIP = "Ce que ça fait : quand vous appuyez sur un sort d'attaque sur n'importe quelle barre d'action (Bongos, barres Blizzard, raccourcis) et qu'un rang inférieur de ce sort tuerait encore la cible, c'est ce rang qui est lancé. Économise du mana sur les cibles presque mortes. Règles : jamais au-dessus du rang de la barre, les dégâts minimaux du rang doivent couvrir la vie restante de la cible plus la marge de sécurité. Nécessite la vie réelle de la cible : du serveur, de MobHealth3 ou MobInfo-2, ou de l'estimation propre de l'addon apprise en combattant un type de monstre. Inconvénients : les estimations peuvent se tromper, les résistances partielles réduisent les dégâts, et sur les monstres inconnus rien ne se passe tant que l'apprentissage est insuffisant. Désactivé par défaut.";
FBLocale["frFR"].DMG_MARGIN = "Marge de sécurité : |cFFFFFFFF%s %%";
FBLocale["frFR"].DMG_SRC = "Source de vie de la cible : |cFFFFFFFF%s";
FBLocale["frFR"].DMG_SRC_REAL = "le serveur envoie des valeurs réelles";
FBLocale["frFR"].DMG_SRC_MH3 = "MobHealth3";
FBLocale["frFR"].DMG_SRC_MI2 = "MobInfo-2";
FBLocale["frFR"].DMG_SRC_EST = "estimation propre (%d types de monstres appris)";
FBLocale["frFR"].DMG_SRC_NONE = "aucune cible";
FBLocale["frFR"].DMG_SRC_UNKNOWN = "pourcentage seulement, rien appris pour le moment";
FBLocale["frFR"].DMG_SPELLS = "Sorts : |cFFFFFFFF%s";
FBLocale["frFR"].DMG_INFO = "La ligne de cible ci-dessus se met à jour avec votre cible actuelle. L'apprentissage est automatique : chaque combat contre un type de monstre améliore l'estimation pour ce type.";
FBLocale["frFR"].DMG_STATUS = "Smart Damage : %s, marge %d %%, %s";
FBLocale["frFR"].DBG_DMG = "Smart Damage : %s -> %s (cible %d PV via %s, dégâts min %d)";
FBLocale["frFR"].DBG_DMG_KEEP = "Smart Damage : %s conservé (cible %d PV via %s)";
FBLocale["frFR"].DMG_LOADED = "Module Smart Damage chargé : section dans l'onglet |cFFFFFFFFExtras|r, /fbp damage";
FBLocale["frFR"].DMG_ON = "Smart Damage |cFF00FF00activé|r.";
FBLocale["frFR"].DMG_OFF = "Smart Damage |cFFFF0000désactivé|r.";
FBLocale["itIT"].DMG_HEADER = "Smart Damage";
FBLocale["itIT"].DMG_ENABLED = "Smart Damage";
FBLocale["itIT"].DMG_ENABLED_TIP = "Cosa fa: quando premi un incantesimo d'attacco su una qualsiasi barra delle azioni (Bongos, barre Blizzard, scorciatoie) e un rango inferiore di quell'incantesimo ucciderebbe comunque il bersaglio, viene lanciato quel rango. Risparmia mana sui bersagli quasi morti. Regole: mai sopra il rango sulla barra, il danno minimo del rango deve coprire la salute rimanente del bersaglio più il margine di sicurezza. Richiede la salute reale del bersaglio: dal server, da MobHealth3 o MobInfo-2, o dalla stima dell'addon appresa combattendo un tipo di mostro. Svantaggi: le stime possono sbagliare, le resistenze parziali riducono il danno e sui mostri sconosciuti non succede nulla finché non si è appreso abbastanza. Disattivato per impostazione predefinita.";
FBLocale["itIT"].DMG_MARGIN = "Margine di sicurezza: |cFFFFFFFF%s %%";
FBLocale["itIT"].DMG_SRC = "Fonte della salute del bersaglio: |cFFFFFFFF%s";
FBLocale["itIT"].DMG_SRC_REAL = "il server invia valori reali";
FBLocale["itIT"].DMG_SRC_MH3 = "MobHealth3";
FBLocale["itIT"].DMG_SRC_MI2 = "MobInfo-2";
FBLocale["itIT"].DMG_SRC_EST = "stima propria (%d tipi di mostro appresi)";
FBLocale["itIT"].DMG_SRC_NONE = "nessun bersaglio";
FBLocale["itIT"].DMG_SRC_UNKNOWN = "solo percentuale, ancora nulla appreso";
FBLocale["itIT"].DMG_SPELLS = "Incantesimi: |cFFFFFFFF%s";
FBLocale["itIT"].DMG_INFO = "La riga del bersaglio qui sopra si aggiorna con il tuo bersaglio attuale. L'apprendimento è automatico: ogni combattimento contro un tipo di mostro migliora la stima per quel tipo.";
FBLocale["itIT"].DMG_STATUS = "Smart Damage: %s, margine %d %%, %s";
FBLocale["itIT"].DBG_DMG = "Smart Damage: %s -> %s (bersaglio %d PS via %s, danno minimo %d)";
FBLocale["itIT"].DBG_DMG_KEEP = "Smart Damage: %s mantenuto (bersaglio %d PS via %s)";
FBLocale["itIT"].DMG_LOADED = "Modulo Smart Damage caricato: sezione nella scheda |cFFFFFFFFExtra|r, /fbp damage";
FBLocale["itIT"].DMG_ON = "Smart Damage |cFF00FF00attivo|r.";
FBLocale["itIT"].DMG_OFF = "Smart Damage |cFFFF0000disattivato|r.";
-- ==========================================================================
-- [ Zustand ]
-- ==========================================================================
FBDmgSpellRanks = {}; -- [Name] = { { rank, id, min, max }, ... } aufsteigend
FBDmgSlotCache = {}; -- [slot] = { name, rank } oder false (kein Zauber)
FBDmgAnchorPct = nil; -- Prozent des Ziels beim letzten Messpunkt
FBDmgAccum = 0; -- Schaden auf das Ziel seit dem Messpunkt
FBDmgTargetName = nil;
FBDmgLastCast = nil; -- { spell, rank, t } fuer das Lernen aus dem Combatlog
function FBDmg_Cfg()
if (not HealBox.Damage) then FBDmg_ApplyDefaults(); end
return HealBox.Damage;
end
function FBDmg_ApplyDefaults()
if (not HealBox.Damage) then HealBox.Damage = {}; end
for k, v in pairs(FBDamageDefaults) do
if (HealBox.Damage[k] == nil) then HealBox.Damage[k] = v; end
end
if (not HealBox.MobHP) then HealBox.MobHP = {}; end -- [Name:Stufe] = { ppp, n }
if (not HealBox.DmgMemory) then HealBox.DmgMemory = {}; end -- [Zauber|Rang] = gelernter Mindestschaden
return true;
end
-- ==========================================================================
-- [ Zauberbuch: Raenge und Schaden ]
-- ==========================================================================
function FBDmg_ScanSpells()
FBDmgSpellRanks = {};
local wanted = {};
for _, n in ipairs(FBDamageSpells[FBClass] or {}) do wanted[n] = true; end
local i = 1;
while true do
local name, rank = GetSpellName(i, BOOKTYPE_SPELL);
if (not name) then break; end
if (wanted[name]) then
local txt = FBPredict_TooltipText(i) or "";
local lo, hi = FBPredict_FindAmountRange(txt);
if (not lo) then
lo = FBPredict_FindAmountSingle(txt);
hi = lo;
end
local isDamage = (string.find(txt, "[Dd]amage") or string.find(txt, "[Ss]chaden")) ~= nil;
if (lo and isDamage) then
if (not FBDmgSpellRanks[name]) then FBDmgSpellRanks[name] = {}; end
table.insert(FBDmgSpellRanks[name], { rank = rank or "", id = i, min = lo, max = hi });
end
end
i = i + 1;
end
FBDmgSlotCache = {};
end
-- Mindestschaden eines Rangs: gelernt (Treffer ohne Teilwiderstand), sonst Tooltip
function FBDmg_MinDamage(spell, entry)
local learned = HealBox.DmgMemory and HealBox.DmgMemory[spell.."|"..entry.rank];
if (learned and learned > entry.min) then return learned; end
return entry.min;
end
-- ==========================================================================
-- [ Leben des Ziels ]
-- ==========================================================================
function FBDmg_MobKey(unit)
local name = UnitName(unit);
if (not name) then return nil; end
local lvl = UnitLevel and UnitLevel(unit) or 0;
return name..":"..tostring(lvl or 0);
end
-- Sendet der Server echte Werte fuer diese Einheit?
function FBDmg_HasRealValues(unit)
local max = UnitHealthMax(unit);
return (max and max ~= 100 and max > 0);
end
-- Liefert cur, max, source oder nil. cur ist bei Schaetzung die Obergrenze.
function FBDmg_TargetHP(unit)
unit = unit or "target";
if (not UnitExists(unit)) then return nil; end
if (FBDmg_HasRealValues(unit)) then
return UnitHealth(unit), UnitHealthMax(unit), "real";
end
-- MobHealth3
if (MobHealth3 and MobHealth3.GetUnitHealth) then
local ok, cur, max, found = pcall(MobHealth3.GetUnitHealth, MobHealth3, unit);
if (ok and found and cur and max and max > 100) then return cur, max, "mh3"; end
end
-- MobInfo-2 (nur fuer das aktuelle Ziel)
if (unit == "target" and MobHealth_GetTargetCurHP and MobHealth_GetTargetMaxHP) then
local ok, cur = pcall(MobHealth_GetTargetCurHP);
local ok2, max = pcall(MobHealth_GetTargetMaxHP);
if (ok and ok2 and cur and max and max > 100) then return cur, max, "mi2"; end
end
-- eigene Schaetzung: Prozent-Obergrenze x Lebenspunkte je Prozent
local key = FBDmg_MobKey(unit);
local est = key and HealBox.MobHP and HealBox.MobHP[key];
if (est and est.ppp and est.ppp > 0) then
local pct = UnitHealth(unit) or 0;
return math.ceil((pct + 1) * est.ppp), math.ceil(100 * est.ppp), "est";
end
return nil;
end
-- [ Lernen: Lebenspunkte je Prozent ] ---------------------------------------
function FBDmg_ResetTarget()
FBDmgTargetName = nil;
FBDmgAnchorPct = nil;
FBDmgAccum = 0;
if (UnitExists("target") and UnitCanAttack and UnitCanAttack("player", "target") and not FBDmg_HasRealValues("target")) then
FBDmgTargetName = UnitName("target");
FBDmgAnchorPct = UnitHealth("target");
end
FBDmg_UpdateSourceText();
end
-- Schaden auf das Ziel vermerken (aus dem Combatlog)
function FBDmg_NoteDamage(victim, amount)
if (not FBDmgTargetName) or (victim ~= FBDmgTargetName) then return; end
FBDmgAccum = FBDmgAccum + (amount or 0);
end
-- UNIT_HEALTH des Ziels: Prozent gefallen -> Messung
function FBDmg_OnTargetHealth()
if (not FBDmgTargetName) or (not FBDmgAnchorPct) then return; end
if (FBDmg_HasRealValues("target")) then return; end
local pct = UnitHealth("target") or 0;
local drop = FBDmgAnchorPct - pct;
if (drop >= FBDMG_MIN_DROP and FBDmgAccum > 0) then
local ppp = FBDmgAccum / drop;
local key = FBDmg_MobKey("target");
if (key) then
if (not HealBox.MobHP) then HealBox.MobHP = {}; end
local e = HealBox.MobHP[key];
if (not e) then
HealBox.MobHP[key] = { ppp = ppp, n = 1 };
else
-- Maximum: Fehler nur nach oben (fremder, nicht gezaehlter Schaden
-- wuerde die Schaetzung sonst nach unten ziehen)
if (ppp > e.ppp) then e.ppp = ppp; end
e.n = (e.n or 0) + 1;
end
end
FBDmgAnchorPct = pct;
FBDmgAccum = 0;
FBDmg_UpdateSourceText();
elseif (pct > FBDmgAnchorPct) then
-- Ziel wurde geheilt: neu ansetzen
FBDmgAnchorPct = pct;
FBDmgAccum = 0;
end
end
-- [ Combatlog ] --------------------------------------------------------------
-- Schadenszeilen: Opfer und Betrag herausziehen (englischer Client, wie das
-- ganze Addon). "(N resisted)" markiert Teilwiderstand.
function FBDmg_ParseLine(msg)
if (not msg) then return nil; end
local _, _, spell, victim, amount;
-- "Your Smite hits Kobold for 95." / "crits"
_, _, spell, victim, amount = string.find(msg, "^Your (.-) hits (.-) for (%d+)");
if (not spell) then _, _, spell, victim, amount = string.find(msg, "^Your (.-) crits (.-) for (%d+)"); end
if (spell) then return victim, tonumber(amount), spell, "self"; end
-- "You hit Kobold for 20." / "You crit"
_, _, victim, amount = string.find(msg, "^You hit (.-) for (%d+)");
if (not victim) then _, _, victim, amount = string.find(msg, "^You crit (.-) for (%d+)"); end
if (victim) then return victim, tonumber(amount), nil, "self"; end
-- "Kobold suffers 50 Shadow damage from your Shadow Word: Pain."
_, _, victim, amount = string.find(msg, "^(.-) suffers (%d+) .- damage from your");
if (victim) then return victim, tonumber(amount), nil, "self"; end
-- "Bob's Fireball hits Kobold for 200." / crits (Gruppe, Begleiter)
_, _, victim, amount = string.find(msg, "^.-'s .- hits (.-) for (%d+)");
if (not victim) then _, _, victim, amount = string.find(msg, "^.-'s .- crits (.-) for (%d+)"); end
if (victim) then return victim, tonumber(amount), nil, "other"; end
-- "Bob hits Kobold for 50." / crits (Nahkampf Gruppe, Begleiter)
_, _, victim, amount = string.find(msg, "^.- hits (.-) for (%d+)");
if (not victim) then _, _, victim, amount = string.find(msg, "^.- crits (.-) for (%d+)"); end
if (victim) then return victim, tonumber(amount), nil, "other"; end
return nil;
end
function FBDmg_OnCombatLog(msg)
-- Nur arbeiten, wenn Smart Damage an ist und es etwas zu lernen gibt
-- (Prozentziel wird vermessen oder ein eigener Cast wartet auf Treffer).
if (FBDmg_Cfg().Enabled ~= 1) then return; end
if (not FBDmgTargetName) and (not FBDmgLastCast) then return; end
local victim, amount, spell, who = FBDmg_ParseLine(msg);
if (not victim) then return; end
FBDmg_NoteDamage(victim, amount);
-- Mindestschaden lernen: eigener Zauber, kein Crit, kein Teilwiderstand
if (who == "self" and spell and FBDmgLastCast and FBDmgLastCast.spell == spell
and (GetTime() - FBDmgLastCast.t) <= FBDMG_LASTCAST_SEC
and not string.find(msg, "crits") and not string.find(msg, "resisted")) then
local key = spell.."|"..(FBDmgLastCast.rank or "");
if (not HealBox.DmgMemory) then HealBox.DmgMemory = {}; end
local old = HealBox.DmgMemory[key];
if (not old) or (amount < old) then
-- kleinster beobachteter Volltreffer = sicherer Mindestschaden
HealBox.DmgMemory[key] = amount;
end
end
end
-- ==========================================================================
-- [ Rangwahl beim Klick ]
-- ==========================================================================
-- Zauber in einem Aktionsslot: name, rank oder nil (Makro, Gegenstand)
function FBDmg_SlotSpell(slot)
local c = FBDmgSlotCache[slot];
if (c ~= nil) then
if (c == false) then return nil; end
return c.name, c.rank;
end
if (not FBPredictTip) or (not FBPredictTip.SetAction) then return nil; end
FBPredictTip:SetOwner(UIParent, "ANCHOR_NONE");
FBPredictTip:ClearLines();
FBPredictTip:SetAction(slot);
local l = getglobal("FBHealBoxScanTipTextLeft1");
local r = getglobal("FBHealBoxScanTipTextRight1");
local name = l and l:GetText();
local rank = r and r:GetText();
if (name and FBDmgSpellRanks[name]) then
FBDmgSlotCache[slot] = { name = name, rank = rank or "" };
return name, rank or "";
end
FBDmgSlotCache[slot] = false;
return nil;
end
-- Liefert den zu wirkenden Cast-String, wenn abgerangt wird, sonst nil
function FBDmg_Choose(spell, rank)
local cfg = FBDmg_Cfg();
local ranks = FBDmgSpellRanks[spell];
if (not ranks) or (table.getn(ranks) < 2) then return nil; end
if (not UnitExists("target")) or (UnitCanAttack and not UnitCanAttack("player", "target")) then return nil; end
if (UnitIsDead and UnitIsDead("target")) then return nil; end
local cur, max, source = FBDmg_TargetHP("target");
if (not cur) or (cur <= 0) then return nil; end
local need = cur * (1 + (cfg.Margin or 20) / 100);
local assignedIdx = nil;
for i, e in ipairs(ranks) do
if (e.rank == rank) then assignedIdx = i; end
end
if (not assignedIdx) then assignedIdx = table.getn(ranks); end
for i = 1, assignedIdx do
local e = ranks[i];
if (FBDmg_MinDamage(spell, e) >= need) then
if (i == assignedIdx) then
if (FBPredictDebug) then
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..format(FBT("DBG_DMG_KEEP"), spell.."("..rank..")", cur, source));
end
return nil;
end
local chosen = spell;
if (e.rank ~= "") then chosen = spell.."("..e.rank..")"; end
if (FBPredictDebug) then
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..format(FBT("DBG_DMG"), spell.."("..rank..")", chosen, cur, source, FBDmg_MinDamage(spell, e)));
end
return chosen, e.rank;
end
end
return nil;
end
-- Vor-Hook auf UseAction. true, wenn das Modul selbst gecastet hat.
function FBDmg_TryDownrank(slot, checkCursor, onSelf)
if (FBDmg_Cfg().Enabled ~= 1) or onSelf then return false; end
if ((CursorHasSpell and CursorHasSpell()) or (CursorHasItem and CursorHasItem())) then return false; end
local name, rank = FBDmg_SlotSpell(slot);
if (not name) then return false; end
local chosen, chosenRank = FBDmg_Choose(name, rank);
FBDmgLastCast = { spell = name, rank = (chosenRank or rank), t = GetTime() };
if (not chosen) then return false; end
CastSpellByName(chosen);
return true;
end
function FBDmg_HookUseAction()
if (FBHealBox_UseActionHooked) or (not UseAction) then return; end
FBHealBox_UseActionHooked = true;
local origUseAction = UseAction;
UseAction = function(slot, checkCursor, onSelf)
if (FBDmg_TryDownrank(slot, checkCursor, onSelf)) then return; end
origUseAction(slot, checkCursor, onSelf);
end
end
-- ==========================================================================
-- [ Events ]
-- ==========================================================================
FBDmgFrame = CreateFrame("Frame", "FBHealBoxDamageFrame", UIParent);
FBDmgFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
FBDmgFrame:RegisterEvent("SPELLS_CHANGED");
FBDmgFrame:RegisterEvent("ACTIONBAR_SLOT_CHANGED");
FBDmgFrame:RegisterEvent("PLAYER_TARGET_CHANGED");
FBDmgFrame:RegisterEvent("UNIT_HEALTH");
for _, ev in ipairs({
"CHAT_MSG_SPELL_SELF_DAMAGE", "CHAT_MSG_COMBAT_SELF_HITS",
"CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE", "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE",
"CHAT_MSG_SPELL_PARTY_DAMAGE", "CHAT_MSG_COMBAT_PARTY_HITS",
"CHAT_MSG_SPELL_PET_DAMAGE", "CHAT_MSG_COMBAT_PET_HITS",
}) do FBDmgFrame:RegisterEvent(ev); end
FBDmgFrame:SetScript("OnEvent", function()
if (event == "PLAYER_ENTERING_WORLD" or event == "SPELLS_CHANGED") then
FBDmg_ScanSpells();
FBDmg_UpdateSpellText();
FBDmg_ResetTarget();
elseif (event == "ACTIONBAR_SLOT_CHANGED") then
if (arg1) then FBDmgSlotCache[arg1] = nil; else FBDmgSlotCache = {}; end
elseif (event == "PLAYER_TARGET_CHANGED") then
FBDmg_ResetTarget();
elseif (event == "UNIT_HEALTH") then
if (arg1 == "target") then FBDmg_OnTargetHealth(); FBDmg_UpdateSourceText(); end
else
FBDmg_OnCombatLog(arg1);
end
end);
-- ==========================================================================
-- [ Optionen: Abschnitt im Reiter "Extras" ]
-- ==========================================================================
function FBDmg_SourceText()
if (not UnitExists("target")) then return FBT("DMG_SRC_NONE"); end
local cur, max, source = FBDmg_TargetHP("target");
if (source == "real") then return FBT("DMG_SRC_REAL"); end
if (source == "mh3") then return FBT("DMG_SRC_MH3"); end
if (source == "mi2") then return FBT("DMG_SRC_MI2"); end
if (source == "est") then
local n = 0;
for _ in pairs(HealBox.MobHP or {}) do n = n + 1; end
return format(FBT("DMG_SRC_EST"), n);
end
return FBT("DMG_SRC_UNKNOWN");
end
function FBDmg_UpdateSourceText()
-- nur, wenn das Optionsfenster offen ist (sonst je UNIT_HEALTH des Ziels umsonst)
if (FBDmgSourceText and panel and panel:IsVisible()) then
FBDmgSourceText:SetText(format(FBT("DMG_SRC"), FBDmg_SourceText()));
end
end
function FBDmg_UpdateSpellText()
if (not FBDmgSpellText) then return; end
local list = {};
for _, n in ipairs(FBDamageSpells[FBClass] or {}) do
if (FBDmgSpellRanks[n]) then table.insert(list, n.." ("..table.getn(FBDmgSpellRanks[n])..")"); end
end
local shown = "-";
if (table.getn(list) > 0) then shown = table.concat(list, ", "); end
FBDmgSpellText:SetText(format(FBT("DMG_SPELLS"), shown));
end
function FBDmg_UpdateMarginText()
if (FBDmgMarginSlider and FBDmgMarginSlider.Text) then
FBDmgMarginSlider.Text:SetText(format(FBT("DMG_MARGIN"), math.floor(FBDmgMarginSlider:GetValue() + 0.5)));
end
end
function FBDmg_BuildOptions()
local tab = FBHealBox_FindOptionsTab("TAB_TICKER");
if (not tab) then return; end
local y = FBOPT_CONTENT_Y - 232; -- unter dem Ticker-Abschnitt
FBDmgHeader = tab:CreateFontString(nil, "OVERLAY", "GameFontNormal");
FBDmgHeader:SetPoint("TOPLEFT", tab, "TOPLEFT", 35, y);
FBDmgHeader:SetText(FBT("DMG_HEADER"));
FBDmgEnabledCheck = FBHealBox_CreateCheck("FBHealBoxDmgEnabledCheck", tab, 34, y - 20, "DMG_ENABLED", "DMG_ENABLED_TIP", function()
FBDmg_Cfg().Enabled = FBDmgEnabledCheck:GetChecked() and 1 or 0;
end);
FBDmgEnabledCheck:SetChecked(nil);
FBDmgMarginSlider = CreateFrame("Slider", "FBDmgMarginSlider", tab, "OptionsSliderTemplate");
FBDmgMarginSlider:SetWidth(170);
FBDmgMarginSlider:SetHeight(16);
FBDmgMarginSlider:SetPoint("TOPLEFT", 268, y - 40);
FBDmgMarginSlider:SetMinMaxValues(0, 50);
FBDmgMarginSlider:SetValueStep(5);
FBDmgMarginSlider:SetValue(FBDmg_Cfg().Margin or 20);
FBDmgMarginSlider.Text = FBDmgMarginSlider:CreateFontString(nil, "BACKGROUND", "GameFontNormal");
FBDmgMarginSlider.Text:SetPoint("CENTER", 0, 15);
getglobal("FBDmgMarginSliderLow"):SetText("0");
getglobal("FBDmgMarginSliderHigh"):SetText("50");
FBDmgMarginSlider:SetScript("OnValueChanged", function()
FBDmg_Cfg().Margin = math.floor(FBDmgMarginSlider:GetValue() + 0.5);
FBDmg_UpdateMarginText();
end);
FBDmg_UpdateMarginText();
FBDmgSourceText = tab:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
FBDmgSourceText:SetPoint("TOPLEFT", tab, "TOPLEFT", 40, y - 80);
FBDmgSourceText:SetWidth(390);
FBDmgSourceText:SetJustifyH("LEFT");
FBDmgSpellText = tab:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
FBDmgSpellText:SetPoint("TOPLEFT", tab, "TOPLEFT", 40, y - 96);
FBDmgSpellText:SetWidth(390);
FBDmgSpellText:SetJustifyH("LEFT");
FBDmgInfoText = tab:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall");
FBDmgInfoText:SetPoint("TOPLEFT", tab, "TOPLEFT", 35, y - 120);
FBDmgInfoText:SetWidth(395);
FBDmgInfoText:SetJustifyH("LEFT");
FBDmgInfoText:SetText(FBT("DMG_INFO"));
FBDmg_UpdateSourceText();
FBDmg_UpdateSpellText();
FBDmg_SyncOptions();
-- beim Oeffnen des Fensters die Zielzeile auffrischen (Vor-Hook auf OnShow)
local prevShow = panel:GetScript("OnShow");
panel:SetScript("OnShow", function()
if (prevShow) then prevShow(); end
FBDmg_UpdateSourceText();
end);
end
function FBDmg_SyncOptions()
local cfg = FBDmg_Cfg();
if (FBDmgEnabledCheck) then FBDmgEnabledCheck:SetChecked(cfg.Enabled == 1); end
if (FBDmgMarginSlider) then FBDmgMarginSlider:SetValue(cfg.Margin or 20); end
return true;
end
function FBDmg_ApplyLocale()
if (FBDmgHeader) then FBDmgHeader:SetText(FBT("DMG_HEADER")); end
if (FBDmgEnabledCheck) then FBDmgEnabledCheck.Text:SetText(FBT("DMG_ENABLED")); FBDmgEnabledCheck.tooltipText = FBT("DMG_ENABLED_TIP"); end
if (FBDmgInfoText) then FBDmgInfoText:SetText(FBT("DMG_INFO")); end
FBDmg_UpdateMarginText();
FBDmg_UpdateSourceText();
FBDmg_UpdateSpellText();
return true;
end
-- ==========================================================================
-- [ Anbindung an den Kern ]
-- ==========================================================================
FBHealBox_RegisterHook("Loaded", function()
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r "..FBT("DMG_LOADED"));
return true;
end);
FBHealBox_RegisterHook("Defaults", function() return FBDmg_ApplyDefaults(); end);
FBHealBox_RegisterHook("SyncOptions", function() return FBDmg_SyncOptions(); end);
FBHealBox_RegisterHook("ApplyLocale", function() return FBDmg_ApplyLocale(); end);
FBHealBox_RegisterHook("Status", function()
local cfg = FBDmg_Cfg();
local state = FBT("FBP_STATE_OFF");
if (cfg.Enabled == 1) then state = FBT("FBP_STATE_ON"); end
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..format(FBT("DMG_STATUS"), state, cfg.Margin or 20, FBDmg_SourceText()));
return true;
end);
FBHealBox_RegisterHook("Slash", function(msg)
if (msg == "damage" or msg == "dmg") then
local cfg = FBDmg_Cfg();
if (cfg.Enabled == 1) then cfg.Enabled = 0; else cfg.Enabled = 1; end
FBDmg_SyncOptions();
local key = "DMG_OFF";
if (cfg.Enabled == 1) then key = "DMG_ON"; end
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r "..FBT(key));
return true;
end
return false;
end);
FBDmg_ApplyDefaults();
FBDmg_BuildOptions();
FBDmg_HookUseAction();
+408 -55
View File
@@ -33,6 +33,10 @@ FBRAID_MANA_H = 3; -- px Manastreifen in der Zelle
FBRAID_LOS_SIZE = 10; -- px Sichtlinien-Auge in der Zelle
FBRAID_HEADER_H = 12; -- px Gruppenkopf
FBRAID_TITLE_H = 14; -- px Titelleiste (Griff zum Verschieben)
FBRAID_BUFFICON_SIZE = 6; -- px Buff-Icons aussen links an der Zelle
FBRAID_BUFFICON_ROWS = 3; -- Stapelhoehe (3 x 4 = 12 Plaetze)
FBRAID_BUFFICON_COLS = 4; -- so viele Spalten bekommen Platz zwischen den Gruppen
FBRAID_BUFFICON_MAX = 12;
-- Vorgaben aller Raid-Einstellungen. Leben in HealBox.Raid.
FBRaidDefaults = {
@@ -52,6 +56,7 @@ FBRaidDefaults = {
HPText = "percent",-- none | percent | deficit
HideEmpty = 1, -- leere Gruppen ausblenden
ShowTitle = 1, -- Titelleiste anzeigen
BuffIcons = 1, -- Buff-Icons links an den Zellen (aus: kein Platz reserviert)
PosX = nil, PosY = nil, -- Position der linken oberen Ecke (Bildschirmpixel)
};
@@ -74,6 +79,8 @@ FBLocale["enUS"].RAID_HIDEEMPTY = "Hide empty groups";
FBLocale["enUS"].RAID_HIDEEMPTY_TIP = "Groups without members take no space. A 20-player raid then fills a single row.";
FBLocale["enUS"].RAID_SHOWTITLE = "Title bar";
FBLocale["enUS"].RAID_SHOWTITLE_TIP = "A thin title bar above the grid that can be dragged. Without it, hold Shift and drag any cell.";
FBLocale["enUS"].RAID_BUFFICONS = "Show buffs";
FBLocale["enUS"].RAID_BUFFICONS_TIP = "Buff icons with clock on the left of each cell, as on the party plates. Off: no icons and no room reserved for them, the groups move closer together.";
FBLocale["enUS"].RAID_HPTEXT = "HP text";
FBLocale["enUS"].RAID_HPTEXT_TIP = "What the right side of a cell shows: nothing, the health percentage, or the missing health (deficit).";
FBLocale["enUS"].RAID_HP_NONE = "none";
@@ -114,6 +121,8 @@ FBLocale["deDE"].RAID_HIDEEMPTY = "Leere Gruppen ausblenden";
FBLocale["deDE"].RAID_HIDEEMPTY_TIP = "Gruppen ohne Mitglieder brauchen keinen Platz. Ein 20er-Raid fuellt dann eine einzige Zeile.";
FBLocale["deDE"].RAID_SHOWTITLE = "Titelleiste";
FBLocale["deDE"].RAID_SHOWTITLE_TIP = "Eine schmale Titelleiste ueber dem Raster, an der man ziehen kann. Ohne sie: Shift halten und eine beliebige Zelle ziehen.";
FBLocale["deDE"].RAID_BUFFICONS = "Buffs anzeigen";
FBLocale["deDE"].RAID_BUFFICONS_TIP = "Buff-Icons mit Uhr links an jeder Zelle, wie auf den Gruppenplaketten. Aus: keine Icons und kein reservierter Platz, die Gruppen ruecken enger zusammen.";
FBLocale["deDE"].RAID_HPTEXT = "HP-Text";
FBLocale["deDE"].RAID_HPTEXT_TIP = "Was die rechte Seite einer Zelle zeigt: nichts, die Lebensprozente oder die fehlenden Lebenspunkte (Defizit).";
FBLocale["deDE"].RAID_HP_NONE = "keiner";
@@ -137,6 +146,134 @@ FBLocale["deDE"].RAID_INFO = "Die Mini-Buttons nutzen die Zauber von But
FBLocale["deDE"].RAID_MOVE_TIP = "Ziehen verschiebt das Raid-Raster.";
FBLocale["deDE"].FBP_RAID = "Raidmodus: %s, %d Mitglieder in %d Gruppen";
FBLocale["deDE"].FBP_RAID_CMDS = "Raid: /fbp raid (an/aus), /fbp raidtest 20|40|off";
-- Weitere Sprachen (Spanisch, Franzoesisch, Italienisch)
FBLocale["esES"].TAB_RAID = "Modo banda";
FBLocale["esES"].RAID_TITLE = "Banda";
FBLocale["esES"].RAID_ENABLED = "Modo banda";
FBLocale["esES"].RAID_ENABLED_TIP = "Interruptor principal de la rejilla de banda compacta: una celda por miembro, agrupadas por grupo de banda, con minibotones de curación. La rejilla aparece sola cuando tu banda tiene al menos el número de jugadores fijado abajo; las bandas más pequeñas conservan la vista de grupo.";
FBLocale["esES"].RAID_MINPLAYERS = "Vista de banda a partir de |cFFFFFFFF%s|r jugadores";
FBLocale["esES"].RAID_HIDEPARTY = "Ocultar placas de grupo";
FBLocale["esES"].RAID_HIDEPARTY_TIP = "Mientras se muestra la rejilla de banda, las cinco placas de grupo se ocultan. Desactívalo para conservar ambas.";
FBLocale["esES"].RAID_HEADERS = "Cabeceras de grupo";
FBLocale["esES"].RAID_HEADERS_TIP = "Un pequeño número de grupo sobre cada bloque.";
FBLocale["esES"].RAID_MANABAR = "Franja de maná";
FBLocale["esES"].RAID_MANABAR_TIP = "Una franja de maná de 3 px en la parte inferior de cada celda, solo para usuarios de maná.";
FBLocale["esES"].RAID_HIDEEMPTY = "Ocultar grupos vacíos";
FBLocale["esES"].RAID_HIDEEMPTY_TIP = "Los grupos sin miembros no ocupan espacio. Una banda de 20 llena entonces una sola fila.";
FBLocale["esES"].RAID_SHOWTITLE = "Barra de título";
FBLocale["esES"].RAID_SHOWTITLE_TIP = "Una fina barra de título sobre la rejilla que se puede arrastrar. Sin ella, mantén Mayús y arrastra cualquier celda.";
FBLocale["esES"].RAID_BUFFICONS = "Mostrar beneficios";
FBLocale["esES"].RAID_BUFFICONS_TIP = "Iconos de beneficios con reloj a la izquierda de cada celda, como en las placas de grupo. Desactivado: sin iconos ni espacio reservado, los grupos se juntan más.";
FBLocale["esES"].RAID_HPTEXT = "Texto de vida";
FBLocale["esES"].RAID_HPTEXT_TIP = "Qué muestra el lado derecho de una celda: nada, el porcentaje de vida o la vida que falta (déficit).";
FBLocale["esES"].RAID_HP_NONE = "nada";
FBLocale["esES"].RAID_HP_PERCENT = "porcentaje";
FBLocale["esES"].RAID_HP_DEFICIT = "déficit";
FBLocale["esES"].RAID_GROUPSPERROW = "Grupos por fila: |cFFFFFFFF%s";
FBLocale["esES"].RAID_SCALE = "Escala de banda: |cFFFFFFFF%s";
FBLocale["esES"].RAID_CELLW = "Ancho de celda: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_CELLH = "Alto de celda: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_BUTTONS = "Botones por unidad: |cFFFFFFFF%s";
FBLocale["esES"].RAID_BTNSIZE = "Tamaño de botón: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_CELLGAP = "Separación de celdas: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_GROUPGAP = "Separación de grupos: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_TEST = "Prueba de banda";
FBLocale["esES"].RAID_TEST_TIP = "Rellena la rejilla de banda con 20 o 40 jugadores fantasma para organizarla sin banda. No se guarda.";
FBLocale["esES"].RAID_TEST_OFF = "desactivada";
FBLocale["esES"].RAID_TEST_20 = "20 jugadores";
FBLocale["esES"].RAID_TEST_40 = "40 jugadores";
FBLocale["esES"].RAID_TEST_CLICK = "Prueba de banda: sin lanzamiento sobre un jugador fantasma.";
FBLocale["esES"].RAID_INFO = "Los minibotones usan los hechizos de los botones 1 a N de la pestaña Botones, incluidos los del clic derecho.";
FBLocale["esES"].RAID_MOVE_TIP = "Arrastra para mover la rejilla de banda.";
FBLocale["esES"].FBP_RAID = "Modo banda: %s, %d miembros en %d grupos";
FBLocale["esES"].FBP_RAID_CMDS = "Banda: /fbp raid (alternar), /fbp raidtest 20|40|off";
FBLocale["esES"].RAID_LOADED = "Módulo de modo banda cargado: pestaña |cFFFFFFFFModo banda|r en las opciones, /fbp raid, /fbp raidtest 20|40";
FBLocale["frFR"].TAB_RAID = "Mode raid";
FBLocale["frFR"].RAID_TITLE = "Raid";
FBLocale["frFR"].RAID_ENABLED = "Mode raid";
FBLocale["frFR"].RAID_ENABLED_TIP = "Interrupteur principal de la grille de raid compacte : une cellule par membre, regroupées par groupe de raid, avec des mini-boutons de soins. La grille apparaît d'elle-même dès que votre raid compte au moins le nombre de joueurs réglé ci-dessous ; les raids plus petits gardent l'affichage de groupe.";
FBLocale["frFR"].RAID_MINPLAYERS = "Vue raid à partir de |cFFFFFFFF%s|r joueurs";
FBLocale["frFR"].RAID_HIDEPARTY = "Masquer les plaques de groupe";
FBLocale["frFR"].RAID_HIDEPARTY_TIP = "Tant que la grille de raid est affichée, les cinq plaques de groupe sont masquées. Désactivez pour garder les deux.";
FBLocale["frFR"].RAID_HEADERS = "En-têtes de groupe";
FBLocale["frFR"].RAID_HEADERS_TIP = "Un petit numéro de groupe au-dessus de chaque bloc.";
FBLocale["frFR"].RAID_MANABAR = "Bande de mana";
FBLocale["frFR"].RAID_MANABAR_TIP = "Une bande de mana de 3 px en bas de chaque cellule, uniquement pour les utilisateurs de mana.";
FBLocale["frFR"].RAID_HIDEEMPTY = "Masquer les groupes vides";
FBLocale["frFR"].RAID_HIDEEMPTY_TIP = "Les groupes sans membres ne prennent pas de place. Un raid de 20 tient alors sur une seule ligne.";
FBLocale["frFR"].RAID_SHOWTITLE = "Barre de titre";
FBLocale["frFR"].RAID_SHOWTITLE_TIP = "Une fine barre de titre au-dessus de la grille, que l'on peut faire glisser. Sans elle, maintenez Maj et faites glisser n'importe quelle cellule.";
FBLocale["frFR"].RAID_BUFFICONS = "Afficher les buffs";
FBLocale["frFR"].RAID_BUFFICONS_TIP = "Icônes de buffs avec horloge à gauche de chaque cellule, comme sur les plaques de groupe. Désactivé : pas d'icônes ni d'espace réservé, les groupes se rapprochent.";
FBLocale["frFR"].RAID_HPTEXT = "Texte de vie";
FBLocale["frFR"].RAID_HPTEXT_TIP = "Ce qu'affiche le côté droit d'une cellule : rien, le pourcentage de vie ou la vie manquante (déficit).";
FBLocale["frFR"].RAID_HP_NONE = "rien";
FBLocale["frFR"].RAID_HP_PERCENT = "pourcentage";
FBLocale["frFR"].RAID_HP_DEFICIT = "déficit";
FBLocale["frFR"].RAID_GROUPSPERROW = "Groupes par ligne : |cFFFFFFFF%s";
FBLocale["frFR"].RAID_SCALE = "Échelle du raid : |cFFFFFFFF%s";
FBLocale["frFR"].RAID_CELLW = "Largeur de cellule : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_CELLH = "Hauteur de cellule : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_BUTTONS = "Boutons par unité : |cFFFFFFFF%s";
FBLocale["frFR"].RAID_BTNSIZE = "Taille des boutons : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_CELLGAP = "Espacement des cellules : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_GROUPGAP = "Espacement des groupes : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_TEST = "Test de raid";
FBLocale["frFR"].RAID_TEST_TIP = "Remplit la grille de raid avec 20 ou 40 joueurs fantômes pour la disposer sans raid. Non sauvegardé.";
FBLocale["frFR"].RAID_TEST_OFF = "désactivé";
FBLocale["frFR"].RAID_TEST_20 = "20 joueurs";
FBLocale["frFR"].RAID_TEST_40 = "40 joueurs";
FBLocale["frFR"].RAID_TEST_CLICK = "Test de raid : aucun lancement sur un joueur fantôme.";
FBLocale["frFR"].RAID_INFO = "Les mini-boutons utilisent les sorts des boutons 1 à N de l'onglet Boutons, sorts du clic droit compris.";
FBLocale["frFR"].RAID_MOVE_TIP = "Faites glisser pour déplacer la grille de raid.";
FBLocale["frFR"].FBP_RAID = "Mode raid : %s, %d membres dans %d groupes";
FBLocale["frFR"].FBP_RAID_CMDS = "Raid : /fbp raid (basculer), /fbp raidtest 20|40|off";
FBLocale["frFR"].RAID_LOADED = "Module mode raid chargé : onglet |cFFFFFFFFMode raid|r dans les options, /fbp raid, /fbp raidtest 20|40";
FBLocale["itIT"].TAB_RAID = "Incursione";
FBLocale["itIT"].RAID_TITLE = "Incursione";
FBLocale["itIT"].RAID_ENABLED = "Modalità incursione";
FBLocale["itIT"].RAID_ENABLED_TIP = "Interruttore principale della griglia compatta d'incursione: una cella per membro, raggruppate per gruppo, con mini pulsanti di cura. La griglia compare da sola quando l'incursione ha almeno il numero di giocatori impostato sotto; le incursioni più piccole mantengono la vista di gruppo.";
FBLocale["itIT"].RAID_MINPLAYERS = "Vista incursione da |cFFFFFFFF%s|r giocatori";
FBLocale["itIT"].RAID_HIDEPARTY = "Nascondi targhette di gruppo";
FBLocale["itIT"].RAID_HIDEPARTY_TIP = "Finché la griglia d'incursione è visibile, le cinque targhette di gruppo sono nascoste. Disattiva per tenerle entrambe.";
FBLocale["itIT"].RAID_HEADERS = "Intestazioni di gruppo";
FBLocale["itIT"].RAID_HEADERS_TIP = "Un piccolo numero di gruppo sopra ogni blocco.";
FBLocale["itIT"].RAID_MANABAR = "Striscia del mana";
FBLocale["itIT"].RAID_MANABAR_TIP = "Una striscia del mana di 3 px in fondo a ogni cella, solo per chi usa mana.";
FBLocale["itIT"].RAID_HIDEEMPTY = "Nascondi gruppi vuoti";
FBLocale["itIT"].RAID_HIDEEMPTY_TIP = "I gruppi senza membri non occupano spazio. Un'incursione da 20 riempie allora una sola riga.";
FBLocale["itIT"].RAID_SHOWTITLE = "Barra del titolo";
FBLocale["itIT"].RAID_SHOWTITLE_TIP = "Una sottile barra del titolo sopra la griglia che si può trascinare. Senza, tieni premuto Maiusc e trascina una cella qualsiasi.";
FBLocale["itIT"].RAID_BUFFICONS = "Mostra benefici";
FBLocale["itIT"].RAID_BUFFICONS_TIP = "Icone dei benefici con orologio a sinistra di ogni cella, come sulle targhette di gruppo. Disattivato: niente icone né spazio riservato, i gruppi si avvicinano.";
FBLocale["itIT"].RAID_HPTEXT = "Testo salute";
FBLocale["itIT"].RAID_HPTEXT_TIP = "Cosa mostra il lato destro di una cella: niente, la percentuale di salute o la salute mancante (deficit).";
FBLocale["itIT"].RAID_HP_NONE = "niente";
FBLocale["itIT"].RAID_HP_PERCENT = "percentuale";
FBLocale["itIT"].RAID_HP_DEFICIT = "deficit";
FBLocale["itIT"].RAID_GROUPSPERROW = "Gruppi per riga: |cFFFFFFFF%s";
FBLocale["itIT"].RAID_SCALE = "Scala dell'incursione: |cFFFFFFFF%s";
FBLocale["itIT"].RAID_CELLW = "Larghezza cella: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_CELLH = "Altezza cella: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_BUTTONS = "Pulsanti per unità: |cFFFFFFFF%s";
FBLocale["itIT"].RAID_BTNSIZE = "Dimensione pulsanti: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_CELLGAP = "Spaziatura celle: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_GROUPGAP = "Spaziatura gruppi: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_TEST = "Test incursione";
FBLocale["itIT"].RAID_TEST_TIP = "Riempie la griglia d'incursione con 20 o 40 giocatori fantasma per sistemarla senza incursione. Non viene salvato.";
FBLocale["itIT"].RAID_TEST_OFF = "disattivato";
FBLocale["itIT"].RAID_TEST_20 = "20 giocatori";
FBLocale["itIT"].RAID_TEST_40 = "40 giocatori";
FBLocale["itIT"].RAID_TEST_CLICK = "Test incursione: nessun lancio su un giocatore fantasma.";
FBLocale["itIT"].RAID_INFO = "I mini pulsanti usano gli incantesimi dei pulsanti da 1 a N della scheda Pulsanti, compresi quelli del clic destro.";
FBLocale["itIT"].RAID_MOVE_TIP = "Trascina per spostare la griglia d'incursione.";
FBLocale["itIT"].FBP_RAID = "Modalità incursione: %s, %d membri in %d gruppi";
FBLocale["itIT"].FBP_RAID_CMDS = "Incursione: /fbp raid (attiva/disattiva), /fbp raidtest 20|40|off";
FBLocale["itIT"].RAID_LOADED = "Modulo modalità incursione caricato: scheda |cFFFFFFFFModalità incursione|r nelle opzioni, /fbp raid, /fbp raidtest 20|40";
FBLocale["deDE"].RAID_LOADED = "Raidmodus-Modul geladen: Reiter |cFFFFFFFFRaidmodus|r in den Optionen, /fbp raid, /fbp raidtest 20|40";
-- ==========================================================================
@@ -206,11 +343,38 @@ function FBRaid_BuildTestGhosts(count)
if (i == 13) then g.state = "offline"; end
if (i == 18) then g.state = "ghost"; end
if (i == 4 or i == 22) then g.debuff = true; end
if (i == 27) then g.debuffType = "Disease"; end
if (i == 9 or i == 31) then g.outOfRange = true; end
if (i == 11) then g.los = true; end
if (i == 2 or i == 26) then g.buffMissing = true; end
if (i == 15) then g.shield = 500; end
if (i == 16 or i == 33) then g.inc = 800; end
if (i == 16) then g.aggro = true; g.hotLeft = 7; end
if (i == 15) then g.shieldLeft = 18; end
-- Buff-Icons: jeder dritte Geist traegt Seelenstaerke mit anderer Restzeit,
-- einer davon dazu Goettlichen Willen, einer einen fremden Buff ohne Zeit
if (math.mod(i, 3) == 0) then
local left = math.mod(i * 7, 18) * 100; -- 0..1700 s von 1800
if (left < 60) then left = 60; end
g.buffs = { { tex = "Interface\\Icons\\Spell_Holy_WordFortitude", left = left, dur = 1800 } };
if (i == 6) then table.insert(g.buffs, { tex = "Interface\\Icons\\Spell_Holy_DivineSpirit", left = 1500, dur = 1800 }); end
if (i == 9) then g.buffs[1].left = nil; end
if (i == 12) then
-- zwoelf Buffs auf einem Geist (3 x 4), um die Darstellung zu pruefen
g.buffs = { { tex = "Interface\\Icons\\Spell_Holy_WordFortitude", left = 540, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Holy_DivineSpirit", left = 1500, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Shadow_AntiShadow", left = 300, dur = 600 },
{ tex = "Interface\\Icons\\Spell_Holy_Excorcism", left = 45, dur = 180 },
{ tex = "Interface\\Icons\\Spell_Nature_Regeneration", dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Magic_MageArmor", left = 1750, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Holy_SealOfWisdom", left = 200, dur = 300 },
{ tex = "Interface\\Icons\\Spell_Holy_FistOfJustice", left = 100, dur = 300 },
{ tex = "Interface\\Icons\\Spell_Nature_Thorns", left = 500, dur = 600 },
{ tex = "Interface\\Icons\\Spell_Holy_InnerFire", left = 1200, dur = 600 },
{ tex = "Interface\\Icons\\Spell_Holy_SealOfSalvation", left = 900, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Fire_FireArmor", dur = 1800 } };
end
end
FBRaidTestGhosts[i] = g;
end
end
@@ -287,6 +451,10 @@ end
function FBRaid_Dispel(cell)
local g = cell.ghost;
if (g) then
if (g.debuffType) then
if (FBDispelColors[g.debuffType]) then return g.debuffType; end
return nil;
end
if (g.debuff) then
for _, dtype in ipairs({ "Magic", "Poison", "Disease", "Curse" }) do
if (FBDispelColors[dtype]) then return dtype; end
@@ -449,17 +617,24 @@ function FBRaid_CreateCell(g, pos)
c.ManaBar.bg:SetTexture(0, 0, 0, FBMANA_BG_ALPHA);
c.ManaBar:Hide();
-- Texte
c.NameText = c:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
-- Texte liegen auf einer eigenen Ebene ueber den Balken (die Balken sind
-- Kind-Frames mit hoeherer Ebene und wuerden Texte der Zelle verdecken)
c.Overlay = CreateFrame("Frame", nil, c);
c.Overlay:SetAllPoints(c);
c.Overlay:SetFrameStrata("MEDIUM");
c.Overlay:SetFrameLevel(base + 6);
c.NameText = c.Overlay:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
c.NameText:SetPoint("LEFT", c, "LEFT", 4, 0);
c.NameText:SetHeight(FBNAME_HEIGHT);
c.NameText:SetJustifyH("LEFT");
c.HPText = c:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall");
c.NameText:SetJustifyV("MIDDLE");
c.HPText = c.Overlay:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall");
c.HPText:SetPoint("RIGHT", c, "RIGHT", -3, 0);
c.HPText:SetJustifyH("RIGHT");
c.HPText:SetTextColor(1, 1, 1, 1);
-- Sichtlinie: kleines Auge oben links
c.LOSIcon = c:CreateTexture(nil, "OVERLAY");
c.LOSIcon = c.Overlay:CreateTexture(nil, "OVERLAY");
c.LOSIcon:SetWidth(FBRAID_LOS_SIZE);
c.LOSIcon:SetHeight(FBRAID_LOS_SIZE);
c.LOSIcon:SetPoint("TOPLEFT", c, "TOPLEFT", -3, 3);
@@ -481,7 +656,7 @@ function FBRaid_CreateCell(g, pos)
b:SetScript("OnClick", function()
if (FBDragSpell) then
local side = "L";
if (arg1 == "RightButton") then side = "R"; end
if (arg1 == "RightButton" or IsShiftKeyDown()) then side = "R"; end
if (FBHealBox_DropSpell(i, side)) then return; end
end
if (FBRaidTestMode > 0) then
@@ -489,7 +664,10 @@ function FBRaid_CreateCell(g, pos)
return;
end
local castString = b.spellName;
if (arg1 == "RightButton") then castString = b.spellNameR; end
if (arg1 == "RightButton") then
if (HealBox.RightClick ~= 1) then return; end
castString = b.spellNameR;
end
FBHealBox_CastOn(b, castString);
end);
b:Hide();
@@ -547,6 +725,7 @@ end
-- ==========================================================================
function FBRaid_UpdateRoster()
FBRaidRosterDirty = false;
local cfg = FBRaid_Cfg();
FBRaidUnitCell = {};
FBRaidMembers = 0;
@@ -559,6 +738,8 @@ function FBRaid_UpdateRoster()
if (FBRaidCells[g] and FBRaidCells[g][pos]) then
local c = FBRaidCells[g][pos];
c.unit = nil; c.name = nil; c.ghost = nil; c.index = nil;
c.dispelKnown = nil; c.lastText = nil; c.lastMax = nil; c.colorKey = nil; c.manaShown = nil; c.lastMpMax = nil;
c.vHp = nil; c.vShield = nil; c.vInc = nil; c.vMp = nil;
c:Hide();
end
end
@@ -602,9 +783,9 @@ end
-- [ Layout ]
-- ==========================================================================
-- Breite einer Zelle samt ihrer Buttons
-- Breite einer Zelle samt Buff-Streifen links und Buttons rechts
function FBRaid_RowWidth(cfg)
local w = cfg.CellW;
local w = FBRaid_BuffStrip(cfg) + cfg.CellW;
if (cfg.Buttons > 0) then w = w + cfg.Buttons * (cfg.ButtonSize + 1); end
return w;
end
@@ -665,12 +846,13 @@ function FBRaid_LayoutAll()
col * (rowW + cfg.GroupGap),
-(titleH + row * (blockH + cfg.GroupGap)));
if (cfg.Headers == 1) then f.header:Show(); else f.header:Hide(); end
-- Zellen im Block
-- Zellen im Block, links davon der Streifen fuer die Buff-Icons
local strip = FBRaid_BuffStrip(cfg);
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaid_GetCell(g, pos);
FBRaid_ApplyCellSize(c, cfg);
c:ClearAllPoints();
c:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -(headH + (pos - 1) * (cfg.CellH + cfg.CellGap)));
c:SetPoint("TOPLEFT", f, "TOPLEFT", strip, -(headH + (pos - 1) * (cfg.CellH + cfg.CellGap)));
end
f:Show();
col = col + 1;
@@ -718,7 +900,35 @@ end
-- [ Zelleninhalt ]
-- ==========================================================================
function FBRaid_UpdateCell(c)
-- Zellenfarbe / Maxima / Text nur bei Aenderung setzen
function FBRaid_SetColor(c, key, r, g, b, a)
if (c.colorKey == key) then return; end
c.colorKey = key;
c.HealthBar:SetStatusBarColor(r, g, b, a);
end
function FBRaid_SetValues(c, hp, shieldTop, incTop)
if (c.vHp ~= hp) then c.vHp = hp; c.HealthBar:SetValue(hp); end
if (c.vShield ~= shieldTop) then c.vShield = shieldTop; c.ShieldBar:SetValue(shieldTop); end
if (c.vInc ~= incTop) then c.vInc = incTop; c.IncHealBar:SetValue(incTop); end
end
function FBRaid_SetMax(c, hpMax)
if (c.lastMax == hpMax) then return; end
c.lastMax = hpMax;
c.HealthBar:SetMinMaxValues(0, hpMax);
c.ShieldBar:SetMinMaxValues(0, hpMax);
c.IncHealBar:SetMinMaxValues(0, hpMax);
end
function FBRaid_SetText(c, text)
if (c.lastText == text) then return; end
c.lastText = text;
if (text == false) then c.HPText:Hide(); else c.HPText:SetText(text); c.HPText:Show(); end
end
-- auraChanged: true bei UNIT_AURA, nur dann wird der Debuff neu gesucht
function FBRaid_UpdateCell(c, auraChanged)
if (not c) or (not c.unit) or (not c:IsShown()) then return; end
local cfg = FBRaid_Cfg();
local hp, hpMax = FBRaid_Health(c);
@@ -729,64 +939,70 @@ function FBRaid_UpdateCell(c)
local key = "STATE_DEAD";
if (state == "ghost") then key = "STATE_GHOST"; end
if (state == "offline") then key = "STATE_OFFLINE"; end
c.HPText:SetText(FBT(key));
c.HPText:Show();
c.HealthBar:SetMinMaxValues(0, hpMax); c.HealthBar:SetValue(0);
c.HealthBar:SetStatusBarColor(0.5, 0.5, 0.5, 1);
c.ShieldBar:SetMinMaxValues(0, hpMax); c.ShieldBar:SetValue(0);
c.IncHealBar:SetMinMaxValues(0, hpMax); c.IncHealBar:SetValue(0);
c.ManaBar:Hide();
FBRaid_SetText(c, FBT(key));
FBRaid_SetMax(c, hpMax);
FBRaid_SetValues(c, 0, 0, 0);
FBRaid_SetColor(c, "state", 0.5, 0.5, 0.5, 1);
if (c.manaShown ~= false) then c.manaShown = false; c.ManaBar:Hide(); end
return;
end
-- Leben und Schichten
c.HealthBar:SetMinMaxValues(0, hpMax);
c.HealthBar:SetValue(hp);
FBRaid_SetMax(c, hpMax);
local inc, shield = FBRaid_Incoming(c);
local shieldTop = math.min(hpMax, hp + shield);
c.ShieldBar:SetMinMaxValues(0, hpMax);
c.ShieldBar:SetValue(shieldTop);
c.IncHealBar:SetMinMaxValues(0, hpMax);
c.IncHealBar:SetValue(math.min(hpMax, shieldTop + inc));
local shieldTop = hp + shield;
if (shieldTop > hpMax) then shieldTop = hpMax; end
local incTop = shieldTop + inc;
if (incTop > hpMax) then incTop = hpMax; end
FBRaid_SetValues(c, hp, shieldTop, incTop);
-- HP-Text
if (cfg.HPText == "percent") then
c.HPText:SetText(format("%d%%", math.floor(hp / hpMax * 100)));
c.HPText:Show();
FBRaid_SetText(c, math.floor(hp / hpMax * 100).."%");
elseif (cfg.HPText == "deficit") then
local def = hpMax - hp;
if (def > 0) then c.HPText:SetText("-"..def); else c.HPText:SetText(""); end
c.HPText:Show();
if (def > 0) then FBRaid_SetText(c, "-"..def); else FBRaid_SetText(c, ""); end
else
c.HPText:Hide();
FBRaid_SetText(c, false);
end
-- Mana
local mp, mpMax, hasMana = FBRaid_Mana(c);
if (cfg.ManaBar == 1 and hasMana and mpMax > 0) then
c.ManaBar:SetMinMaxValues(0, mpMax);
c.ManaBar:SetValue(mp);
c.ManaBar:Show();
else
c.ManaBar:Hide();
if (c.lastMpMax ~= mpMax) then c.lastMpMax = mpMax; c.ManaBar:SetMinMaxValues(0, mpMax); end
if (c.vMp ~= mp) then c.vMp = mp; c.ManaBar:SetValue(mp); end
if (c.manaShown ~= true) then c.manaShown = true; c.ManaBar:Show(); end
elseif (c.manaShown ~= false) then
c.manaShown = false; c.ManaBar:Hide();
end
-- Farbe: Dispel schlaegt HP-Stand
-- Farbe: Dispel schlaegt HP-Stand (Debuff nur bei Aura-Aenderung neu suchen)
if (auraChanged or (not c.dispelKnown) or c.ghost) then
c.dispelType = FBRaid_Dispel(c);
c.dispelKnown = true;
end
local frac = hp / hpMax;
local dtype = FBRaid_Dispel(c);
local dtype = c.dispelType;
if (dtype and FBDispelColors[dtype]) then
local col = FBDispelColors[dtype];
c.HealthBar:SetStatusBarColor(col[1], col[2], col[3], col[4]);
FBRaid_SetColor(c, dtype, col[1], col[2], col[3], col[4]);
elseif (frac > LowHP) then
c.HealthBar:SetStatusBarColor(0, 1, 0, 1);
FBRaid_SetColor(c, "green", 0, 1, 0, 1);
elseif (frac > VeryLowHP) then
c.HealthBar:SetStatusBarColor(1, 0.9, 0, 1);
FBRaid_SetColor(c, "yellow", 1, 0.9, 0, 1);
else
c.HealthBar:SetStatusBarColor(1, 0, 0, 1);
FBRaid_SetColor(c, "red", 1, 0, 0, 1);
end
end
-- Rahmen: Buff-Wache (orange) oder normal
-- Rahmen: Angegriffener (rot) vor Buff-Wache (orange) vor normal
function FBRaid_ApplyBorder(c)
local col = FBBUFF_NORMAL_COLOR;
if (c.buffMissing) then col = FBBUFF_MISSING_COLOR; end
if (c.underAttack) then col = FBAGGRO_COLOR; end
c:SetBackdropBorderColor(col[1], col[2], col[3], col[4]);
end
function FBRaid_UpdateBuffBorder(c)
if (not c) or (not c.unit) then return; end
local missing = false;
@@ -795,9 +1011,123 @@ function FBRaid_UpdateBuffBorder(c)
end
if (c.buffMissing ~= missing) then
c.buffMissing = missing;
local col = FBBUFF_NORMAL_COLOR;
if (missing) then col = FBBUFF_MISSING_COLOR; end
c:SetBackdropBorderColor(col[1], col[2], col[3], col[4]);
FBRaid_ApplyBorder(c);
end
end
-- Wer wird angegriffen (Hook "Aggro" des Kerns, tt = "targettarget" oder nil)
function FBRaid_CheckAggro(tt)
if (not FBRaid_IsActive()) then return false; end
local any = false;
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c and c.unit) then
local flag = false;
if (HealBox.AggroMark == 1 and c:IsShown()) then
if (c.ghost) then flag = (c.ghost.aggro == true);
elseif (tt) then flag = FBHealBox_UnitIsAggroFast(c.unit, c.name, tt); end
end
if (c.underAttack ~= flag) then
c.underAttack = flag;
FBRaid_ApplyBorder(c);
end
if (flag) then any = true; end
end
end
end
end
return any;
end
-- Platz links neben jeder Zelle fuer die Buff-Icons (aussen links)
function FBRaid_BuffStrip(cfg)
if (HealBox.BuffIcons ~= 1) or (cfg.BuffIcons ~= 1) then return 0; end
return -FBBUFFICON_XOFF + FBRAID_BUFFICON_COLS * (FBRAID_BUFFICON_SIZE + FBBUFFICON_GAP);
end
-- Buff-Icons mit Uhr aussen links an den Zellen (Hook "BuffIcons")
function FBRaid_UpdateBuffIcons()
if (not FBRaid_IsActive()) then return; end
local on = (FBRaid_Cfg().BuffIcons == 1);
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c) then
if (on and c.unit and c:IsShown()) then
FBHealBox_UpdateBuffIcons(c, c.name, c.ghost, FBRAID_BUFFICON_SIZE, FBRAID_BUFFICON_ROWS, FBRAID_BUFFICON_MAX);
else
FBHealBox_UpdateBuffIcons(c, nil, nil, FBRAID_BUFFICON_SIZE, FBRAID_BUFFICON_ROWS, FBRAID_BUFFICON_MAX);
end
end
end
end
end
end
-- HoT-/Schild-Timer auf den Mini-Buttons (Hook "SpellTimers")
function FBRaid_UpdateSpellTimers(now)
local on = (HealBox.SpellTimers == 1) and FBRaid_IsActive();
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c) then
-- ohne verfolgten HoT/Schild auf der Einheit nur einmal ausblenden
local shown = on and c.unit and c:IsShown() and (c.ghost or FBHoTs[c.name] or FBShields[c.name]);
if (not shown and not c.timersShown) then shown = nil; end
c.timersShown = shown and true or false;
if (shown == nil) then shown = false; end
for i = 1, FBRAID_MAX_BUTTONS do
local b = c.buttons[i];
if (shown and b:IsShown()) then
local text, color = FBHealBox_SpellTimerFor(b.spellBase, c.name, now, c.ghost, i);
if (not text and b.spellBaseR) then
text, color = FBHealBox_SpellTimerFor(b.spellBaseR, c.name, now, nil, i);
end
FBHealBox_SetButtonTimer(b, text, color);
else
FBHealBox_SetButtonTimer(b, nil);
end
end
end
end
end
end
end
function FBRaid_UpdateCooldowns()
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c) then
for i = 1, FBRAID_MAX_BUTTONS do
local b = c.buttons[i];
b.cdKey = nil;
FBHealBox_UpdateButtonState(b, "SPELL_UPDATE_COOLDOWN");
end
end
end
end
end
end
-- Button-Zustaende der sichtbaren Zellen (Hook "ButtonStates")
function FBRaid_UpdateButtonStates(event)
if (not FBRaid_IsActive()) then return; end
local nBtn = FBRaid_Cfg().Buttons or 0;
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c and c.unit and c:IsShown()) then
for i = 1, nBtn do FBHealBox_UpdateButtonState(c.buttons[i], event); end
end
end
end
end
end
@@ -845,11 +1175,9 @@ function FBRaid_SyncButtons()
if (rightOn) then
b.spellNameR = FBDropDownButtonR[i];
b.idR = FBActiveSpellIDsR[i];
b:RegisterForClicks("LeftButtonUp", "RightButtonUp");
else
b.spellNameR = nil;
b.idR = nil;
b:RegisterForClicks("LeftButtonUp");
end
if (b.subIcon) then
if (b.spellNameR and FBDropDownButtonIconR[i]) then
@@ -859,6 +1187,10 @@ function FBRaid_SyncButtons()
b.subIcon:Hide();
end
end
b.cdKey = nil;
b.colorState = nil;
if (b.spellName) then b.spellBase = FBPredict_SplitCast(b.spellName); else b.spellBase = nil; end
if (b.spellNameR) then b.spellBaseR = FBPredict_SplitCast(b.spellNameR); else b.spellBaseR = nil; end
end
end
end
@@ -907,21 +1239,28 @@ FBRaidEventFrame:RegisterEvent("UNIT_MAXMANA");
FBRaidEventFrame:RegisterEvent("UNIT_DISPLAYPOWER");
FBRaidEventFrame:RegisterEvent("UNIT_AURA");
FBRaidRosterDirty = false;
FBRaidEventFrame:SetScript("OnEvent", function()
if (event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" or event == "PLAYER_ENTERING_WORLD") then
FBRaid_UpdateRoster();
FBRaid_SyncButtons();
FBRaid_RefreshBuffBorders();
-- Salven (mehrere Roster-Events je Frame beim Zonen) auf einen
-- Durchlauf im naechsten Frame zusammenfassen
FBRaidRosterDirty = true;
return;
end
if (FBRaidTestMode > 0) then return; end
local c = FBRaidUnitCell[arg1];
if (not c) then return; end
FBRaid_UpdateCell(c);
FBRaid_UpdateCell(c, (event == "UNIT_AURA"));
if (event == "UNIT_AURA") then FBRaid_UpdateBuffBorder(c); end
end);
FBRaidEventFrame:SetScript("OnUpdate", function()
if (FBRaidRosterDirty) then
FBRaidRosterDirty = false;
FBRaid_UpdateRoster();
FBRaid_RefreshBuffBorders();
end
FBRaidTickAccum = FBRaidTickAccum + (arg1 or 0);
if (FBRaidTickAccum < FBRAID_TICK) then return; end
FBRaidTickAccum = 0;
@@ -932,8 +1271,8 @@ end);
-- fuer den Raid-Test haengen wir uns an denselben Takt und fordern ihn an.
FBRaidGhostAccum = 0;
FBRaidEventFrame.ghostTicker = CreateFrame("Frame", nil, UIParent);
FBRaidEventFrame.ghostTicker:Hide(); -- laeuft nur im Raid-Test (siehe FBRaid_SetTest)
FBRaidEventFrame.ghostTicker:SetScript("OnUpdate", function()
if (FBRaidTestMode == 0) then return; end
FBRaidGhostAccum = FBRaidGhostAccum + (arg1 or 0);
if (FBRaidGhostAccum < 0.2) then return; end
FBRaidGhostAccum = 0;
@@ -951,6 +1290,8 @@ function FBRaid_SetTest(mode)
mode = tonumber(mode) or 0;
if (mode ~= 20 and mode ~= 40) then mode = 0; end
FBRaidTestMode = mode;
-- Geister-Atmung nur im Test rechnen lassen
if (mode > 0) then FBRaidEventFrame.ghostTicker:Show(); else FBRaidEventFrame.ghostTicker:Hide(); end
if (mode > 0) then
FBRaid_BuildTestGhosts(mode);
if (HealBox.Active ~= 1) then HealBox.Active = 1; end
@@ -1052,9 +1393,14 @@ function FBRaid_BuildOptions()
FBRaid_Cfg().ShowTitle = FBRaidTitleCheck:GetChecked() and 1 or 0;
FBRaid_LayoutAll();
end);
FBRaidBuffIconsCheck = FBHealBox_CreateCheck("FBHealBoxRaidBuffIconsCheck", tab, 40, y - 90, "RAID_BUFFICONS", "RAID_BUFFICONS_TIP", function()
FBRaid_Cfg().BuffIcons = FBRaidBuffIconsCheck:GetChecked() and 1 or 0;
FBRaid_LayoutAll();
FBRaid_UpdateBuffIcons();
end);
-- HP-Text: Auswahlknopf mit Kaskadenmenue
FBRaidHPTextBtn = FBHealBox_CreatePickButton("FBHealBoxRaidHPTextBtn", tab, 35, y - 96, 180, 26, 18);
FBRaidHPTextBtn = FBHealBox_CreatePickButton("FBHealBoxRaidHPTextBtn", tab, 35, y - 126, 180, 26, 18);
FBRaidHPTextBtn.icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01");
FBRaidHPTextBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
@@ -1084,7 +1430,7 @@ function FBRaid_BuildOptions()
end);
-- Raid-Test: aus / 20 / 40
FBRaidTestBtn = FBHealBox_CreatePickButton("FBHealBoxRaidTestBtn", tab, 245, y - 96, 180, 26, 18);
FBRaidTestBtn = FBHealBox_CreatePickButton("FBHealBoxRaidTestBtn", tab, 245, y - 126, 180, 26, 18);
FBRaidTestBtn.icon:SetTexture("Interface\\Icons\\Spell_Shadow_Twilight");
FBRaidTestBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
@@ -1111,7 +1457,7 @@ function FBRaid_BuildOptions()
end);
-- Schieberegler (Beschriftung sitzt ueber dem Regler)
local sy = y - 150;
local sy = y - 180;
FBRaid_CreateSlider("FBRaidGroupsPerRowSlider", tab, 75, sy, "RAID_GROUPSPERROW", "GroupsPerRow", 1, 8, 1, false, FBRaid_LayoutAll);
FBRaid_CreateSlider("FBRaidScaleSlider", tab, 260, sy, "RAID_SCALE", "Scale", 0.5, 1.5, 0.1, true, FBRaid_LayoutAll);
FBRaid_CreateSlider("FBRaidCellWSlider", tab, 75, sy - 50, "RAID_CELLW", "CellW", 50, 120, 1, false, FBRaid_LayoutAll);
@@ -1141,6 +1487,7 @@ function FBRaid_SyncOptions()
if (FBRaidManaCheck) then FBRaidManaCheck:SetChecked(cfg.ManaBar == 1); end
if (FBRaidHideEmptyCheck) then FBRaidHideEmptyCheck:SetChecked(cfg.HideEmpty == 1); end
if (FBRaidTitleCheck) then FBRaidTitleCheck:SetChecked(cfg.ShowTitle == 1); end
if (FBRaidBuffIconsCheck) then FBRaidBuffIconsCheck:SetChecked(cfg.BuffIcons == 1); end
for key, s in pairs(FBRaidSliders) do
if (cfg[key] ~= nil) then s:SetValue(cfg[key]); end
end
@@ -1156,6 +1503,7 @@ function FBRaid_ApplyLocale()
if (FBRaidManaCheck) then FBRaidManaCheck.Text:SetText(FBT("RAID_MANABAR")); FBRaidManaCheck.tooltipText = FBT("RAID_MANABAR_TIP"); end
if (FBRaidHideEmptyCheck) then FBRaidHideEmptyCheck.Text:SetText(FBT("RAID_HIDEEMPTY")); FBRaidHideEmptyCheck.tooltipText = FBT("RAID_HIDEEMPTY_TIP"); end
if (FBRaidTitleCheck) then FBRaidTitleCheck.Text:SetText(FBT("RAID_SHOWTITLE")); FBRaidTitleCheck.tooltipText = FBT("RAID_SHOWTITLE_TIP"); end
if (FBRaidBuffIconsCheck) then FBRaidBuffIconsCheck.Text:SetText(FBT("RAID_BUFFICONS")); FBRaidBuffIconsCheck.tooltipText = FBT("RAID_BUFFICONS_TIP"); end
if (FBRaidInfoText) then FBRaidInfoText:SetText(FBT("RAID_INFO")); end
for _, s in pairs(FBRaidSliders) do FBRaid_SliderText(s); end
FBRaid_UpdateHPTextLabel();
@@ -1197,6 +1545,11 @@ FBHealBox_RegisterHook("ButtonsChanged", function()
FBRaid_SyncButtons();
return true;
end);
FBHealBox_RegisterHook("Aggro", function(tt) return FBRaid_CheckAggro(tt); end);
FBHealBox_RegisterHook("SpellTimers", function(now) FBRaid_UpdateSpellTimers(now); return true; end);
FBHealBox_RegisterHook("Cooldowns", function() FBRaid_UpdateCooldowns(); return true; end);
FBHealBox_RegisterHook("ButtonStates", function(ev) FBRaid_UpdateButtonStates(ev); return true; end);
FBHealBox_RegisterHook("BuffIcons", function() FBRaid_UpdateBuffIcons(); return true; end);
FBHealBox_RegisterHook("Status", function()
local cfg = FBRaid_Cfg();
local state = FBT("FBP_STATE_OFF");
+564
View File
@@ -0,0 +1,564 @@
-- ==========================================================================
-- Heal Box Vanilla: Mana-Ticker (Modul, ab v1.4.3)
--
-- Ein Funke wandert ueber den Manastreifen der eigenen Plakette (und der
-- eigenen Raid-Zelle) und zeigt den 2-Sekunden-Regenerationstick des
-- Servers. Nach jedem Manaverbrauch laeuft er orange ueber die
-- 5-Sekunden-Regel, verlaengert bis zum ersten Tick nach deren Ablauf,
-- denn erst dort setzt die Regeneration wieder ein.
--
-- Erkennung ohne Combatlog und ohne SuperWoW:
-- * UNIT_MANA fuer "player": Mana gestiegen = Tick-Kandidat,
-- Mana gesunken = Zauber hat gekostet, Regel startet.
-- * Das Tickraster ist serverseitig fest (2 s). Der letzte erkannte Tick
-- wird gemerkt, weitere werden extrapoliert (auch bei vollem Mana),
-- jeder passende echte Tick synchronisiert neu.
-- * Kandidaten ausserhalb der Toleranz um den erwarteten Zeitpunkt
-- (Manaquelle-Totem, Innervate-Pulse) werden verworfen; erst nach
-- mehreren Fehlschlaegen in Folge wird ein neues Raster angenommen.
-- Grosse Spruenge (Traenke, Runen) zaehlen nie als Tick.
--
-- Bei vollem Mana ist der Funke aus (nichts zu regenerieren).
-- Haengt sich wie der Raidmodus nur ueber die Hook-Schnittstelle ein.
-- ==========================================================================
if (FBHealBox_TickerLoaded) then return; end
FBHealBox_TickerLoaded = true;
FBTICK_PERIOD = 2.0; -- Sek. zwischen zwei Regenerationsticks
FBTICK_FSR = 5.0; -- Sek. Fuenf-Sekunden-Regel
FBTICK_RESYNC_MISS = 3; -- so viele Kandidaten ausserhalb der Toleranz -> neues Raster
FBTICK_POTION_FACT = 4; -- Zuwachs groesser als das Vielfache der Tickgroesse = Trank
FBTICK_POTION_MIN = 300; -- ... und mindestens so viel Mana
FBTICK_COLOR_TICK = { 1.00, 1.00, 1.00, 0.95 }; -- Funke im Tickzyklus
FBTICK_COLOR_FSR = { 1.00, 0.55, 0.10, 1.00 }; -- Funke waehrend der Regel
FBTickerDefaults = {
Enabled = 1, -- Ticker an/aus
FSR = 1, -- 5-Sekunden-Regel anzeigen
Tolerance = 0.3, -- Sek. Toleranz um den erwarteten Tick
Offset = 0.0, -- Sek. Vorlauf (Latenzausgleich): Funke laeuft frueher los
SparkW = 2, -- px Funkenbreite
};
-- ==========================================================================
-- [ Texte ]
-- ==========================================================================
FBLocale["enUS"].TAB_TICKER = "Extras";
FBLocale["enUS"].TICK_HEADER = "Mana ticker";
FBLocale["enUS"].TICK_ENABLED = "Mana ticker";
FBLocale["enUS"].TICK_ENABLED_TIP = "A spark travels across your own mana bar every 2 seconds, in step with the server's mana regeneration tick. Cast right after the spark reaches the end and you lose no regeneration. Needs the mana bar to be on.";
FBLocale["enUS"].TICK_FSR = "Five-second rule";
FBLocale["enUS"].TICK_FSR_TIP = "After you spend mana the spark turns orange and runs over the five seconds (plus the wait for the next tick) until spirit regeneration resumes.";
FBLocale["enUS"].TICK_TOLERANCE = "Tick tolerance: |cFFFFFFFF%s s";
FBLocale["enUS"].TICK_OFFSET = "Tick offset: |cFFFFFFFF%s s";
FBLocale["enUS"].TICK_SPARKW = "Spark width: |cFFFFFFFF%s px";
FBLocale["enUS"].TICK_INFO = "White spark: time until the next regeneration tick. Orange spark: five-second rule running, regeneration resumes when it reaches the end. Raise the tolerance if ticks are missed (totems, Innervate); use the offset if the spark arrives late compared to your mana jumps.";
FBLocale["enUS"].TICK_STATUS = "Ticker: %s, next tick in %.1f s%s";
FBLocale["enUS"].TICK_SYNCED = "synced";
FBLocale["enUS"].TICK_UNSYNCED = "waiting for first tick";
FBLocale["enUS"].TICK_STATUS_FSR = ", five-second rule %.1f s";
FBLocale["enUS"].TICK_LOADED = "Mana ticker module loaded: tab |cFFFFFFFFTicker|r in the options, /fbp ticker";
FBLocale["enUS"].TICK_ON = "Mana ticker |cFF00FF00on|r.";
FBLocale["enUS"].TICK_OFF = "Mana ticker |cFFFF0000off|r.";
FBLocale["deDE"].TAB_TICKER = "Extras";
FBLocale["deDE"].TICK_HEADER = "Mana-Ticker";
FBLocale["deDE"].TICK_ENABLED = "Mana-Ticker";
FBLocale["deDE"].TICK_ENABLED_TIP = "Ein Funke wandert alle 2 Sekunden ueber deinen eigenen Manastreifen, im Takt des Regenerationsticks des Servers. Wer direkt nach dem Funken am Ende castet, verliert keine Regeneration. Braucht den eingeschalteten Manabalken.";
FBLocale["deDE"].TICK_FSR = "Fuenf-Sekunden-Regel";
FBLocale["deDE"].TICK_FSR_TIP = "Nach jedem Manaverbrauch wird der Funke orange und laeuft ueber die fuenf Sekunden (plus Wartezeit bis zum naechsten Tick), bis die Willenskraft-Regeneration wieder einsetzt.";
FBLocale["deDE"].TICK_TOLERANCE = "Tick-Toleranz: |cFFFFFFFF%s s";
FBLocale["deDE"].TICK_OFFSET = "Tick-Vorlauf: |cFFFFFFFF%s s";
-- Weitere Sprachen (Spanisch, Franzoesisch, Italienisch)
FBLocale["esES"].TAB_TICKER = "Extras";
FBLocale["esES"].TICK_HEADER = "Ticker de maná";
FBLocale["esES"].TICK_ENABLED = "Ticker de maná";
FBLocale["esES"].TICK_ENABLED_TIP = "Una chispa recorre tu propia barra de maná cada 2 segundos, al compás del pulso de regeneración de maná del servidor. Lanza justo después de que la chispa llegue al final y no perderás regeneración. Requiere la barra de maná activada.";
FBLocale["esES"].TICK_FSR = "Regla de los cinco segundos";
FBLocale["esES"].TICK_FSR_TIP = "Tras gastar maná, la chispa se vuelve naranja y recorre los cinco segundos (más la espera hasta el siguiente pulso) hasta que la regeneración por espíritu se reanuda.";
FBLocale["esES"].TICK_TOLERANCE = "Tolerancia del pulso: |cFFFFFFFF%s s";
FBLocale["esES"].TICK_OFFSET = "Desfase del pulso: |cFFFFFFFF%s s";
FBLocale["esES"].TICK_SPARKW = "Ancho de la chispa: |cFFFFFFFF%s px";
FBLocale["esES"].TICK_INFO = "Chispa blanca: tiempo hasta el siguiente pulso de regeneración. Chispa naranja: regla de los cinco segundos en curso, la regeneración se reanuda cuando llega al final. Sube la tolerancia si se pierden pulsos (tótems, Estimular); usa el desfase si la chispa llega tarde respecto a tus saltos de maná.";
FBLocale["esES"].TICK_STATUS = "Ticker: %s, siguiente pulso en %.1f s%s";
FBLocale["esES"].TICK_SYNCED = "sincronizado";
FBLocale["esES"].TICK_UNSYNCED = "esperando el primer pulso";
FBLocale["esES"].TICK_STATUS_FSR = ", regla de los cinco segundos %.1f s";
FBLocale["esES"].TICK_LOADED = "Módulo de ticker de maná cargado: pestaña |cFFFFFFFFExtras|r en las opciones, /fbp ticker";
FBLocale["esES"].TICK_ON = "Ticker de maná |cFF00FF00activado|r.";
FBLocale["esES"].TICK_OFF = "Ticker de maná |cFFFF0000desactivado|r.";
FBLocale["frFR"].TAB_TICKER = "Extras";
FBLocale["frFR"].TICK_HEADER = "Ticker de mana";
FBLocale["frFR"].TICK_ENABLED = "Ticker de mana";
FBLocale["frFR"].TICK_ENABLED_TIP = "Une étincelle parcourt votre propre barre de mana toutes les 2 secondes, au rythme du tick de régénération de mana du serveur. Lancez juste après que l'étincelle atteint la fin et vous ne perdez aucune régénération. Nécessite la barre de mana activée.";
FBLocale["frFR"].TICK_FSR = "Règle des cinq secondes";
FBLocale["frFR"].TICK_FSR_TIP = "Après une dépense de mana, l'étincelle devient orange et parcourt les cinq secondes (plus l'attente du prochain tick) jusqu'à la reprise de la régénération par l'esprit.";
FBLocale["frFR"].TICK_TOLERANCE = "Tolérance du tick : |cFFFFFFFF%s s";
FBLocale["frFR"].TICK_OFFSET = "Décalage du tick : |cFFFFFFFF%s s";
FBLocale["frFR"].TICK_SPARKW = "Largeur de l'étincelle : |cFFFFFFFF%s px";
FBLocale["frFR"].TICK_INFO = "Étincelle blanche : temps jusqu'au prochain tick de régénération. Étincelle orange : règle des cinq secondes en cours, la régénération reprend quand elle atteint la fin. Augmentez la tolérance si des ticks sont manqués (totems, Innervation) ; utilisez le décalage si l'étincelle arrive en retard par rapport à vos sauts de mana.";
FBLocale["frFR"].TICK_STATUS = "Ticker : %s, prochain tick dans %.1f s%s";
FBLocale["frFR"].TICK_SYNCED = "synchronisé";
FBLocale["frFR"].TICK_UNSYNCED = "en attente du premier tick";
FBLocale["frFR"].TICK_STATUS_FSR = ", règle des cinq secondes %.1f s";
FBLocale["frFR"].TICK_LOADED = "Module ticker de mana chargé : onglet |cFFFFFFFFExtras|r dans les options, /fbp ticker";
FBLocale["frFR"].TICK_ON = "Ticker de mana |cFF00FF00activé|r.";
FBLocale["frFR"].TICK_OFF = "Ticker de mana |cFFFF0000désactivé|r.";
FBLocale["itIT"].TAB_TICKER = "Extra";
FBLocale["itIT"].TICK_HEADER = "Ticker del mana";
FBLocale["itIT"].TICK_ENABLED = "Ticker del mana";
FBLocale["itIT"].TICK_ENABLED_TIP = "Una scintilla attraversa la tua barra del mana ogni 2 secondi, a tempo con il tick di rigenerazione del mana del server. Lancia subito dopo che la scintilla arriva in fondo e non perdi rigenerazione. Richiede la barra del mana attiva.";
FBLocale["itIT"].TICK_FSR = "Regola dei cinque secondi";
FBLocale["itIT"].TICK_FSR_TIP = "Dopo aver speso mana la scintilla diventa arancione e percorre i cinque secondi (più l'attesa del tick successivo) finché la rigenerazione da spirito riprende.";
FBLocale["itIT"].TICK_TOLERANCE = "Tolleranza del tick: |cFFFFFFFF%s s";
FBLocale["itIT"].TICK_OFFSET = "Anticipo del tick: |cFFFFFFFF%s s";
FBLocale["itIT"].TICK_SPARKW = "Larghezza scintilla: |cFFFFFFFF%s px";
FBLocale["itIT"].TICK_INFO = "Scintilla bianca: tempo al prossimo tick di rigenerazione. Scintilla arancione: regola dei cinque secondi in corso, la rigenerazione riprende quando arriva in fondo. Alza la tolleranza se i tick vengono persi (totem, Innervazione); usa l'anticipo se la scintilla arriva in ritardo rispetto ai tuoi salti di mana.";
FBLocale["itIT"].TICK_STATUS = "Ticker: %s, prossimo tick tra %.1f s%s";
FBLocale["itIT"].TICK_SYNCED = "sincronizzato";
FBLocale["itIT"].TICK_UNSYNCED = "in attesa del primo tick";
FBLocale["itIT"].TICK_STATUS_FSR = ", regola dei cinque secondi %.1f s";
FBLocale["itIT"].TICK_LOADED = "Modulo ticker del mana caricato: scheda |cFFFFFFFFExtra|r nelle opzioni, /fbp ticker";
FBLocale["itIT"].TICK_ON = "Ticker del mana |cFF00FF00attivo|r.";
FBLocale["itIT"].TICK_OFF = "Ticker del mana |cFFFF0000disattivato|r.";
FBLocale["deDE"].TICK_SPARKW = "Funkenbreite: |cFFFFFFFF%s px";
FBLocale["deDE"].TICK_INFO = "Weisser Funke: Zeit bis zum naechsten Regenerationstick. Oranger Funke: Fuenf-Sekunden-Regel laeuft, am Ende setzt die Regeneration wieder ein. Toleranz erhoehen, wenn Ticks verpasst werden (Totems, Anregen); Vorlauf nutzen, wenn der Funke spaeter ankommt als deine Manaspruenge.";
FBLocale["deDE"].TICK_STATUS = "Ticker: %s, naechster Tick in %.1f s%s";
FBLocale["deDE"].TICK_SYNCED = "synchron";
FBLocale["deDE"].TICK_UNSYNCED = "wartet auf ersten Tick";
FBLocale["deDE"].TICK_STATUS_FSR = ", Fuenf-Sekunden-Regel %.1f s";
FBLocale["deDE"].TICK_LOADED = "Mana-Ticker-Modul geladen: Reiter |cFFFFFFFFTicker|r in den Optionen, /fbp ticker";
FBLocale["deDE"].TICK_ON = "Mana-Ticker |cFF00FF00an|r.";
FBLocale["deDE"].TICK_OFF = "Mana-Ticker |cFFFF0000aus|r.";
-- ==========================================================================
-- [ Zustand ]
-- ==========================================================================
FBTicker = {
lastTick = nil, -- GetTime() des letzten akzeptierten Ticks (Rasterpunkt)
lastMana = nil, -- zuletzt gesehener Manawert
tickSize = nil, -- gelernte typische Tickgroesse (fuer den Trankfilter)
misses = 0, -- Kandidaten in Folge ausserhalb der Toleranz
fsrStart = nil, -- Beginn der Fuenf-Sekunden-Regel
fsrUntil = nil, -- ihr Ende (5 s nach Beginn)
sparks = {}, -- Funken auf den Manabalken (Plakette, Raid-Zelle)
};
function FBTicker_Cfg()
if (not HealBox.Ticker) then FBTicker_ApplyDefaults(); end
return HealBox.Ticker;
end
function FBTicker_ApplyDefaults()
if (not HealBox.Ticker) then HealBox.Ticker = {}; end
for k, v in pairs(FBTickerDefaults) do
if (HealBox.Ticker[k] == nil) then HealBox.Ticker[k] = v; end
end
return true;
end
-- Volles Mana? (dann gibt es nichts zu regenerieren, Funke aus)
function FBTicker_ManaFull()
local mp, mpMax = UnitMana("player"), UnitManaMax("player");
if (not mp) or (not mpMax) or (mpMax <= 0) then return true; end
return (mp >= mpMax);
end
-- Nutzt der Spieler gerade Mana? (Druiden in Gestalt: nein)
function FBTicker_PlayerHasMana()
if (UnitPowerType and UnitPowerType("player") ~= 0) then return false; end
local max = UnitManaMax("player");
return (max and max > 0);
end
-- ==========================================================================
-- [ Erkennung ]
-- ==========================================================================
-- Naechster Rasterpunkt ab Zeitpunkt t (t selbst, wenn t genau auf dem Raster liegt)
function FBTicker_NextTickAfter(t)
local last = FBTicker.lastTick;
if (not last) then return nil; end
if (t <= last) then return last; end
local k = math.ceil((t - last) / FBTICK_PERIOD - 0.0001);
return last + k * FBTICK_PERIOD;
end
-- Abstand von t zum naechstgelegenen Rasterpunkt
function FBTicker_GridDistance(t)
local last = FBTicker.lastTick;
if (not last) then return nil; end
local phase = math.mod(t - last, FBTICK_PERIOD);
if (phase < 0) then phase = phase + FBTICK_PERIOD; end
if (phase > FBTICK_PERIOD / 2) then phase = FBTICK_PERIOD - phase; end
return phase;
end
function FBTicker_OnManaChanged()
local cfg = FBTicker_Cfg();
local now = GetTime();
local mana = UnitMana("player") or 0;
local prev = FBTicker.lastMana;
FBTicker.lastMana = mana;
if (prev == nil) then return; end
local delta = mana - prev;
if (delta < 0) then
-- Mana ausgegeben: Fuenf-Sekunden-Regel beginnt jetzt
FBTicker.fsrStart = now;
FBTicker.fsrUntil = now + FBTICK_FSR;
return;
end
if (delta == 0) then return; end
-- Zuwachs: Trank oder Rune? (grosser Sprung im Vergleich zur Tickgroesse)
if (FBTicker.tickSize and delta >= FBTICK_POTION_MIN and delta > FBTicker.tickSize * FBTICK_POTION_FACT) then
return;
end
if (not FBTicker.lastTick) then
FBTicker.lastTick = now;
FBTicker.misses = 0;
else
local dist = FBTicker_GridDistance(now);
if (dist <= (cfg.Tolerance or 0.3)) then
FBTicker.lastTick = now; -- passt ins Raster: neu synchronisieren
FBTicker.misses = 0;
else
FBTicker.misses = FBTicker.misses + 1;
if (FBTicker.misses >= FBTICK_RESYNC_MISS) then
FBTicker.lastTick = now; -- Raster hat sich verschoben
FBTicker.misses = 0;
end
return; -- fremder Puls, Tickgroesse nicht lernen
end
end
-- Tickgroesse lernen (gleitend), nur ausserhalb der Regel (volle Ticks)
local inFSR = (FBTicker.fsrUntil and now < FBTicker.fsrUntil);
if (not inFSR) then
if (not FBTicker.tickSize) then
FBTicker.tickSize = delta;
else
FBTicker.tickSize = FBTicker.tickSize * 0.7 + delta * 0.3;
end
end
end
-- Zeitpunkt, an dem die Regeneration nach der Regel wieder einsetzt:
-- erster Rasterpunkt nach fsrUntil (ohne Raster: fsrUntil selbst)
function FBTicker_FSREnd()
if (not FBTicker.fsrUntil) then return nil; end
local grid = FBTicker_NextTickAfter(FBTicker.fsrUntil);
if (grid) then return grid; end
return FBTicker.fsrUntil;
end
-- Anteil 0..1 und Modus ("tick" | "fsr" | nil) fuer den Funken zur Zeit now
function FBTicker_Progress(now)
local cfg = FBTicker_Cfg();
local t = now + (cfg.Offset or 0);
if (cfg.FSR == 1 and FBTicker.fsrStart) then
local fsrEnd = FBTicker_FSREnd();
if (fsrEnd and t < fsrEnd) then
local total = fsrEnd - FBTicker.fsrStart;
if (total <= 0) then total = FBTICK_FSR; end
local frac = (t - FBTicker.fsrStart) / total;
if (frac < 0) then frac = 0; end
if (frac > 1) then frac = 1; end
return frac, "fsr";
end
-- Regel vorbei
FBTicker.fsrStart = nil;
FBTicker.fsrUntil = nil;
end
if (not FBTicker.lastTick) then return nil, nil; end
local phase = math.mod(t - FBTicker.lastTick, FBTICK_PERIOD);
if (phase < 0) then phase = phase + FBTICK_PERIOD; end
return phase / FBTICK_PERIOD, "tick";
end
function FBTicker_Reset()
FBTicker.lastTick = nil;
FBTicker.lastMana = UnitMana("player");
FBTicker.tickSize = nil;
FBTicker.misses = 0;
FBTicker.fsrStart = nil;
FBTicker.fsrUntil = nil;
end
-- ==========================================================================
-- [ Funken ]
-- ==========================================================================
-- Funke auf einem Manabalken anlegen (einmal je Balken)
function FBTicker_SparkFor(bar)
if (not bar) then return nil; end
if (bar.fbSpark) then return bar.fbSpark; end
local s = bar:CreateTexture(nil, "OVERLAY");
s:SetTexture(1, 1, 1, 1);
s:SetWidth(2);
s:SetHeight(4);
s:SetPoint("LEFT", bar, "LEFT", 0, 0);
s:Hide();
bar.fbSpark = s;
return s;
end
-- Balken einsammeln: eigene Plakette und, falls vorhanden, eigene Raid-Zelle
function FBTicker_CollectBars()
-- alte Funken verstecken, Zwischenspeicher neu
FBTicker_HideAll();
FBTicker.sparks = {};
if (FBHealBox1 and FBHealBox1.ManaBar) then
table.insert(FBTicker.sparks, { bar = FBHealBox1.ManaBar, spark = FBTicker_SparkFor(FBHealBox1.ManaBar) });
end
if (FBRaidUnitCell and FBRaidTestMode == 0) then
local me = UnitName("player");
for unit, cell in pairs(FBRaidUnitCell) do
if (cell.name == me and cell.ManaBar) then
table.insert(FBTicker.sparks, { bar = cell.ManaBar, spark = FBTicker_SparkFor(cell.ManaBar) });
end
end
end
end
function FBTicker_HideAll()
for _, e in ipairs(FBTicker.sparks) do
if (e.spark and e.shown ~= false) then e.shown = false; e.spark:Hide(); end
end
end
-- Torwaechter fuer das Zeichnen: wird nur bei Events und Optionswechseln
-- neu bewertet (Mana-Events, Gestaltwechsel, Schalter), nicht jeden Frame.
FBTicker.gate = false;
function FBTicker_UpdateGate()
local cfg = FBTicker_Cfg();
FBTicker.gate = (cfg.Enabled == 1) and (HealBox.Active == 1)
and FBTicker_PlayerHasMana() and (not FBTicker_ManaFull());
if (not FBTicker.gate) then FBTicker_HideAll(); end
end
function FBTicker_Draw()
if (not FBTicker.gate) then return; end
local frac, mode = FBTicker_Progress(GetTime());
if (not frac) then
FBTicker_HideAll();
return;
end
local col = FBTICK_COLOR_TICK;
if (mode == "fsr") then col = FBTICK_COLOR_FSR; end
local w = FBTicker_Cfg().SparkW or 2;
for _, e in ipairs(FBTicker.sparks) do
local bar, s = e.bar, e.spark;
if (s and bar) then
if (bar:IsShown()) then
local bw = bar:GetWidth() or 0;
local bh = bar:GetHeight() or 0;
if (bw > w and bh > 0) then
if (e.w ~= w or e.h ~= bh) then
e.w = w; e.h = bh;
s:SetWidth(w);
s:SetHeight(bh);
end
if (e.mode ~= mode) then
e.mode = mode;
s:SetTexture(col[1], col[2], col[3], col[4]);
end
-- Anker nur setzen, wenn der Funke mindestens einen halben
-- Pixel weitergewandert ist
local x = frac * (bw - w);
if (not e.x) or (math.abs(x - e.x) >= 0.5) then
e.x = x;
s:ClearAllPoints();
s:SetPoint("LEFT", bar, "LEFT", x, 0);
end
if (not e.shown) then e.shown = true; s:Show(); end
else
if (e.shown) then e.shown = false; s:Hide(); end
end
elseif (e.shown) then
e.shown = false; s:Hide();
end
end
end
end
-- ==========================================================================
-- [ Events ]
-- ==========================================================================
FBTickerFrame = CreateFrame("Frame", "FBHealBoxTickerFrame", UIParent);
FBTickerFrame:RegisterEvent("UNIT_MANA");
FBTickerFrame:RegisterEvent("UNIT_MAXMANA");
FBTickerFrame:RegisterEvent("UNIT_DISPLAYPOWER");
FBTickerFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
FBTickerFrame:SetScript("OnEvent", function()
if (event == "PLAYER_ENTERING_WORLD") then
FBTicker_Reset();
FBTicker_CollectBars();
FBTicker_UpdateGate();
return;
end
if (arg1 ~= "player") then return; end
if (event == "UNIT_MANA") then
FBTicker_OnManaChanged();
elseif (event == "UNIT_DISPLAYPOWER") then
FBTicker_Reset();
else
FBTicker.lastMana = UnitMana("player");
end
FBTicker_UpdateGate();
end);
FBTickerFrame:SetScript("OnUpdate", function()
FBTicker_Draw();
end);
-- ==========================================================================
-- [ Options-Reiter "Ticker" ]
-- ==========================================================================
FBTickerSliders = {};
function FBTicker_SliderText(s)
if (not s or not s.Text) then return; end
local v = s:GetValue();
local shown;
if (s.decimals) then shown = format("%.1f", v); else shown = tostring(math.floor(v + 0.5)); end
s.Text:SetText(format(FBT(s.labelKey), shown));
end
function FBTicker_CreateSlider(name, parent, x, y, labelKey, cfgKey, minV, maxV, step, decimals)
local s = CreateFrame("Slider", name, parent, "OptionsSliderTemplate");
s:SetWidth(128);
s:SetHeight(16);
s:SetPoint("TOPLEFT", x, y);
s:SetMinMaxValues(minV, maxV);
s:SetValueStep(step);
s.labelKey = labelKey;
s.cfgKey = cfgKey;
s.decimals = decimals;
s.Text = s:CreateFontString(nil, "BACKGROUND", "GameFontNormal");
s.Text:SetPoint("CENTER", 0, 15);
getglobal(name.."Low"):SetText(tostring(minV));
getglobal(name.."High"):SetText(tostring(maxV));
s:SetValue(FBTicker_Cfg()[cfgKey] or minV);
FBTicker_SliderText(s);
s:SetScript("OnValueChanged", function()
local v = s:GetValue();
if (s.decimals) then v = math.floor(v * 10 + 0.5) / 10; else v = math.floor(v + 0.5); end
FBTicker_Cfg()[s.cfgKey] = v;
FBTicker_SliderText(s);
end);
FBTickerSliders[cfgKey] = s;
return s;
end
function FBTicker_BuildOptions()
local tab = FBHealBox_AddOptionsTab("TAB_TICKER");
if (not tab) then return; end
-- Abschnittskopf: der Reiter "Extras" wird mit Smart Damage geteilt
FBTickerHeader = tab:CreateFontString(nil, "OVERLAY", "GameFontNormal");
FBTickerHeader:SetPoint("TOPLEFT", tab, "TOPLEFT", 35, FBOPT_CONTENT_Y);
FBTickerHeader:SetText(FBT("TICK_HEADER"));
local y = FBOPT_CONTENT_Y - 26;
FBTickerEnabledCheck = FBHealBox_CreateCheck("FBHealBoxTickerEnabledCheck", tab, 40, y, "TICK_ENABLED", "TICK_ENABLED_TIP", function()
FBTicker_Cfg().Enabled = FBTickerEnabledCheck:GetChecked() and 1 or 0;
FBTicker_UpdateGate();
FBTicker_Draw();
end);
FBTickerFSRCheck = FBHealBox_CreateCheck("FBHealBoxTickerFSRCheck", tab, 250, y, "TICK_FSR", "TICK_FSR_TIP", function()
FBTicker_Cfg().FSR = FBTickerFSRCheck:GetChecked() and 1 or 0;
FBTicker_Draw();
end);
local sy = y - 60;
FBTicker_CreateSlider("FBTickerToleranceSlider", tab, 75, sy, "TICK_TOLERANCE", "Tolerance", 0.1, 0.6, 0.1, true);
FBTicker_CreateSlider("FBTickerOffsetSlider", tab, 260, sy, "TICK_OFFSET", "Offset", 0.0, 0.5, 0.1, true);
FBTicker_CreateSlider("FBTickerSparkWSlider", tab, 75, sy - 46, "TICK_SPARKW", "SparkW", 1, 4, 1, false);
FBTickerInfoText = tab:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall");
FBTickerInfoText:SetPoint("TOPLEFT", tab, "TOPLEFT", 35, sy - 84);
FBTickerInfoText:SetWidth(395);
FBTickerInfoText:SetJustifyH("LEFT");
FBTickerInfoText:SetText(FBT("TICK_INFO"));
FBTicker_SyncOptions();
end
function FBTicker_SyncOptions()
local cfg = FBTicker_Cfg();
if (FBTickerEnabledCheck) then FBTickerEnabledCheck:SetChecked(cfg.Enabled == 1); end
if (FBTickerFSRCheck) then FBTickerFSRCheck:SetChecked(cfg.FSR == 1); end
for key, s in pairs(FBTickerSliders) do
if (cfg[key] ~= nil) then s:SetValue(cfg[key]); end
end
return true;
end
function FBTicker_ApplyLocale()
if (FBTickerHeader) then FBTickerHeader:SetText(FBT("TICK_HEADER")); end
if (FBTickerEnabledCheck) then FBTickerEnabledCheck.Text:SetText(FBT("TICK_ENABLED")); FBTickerEnabledCheck.tooltipText = FBT("TICK_ENABLED_TIP"); end
if (FBTickerFSRCheck) then FBTickerFSRCheck.Text:SetText(FBT("TICK_FSR")); FBTickerFSRCheck.tooltipText = FBT("TICK_FSR_TIP"); end
if (FBTickerInfoText) then FBTickerInfoText:SetText(FBT("TICK_INFO")); end
for _, s in pairs(FBTickerSliders) do FBTicker_SliderText(s); end
return true;
end
-- ==========================================================================
-- [ Anbindung an den Kern ]
-- ==========================================================================
FBHealBox_RegisterHook("Loaded", function()
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r "..FBT("TICK_LOADED"));
return true;
end);
FBHealBox_RegisterHook("Defaults", function() return FBTicker_ApplyDefaults(); end);
FBHealBox_RegisterHook("SyncOptions", function() return FBTicker_SyncOptions(); end);
FBHealBox_RegisterHook("ApplyLocale", function() return FBTicker_ApplyLocale(); end);
FBHealBox_RegisterHook("UpdateNames", function() FBTicker_CollectBars(); FBTicker_UpdateGate(); return true; end);
FBHealBox_RegisterHook("ActiveToggle", function() FBTicker_UpdateGate(); FBTicker_Draw(); return true; end);
FBHealBox_RegisterHook("Status", function()
local state = FBT("TICK_UNSYNCED");
local nextIn = 0;
if (FBTicker.lastTick) then
state = FBT("TICK_SYNCED");
local nxt = FBTicker_NextTickAfter(GetTime());
if (nxt) then nextIn = nxt - GetTime(); end
end
local fsr = "";
local fsrEnd = FBTicker_FSREnd();
if (fsrEnd and fsrEnd > GetTime()) then fsr = format(FBT("TICK_STATUS_FSR"), fsrEnd - GetTime()); end
DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..format(FBT("TICK_STATUS"), state, nextIn, fsr));
return true;
end);
FBHealBox_RegisterHook("Slash", function(msg)
if (msg == "ticker") then
local cfg = FBTicker_Cfg();
if (cfg.Enabled == 1) then cfg.Enabled = 0; else cfg.Enabled = 1; end
FBTicker_SyncOptions();
FBTicker_UpdateGate();
FBTicker_Draw();
local key = "TICK_OFF";
if (cfg.Enabled == 1) then key = "TICK_ON"; end
DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r "..FBT(key));
return true;
end
return false;
end);
FBTicker_ApplyDefaults();
FBTicker_BuildOptions();
FBTicker_CollectBars();
+242 -40
View File
@@ -6,11 +6,12 @@ Party, pet and self heal display with quick-cast buttons for healers. One name p
**The short version:** click the minimap button (or type `/fbp config`) → options window → pick a spell for each button → done. Everything else happens on its own. `/fbp` tells you at any time what the prediction currently believes. **New in 1.4.1:** tick *Test mode* to fill the display with ghost players and arrange everything without a group.
<img width="585" height="441" alt="Screenshot 2026-09-05 003418" src="https://github.com/user-attachments/assets/4fca55e1-3dfb-475b-950a-da86fab4c196" />
<img width="770" height="479" alt="Screenshot 2026-09-05 210211" src="https://github.com/user-attachments/assets/83d85071-4482-4762-be24-f9eda1578ef6" />
<img width="1146" height="527" alt="Screenshot 2026-09-05 081047" src="https://github.com/user-attachments/assets/ee18f0d1-d448-4129-b72b-dd999b79a91b" />
<img width="956" height="847" alt="Screenshot 2026-09-05 210241" src="https://github.com/user-attachments/assets/d7508232-eb56-47ef-8f7e-c39ab0c2b434" />
<img width="841" height="1104" alt="grafik" src="https://github.com/user-attachments/assets/aa821a3b-4b9e-4a3d-89e9-90e54726dd75" />
<img width="844" height="1100" alt="Screenshot 2026-09-05 081138" src="https://github.com/user-attachments/assets/e7a8bb0e-f800-4884-94ed-6e51bddf0283" />
\---
@@ -18,13 +19,15 @@ Party, pet and self heal display with quick-cast buttons for healers. One name p
Download zip, unpack into your Addons folder.
The folder under `Interface\\AddOns` is called **`FBHealBox`** and holds five files:
The folder under `Interface\\AddOns` is called **`FBHealBox`** and holds seven files:
|File|Contents|
|-|-|
|`FBHealBox.toc`|Metadata, load order, saved variables|
|`FBHealBox.lua`|The core: party and pet display, prediction, HealComm, options|
|`FBHealBox_Raid.lua`|The raid mode module (see [Raid mode](#raid-mode)). Remove it from the `.toc` and the core runs exactly as in 1.4.1|
|`FBHealBox_Ticker.lua`|The mana ticker module (see [Mana ticker](#mana-ticker)), also removable|
|`FBHealBox_Damage.lua`|The Smart Damage module (see [Smart Damage](#smart-damage)), also removable|
|`FBHealBox.xml`|Only the event frame that calls `FBHealBox\_OnLoad` and `FBHealBox\_OnEvent`|
|`CHANGELOG.md`|What changed in which version|
@@ -46,9 +49,13 @@ Plates are stacked in the order **owner, then that owner's pet**: you, your pet,
A **left click** on a name or health bar targets that unit; so does a **right click**. Both are configurable on the *General* tab (*Left click on plate* / *Right click on plate*): **Target**, **Unit menu** (Blizzard's own unit menu with whisper, invite, promote, leave, for players and your own pet; group pets fall back to targeting), **Move display** (drag without holding Shift) or **Nothing**. Shift + left drag always moves the display, whatever is set. In test mode clicks on ghosts only print a note.
### Who is being attacked
If your current hostile target is targeting a group member, that member's plate (and raid cell) gets a **red border**, refreshed five times a second from `targettarget`. Red takes precedence over the orange buff-watch border. Option *Mark who is attacked*.
### Line of sight
While a unit is out of your line of sight an **eye badge** (`FBLOS\_ICON`, the Blind icon) hangs on the left edge of its plate. Vanilla has no API for this, so two paths are used: with the **UnitXP SP3** client mod installed the addon asks `UnitXP("inSight", "player", unit)` every half second, live and exact. Without it, the addon watches for the *not in line of sight* error after one of your heals and marks the unit you tried to heal for `FBLOS\_TIMEOUT` (8) seconds; the mark is cleared as soon as a cast on that unit starts or one of your heals or HoT ticks lands on it. `/fbp` reports which path is active. Option *Line of sight*; badge position via `FBLOS\_ICON\_X/Y`.
While a unit is out of your line of sight an **eye badge** (`FBLOS\_ICON`, the Blind icon) sits on the top-left corner of its plate. Vanilla has no API for this, so two paths are used: with the **UnitXP SP3** client mod installed the addon asks `UnitXP("inSight", "player", unit)` every half second, live and exact. Without it, the addon watches for the *not in line of sight* error after one of your heals and marks the unit you tried to heal for `FBLOS\_TIMEOUT` (8) seconds; the mark is cleared as soon as a cast on that unit starts or one of your heals or HoT ticks lands on it. `/fbp` reports which path is active. Option *Line of sight*; badge position via `FBLOS\_ICON\_X/Y`.
### Pets
@@ -90,10 +97,18 @@ The thresholds for the HP colours are `LowHP` (0.6) and `VeryLowHP` (0.3) near t
Up to ten buttons sit next to each plate, each showing its spell's icon. A **left click** casts that spell on exactly this group member, no matter who you currently have targeted.
**Assigning by drag and drop.** Open the spellbook, drag a spell and drop it on any heal button, on a raid mini button, or on a field in the *Buttons* tab; that button number takes the spell on every plate. With the right-click spell enabled, dropping with the right mouse button fills the right-click side. Spells outside the class list (Dispel Magic, say) are accepted too and keep their icon after a reload. Vanilla has no `GetCursorInfo()`, so the addon remembers what `PickupSpell` last put on the cursor.
**Assigning by drag and drop.** Open the spellbook, drag a spell and drop it on any heal button, on a raid mini button, or on a field in the *Buttons* tab; that button number takes the spell on every plate. With the right-click spell enabled, dropping with the right mouse button, or with Shift held while dropping, fills the right-click side. Spells outside the class list (Dispel Magic, say) are accepted too and keep their icon after a reload. Vanilla has no `GetCursorInfo()`, so the addon remembers what `PickupSpell` last put on the cursor.
**Right-click spell (optional).** Each button can carry a second spell for **right click** (Flash Heal left, Greater Heal right, say), which doubles the density without adding buttons. This is off by default and is enabled only through the switch on the *Buttons* tab. When on, a second column appears in the assignment and a small icon in the bottom-right corner of every button shows its right-click spell; the tooltip lists it as well. Switching it off keeps the assignments, it just stops the buttons from reacting to right click.
**Smart Healing (off by default).** With *Smart Healing* on, a click casts the lowest rank of the assigned spell whose expected heal covers the target's missing health, minus healing already on the way, plus the *Safety margin* (default 20 %). Expected heals come from the learned values of the prediction where available, otherwise from the tooltip average. It never goes above the rank you assigned, applies to direct heals only (HoTs, shields and buffs such as Fortitude are always cast as assigned; a spell qualifies only if its tooltip describes a heal), and below 30 % health it always casts the assigned rank. Downsides: the estimate ignores crits, and with burst damage or when you want to overheal on purpose (a tank before a big hit) the lower rank can fall short. Leave it off whenever overhealing is what you want. `/fbp debug` prints every decision, `/fbp` shows the state.
**Cooldowns.** Every button shows the usual cooldown sweep for its spell. The global cooldown is not shown (`FBCD\_MIN\_DURATION`). Option *Cooldowns on buttons*.
**HoT and shield timers.** The button of a spell shows, for its unit, the remaining seconds of your own HoT (green) or shield (blue) of that spell. When Power Word: Shield is used up, the same button shows Weakened Soul in red until the target can be shielded again. The left-click spell is checked first, then the right-click spell. Only your own effects are tracked. Option *HoT and shield timers*.
**Buff icons left of the bar.** Buffs with a duration that sit on your buttons (Fortitude, Divine Spirit, Fear Ward, Inner Fire, left or right click) appear as small 8 px icons on the **outer left side** of the plate while the buff is up, stacked two high: the first at the top next to the plate, the second below it, the third at the top of the next column to the left, and so on. The icon is a **clock**: like a minute hand, it turns black and white clockwise from twelve o'clock as the time runs out. Fresh buff: fully coloured. Half the time left: right half grey. A quarter left: three quarters grey. Vanilla cannot draw a true circular sweep, so the icon is split into four quadrants; on 8 px that is the finest resolution that still reads. Elapsed quadrants are drawn dark (desaturated where the client supports it, with a dark wash `FBBUFFICON\_WASH`) on a small frame one level above the icon, so the split is visible on every client; `/fbp buffs` also prints the grey-quadrant count per icon. Hovering an icon shows the buff name and the remaining time. The remaining time is exact on yourself (buff API, re-read on every `PLAYER_AURAS_CHANGED` and once a second in between, because a refresh of a running buff fires no event; a recast therefore resets the clock) and counted from your own cast on others; a buff cast by someone else stays fully coloured (time unknown). Up to six icons per plate (`FBBUFFICON\_MAX`). Option *Buff icons left of the bar*, on by default. Raid cells show the same clock icons outside their left edge at 6 px in a 3 by 4 grid (twelve slots, columns filling from the cell outward), and the grid reserves the room for them between the groups. After a `/reload` or a group change the addon scans all units once, so existing buffs show up without waiting for an aura event. `/fbp buffs` lists the tracked buffs and their state on you.
**Dead, ghost, offline.** Instead of `0 %` the bar shows *Dead*, *Ghost* or *Offline*, empty and grey, with no mana strip.
**Debuff icon.** When a unit carries a debuff your class can remove, its icon appears right of the name with the stack count (from 2 up). The health bar takes the debuff colour as before.
@@ -121,7 +136,7 @@ Raid mode is an addon within the addon: it lives in its own file `FBHealBox_Raid
Once the raid reaches the threshold, a compact **grid** appears: one **cell** per member, grouped in **blocks of five** by raid group, the blocks arranged in rows. Each cell (70 by 22 px by default) shows the name in class colour, the health bar with the same shield and incoming-heal layers as the party plates, a 3 px mana strip for mana users, the HP percentage or the missing health, dispel colouring, *Dead* / *Ghost* / *Offline* text, range fading, the line-of-sight eye and the orange buff-watch border. To the right of every cell sit up to four **mini buttons** that cast the spells of Button 1 to N from the *Buttons* tab, right-click spells included. A click on the cell itself does what *Left click on plate* / *Right click on plate* says (target by default; the unit menu comes from Blizzard's raid frame).
Group blocks are laid out with *Groups per row* (default 4): a 40-player raid is two rows of four blocks, about 450 by 270 px with two buttons per cell. **Empty groups take no space**, so a 20-player raid is a single row. The whole grid scales independently of the party plates and remembers its position.
Group blocks are laid out with *Groups per row* (default 4): a 40-player raid is two rows of four blocks, about 570 by 270 px with two buttons per cell and the buff-icon strip. **Empty groups take no space**, so a 20-player raid is a single row. The whole grid scales independently of the party plates and remembers its position.
While the grid is shown the five party plates are hidden (option *Hide party plates in raid*), so the screen is never filled twice.
@@ -136,8 +151,9 @@ While the grid is shown the five party plates are hidden (option *Hide party pla
|Mana strip|3 px mana bar in the cell. On|
|Hide empty groups|Groups without members collapse. On|
|Title bar|Thin bar above the grid to drag it by. Without it, Shift + drag any cell. On|
|Show buffs|Buff icons with clock left of each cell. Off: no icons and no reserved strip, the groups move closer together. On|
|HP text|none, percent or deficit (missing health as a negative number)|
|Raid test|Fills the grid with 20 or 40 ghosts: dead, offline, ghost, debuffed, out of range, out of sight, missing buff, shield and incoming heal are all represented. Not saved|
|Raid test|Fills the grid with 20 or 40 ghosts: dead, offline, ghost, magic and disease debuffs, out of range, out of sight, missing buff, shield, incoming heal and buff icons (every third ghost, ghost 12 with twelve) are all represented. Not saved|
|Groups per row|1 to 8. Default 4|
|Raid scale|0.5 to 1.5|
|Cell width / height|50 to 120 px / 14 to 32 px|
@@ -149,7 +165,61 @@ All raid settings live in `HealBox.Raid`. `/fbp raidtest 20`, `/fbp raidtest 40`
### How it hooks in
The core exposes `FBHealBox\_RegisterHook(name, fn)` and runs the hooks at fixed points: `Defaults`, `SyncOptions`, `ApplyLocale`, `UpdateNames`, `RefreshAllBars`, `ButtonsChanged`, `ActiveToggle`, `Status` and `Slash`. `FBHealBox\_AddOptionsTab(labelKey)` adds a tab to the options window. The raid module registers its roster refresh on `UpdateNames`, its cell refresh on `RefreshAllBars` (so the heal prediction reaches raid cells for free), and its slash commands on `Slash`. Raid units are added to `FBPredictUnits`, which lets HoT and shield confirmation via `UNIT\_AURA` work for `raid1` to `raid40`. Pets are not shown in raid mode.
The core exposes `FBHealBox\_RegisterHook(name, fn)` and runs the hooks at fixed points: `Defaults`, `SyncOptions`, `ApplyLocale`, `UpdateNames`, `RefreshAllBars`, `ButtonsChanged`, `ActiveToggle`, `Status`, `Slash`, `Loaded`, `Aggro`, `SpellTimers` and `Cooldowns`. `FBHealBox\_AddOptionsTab(labelKey)` adds a tab to the options window. The raid module registers its roster refresh on `UpdateNames`, its cell refresh on `RefreshAllBars` (so the heal prediction reaches raid cells for free), and its slash commands on `Slash`. Raid units are added to `FBPredictUnits`, which lets HoT and shield confirmation via `UNIT\_AURA` work for `raid1` to `raid40`. Pets are not shown in raid mode.
\---
## Mana ticker
The mana ticker lives in its own module `FBHealBox_Ticker.lua` and is on by default. A **spark** travels across the mana strip of your own plate (and of your own cell in the raid grid) every 2 seconds, in step with the server's mana regeneration tick. When you spend mana the spark turns **orange** and runs the five-second rule down; it reaches the end exactly when spirit regeneration resumes, which is the first tick after the five seconds, not the five seconds themselves. Casting right after the spark reaches the end wastes no regeneration.
### How it works
There is no API for this in 1.12, so the module watches `UNIT\_MANA` for the player. Mana going down starts the five-second rule. Mana going up is a tick candidate: the first one sets the 2 s grid, every later one that lands within *Tick tolerance* of the expected time re-synchronises it. Candidates outside the tolerance (Mana Spring pulses, Innervate) are ignored; only after three misses in a row is a new grid accepted. Jumps of at least 300 mana and more than four times the learned tick size (potions, runes) never count. With full mana no ticks arrive, so the grid runs on silently and the spark stays hidden until mana is missing again; it re-synchronises on the next real tick. The spark needs the mana strip (option *Mana bar*) and disappears in bear or cat form.
### Tab *Extras*, section *Mana ticker*
|Setting|Effect|
|-|-|
|Mana ticker|Master switch. On by default|
|Five-second rule|Show the orange phase after spending mana. On|
|Tick tolerance|0.1 to 0.6 s. Raise it if ticks are being missed|
|Tick offset|0.0 to 0.5 s. Runs the spark earlier to compensate latency if it arrives late compared to your mana jumps|
|Spark width|1 to 4 px|
`/fbp ticker` toggles the ticker; `/fbp` reports whether the grid is synced, the time to the next tick and a running five-second rule. Settings live in `HealBox.Ticker`. The ticker shares the *Extras* tab with Smart Damage.
\---
## Smart Damage
Rank selection for **attack spells on any action bar**, in its own module `FBHealBox_Damage.lua`, **off by default**. When you press Smite rank 4 on a Bongos or Blizzard bar (or its key binding) and rank 2 would still kill the target, rank 2 is cast instead. Never above the rank on the bar.
### How it works
Every bar and key binding ends in `UseAction(slot)`. The module hooks that function, reads the spell and rank in the slot from a tooltip scan, and decides. Damage per rank is the **minimum** damage from the spell's tooltip ("86 to 98 Holy damage"), raised by the smallest full hit you have actually landed with that rank (crits and partial resists are not counted). A rank qualifies if its minimum damage covers the target's remaining health plus the *Safety margin* (default 20 %). Macros with `/cast` are not affected.
The target's remaining health comes from the first source that answers:
|Source|When|Accuracy|
|-|-|-|
|Server|`UnitHealthMax("target")` is not 100, i.e. the server sends real values (Turtle WoW and others)|exact|
|MobHealth3|`MobHealth3:GetUnitHealth()` knows the mob|as good as that addon|
|MobInfo-2|`MobHealth_GetTargetCurHP()` knows the mob|as good as that addon|
|Own estimate|The addon has fought this mob type (name and level) before|see below|
**Own estimate.** On percent-only servers the addon learns *health points per percent* for each mob type: it adds up the damage it can see in the combat log (yours, your party's, pets') and divides by the drop in the target's percentage, but only once the drop reaches 3 % to keep the 1 % rounding out of it. Of all measurements the **highest** is kept, because damage from raid members outside your party is invisible and would otherwise pull the estimate down; erring high only costs a slightly bigger rank. The remaining health is then the upper edge of the current percent times that value. The estimate is saved per character in `HealBox.MobHP` and improves with every fight; until a type has been measured, Smart Damage leaves that target alone.
### Section *Smart Damage* on the *Extras* tab
|Setting|Effect|
|-|-|
|Smart Damage|Master switch. Off by default|
|Safety margin|0 to 50 %, default 20. Covers partial resists and estimate error|
|Target health source|Live line: which of the four sources answers for your current target|
|Spells|The attack spells found in your spellbook with their rank count|
Spells per class: Priest Smite, Holy Fire, Mind Blast; Druid Wrath, Starfire, Moonfire; Shaman Lightning Bolt, Chain Lightning, Earth/Flame/Frost Shock; Paladin Holy Shock, Hammer of Wrath, Exorcism, Holy Wrath; Mage Fireball, Frostbolt, Fire Blast, Scorch, Pyroblast; Warlock Shadow Bolt, Searing Pain, Immolate, Soul Fire, Conflagrate; Hunter Arcane Shot, Aimed Shot (list `FBDamageSpells`). `/fbp damage` toggles, `/fbp debug` logs every decision with the health source used.
\---
@@ -173,6 +243,8 @@ The window has two tabs.
**Show N buttons**: how many of the ten buttons actually appear (0 to 10). Assigned but hidden buttons keep their spell.
**Smart Healing / Safety margin**: see [The buttons](#the-buttons). Off by default; margin 0 to 50 %, default 20.
### Tab *General*
**Frame scale**: scales the plates from 0.6 to 1.5. Has no effect in party-frame mode.
@@ -191,6 +263,8 @@ The window has two tabs.
**Line of sight**: shows or hides the eye badge for units out of line of sight (see [Line of sight](#line-of-sight)). On by default.
**Mark who is attacked**, **HoT and shield timers**, **Cooldowns on buttons**, **Buff icons left of the bar**: the indicators described under [The buttons](#the-buttons) and [Who is being attacked](#who-is-being-attacked). All on by default.
**Left click on plate / Right click on plate**: what a click on a name plate does: Target (default), Unit menu, Move display, Nothing. See [Clicking a plate](#clicking-a-plate).
**Test mode**: fills slots 2 to 10 with ghost players and pets so you can arrange the display without a group (see [Test mode](#test-mode)). Not saved; always off after login.
@@ -219,13 +293,13 @@ The ghosts are built to show every visual state at once:
|Ghost|Shows|
|-|-|
|Brynn|warrior: no mana bar, health near full, **missing buff** (orange border when a buff watch is set)|
|Brynn|warrior: **dead**, no mana bar, **missing buff** (orange border when a buff watch is set)|
|Cerys|warlock: mana bar, an **absorb shield** segment and the **line-of-sight** badge|
|Dorn|hunter: low health (red) with **incoming healing** behind it|
|Elowen|druid: a **dispellable debuff** (the health bar takes the dispel colour of your class) and **out of range** (faded)|
|Fang, Zorbek, Bramble|pets: a focus pet without mana bar, a mana pet with one, and a pet with incoming healing|
|Fang, Zorbek, Bramble|pets: a focus pet without mana bar, a mana pet with one, and a pet with incoming healing and a **disease** debuff|
Their health drifts slowly up and down so you can watch the yellow and red thresholds come and go. Spell buttons on ghosts do nothing except print a short note; tooltips work. The ghost table is `FBTestGhosts` near the top of the file; names, values and which extras each ghost carries can be edited freely.
Their health drifts slowly up and down so you can watch the yellow and red thresholds come and go. Dorn also carries the red attacked border, sample timers on buttons 1 and 2 and **six buff icons** with different remaining times (one without a clock). Spell buttons on ghosts do nothing except print a short note; tooltips work. The ghost table is `FBTestGhosts` near the top of the file; names, values and which extras each ghost carries can be edited freely.
Test mode is meant for the addon's own plates; in party-frame mode the ghosts sit at Blizzard's (hidden) frames and are of little use.
@@ -325,8 +399,11 @@ On receive, your own messages are filtered by sender name, and since HealComm st
|`/fbp reset`|Discards all learned values and re-reads the tooltips|
|`/fbp test`|Toggles test mode (same as the checkbox)|
|`/fbp config`|Opens or closes the options window (same as the minimap button)|
|`/fbp buffs`|Lists the tracked buff spells, their presence and remaining time on you, and your raw buff textures|
|`/fbp raid`|Toggles raid mode|
|`/fbp raidtest 20` · `40` · `off`|Raid test with 20 or 40 ghosts, or off|
|`/fbp ticker`|Toggles the mana ticker|
|`/fbp damage`|Toggles Smart Damage|
`/fbp` is the first place to look when something is not displayed: if a spell is not listed there, the tooltip parser did not recognise it; the display is not at fault.
@@ -344,7 +421,7 @@ Everything lives in the `HealBox` table, saved **per character**:
|`AttachMode`|0 = own plates, 1 = default party frames|
|`Active`|Display on/off (shift + left click on the minimap)|
|`HealComm`|1 = sync on, 0 = off|
|`Locale`|`deDE` or `enUS`|
|`Locale`|`deDE`, `enUS`, `esES`, `frFR` or `itIT`|
|`ButtonSpacing`|Gap between the buttons in px (0 to 20)|
|`RowSpacing`|Gap between the plates in px (0 to 20)|
|`ManaBar`|1 = mana strip on, 0 = off|
@@ -354,11 +431,15 @@ Everything lives in the `HealBox` table, saved **per character**:
|`DebuffIcon`|1 = debuff icon on, 0 = off|
|`LOSIcon`|1 = line-of-sight badge on, 0 = off|
|`PlateLeft` · `PlateRight`|Click action on a plate: `target`, `menu`, `move` or `none`|
|`SmartRank` · `SmartMargin`|Smart Healing on/off (default off) and safety margin in percent|
|`Cooldowns` · `AggroMark` · `SpellTimers` · `BuffIcons`|Cooldown sweep, red border for the attacked member, HoT/shield timers, buff icons left of the bar|
|`ClassColors`|1 = names in class colour, 0 = white|
|`RangeFade`|1 = fade plates out of range, 0 = off|
|`WatchBuff`|Spell name of the buff watch, or nil|
|`BuffWatchPets`|1 = buff watch also on pets, 0 = players only (default)|
|`PosX` · `PosY`|Top-left corner of the player plate in screen pixels|
|`Ticker`|Sub-table with the mana ticker settings (see [Mana ticker](#mana-ticker))|
|`Damage`|Sub-table with the Smart Damage settings; `MobHP` and `DmgMemory` hold the learned mob health and minimum damage values (see [Smart Damage](#smart-damage))|
|`Raid`|Sub-table with every raid-mode setting (see [Raid mode](#raid-mode)), including `PosX` / `PosY` of the grid|
|`PredictMemory`|Learned heal values per spell and rank|
@@ -381,12 +462,19 @@ Every knob is a global at the top of its own section and can be changed without
|`FBPET\_INDENT`|12|Indent (and width reduction) of pet plates|
|`FBPET\_ICON` · `FBPET\_ICON\_SIZE`|footprint · 12|Paw icon in front of pet names|
|`FBDEBUFF\_ICON\_SIZE`|14|Edge length of the debuff icon|
|`FBLOS\_ICON` · `FBLOS\_ICON\_SIZE`|Blind icon · 16|Line-of-sight badge|
|`FBLOS\_ICON\_X` · `FBLOS\_ICON\_Y`|6 · 0|Badge offset from the plate's left edge|
|`FBLOS\_ICON` · `FBLOS\_ICON\_SIZE`|Blind icon · 12|Line-of-sight badge|
|`FBLOS\_ICON\_X` · `FBLOS\_ICON\_Y`|-3 · 3|Badge offset from the plate's top-left corner|
|`FBLOS\_TIMEOUT`|8|Seconds a line-of-sight error stays marked without UnitXP|
|`FBNAME\_WIDTH\_FULL` · `FBNAME\_WIDTH\_ICON`|78 · 60|Width of the name box without / with a visible debuff icon|
|`FBRANGE\_ALPHA` · `FBRANGE\_INTERVAL`|0.5 · 0.5|Faded opacity and check interval of the range fading|
|`FBBUFF\_MISSING\_COLOR`|`{1, 0.5, 0, 1}`|Border colour when the watched buff is missing|
|`FBAGGRO\_COLOR`|`{1, 0.15, 0.15, 1}`|Border colour for the attacked member|
|`FBCD\_MIN\_DURATION`|2|Cooldowns shorter than this (the global cooldown) are not shown|
|`FBTIMER\_COLOR\_HOT` · `\_SHIELD` · `\_WS`|green · blue · red|Timer colours on the buttons|
|`FBBUFFICON\_SIZE` · `FBBUFFICON\_GAP` · `FBBUFFICON\_MAX` · `FBBUFFICON\_XOFF`|8 · 1 · 6 · -2|Buff icons left of the plate|
|`FBBUFFICON\_GREY`|0.55|Grey tint of elapsed quadrants when the client cannot desaturate|
|`FBNAME\_HEIGHT`|12|Height of the name box (one line, vertically centred)|
|`FBWEAKENED\_SOUL\_SEC`|15|Length of Weakened Soul|
|`FBClassColors`|(table)|Fallback class colours if the client has no `RAID\_CLASS\_COLORS`|
|`FBBuffWatchSpells` · `FBBuffAlternates`|(table)|Buffs offered per class, and which group version counts as the same|
|`FBPartyUnit` · `FBLayoutOrder`|(table)|The ten slots and their display order|
@@ -423,6 +511,10 @@ Every knob is a global at the top of its own section and can be changed without
|`FBHealBox\_UpdateDebuffIcon(frame, tex, count)`|Debuff icon and stack count|
|`FBHealBox\_CastOn(button, castString)`|Casts a button's spell (left or right) on its target|
|`FBHealBox\_DropSpell(btnIndex, side)` · `FBHealBox\_CursorSpell()`|Drag and drop from the spellbook|
|`FBHealBox\_SmartRank(castString, unit)`|Picks the rank to cast|
|`FBHealBox\_CheckAggroAll()` · `FBHealBox\_ApplyBorder(f)`|Red border for the attacked member; border precedence|
|`FBHealBox\_UpdateSpellTimers()` · `FBHealBox\_SpellTimerFor(...)`|HoT/shield timers on buttons|
|`FBHealBox\_UpdateButtonCooldown(b)` · `FBHealBox\_UpdateAllCooldowns()`|Cooldown sweep|
|`FBHealBox\_ShowTab(n)` · `FBHealBox\_ApplyRightClickLayout()`|Options tabs; show/hide the right-click column|
|`FBHealBox\_PlateMouseDown(f)` · `FBHealBox\_PlateMouseUp(f)` · `FBHealBox\_RunPlateAction(f, action)`|Click and drag on a plate|
|`FBHealBox\_RefreshAllBars()`|Updates all ten|
@@ -552,10 +644,12 @@ The preset lists, freely extensible in `Spell.Name`:
|Class|Spells|
|-|-|
|Priest|Renew · Flash Heal · Lesser Heal · Heal · Greater Heal · Binding Heal · Prayer of Healing · Prayer of Mending · Circle of Healing · Power Word: Shield · Abolish Disease · Cure Disease · Dispel Magic|
|Druid|Rejuvenation · Regrowth · Lifebloom · Healing Touch · Swiftmend · Remove Curse · Abolish Poison|
|Shaman|Lesser Healing Wave · Healing Wave · Chain Heal · Earth Shield · Cure Poison · Cure Disease|
|Paladin|Flash of Light · Holy Light · Holy Shock · Lay on Hands · Purify · Cleanse · Blessing of Protection|
|Priest|Renew · Flash Heal · Lesser Heal · Heal · Greater Heal · Binding Heal · Prayer of Healing · Prayer of Mending · Circle of Healing · Power Word: Shield · Abolish Disease · Cure Disease · Dispel Magic · Power Word: Fortitude · Prayer of Fortitude · Divine Spirit · Prayer of Spirit · Shadow Protection · Prayer of Shadow Protection · Fear Ward · Power Infusion · Resurrection|
|Druid|Healing Touch · Regrowth · Rejuvenation · Swiftmend · Tranquility · Lifebloom · Abolish Poison · Cure Poison · Remove Curse · Mark of the Wild · Gift of the Wild · Thorns · Innervate · Rebirth|
|Shaman|Lesser Healing Wave · Healing Wave · Chain Heal · Earth Shield · Water Shield · Cure Poison · Cure Disease · Purge · Ancestral Spirit · Water Walking · Water Breathing|
|Paladin|Flash of Light · Holy Light · Holy Shock · Lay on Hands · Cleanse · Purify · Blessing of Protection · Blessing of Freedom · Blessing of Sacrifice · Redemption · Divine Intervention · Blessing of Wisdom / Might / Kings / Salvation / Light / Sanctuary · Greater Blessing of Wisdom / Might / Kings / Salvation / Light / Sanctuary|
|Mage|Remove Lesser Curse · Arcane Intellect · Arcane Brilliance · Dampen Magic · Amplify Magic|
|Warlock|Unending Breath · Detect Invisibility · Detect Lesser / Greater Invisibility|
Entries that do not exist do no harm: if the spellbook scan does not find them, they are quietly skipped.
@@ -570,20 +664,23 @@ Entries that do not exist do no harm: if the spellbook scan does not find them,
* a custom cascading menu for spell and rank selection, because `UIDropDownMenu` provides no usable submenus in 1.12
* heal prediction for direct heals, remaining HoT ticks and absorb shields, self-correcting from the combat log
* HealComm sync with Puppeteer, pfUI, Luna and others, without any Ace libraries
* English / German localization, switchable in game
* English, German, Spanish, French and Italian localization, switchable in game
* 1.4.4.1: buff tracking & tooltip scanning fix (Divine Spirit clock icon), expanded class spell lists with blessings, buffs, utility and rez spells, group buff alternate tracking; see CHANGELOG
* 1.4.4: performance pass without functional change (central button states, display caches, shared aura scans, coalesced event bursts); see CHANGELOG
* 1.4.3: mana ticker module (2 s regeneration tick and five-second rule as a spark in the mana bar), Smart Damage module (rank selection for attack spells on any action bar), Smart Healing (auto-downrank, off by default), cooldown sweep on buttons, red border for the attacked member, HoT/shield timers on buttons
* 1.4.2: raid mode as a separate module (`FBHealBox_Raid.lua`) with a compact 20/40 grid, mini buttons, own options tab and raid test; hook interface in the core
* 1.4.1: pet plates, mana bar inside the health bar, adjustable button and row spacing, test mode with ghost players, saved plate position, class colours, buff watch, range fading, tabbed options, right-click spell, dead/ghost/offline text, debuff icon. See CHANGELOG.md.
\---
Heal Box Vanilla v1.4.2 · original by Dourd, UI Overhauled · ported to Vanilla and extended 09/2026 by Mquadrat
Heal Box Vanilla v1.4.4.1 · original by Dourd, UI Overhauled · ported to Vanilla and extended 09/2026 by Mquadrat
_______________________________________________________________________
GERMAN
# Heal Box Vanilla
ADDON-DOKUMENTATION · VERSION 1.4.2 · CLIENT 1.12.1
ADDON-DOKUMENTATION · VERSION 1.4.4.1 · CLIENT 1.12.1
Party-, Begleiter- und Selbst-Heilanzeige mit Schnellzugriff-Buttons für Heiler. Für jeden Gruppenplatz eine Namensplakette mit Lebensbalken, dazu eine für jeden Begleiter in der Gruppe direkt unter seinem Besitzer, daneben bis zu zehn frei belegbare Zauber-Buttons. Ein schmaler Manabalken liegt im Lebensbalken, bei allen, die tatsächlich Mana nutzen. Dazu eine vollständige Heilvorhersage (Direktheilung, HoT-Restticks und Absorb-Schilde), die sich über den Combatlog selbst korrigiert und ihre Werte im HealComm-Format mit anderen Heilern teilt. Die Oberfläche gibt es auf **Deutsch und Englisch**, umschaltbar im Optionsfenster.
@@ -593,13 +690,15 @@ Party-, Begleiter- und Selbst-Heilanzeige mit Schnellzugriff-Buttons für Heiler
## Installation
Der Ordner unter `Interface\\AddOns` heißt **`FBHealBox`** und enthält fünf Dateien:
Der Ordner unter `Interface\\AddOns` heißt **`FBHealBox`** und enthält sieben Dateien:
|Datei|Inhalt|
|-|-|
|`FBHealBox.toc`|Metadaten, Ladereihenfolge, SavedVariables|
|`FBHealBox.lua`|Der Kern: Gruppen- und Begleiteranzeige, Vorhersage, HealComm, Optionen|
|`FBHealBox_Raid.lua`|Das Raidmodus-Modul (siehe [Raidmodus](#raidmodus)). Aus der `.toc` entfernt, läuft der Kern genau wie in 1.4.1|
|`FBHealBox_Ticker.lua`|Das Mana-Ticker-Modul (siehe [Mana-Ticker](#mana-ticker)), ebenfalls entfernbar|
|`FBHealBox_Damage.lua`|Das Smart-Damage-Modul (siehe [Smart Damage](#smart-damage)), ebenfalls entfernbar|
|`FBHealBox.xml`|Nur der Event-Frame, der `FBHealBox\_OnLoad` und `FBHealBox\_OnEvent` aufruft|
|`CHANGELOG.md`|Was sich in welcher Version geaendert hat|
@@ -621,9 +720,13 @@ Gestapelt wird in der Reihenfolge **Besitzer, darunter sein Begleiter**: du, dei
Ein **Linksklick** auf Name oder Lebensbalken visiert die Einheit an; ein **Rechtsklick** ebenfalls. Beides ist im Reiter *Allgemein* belegbar (*Linksklick auf Plakette* / *Rechtsklick auf Plakette*): **Anvisieren**, **Einheitenmenü** (Blizzards eigenes Menü mit Flüstern, Einladen, Befördern, Verlassen, für Spieler und den eigenen Begleiter; Gruppen-Begleiter fallen auf Anvisieren zurück), **Anzeige verschieben** (Ziehen ohne Shift) oder **Nichts**. Shift + Linksklick ziehen verschiebt die Anzeige immer, egal was eingestellt ist. Im Testmodus geben Klicks auf Geister nur eine Chatmeldung.
### Wer wird angegriffen
Hat dein aktuelles feindliches Ziel ein Gruppenmitglied im Ziel, bekommt dessen Plakette (und Raid-Zelle) einen **roten Rahmen**, fünfmal je Sekunde aus `targettarget` aufgefrischt. Rot hat Vorrang vor dem orangen Buff-Wache-Rahmen. Option *Angegriffenen markieren*.
### Sichtlinie
Solange eine Einheit außerhalb deiner Sichtlinie ist, hängt ein **Augen-Abzeichen** (`FBLOS\_ICON`, das Blenden-Icon) am linken Rand ihrer Plakette. Vanilla hat dafür keine API, deshalb zwei Wege: Ist der Client-Mod **UnitXP SP3** installiert, fragt das Addon alle halbe Sekunde `UnitXP("inSight", "player", unit)` ab, live und exakt. Ohne ihn achtet das Addon auf die Fehlermeldung *nicht in Sichtlinie* nach einer eigenen Heilung und markiert die Einheit, die du heilen wolltest, für `FBLOS\_TIMEOUT` (8) Sekunden; die Markierung verschwindet, sobald ein Cast auf sie startet oder eine eigene Heilung bzw. ein HoT-Tick dort ankommt. `/fbp` zeigt, welcher Weg aktiv ist. Option *Sichtlinie*; Position des Abzeichens über `FBLOS\_ICON\_X/Y`.
Solange eine Einheit außerhalb deiner Sichtlinie ist, sitzt ein **Augen-Abzeichen** (`FBLOS\_ICON`, das Blenden-Icon) auf der linken oberen Ecke ihrer Plakette. Vanilla hat dafür keine API, deshalb zwei Wege: Ist der Client-Mod **UnitXP SP3** installiert, fragt das Addon alle halbe Sekunde `UnitXP("inSight", "player", unit)` ab, live und exakt. Ohne ihn achtet das Addon auf die Fehlermeldung *nicht in Sichtlinie* nach einer eigenen Heilung und markiert die Einheit, die du heilen wolltest, für `FBLOS\_TIMEOUT` (8) Sekunden; die Markierung verschwindet, sobald ein Cast auf sie startet oder eine eigene Heilung bzw. ein HoT-Tick dort ankommt. `/fbp` zeigt, welcher Weg aktiv ist. Option *Sichtlinie*; Position des Abzeichens über `FBLOS\_ICON\_X/Y`.
### Begleiter
@@ -665,10 +768,18 @@ Die Schwellwerte für die HP-Farben stehen als `LowHP` (0.6) und `VeryLowHP` (0.
Rechts neben jeder Plakette liegen bis zu zehn Buttons, jeder mit dem Icon seines Zaubers. Ein **Linksklick** wirkt den Zauber auf genau dieses Gruppenmitglied, unabhängig davon, wen du gerade im Ziel hast.
**Belegen per Drag & Drop.** Zauberbuch öffnen, einen Zauber ziehen und auf einen beliebigen Heil-Button, einen Raid-Mini-Button oder ein Feld im Reiter *Buttons* fallen lassen; diese Buttonnummer übernimmt den Zauber auf allen Plaketten. Ist der Rechtsklick-Zauber aktiv, füllt ein Ablegen mit der rechten Maustaste die Rechtsklick-Seite. Auch Zauber außerhalb der Klassenliste (etwa Magie bannen) werden angenommen und behalten ihr Icon über einen Reload. Vanilla hat kein `GetCursorInfo()`, deshalb merkt sich das Addon, was `PickupSpell` zuletzt an den Cursor gehängt hat.
**Belegen per Drag & Drop.** Zauberbuch öffnen, einen Zauber ziehen und auf einen beliebigen Heil-Button, einen Raid-Mini-Button oder ein Feld im Reiter *Buttons* fallen lassen; diese Buttonnummer übernimmt den Zauber auf allen Plaketten. Ist der Rechtsklick-Zauber aktiv, füllt ein Ablegen mit der rechten Maustaste oder mit gehaltener Shift-Taste die Rechtsklick-Seite. Auch Zauber außerhalb der Klassenliste (etwa Magie bannen) werden angenommen und behalten ihr Icon über einen Reload. Vanilla hat kein `GetCursorInfo()`, deshalb merkt sich das Addon, was `PickupSpell` zuletzt an den Cursor gehängt hat.
**Rechtsklick-Zauber (optional).** Jeder Button kann einen zweiten Zauber für **Rechtsklick** tragen (etwa Blitzheilung links, Große Heilung rechts), was die Anzeige verdichtet, ohne Buttons hinzuzufügen. Das ist standardmäßig aus und wird ausschließlich über den Schalter im Reiter *Buttons* eingeschaltet. Eingeschaltet erscheint eine zweite Spalte in der Belegung, und ein kleines Icon unten rechts auf jedem Button zeigt seinen Rechtsklick-Zauber; der Tooltip nennt ihn ebenfalls. Ausschalten behält die Belegung, die Buttons reagieren nur nicht mehr auf Rechtsklick.
**Smart Healing (standardmäßig aus).** Mit *Smart Healing* wirkt ein Klick den niedrigsten Rang des belegten Zaubers, dessen erwartete Heilung das fehlende Leben des Ziels abzüglich schon eingehender Heilung plus *Sicherheitsaufschlag* (Standard 20 %) deckt. Die erwartete Heilung stammt aus den gelernten Werten der Vorhersage, wo vorhanden, sonst aus dem Tooltip-Mittelwert. Nie über dem belegten Rang, nur für Direktheilungen (HoTs, Schilde und Buffs wie Seelenstärke gehen immer wie belegt raus; ein Zauber kommt nur in Frage, wenn sein Tooltip eine Heilung beschreibt), und unter 30 % Leben immer der belegte Rang. Nachteile: Die Schätzung kennt keine Crits, und bei Schadensspitzen oder gewolltem Überheilen (Tank vor einem großen Treffer) kann der kleinere Rang zu wenig sein. Aus lassen, wann immer Overheal gewollt ist. `/fbp debug` zeigt jede Entscheidung, `/fbp` den Zustand.
**Cooldowns.** Jeder Button zeigt die gewohnte Cooldown-Uhr seines Zaubers. Der globale Cooldown wird nicht gezeigt (`FBCD\_MIN\_DURATION`). Option *Cooldowns auf den Buttons*.
**HoT- und Schild-Timer.** Der Button eines Zaubers zeigt für seine Einheit die Restsekunden deines eigenen HoTs (grün) oder Schilds (blau) dieses Zaubers. Ist Machtwort: Schild verbraucht, zeigt derselbe Button rot die Geschwächte Seele, bis das Ziel wieder schildbar ist. Geprüft wird zuerst der Linksklick-Zauber, dann der Rechtsklick-Zauber. Nur eigene Effekte werden verfolgt. Option *HoT- und Schild-Timer*.
**Buff-Icons links am Balken.** Buffs mit Laufzeit, die auf deinen Buttons liegen (Seelenstärke, Göttlicher Willen, Furchtzauberschutz, Inneres Feuer, links oder rechts), erscheinen als kleine 8-px-Icons **außen links** neben der Plakette, solange der Buff wirkt, in Zweierstapeln: das erste oben an der Platte, das zweite darunter, das dritte oben in der nächsten Spalte links, und so weiter. Das Icon ist eine **Uhr**: Wie ein Minutenzeiger wird es mit ablaufender Zeit im Uhrzeigersinn ab zwölf Uhr schwarz-weiß. Frischer Buff: ganz farbig. Halbe Zeit übrig: rechte Hälfte grau. Ein Viertel übrig: drei Viertel grau. Vanilla kann keinen echten Kreis-Sweep zeichnen, deshalb ist das Icon in vier Quadranten geteilt; bei 8 px ist das die feinste Auflösung, die noch lesbar ist. Abgelaufene Quadranten werden dunkel gezeichnet (entsättigt, wo der Client das kann, mit dunkler Wäsche `FBBUFFICON\_WASH`) auf einem kleinen Frame eine Ebene über dem Icon, damit die Teilung auf jedem Client sichtbar ist; `/fbp buffs` nennt außerdem je Icon die Zahl grauer Quadranten. Mouseover auf ein Icon zeigt Buffname und Restzeit. Die Restzeit ist bei dir selbst exakt (Buff-API, bei jedem `PLAYER_AURAS_CHANGED` und dazwischen einmal je Sekunde neu gelesen, weil ein Refresh eines laufenden Buffs kein Event feuert; ein Neucast stellt die Uhr also zurück) und wird bei anderen ab deinem eigenen Cast gezählt; ein fremd gewirkter Buff bleibt ganz farbig (Zeit unbekannt). Bis zu sechs Icons je Plakette (`FBBUFFICON\_MAX`). Option *Buff-Icons links am Balken*, standardmäßig an. Die Raid-Zellen zeigen dieselben Uhr-Icons außen an ihrer linken Kante mit 6 px in einem 3-mal-4-Raster (zwölf Plätze, Spalten füllen sich von der Zelle nach außen), das Raster hält zwischen den Gruppen Platz dafür frei. Nach einem `/reload` oder Gruppenwechsel scannt das Addon alle Einheiten einmal aktiv, damit vorhandene Buffs sofort erscheinen und nicht erst beim nächsten Aura-Event. `/fbp buffs` listet die verfolgten Buffs und ihren Zustand auf dir.
**Tot, Geist, Offline.** Statt `0 %` zeigt der Balken *Tot*, *Geist* oder *Offline*, leer und grau, ohne Manastreifen.
**Debuff-Icon.** Trägt eine Einheit einen Debuff, den deine Klasse entfernen kann, erscheint sein Icon rechts neben dem Namen mit Stackzahl (ab 2). Der Lebensbalken nimmt wie bisher die Debuff-Farbe an.
@@ -696,7 +807,7 @@ Der Raidmodus ist ein Addon im Addon: Er lebt in der eigenen Datei `FBHealBox_Ra
Erreicht der Raid die Schwelle, erscheint ein kompaktes **Raster**: eine **Zelle** je Mitglied, in **Fünferblöcken** nach Gruppe geordnet, die Blöcke in Zeilen. Jede Zelle (standardmäßig 70 mal 22 px) zeigt den Namen in Klassenfarbe, den Lebensbalken mit denselben Schild- und Vorhersage-Schichten wie die Plaketten, einen 3 px hohen Manastreifen bei Mana-Nutzern, die Lebensprozente oder die fehlenden Lebenspunkte, die Dispel-Färbung, *Tot* / *Geist* / *Offline*, das Reichweiten-Fading, das Sichtlinien-Auge und den orangen Buff-Wache-Rahmen. Rechts an jeder Zelle sitzen bis zu vier **Mini-Buttons**, die die Zauber von Button 1 bis N aus dem Reiter *Buttons* wirken, Rechtsklick-Zauber inklusive. Ein Klick auf die Zelle selbst tut, was *Linksklick auf Plakette* / *Rechtsklick auf Plakette* vorgibt (Standard Anvisieren; das Einheitenmenü kommt aus Blizzards Raid-Fenster).
Die Gruppenblöcke stehen mit *Gruppen je Zeile* (Standard 4) im Raster: Ein 40er-Raid sind zwei Zeilen zu vier Blöcken, etwa 450 mal 270 px bei zwei Buttons je Zelle. **Leere Gruppen brauchen keinen Platz**, ein 20er-Raid ist also eine einzige Zeile. Das ganze Raster skaliert unabhängig von den Plaketten und merkt sich seine Position.
Die Gruppenblöcke stehen mit *Gruppen je Zeile* (Standard 4) im Raster: Ein 40er-Raid sind zwei Zeilen zu vier Blöcken, etwa 570 mal 270 px bei zwei Buttons je Zelle und dem Streifen für die Buff-Icons. **Leere Gruppen brauchen keinen Platz**, ein 20er-Raid ist also eine einzige Zeile. Das ganze Raster skaliert unabhängig von den Plaketten und merkt sich seine Position.
Solange das Raster zu sehen ist, werden die fünf Gruppenplaketten ausgeblendet (Option *Gruppenplaketten im Raid ausblenden*), damit der Bildschirm nie doppelt belegt ist.
@@ -711,8 +822,9 @@ Solange das Raster zu sehen ist, werden die fünf Gruppenplaketten ausgeblendet
|Manastreifen|3 px Manabalken in der Zelle. An|
|Leere Gruppen ausblenden|Gruppen ohne Mitglieder fallen weg. An|
|Titelleiste|Schmale Leiste über dem Raster zum Ziehen. Ohne sie: Shift + eine beliebige Zelle ziehen. An|
|Buffs anzeigen|Buff-Icons mit Uhr links an jeder Zelle. Aus: keine Icons und kein reservierter Streifen, die Gruppen rücken enger zusammen. An|
|HP-Text|keiner, Prozent oder Defizit (fehlende Lebenspunkte als negative Zahl)|
|Raid-Test|Füllt das Raster mit 20 oder 40 Geistern: tot, offline, Geist, Debuff, außer Reichweite, außer Sicht, fehlender Buff, Schild und eingehende Heilung sind alle vertreten. Wird nicht gespeichert|
|Raid-Test|Füllt das Raster mit 20 oder 40 Geistern: tot, offline, Geist, Magie- und Krankheits-Debuff, außer Reichweite, außer Sicht, fehlender Buff, Schild, eingehende Heilung und Buff-Icons (jeder dritte Geist, Geist 12 mit zwölf) sind alle vertreten. Wird nicht gespeichert|
|Gruppen je Zeile|1 bis 8. Standard 4|
|Raid-Skalierung|0.5 bis 1.5|
|Zellenbreite / -höhe|50 bis 120 px / 14 bis 32 px|
@@ -724,7 +836,61 @@ Alle Raid-Einstellungen liegen in `HealBox.Raid`. `/fbp raidtest 20`, `/fbp raid
### Wie er andockt
Der Kern bietet `FBHealBox\_RegisterHook(name, fn)` und ruft die Hooks an festen Stellen: `Defaults`, `SyncOptions`, `ApplyLocale`, `UpdateNames`, `RefreshAllBars`, `ButtonsChanged`, `ActiveToggle`, `Status` und `Slash`. `FBHealBox\_AddOptionsTab(labelKey)` legt einen Reiter im Optionsfenster an. Das Raid-Modul hängt sein Roster-Update an `UpdateNames`, sein Zellen-Update an `RefreshAllBars` (so erreicht die Heilvorhersage die Raid-Zellen ohne Zusatzaufwand) und seine Slash-Befehle an `Slash`. Die Raid-Einheiten werden in `FBPredictUnits` eingetragen, damit die HoT- und Schild-Bestätigung über `UNIT\_AURA` auch für `raid1` bis `raid40` greift. Begleiter werden im Raidmodus nicht angezeigt.
Der Kern bietet `FBHealBox\_RegisterHook(name, fn)` und ruft die Hooks an festen Stellen: `Defaults`, `SyncOptions`, `ApplyLocale`, `UpdateNames`, `RefreshAllBars`, `ButtonsChanged`, `ActiveToggle`, `Status`, `Slash`, `Loaded`, `Aggro`, `SpellTimers` und `Cooldowns`. `FBHealBox\_AddOptionsTab(labelKey)` legt einen Reiter im Optionsfenster an. Das Raid-Modul hängt sein Roster-Update an `UpdateNames`, sein Zellen-Update an `RefreshAllBars` (so erreicht die Heilvorhersage die Raid-Zellen ohne Zusatzaufwand) und seine Slash-Befehle an `Slash`. Die Raid-Einheiten werden in `FBPredictUnits` eingetragen, damit die HoT- und Schild-Bestätigung über `UNIT\_AURA` auch für `raid1` bis `raid40` greift. Begleiter werden im Raidmodus nicht angezeigt.
\---
## Mana-Ticker
Der Mana-Ticker lebt im eigenen Modul `FBHealBox_Ticker.lua` und ist standardmäßig an. Ein **Funke** wandert alle 2 Sekunden über den Manastreifen deiner eigenen Plakette (und deiner eigenen Zelle im Raid-Raster), im Takt des Regenerationsticks des Servers. Gibst du Mana aus, wird der Funke **orange** und läuft die Fünf-Sekunden-Regel herunter; er erreicht das Ende genau dann, wenn die Willenskraft-Regeneration wieder einsetzt, also am ersten Tick nach den fünf Sekunden, nicht nach den fünf Sekunden selbst. Wer direkt nach dem Funken am Ende castet, verschenkt keine Regeneration.
### Wie er arbeitet
In 1.12 gibt es dafür keine API, deshalb beobachtet das Modul `UNIT\_MANA` für den Spieler. Sinkendes Mana startet die Fünf-Sekunden-Regel. Steigendes Mana ist ein Tick-Kandidat: Der erste setzt das 2-s-Raster, jeder spätere, der innerhalb der *Tick-Toleranz* um den erwarteten Zeitpunkt liegt, synchronisiert es neu. Kandidaten außerhalb der Toleranz (Manaquelle-Pulse, Anregen) werden ignoriert; erst nach drei Fehlschlägen in Folge gilt ein neues Raster. Sprünge ab 300 Mana und mehr als dem Vierfachen der gelernten Tickgröße (Tränke, Runen) zählen nie. Bei vollem Mana kommen keine Ticks, das Raster läuft dann still weiter und der Funke bleibt ausgeblendet, bis wieder Mana fehlt; am nächsten echten Tick synchronisiert er sich neu. Der Funke braucht den Manastreifen (Option *Manabalken*) und verschwindet in Bären- oder Katzengestalt.
### Reiter *Extras*, Abschnitt *Mana-Ticker*
|Einstellung|Wirkung|
|-|-|
|Mana-Ticker|Hauptschalter. Standardmäßig an|
|Fünf-Sekunden-Regel|Orange Phase nach Manaverbrauch anzeigen. An|
|Tick-Toleranz|0.1 bis 0.6 s. Erhöhen, wenn Ticks verpasst werden|
|Tick-Vorlauf|0.0 bis 0.5 s. Lässt den Funken früher loslaufen, um Latenz auszugleichen, falls er später ankommt als deine Manasprünge|
|Funkenbreite|1 bis 4 px|
`/fbp ticker` schaltet den Ticker um; `/fbp` meldet, ob das Raster synchron ist, die Zeit bis zum nächsten Tick und eine laufende Fünf-Sekunden-Regel. Einstellungen liegen in `HealBox.Ticker`. Der Ticker teilt sich den Reiter *Extras* mit Smart Damage.
\---
## Smart Damage
Rangwahl für **Angriffszauber auf jeder Aktionsleiste**, im eigenen Modul `FBHealBox_Damage.lua`, **standardmäßig aus**. Drückst du Göttliche Pein Rang 4 auf einer Bongos- oder Blizzard-Leiste (oder deren Tastenkürzel) und Rang 2 würde das Ziel noch töten, wird Rang 2 gewirkt. Nie über dem Rang auf der Leiste.
### Wie es arbeitet
Jede Leiste und jedes Tastenkürzel endet in `UseAction(slot)`. Das Modul hängt sich davor, liest Zauber und Rang im Slot per Tooltip-Scan und entscheidet. Der Schaden je Rang ist der **Mindestschaden** aus dem Tooltip („86 to 98 Holy damage"), angehoben durch den kleinsten Volltreffer, den du mit diesem Rang tatsächlich gelandet hast (Crits und Teilwiderstände zählen nicht). Ein Rang kommt in Frage, wenn sein Mindestschaden das Restleben des Ziels plus *Sicherheitsaufschlag* (Standard 20 %) deckt. Makros mit `/cast` bleiben unberührt.
Das Restleben des Ziels liefert die erste Quelle, die antwortet:
|Quelle|Wann|Genauigkeit|
|-|-|-|
|Server|`UnitHealthMax("target")` ist nicht 100, der Server sendet also echte Werte (Turtle WoW und andere)|exakt|
|MobHealth3|`MobHealth3:GetUnitHealth()` kennt den Mob|so gut wie dieses Addon|
|MobInfo-2|`MobHealth_GetTargetCurHP()` kennt den Mob|so gut wie dieses Addon|
|Eigene Schätzung|Das Addon hat diesen Mobtyp (Name und Stufe) schon einmal bekämpft|siehe unten|
**Eigene Schätzung.** Auf reinen Prozent-Servern lernt das Addon je Mobtyp die *Lebenspunkte je Prozent*: Es summiert den Schaden, den es im Combatlog sieht (deinen, den deiner Gruppe, den der Begleiter), und teilt durch den Prozentabfall des Ziels, aber erst ab 3 % Abfall, damit die Rundung auf ganze Prozent nicht stört. Von allen Messungen bleibt die **höchste**, weil Schaden von Raidmitgliedern außerhalb deiner Gruppe unsichtbar ist und die Schätzung sonst nach unten zöge; ein zu hoher Wert kostet nur einen etwas größeren Rang. Das Restleben ist dann die Obergrenze des aktuellen Prozentwerts mal diesem Faktor. Die Schätzung wird je Charakter in `HealBox.MobHP` gespeichert und wird mit jedem Kampf besser; bis ein Typ vermessen ist, lässt Smart Damage dieses Ziel in Ruhe.
### Abschnitt *Smart Damage* im Reiter *Extras*
|Einstellung|Wirkung|
|-|-|
|Smart Damage|Hauptschalter. Standardmäßig aus|
|Sicherheitsaufschlag|0 bis 50 %, Standard 20. Deckt Teilwiderstände und Schätzfehler|
|Lebensquelle des Ziels|Livezeile: welche der vier Quellen für dein aktuelles Ziel antwortet|
|Zauber|Die im Zauberbuch gefundenen Angriffszauber mit Rangzahl|
Zauber je Klasse: Priester Göttliche Pein, Heiliges Feuer, Gedankenschlag; Druide Zorn, Sternenfeuer, Mondfeuer; Schamane Blitzschlag, Kettenblitzschlag, Erd-/Flammen-/Frostschock; Paladin Heiliger Schock, Hammer des Zorns, Exorzismus, Heiliger Zorn; Magier Feuerball, Frostblitz, Feuerschlag, Versengen, Pyroschlag; Hexenmeister Schattenblitz, Sengender Schmerz, Feuerbrand, Seelenfeuer, Feuersbrunst; Jäger Arkaner Schuss, Gezielter Schuss (Liste `FBDamageSpells`). `/fbp damage` schaltet um, `/fbp debug` protokolliert jede Entscheidung mit der genutzten Lebensquelle.
\---
@@ -748,6 +914,8 @@ Das Fenster hat zwei Reiter.
**N Buttons anzeigen**: wie viele der zehn Buttons tatsächlich erscheinen (0 bis 10). Belegte, aber ausgeblendete Buttons behalten ihre Zuordnung.
**Smart Healing / Sicherheitsaufschlag**: siehe [Die Buttons](#die-buttons). Standardmäßig aus; Aufschlag 0 bis 50 %, Standard 20.
### Reiter *Allgemein*
**Skalierung**: Skalierung der Plaketten von 0.6 bis 1.5. Wirkt nicht im Party-Frame-Modus.
@@ -766,6 +934,8 @@ Das Fenster hat zwei Reiter.
**Sichtlinie**: Augen-Abzeichen für Einheiten außerhalb der Sichtlinie an/aus (siehe [Sichtlinie](#sichtlinie)). Standardmäßig an.
**Angegriffenen markieren**, **HoT- und Schild-Timer**, **Cooldowns auf den Buttons**, **Buff-Icons links am Balken**: die Anzeigen aus [Die Buttons](#die-buttons) und [Wer wird angegriffen](#wer-wird-angegriffen). Alle standardmäßig an.
**Linksklick auf Plakette / Rechtsklick auf Plakette**: was ein Klick auf eine Plakette tut: Anvisieren (Standard), Einheitenmenü, Anzeige verschieben, Nichts. Siehe [Klick auf eine Plakette](#klick-auf-eine-plakette).
**Testmodus**: füllt die Plätze 2 bis 10 mit Geisterspielern und -begleitern, damit du die Anzeige ohne Gruppe einrichten kannst (siehe [Testmodus](#testmodus)). Wird nicht gespeichert; nach dem Login immer aus.
@@ -792,13 +962,13 @@ Die Geister sind so gebaut, dass jeder Anzeigezustand gleichzeitig zu sehen ist:
|Geist|Zeigt|
|-|-|
|Brynn|Krieger: keinen Manabalken, Leben fast voll, **fehlender Buff** (oranger Rahmen, sobald eine Buff-Wache gesetzt ist)|
|Brynn|Krieger: **tot**, kein Manabalken, **fehlender Buff** (oranger Rahmen, sobald eine Buff-Wache gesetzt ist)|
|Cerys|Hexenmeister: Manabalken, einen **Absorb-Schild**-Anteil und das **Sichtlinien**-Abzeichen|
|Dorn|Jäger: wenig Leben (rot) mit **eingehender Heilung** dahinter|
|Elowen|Druide: einen **entfernbaren Debuff** (der Lebensbalken nimmt die Dispel-Farbe deiner Klasse an) und **außer Reichweite** (abgeblendet)|
|Fang, Zorbek, Bramble|Begleiter: ein Fokus-Pet ohne Manabalken, ein Mana-Pet mit, ein Pet mit eingehender Heilung|
|Fang, Zorbek, Bramble|Begleiter: ein Fokus-Pet ohne Manabalken, ein Mana-Pet mit, ein Pet mit eingehender Heilung und **Krankheits**-Debuff|
Ihre HP wandern langsam auf und ab, damit man die gelbe und rote Schwelle kommen und gehen sieht. Zauber-Buttons auf Geistern tun nichts außer einer kurzen Chatmeldung; Tooltips funktionieren. Die Geistertabelle heißt `FBTestGhosts` und steht am Dateianfang; Namen, Werte und Extras lassen sich frei ändern.
Ihre HP wandern langsam auf und ab, damit man die gelbe und rote Schwelle kommen und gehen sieht. Dorn trägt zusätzlich den roten Angriffsrahmen, Beispiel-Timer auf den Buttons 1 und 2 und **sechs Buff-Icons** mit unterschiedlicher Restzeit (eines ohne Uhr). Zauber-Buttons auf Geistern tun nichts außer einer kurzen Chatmeldung; Tooltips funktionieren. Die Geistertabelle heißt `FBTestGhosts` und steht am Dateianfang; Namen, Werte und Extras lassen sich frei ändern.
Der Testmodus ist für die eigenen Plaketten gedacht; im Party-Frame-Modus sitzen die Geister an Blizzards (ausgeblendeten) Frames und nützen wenig.
@@ -898,8 +1068,11 @@ Beim Empfang werden eigene Nachrichten über den Absendernamen gefiltert, und da
|`/fbp reset`|Verwirft alle gelernten Werte und liest die Tooltips neu ein|
|`/fbp test`|Testmodus an/aus (wie der Haken in den Optionen)|
|`/fbp config`|Optionsfenster auf/zu (wie der Minimap-Button)|
|`/fbp buffs`|Listet die verfolgten Buff-Zauber, ihre Präsenz und Restzeit auf dir sowie deine rohen Buff-Texturen|
|`/fbp raid`|Raidmodus an/aus|
|`/fbp raidtest 20` · `40` · `off`|Raid-Test mit 20 oder 40 Geistern, oder aus|
|`/fbp ticker`|Mana-Ticker an/aus|
|`/fbp damage`|Smart Damage an/aus|
`/fbp` ist die erste Anlaufstelle, wenn etwas nicht angezeigt wird: Steht ein Zauber dort nicht drin, hat der Tooltip-Parser ihn nicht erkannt und nicht die Anzeige versagt.
@@ -917,7 +1090,7 @@ Alles liegt in der Tabelle `HealBox`, gespeichert **pro Charakter**:
|`AttachMode`|0 = eigene Plaketten, 1 = Standard-Gruppenfenster|
|`Active`|Anzeige ein/aus (Shift + Linksklick auf die Minimap)|
|`HealComm`|1 = Sync an, 0 = aus|
|`Locale`|`deDE` oder `enUS`|
|`Locale`|`deDE`, `enUS`, `esES`, `frFR` oder `itIT`|
|`ButtonSpacing`|Abstand der Buttons in px (0 bis 20)|
|`RowSpacing`|Abstand der Plaketten in px (0 bis 20)|
|`ManaBar`|1 = Manastreifen an, 0 = aus|
@@ -927,11 +1100,15 @@ Alles liegt in der Tabelle `HealBox`, gespeichert **pro Charakter**:
|`DebuffIcon`|1 = Debuff-Icon an, 0 = aus|
|`LOSIcon`|1 = Sichtlinien-Abzeichen an, 0 = aus|
|`PlateLeft` · `PlateRight`|Klickaktion auf einer Plakette: `target`, `menu`, `move` oder `none`|
|`SmartRank` · `SmartMargin`|Smart Healing an/aus (Standard aus) und Sicherheitsaufschlag in Prozent|
|`Cooldowns` · `AggroMark` · `SpellTimers` · `BuffIcons`|Cooldown-Uhr, roter Rahmen für den Angegriffenen, HoT/Schild-Timer, Buff-Icons links am Balken|
|`ClassColors`|1 = Namen in Klassenfarbe, 0 = weiß|
|`RangeFade`|1 = Plaketten außer Reichweite abblenden, 0 = aus|
|`WatchBuff`|Zaubername der Buff-Wache oder nil|
|`BuffWatchPets`|1 = Buff-Wache auch für Pets, 0 = nur Spieler (Standard)|
|`PosX` · `PosY`|Linke obere Ecke der Spielerplakette in Bildschirmpixeln|
|`Ticker`|Untertabelle mit den Mana-Ticker-Einstellungen (siehe [Mana-Ticker](#mana-ticker))|
|`Damage`|Untertabelle mit den Smart-Damage-Einstellungen; `MobHP` und `DmgMemory` halten gelernte Mob-Leben und Mindestschäden (siehe [Smart Damage](#smart-damage))|
|`Raid`|Untertabelle mit allen Raidmodus-Einstellungen (siehe [Raidmodus](#raidmodus)), einschließlich `PosX` / `PosY` des Rasters|
|`PredictMemory`|Gelernte Heilwerte je Zauber und Rang|
@@ -952,12 +1129,19 @@ Alle Stellschrauben stehen als Globals oben in ihrem jeweiligen Abschnitt und la
|`FBPET\_INDENT`|12|Einrückung (und Verschmälerung) der Pet-Plaketten|
|`FBPET\_ICON` · `FBPET\_ICON\_SIZE`|Fußabdruck · 12|Pfoten-Icon vor Pet-Namen|
|`FBDEBUFF\_ICON\_SIZE`|14|Kantenlänge des Debuff-Icons|
|`FBLOS\_ICON` · `FBLOS\_ICON\_SIZE`|Blenden-Icon · 16|Sichtlinien-Abzeichen|
|`FBLOS\_ICON\_X` · `FBLOS\_ICON\_Y`|6 · 0|Versatz des Abzeichens vom linken Plattenrand|
|`FBLOS\_ICON` · `FBLOS\_ICON\_SIZE`|Blenden-Icon · 12|Sichtlinien-Abzeichen|
|`FBLOS\_ICON\_X` · `FBLOS\_ICON\_Y`|-3 · 3|Versatz des Abzeichens von der linken oberen Plattenecke|
|`FBLOS\_TIMEOUT`|8|Sekunden, die ein Sichtlinien-Fehler ohne UnitXP markiert bleibt|
|`FBNAME\_WIDTH\_FULL` · `FBNAME\_WIDTH\_ICON`|78 · 60|Breite der Namensbox ohne / mit sichtbarem Debuff-Icon|
|`FBRANGE\_ALPHA` · `FBRANGE\_INTERVAL`|0.5 · 0.5|Deckkraft und Prüfintervall des Reichweiten-Fadings|
|`FBBUFF\_MISSING\_COLOR`|`{1, 0.5, 0, 1}`|Rahmenfarbe bei fehlendem Wache-Buff|
|`FBAGGRO\_COLOR`|`{1, 0.15, 0.15, 1}`|Rahmenfarbe für den Angegriffenen|
|`FBCD\_MIN\_DURATION`|2|Kürzere Cooldowns (globaler Cooldown) werden nicht gezeigt|
|`FBTIMER\_COLOR\_HOT` · `\_SHIELD` · `\_WS`|grün · blau · rot|Timerfarben auf den Buttons|
|`FBBUFFICON\_SIZE` · `FBBUFFICON\_GAP` · `FBBUFFICON\_MAX` · `FBBUFFICON\_XOFF`|8 · 1 · 6 · -2|Buff-Icons links neben der Plakette|
|`FBBUFFICON\_GREY`|0.55|Grauton abgelaufener Quadranten, falls der Client nicht entsättigen kann|
|`FBNAME\_HEIGHT`|12|Höhe der Namensbox (eine Zeile, vertikal zentriert)|
|`FBWEAKENED\_SOUL\_SEC`|15|Dauer der Geschwächten Seele|
|`FBClassColors`|(Tabelle)|Ersatz-Klassenfarben, falls der Client kein `RAID\_CLASS\_COLORS` hat|
|`FBBuffWatchSpells` · `FBBuffAlternates`|(Tabelle)|Angebotene Buffs je Klasse und welche Gruppenversion als gleich zählt|
|`FBPartyUnit` · `FBLayoutOrder`|(Tabelle)|Die zehn Plätze und ihre Anzeigereihenfolge|
@@ -993,6 +1177,10 @@ Alle Stellschrauben stehen als Globals oben in ihrem jeweiligen Abschnitt und la
|`FBHealBox\_UpdateDebuffIcon(frame, tex, count)`|Debuff-Icon und Stackzahl|
|`FBHealBox\_CastOn(button, castString)`|Wirkt den Zauber eines Buttons (links oder rechts) auf sein Ziel|
|`FBHealBox\_DropSpell(btnIndex, side)` · `FBHealBox\_CursorSpell()`|Drag & Drop aus dem Zauberbuch|
|`FBHealBox\_SmartRank(castString, unit)`|Wählt den zu wirkenden Rang|
|`FBHealBox\_CheckAggroAll()` · `FBHealBox\_ApplyBorder(f)`|Roter Rahmen für den Angegriffenen; Rahmen-Vorrang|
|`FBHealBox\_UpdateSpellTimers()` · `FBHealBox\_SpellTimerFor(...)`|HoT/Schild-Timer auf Buttons|
|`FBHealBox\_UpdateButtonCooldown(b)` · `FBHealBox\_UpdateAllCooldowns()`|Cooldown-Uhr|
|`FBHealBox\_ShowTab(n)` · `FBHealBox\_ApplyRightClickLayout()`|Options-Reiter; Rechtsklick-Spalte ein-/ausblenden|
|`FBHealBox\_PlateMouseDown(f)` · `FBHealBox\_PlateMouseUp(f)` · `FBHealBox\_RunPlateAction(f, action)`|Klick und Ziehen auf einer Plakette|
|`FBHealBox\_RefreshAllBars()`|Aktualisiert alle zehn|
@@ -1111,10 +1299,12 @@ Die vorbelegten Listen, frei erweiterbar in `Spell.Name`:
|Klasse|Zauber|
|-|-|
|Priester|Renew · Flash Heal · Lesser Heal · Heal · Greater Heal · Binding Heal · Prayer of Healing · Prayer of Mending · Circle of Healing · Power Word: Shield · Abolish Disease · Cure Disease · Dispel Magic|
|Druide|Rejuvenation · Regrowth · Lifebloom · Healing Touch · Swiftmend · Remove Curse · Abolish Poison|
|Schamane|Lesser Healing Wave · Healing Wave · Chain Heal · Earth Shield · Cure Poison · Cure Disease|
|Paladin|Flash of Light · Holy Light · Holy Shock · Lay on Hands · Purify · Cleanse · Blessing of Protection|
|Priester|Renew · Flash Heal · Lesser Heal · Heal · Greater Heal · Binding Heal · Prayer of Healing · Prayer of Mending · Circle of Healing · Power Word: Shield · Abolish Disease · Cure Disease · Dispel Magic · Power Word: Fortitude · Prayer of Fortitude · Divine Spirit · Prayer of Spirit · Shadow Protection · Prayer of Shadow Protection · Fear Ward · Power Infusion · Resurrection|
|Druide|Healing Touch · Regrowth · Rejuvenation · Swiftmend · Tranquility · Lifebloom · Abolish Poison · Cure Poison · Remove Curse · Mark of the Wild · Gift of the Wild · Thorns · Innervate · Rebirth|
|Schamane|Lesser Healing Wave · Healing Wave · Chain Heal · Earth Shield · Water Shield · Cure Poison · Cure Disease · Purge · Ancestral Spirit · Water Walking · Water Breathing|
|Paladin|Flash of Light · Holy Light · Holy Shock · Lay on Hands · Cleanse · Purify · Blessing of Protection · Blessing of Freedom · Blessing of Sacrifice · Redemption · Divine Intervention · Blessing of Wisdom / Might / Kings / Salvation / Light / Sanctuary · Greater Blessing of Wisdom / Might / Kings / Salvation / Light / Sanctuary|
|Magier|Remove Lesser Curse · Arcane Intellect · Arcane Brilliance · Dampen Magic · Amplify Magic|
|Hexenmeister|Unending Breath · Detect Invisibility · Detect Lesser / Greater Invisibility|
Nicht vorhandene Einträge stören nicht: Findet der Zauberbuch-Scan sie nicht, werden sie stillschweigend übergangen.
@@ -1129,11 +1319,23 @@ Nicht vorhandene Einträge stören nicht: Findet der Zauberbuch-Scan sie nicht,
* eigenes Kaskadenmenü für Zauber- und Rangwahl, weil `UIDropDownMenu` in 1.12 keine brauchbaren Untermenüs liefert
* Heilvorhersage für Direktheilung, HoT-Restticks und Absorb-Schilde, selbstkorrigierend über den Combatlog
* HealComm-Sync mit Puppeteer, pfUI, Luna und Co., ohne Ace-Bibliotheken
* Lokalisierung Deutsch / Englisch, im laufenden Spiel umschaltbar
* Lokalisierung Deutsch, Englisch, Spanisch, Französisch und Italienisch, im laufenden Spiel umschaltbar
* 1.4.4.1: Buff-Erkennung & Tooltip-Scan behoben (Göttlicher Willen Uhr-Icon), vollständige Klassen-Zauberlisten mit Segen, Buffs, Hilfszaubern und Wiederbelebung, Gruppen-Buff-Erkennung; siehe CHANGELOG
* 1.4.4: Leistungsdurchgang ohne Funktionsänderung (zentrale Button-Zustände, Anzeige-Zwischenspeicher, gemeinsame Aura-Scans, zusammengefasste Event-Salven); siehe CHANGELOG
* 1.4.3: Mana-Ticker-Modul (2-s-Regenerationstick und Fünf-Sekunden-Regel als Funke im Manabalken), Smart-Damage-Modul (Rangwahl für Angriffszauber auf jeder Aktionsleiste), Smart Healing (automatisches Abrangen, standardmäßig aus), Cooldown-Uhr auf den Buttons, roter Rahmen für den Angegriffenen, HoT/Schild-Timer auf den Buttons
* 1.4.2: Raidmodus als eigenes Modul (`FBHealBox_Raid.lua`) mit kompaktem 20/40-Raster, Mini-Buttons, eigenem Options-Reiter und Raid-Test; Hook-Schnittstelle im Kern
* 1.4.1: Begleiter-Plaketten, Manabalken im Lebensbalken, einstellbare Button- und Zeilenabstände, Testmodus mit Geisterspielern, gespeicherte Plattenposition, Klassenfarben, Buff-Wache, Reichweiten-Fading, Optionen in Reitern, Rechtsklick-Zauber, Tot/Geist/Offline-Text, Debuff-Icon. Siehe CHANGELOG.md.
\---
Heal Box Vanilla v1.4.2 · Original von Dourd, UI Overhauled · Vanilla-Portierung und Erweiterung 09/2026 von Mquadrat
Heal Box Vanilla v1.4.4.1 · Original von Dourd, UI Overhauled · Vanilla-Portierung und Erweiterung 09/2026 von Mquadrat
\---
## Keywords
**English:** World of Warcraft Vanilla 1.12.1 healer addon, Classic WoW healing addon, WoW 1.12 heal frames with click buttons, Healium alternative for Vanilla, HealComm compatible, heal prediction and incoming heals, absorb shield tracking, HoT timers, Power Word: Shield and Weakened Soul tracker, buff timers with clock icons, Fortitude rebuff reminder, dispel indicator, party frames and pet frames, compact raid frames for 20 and 40 player raids, raid grid for healers, mana ticker and five second rule, spirit regeneration tick bar, smart downranking, auto downrank heals, Smart Damage rank selection, target of target aggro marker, line of sight indicator, range fading, class colors, SuperWoW support, UnitXP support, MobHealth3 and MobInfo-2 support, works on Turtle WoW, Nordanaar, Kronos, Everlook and other 1.12 private servers, Priest Druid Paladin Shaman healer UI, Lua 5.0 addon, German English Spanish French Italian.
**Deutsch:** World of Warcraft Vanilla 1.12.1 Heiler-Addon, Classic WoW Heil-Addon, Heilfenster mit Klick-Buttons, Healium-Alternative für Vanilla, HealComm-kompatibel, Heilvorhersage und eingehende Heilung, Absorb-Schild-Anzeige, HoT-Timer, Machtwort: Schild und Geschwächte Seele, Buff-Timer als Uhr-Icons, Seelenstärke nachbuffen, Dispel-Anzeige, Gruppen- und Begleiterfenster, kompakte Raidframes für 20er und 40er Schlachtzüge, Raid-Raster für Heiler, Mana-Ticker und Fünf-Sekunden-Regel, Regenerations-Tick-Balken, automatisches Abrangen, Smart Healing, Smart Damage, Angriffsziel-Markierung, Sichtlinien-Anzeige, Reichweiten-Fading, Klassenfarben, SuperWoW, UnitXP, MobHealth3 und MobInfo-2, läuft auf Turtle WoW und anderen 1.12-Privatservern, Priester Druide Paladin Schamane Heiler-UI, Lua 5.0 Addon, Deutsch Englisch Spanisch Französisch Italienisch.
#WoWVanilla #Vanilla112 #WoW1121 #ClassicWoW #TurtleWoW #WoWAddon #VanillaAddon #HealerAddon #HealingAddon #Healium #HealComm #HealPrediction #RaidFrames #PartyFrames #UnitFrames #ClickCasting #ManaTicker #FiveSecondRule #Downranking #SmartHealing #SmartDamage #BuffTimer #HoTTracker #DispelTracker #Priest #Druid #Paladin #Shaman #SuperWoW #UnitXP #MobHealth #Lua50 #HealBoxVanilla