mirror of
https://github.com/QbiXibQ/FBHealBox.git
synced 2026-10-07 22:50:01 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de37986255 | ||
|
|
423f61ac3e | ||
|
|
de4daab039 | ||
|
|
6f5ad76d63 | ||
|
|
a6ac7f8440 | ||
|
|
4149328e01 | ||
|
|
68d956e838 | ||
|
|
b09ae77764 | ||
|
|
7f3b7b3518 |
+187
@@ -0,0 +1,187 @@
|
||||
# Changelog
|
||||
|
||||
## 1.4.3 (unreleased)
|
||||
|
||||
### 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, so recasting a buff on yourself resets its clock and cancelling it removes the icon. 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, sodass ein Neucast auf dich selbst die Uhr zuruecksetzt und ein Wegklicken das Icon entfernt. 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
|
||||
|
||||
**New: raid mode** (module `FBHealBox_Raid.lua`, on by default, switches in automatically from 11 raid members; *Raid view from N players* is adjustable)
|
||||
|
||||
- Compact grid for 20 and 40 player raids: one cell per member in blocks of five per raid group, blocks arranged in rows (*Groups per row*). Empty groups collapse, so a 20-player raid is a single row.
|
||||
- Each cell: name in class colour, health bar with shield and incoming-heal layers, 3 px mana strip, HP percent or deficit, dispel colouring, dead/ghost/offline text, range fading, line-of-sight eye, buff-watch border.
|
||||
- Up to four mini buttons per cell, wired to Button 1 to N from the Buttons tab (right-click spells included). Click on a cell targets the unit (or unit menu / move / nothing, as configured for plates).
|
||||
- Own options tab *Raid mode* with size, spacing, scale, headers, mana strip, HP text mode, title bar, hide-empty-groups and hide-party-plates switches.
|
||||
- Raid test with 20 or 40 ghosts covering every visual state. `/fbp raid`, `/fbp raidtest 20|40|off`, raid line in `/fbp`.
|
||||
- Grid position and scale are saved separately from the party plates. Party plates are hidden while the grid is up (switchable).
|
||||
|
||||
**New: drag and drop.** Spells can be dragged from the spellbook onto any heal button, raid mini button or options field. Right mouse button, or Shift held while dropping, fills the right-click side when that option is on. Spells outside the class list are accepted.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- **Double loading.** The `.xml` included `FBHealBox.lua` a second time on top of the `.toc`. Every frame, hook and the options window existed twice. Visible effects: the raid tab was missing (added to the first, overwritten options window), Escape could crash the client (the second hook called itself), and learned absorb values were doubled (two prediction frames counted every absorb line). The `.xml` no longer loads scripts, and both Lua files refuse to run twice.
|
||||
- Escape hook stores the original in a local upvalue, is installed at most once, and no longer uses a tail call.
|
||||
- Learned absorb values above 1.5 times the tooltip are dropped on load; doubled values from earlier versions clean themselves up.
|
||||
|
||||
**Changed**
|
||||
|
||||
- Core gained a hook interface (`FBHealBox_RegisterHook`, `FBHealBox_AddOptionsTab`) so modules can attach without editing the core. No behaviour change for the party display.
|
||||
- `.toc` now loads `FBHealBox_Raid.lua` after the core; removing that line disables raid mode entirely.
|
||||
|
||||
### Deutsch
|
||||
|
||||
**Neu: Raidmodus** (Modul `FBHealBox_Raid.lua`, standardmaessig an, schaltet ab 11 Raidmitgliedern automatisch um; *Raid-Ansicht ab N Spielern* ist einstellbar)
|
||||
|
||||
- Kompaktes Raster fuer 20er- und 40er-Raids: eine Zelle je Mitglied in Fuenferbloecken je Gruppe, die Bloecke in Zeilen (*Gruppen je Zeile*). Leere Gruppen fallen weg, ein 20er-Raid ist also eine einzige Zeile.
|
||||
- Jede Zelle: Name in Klassenfarbe, Lebensbalken mit Schild- und Vorhersage-Schicht, 3 px Manastreifen, HP-Prozent oder Defizit, Dispel-Faerbung, Tot/Geist/Offline, Reichweiten-Fading, Sichtlinien-Auge, Buff-Wache-Rahmen.
|
||||
- Bis zu vier Mini-Buttons je Zelle, belegt wie Button 1 bis N aus dem Reiter Buttons (Rechtsklick-Zauber inklusive). Klick auf die Zelle visiert an (oder Einheitenmenue / Verschieben / Nichts, wie fuer Plaketten eingestellt).
|
||||
- Eigener Options-Reiter *Raidmodus* mit Groesse, Abstaenden, Skalierung, Gruppenkoepfen, Manastreifen, HP-Text-Modus, Titelleiste, Leere-Gruppen- und Plaketten-Ausblenden-Schaltern.
|
||||
- Raid-Test mit 20 oder 40 Geistern, die jeden Anzeigezustand abdecken. `/fbp raid`, `/fbp raidtest 20|40|off`, Raid-Zeile in `/fbp`.
|
||||
- Position und Skalierung des Rasters werden getrennt von den Plaketten gespeichert. Die Gruppenplaketten sind ausgeblendet, solange das Raster steht (abschaltbar).
|
||||
|
||||
**Neu: Drag & Drop.** Zauber lassen sich aus dem Zauberbuch auf jeden Heil-Button, Raid-Mini-Button oder jedes Optionsfeld ziehen. Die rechte Maustaste oder gehaltene Shift-Taste beim Ablegen fuellt die Rechtsklick-Seite, wenn die Option an ist. Zauber ausserhalb der Klassenliste werden angenommen.
|
||||
|
||||
**Behoben**
|
||||
|
||||
- **Doppeltes Laden.** Die `.xml` band `FBHealBox.lua` zusaetzlich zur `.toc` ein zweites Mal ein. Jeder Frame, jeder Hook und das Optionsfenster existierten doppelt. Sichtbare Folgen: der Raid-Reiter fehlte (an das erste, ueberschriebene Optionsfenster gehaengt), Escape konnte den Client zum Absturz bringen (der zweite Hook rief sich selbst auf), und gelernte Absorb-Werte waren verdoppelt (zwei Vorhersage-Frames zaehlten jede Absorb-Zeile). Die `.xml` laedt keine Skripte mehr, beide Lua-Dateien verweigern einen zweiten Durchlauf.
|
||||
- Der Escape-Hook haelt das Original in einem lokalen Upvalue, wird hoechstens einmal gesetzt und nutzt keinen Tail-Call mehr.
|
||||
- Gelernte Absorb-Werte ueber dem 1,5-fachen des Tooltips werden beim Laden verworfen; verdoppelte Werte aus frueheren Versionen bereinigen sich selbst.
|
||||
|
||||
**Geaendert**
|
||||
|
||||
- Der Kern hat eine Hook-Schnittstelle bekommen (`FBHealBox_RegisterHook`, `FBHealBox_AddOptionsTab`), damit Module andocken koennen, ohne den Kern zu aendern. Kein Verhaltensunterschied fuer die Gruppenanzeige.
|
||||
- Die `.toc` laedt `FBHealBox_Raid.lua` nach dem Kern; wer die Zeile entfernt, hat keinen Raidmodus.
|
||||
|
||||
## 1.4.1 (2026-09-04)
|
||||
|
||||
### English
|
||||
|
||||
**New**
|
||||
|
||||
- **Pets.** Every pet in the group (`pet`, `partypet1` to `partypet4`) gets its own plate with the full set of heal buttons, stacked directly below its owner, indented by 12 px, with a paw icon and a light-blue name to tell it apart. Appears and disappears with `UNIT_PET`. Option *Show pets*.
|
||||
- **Mana bar.** A 5 px blue bar along the bottom edge of the health bar ("bar in bar"), only for units whose power type is mana. Option *Mana bar*.
|
||||
- **Button and row spacing.** Two sliders, 0 to 20 px each, for the gap between buttons and the gap between plates.
|
||||
- **Test mode.** Fills the display with ghost players and pets (health, mana, shield, incoming heal, dispellable debuff, missing buff, out of range) so the layout can be arranged without a group. `/fbp test` or the checkbox. Not saved.
|
||||
- **Options window with two tabs.** *Buttons* holds the button assignment, the button count and the right-click switch; *General* holds scale, spacing, all other switches, language and buff watch.
|
||||
- **Right-click spell.** Every button can carry a second spell for right click. Off by default and only enabled through the *Buttons* tab; when on, a second column appears in the assignment and a small corner icon on each button shows the right-click spell. Assignments are kept when the switch is off.
|
||||
- **Click on a plate.** Left and right click on a name or health bar target the unit by default. Both are configurable (*General* tab): Target, Unit menu, Move display, Nothing. Moving the display is now Shift + left drag (or the *Move display* action).
|
||||
- **Dead / ghost / offline.** Shown as text in the bar instead of 0 %, with an empty grey bar and no mana strip.
|
||||
- **Line of sight.** An eye badge on the plate's left edge while the unit is out of line of sight: live via the UnitXP client mod, otherwise inferred from the 'not in line of sight' error after your own heal attempt (8 s, cleared when a cast starts or a heal lands). Option *Line of sight*.
|
||||
- **Debuff icon.** The icon of the first debuff your class can remove is shown next to the name, with its stack count. Option *Debuff icon*.
|
||||
- **Class colours.** Names in class colour (client `RAID_CLASS_COLORS`, fallback table built in). Option *Class colours*.
|
||||
- **Buff watch.** Pick one of your buffs; every plate whose unit is missing it gets an orange border. Group versions count as well, even when cast by another healer (texture check first, tooltip name scan as fallback). Pets are excluded unless *Buff watch on pets* is on.
|
||||
- **Range fading.** Plates including their buttons fade to 50 % when the unit is out of range of your first assigned spell (28 yards without a spell). Option *Range fading*.
|
||||
- **`/fbp config`** opens the options window; **Escape** closes it (registered in `UISpecialFrames`, plus a hook on `ToggleGameMenu` for clients that ignore that list). `/fbp` ends with a list of all commands.
|
||||
- **Saved plate position.** The player plate keeps its position across reloads; scaling no longer moves it.
|
||||
|
||||
**Changed**
|
||||
|
||||
- All UNIT events run through one slot table (`FBPartyUnit`) instead of five copied handlers; `HealBoxAttachMode` and `FBUpdateNames` are loops.
|
||||
- Spell buttons are created once and only re-assigned afterwards (previously rebuilt on every `SPELLS_CHANGED`, with random global names like `Button3`).
|
||||
- Settings missing in an old saved-variables table are filled in on load (`FBHealBox_ApplyDefaults`).
|
||||
- `UNIT_MAXHEALTH` is handled (was missing).
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Documentation named a wrong client version; the addon targets client 1.12.1.
|
||||
- Tooltip error on a button whose target does not exist.
|
||||
- Division by zero for offline members (`UnitHealthMax` = 0).
|
||||
- Plate position reset on every login.
|
||||
|
||||
### Deutsch
|
||||
|
||||
**Neu**
|
||||
|
||||
- **Begleiter.** Jeder Begleiter in der Gruppe (`pet`, `partypet1` bis `partypet4`) bekommt eine eigene Plakette mit allen Heil-Buttons, direkt unter seinem Besitzer, um 12 px eingerueckt, mit Pfoten-Icon und hellblauem Namen zur Unterscheidung. Kommt und geht mit `UNIT_PET`. Option *Begleiter anzeigen*.
|
||||
- **Manabalken.** 5 px blau am unteren Rand des Lebensbalkens ("Balken im Balken"), nur bei Einheiten mit Powertyp Mana. Option *Manabalken*.
|
||||
- **Button- und Zeilen-Abstand.** Zwei Regler, je 0 bis 20 px, fuer den Abstand der Buttons und den Abstand der Plaketten.
|
||||
- **Testmodus.** Fuellt die Anzeige mit Geisterspielern und -begleitern (Leben, Mana, Schild, eingehende Heilung, entfernbarer Debuff, fehlender Buff, ausser Reichweite), damit sich alles ohne Gruppe einrichten laesst. `/fbp test` oder der Haken. Wird nicht gespeichert.
|
||||
- **Optionsfenster mit zwei Reitern.** *Buttons* enthaelt die Belegung, die Buttonzahl und den Rechtsklick-Schalter; *Allgemein* enthaelt Skalierung, Abstaende, alle uebrigen Schalter, Sprache und Buff-Wache.
|
||||
- **Rechtsklick-Zauber.** Jeder Button kann einen zweiten Zauber fuer Rechtsklick tragen. Standardmaessig aus und nur ueber den Reiter *Buttons* einschaltbar; eingeschaltet erscheint eine zweite Spalte in der Belegung und ein kleines Eck-Icon auf jedem Button zeigt den Rechtsklick-Zauber. Die Belegung bleibt bei ausgeschaltetem Schalter erhalten.
|
||||
- **Klick auf die Plakette.** Links- und Rechtsklick auf Name oder Lebensbalken visieren die Einheit an (Standard). Beides belegbar (Reiter *Allgemein*): Anvisieren, Einheitenmenue, Anzeige verschieben, Nichts. Verschieben geht jetzt per Shift + Linksklick ziehen (oder Aktion *Anzeige verschieben*).
|
||||
- **Tot / Geist / Offline.** Als Text im Balken statt 0 %, mit leerem grauem Balken und ohne Manastreifen.
|
||||
- **Sichtlinie.** Augen-Abzeichen am linken Plattenrand, solange die Einheit ausserhalb der Sichtlinie ist: live ueber den Client-Mod UnitXP, sonst aus der Fehlermeldung 'nicht in Sichtlinie' nach einem eigenen Heilversuch abgeleitet (8 s, geloescht sobald ein Cast startet oder eine Heilung ankommt). Option *Sichtlinie*.
|
||||
- **Debuff-Icon.** Das Icon des ersten von deiner Klasse entfernbaren Debuffs erscheint neben dem Namen, mit Stackzahl. Option *Debuff-Icon*.
|
||||
- **Klassenfarben.** Namen in Klassenfarbe (`RAID_CLASS_COLORS` des Clients, Ersatztabelle eingebaut). Option *Klassenfarben*.
|
||||
- **Buff-Wache.** Einen eigenen Buff waehlen; jede Plakette, deren Einheit ihn nicht traegt, bekommt einen orangen Rahmen. Gruppenversionen zaehlen mit, auch von anderen Heilern (erst Texturvergleich, dann Tooltip-Namensscan). Begleiter sind ausgenommen, solange *Buff-Wache auch fuer Begleiter* aus ist.
|
||||
- **Reichweiten-Fading.** Plaketten samt Buttons werden auf 50 % abgeblendet, wenn die Einheit ausser Reichweite des ersten belegten Zaubers ist (ohne Zauber 28 Meter). Option *Reichweiten-Fading*.
|
||||
- **`/fbp config`** oeffnet das Optionsfenster; **Escape** schliesst es (in `UISpecialFrames` eingetragen, dazu ein Hook auf `ToggleGameMenu` fuer Clients, die diese Liste ignorieren). `/fbp` endet mit einer Liste aller Befehle.
|
||||
- **Gespeicherte Plattenposition.** Die Spielerplakette behaelt ihre Position ueber Reloads; Skalieren verschiebt sie nicht mehr.
|
||||
|
||||
**Geaendert**
|
||||
|
||||
- Alle UNIT-Events laufen ueber eine Slot-Tabelle (`FBPartyUnit`) statt fuenf kopierter Handler; `HealBoxAttachMode` und `FBUpdateNames` sind Schleifen.
|
||||
- Zauber-Buttons werden einmal angelegt und danach nur umbelegt (frueher bei jedem `SPELLS_CHANGED` neu gebaut, mit zufaelligen Global-Namen wie `Button3`).
|
||||
- In einer alten SavedVariables-Tabelle fehlende Einstellungen werden beim Laden nachgezogen (`FBHealBox_ApplyDefaults`).
|
||||
- `UNIT_MAXHEALTH` wird ausgewertet (fehlte).
|
||||
|
||||
**Behoben**
|
||||
|
||||
- Die Dokumentation nannte eine falsche Client-Version; das Addon zielt auf Client 1.12.1.
|
||||
- Tooltip-Fehler auf einem Button, dessen Ziel nicht existiert.
|
||||
- Division durch Null bei Offline-Mitgliedern (`UnitHealthMax` = 0).
|
||||
- Plattenposition wurde bei jedem Login zurueckgesetzt.
|
||||
|
||||
## 1.4
|
||||
|
||||
- Vanilla port, cascading spell menu, heal prediction, HealComm sync, German/English localization. See README.
|
||||
+3247
-557
File diff suppressed because it is too large
Load Diff
+7
-4
@@ -1,11 +1,14 @@
|
||||
## Interface: 11200
|
||||
## Title: Heal Box Vanilla |cFFFFFF00v1.4|r
|
||||
## Notes: Party and self heal display with quick-cast buttons for healers. Ported to client 1.21.1 (optional SuperWoW). Up to 10 buttons per row, settings saved per character. Heal prediction for direct heals, remaining HoT ticks and absorb shields, self-correcting from the combat log. HealComm sync with Puppeteer, pfUI and others. German/English, switchable in the options. Original by Dourd, ported to Vanilla and extended 09/2026 by Mquadrat.
|
||||
## Notes-deDE: Party- und Selbst-Heilanzeige mit Schnellzugriff-Buttons fuer Heiler. Portiert fuer Client 1.21.1 (optional SuperWoW). Bis zu 10 Buttons pro Reihe, Einstellungen pro Charakter. Heal-Prediction fuer Direktheilung, HoT-Restticks und Absorb-Schilde, selbstkorrigierend ueber den Combatlog. HealComm-Sync mit Puppeteer, pfUI und Co. Deutsch/Englisch, in den Optionen umschaltbar. Original von Dourd, Vanilla-Portierung und Erweiterung 09/2026 von Mquadrat.
|
||||
## Title: Heal Box Vanilla |cFFFFFF00v1.4.3|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
|
||||
## Version: 1.4.3
|
||||
## SavedVariablesPerCharacter: HealBox
|
||||
## OptionalDeps: SuperWoW
|
||||
|
||||
FBHealBox.lua
|
||||
FBHealBox_Raid.lua
|
||||
FBHealBox_Ticker.lua
|
||||
FBHealBox_Damage.lua
|
||||
FBHealBox.xml
|
||||
|
||||
+3
-1
@@ -4,7 +4,9 @@
|
||||
Original: "Heal Box" von Dourd (Argent Dawn EU) - UI Overhauled.
|
||||
Vanilla-Portierung und Funktionserweiterung 09/2026 von Mquadrat.
|
||||
-->
|
||||
<Script file="FBHealBox.lua"/>
|
||||
<!-- Die Lua-Dateien laedt ausschliesslich die .toc (FBHealBox.lua,
|
||||
FBHealBox_Raid.lua). Ein <Script file="..."/> hier wuerde den Kern
|
||||
ein zweites Mal ausfuehren. -->
|
||||
|
||||
<Frame name="FBHealBoxEventFrame" parent="UIParent">
|
||||
<Scripts>
|
||||
|
||||
@@ -0,0 +1,617 @@
|
||||
-- ==========================================================================
|
||||
-- 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)
|
||||
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()
|
||||
if (FBDmgSourceText) 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();
|
||||
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();
|
||||
+1510
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,549 @@
|
||||
-- ==========================================================================
|
||||
-- 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()
|
||||
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) then e.spark:Hide(); end
|
||||
end
|
||||
end
|
||||
|
||||
function FBTicker_Draw()
|
||||
local cfg = FBTicker_Cfg();
|
||||
if (cfg.Enabled ~= 1) or (HealBox.Active ~= 1) or (not FBTicker_PlayerHasMana()) then
|
||||
FBTicker_HideAll();
|
||||
return;
|
||||
end
|
||||
-- Volles Mana: nichts zu regenerieren, Funke aus
|
||||
if (FBTicker_ManaFull()) then
|
||||
FBTicker_HideAll();
|
||||
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 = 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
|
||||
s:SetWidth(w);
|
||||
s:SetHeight(bh);
|
||||
if (e.mode ~= mode) then
|
||||
e.mode = mode;
|
||||
s:SetTexture(col[1], col[2], col[3], col[4]);
|
||||
end
|
||||
s:ClearAllPoints();
|
||||
s:SetPoint("LEFT", bar, "LEFT", frac * (bw - w), 0);
|
||||
s:Show();
|
||||
else
|
||||
s:Hide();
|
||||
end
|
||||
else
|
||||
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();
|
||||
return;
|
||||
end
|
||||
if (arg1 ~= "player") then return; end
|
||||
if (event == "UNIT_MANA") then
|
||||
FBTicker_OnManaChanged();
|
||||
if (FBTicker_ManaFull()) then FBTicker_HideAll(); end
|
||||
elseif (event == "UNIT_DISPLAYPOWER") then
|
||||
FBTicker_Reset();
|
||||
else
|
||||
FBTicker.lastMana = UnitMana("player");
|
||||
end
|
||||
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_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(); return true; end);
|
||||
FBHealBox_RegisterHook("ActiveToggle", function() 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_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();
|
||||
Reference in New Issue
Block a user