From f3de187efc396b29f9131599af962047a18a1589 Mon Sep 17 00:00:00 2001 From: Mquadrat Date: Wed, 9 Sep 2026 08:02:31 +0200 Subject: [PATCH] Add files via upload --- CHANGELOG.md | 51 ++++ FBHealBox.lua | 592 ++++++++++++++++++++++++++++++++++++++----- FBHealBox.toc | 4 +- FBHealBox_Raid.lua | 58 ++++- FBHealBox_Ticker.lua | 13 +- 5 files changed, 648 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e892112..e23e0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # Changelog +## 1.4.5 (2026-09-08) + +Three additions around the frames and the prediction: Blizzard's party frames can go away, the resource bar is no longer reserved for mana, and gear healing is counted where an API can supply it. Plus a performance pass with no change in behaviour. + +### English + +**New & Changed** + +- **Hide Blizzard party frames.** New switch on the *General* tab that hides `PartyMemberFrame1` to `4` while you are grouped, leaving only the Heal Box plates. Off by default. Blizzard shows those frames again on every roster change, so the addon hooks their `OnShow` once instead of hiding them a single time; a frame that reappears is hidden again in the same moment. +- **Mutually exclusive with the attach mode.** *Default party frames* docks the plates onto exactly those frames, so the two options can never both be on. Whichever one is ticked disables and greys out the other (`FBHealBox_UpdatePartyExclusion`), and `FBHealBox_HideBlizzPartyActive()` re-checks the same rule in code for saved settings that were edited by hand. Switching the option off brings the frames back immediately, and so does the class gate: an addon that stays asleep for warriors, rogues and hunters returns the frames as well. +- **Rage, energy and focus in the resource bar.** The thin strip under the health bar used to be reserved for mana and stayed empty for everyone else. New switch *Show rage, energy, focus* on the *General* tab gives every unit its own resource in its usual colour, from the new table `FBPOWER_COLORS`: mana blue, rage red, focus orange, energy yellow. `FBUnitMana()` now returns the power type as a fourth value, and both the plates and the raid cells colour their bar from it. Off by default, and it still needs the mana bar to be on. +- **Equipment healing bonus in the prediction.** Vanilla spell tooltips show the naked base value; the bonus from your gear is missing. Learned values from the combat log carry it automatically, so the gap only affected ranks you had never cast. `FBHealBox_ProbeHealBonus()` now looks for `GetSpellBonusHealing`, `GetSpellBonusHeal` and `GetHealingBonus`, first as globals and then inside a `ClassicAPI` table, and `FBPredict_ExpectedDirect()` adds the share that belongs to the spell. +- **Weighted the vanilla way.** The bonus counts as cast time divided by 3.5, capped at 3.5 seconds, with instants counting as 1.5 and an unreadable cast time as 2.5. Cast time is read once per spellbook slot from the tooltip (`FBHealBox_SpellCastSeconds`, right-hand side of line 2), so a 3 second Greater Heal takes 600 of a +700 bonus and a 1.5 second Flash Heal takes 300. Without a suitable API the bonus stays 0 and nothing about the old behaviour changes. +- **New command `/fbp healbonus`.** Turns the equipment bonus off and on, saved per character in `HealBox.HealBonus` (on by default). `/fbp` reports the value found and whether it is being applied; the command says so when no API supplies a bonus at all. + +**Performance (no change in behaviour)** + +- **Own buff timers read once per frame.** `FBPredict_PlayerBuffTimeLeft` used to walk the whole player buff list once per watched buff, calling `strupper` on every texture along the way: with six watches and thirty buffs that was around 200 API calls and 200 throwaway strings a second. The list is now read once per frame into a texture-to-seconds map (`FBPredict_ScanPlayerBuffTimes`), the way `FBHealBox_UnitBuffs` already worked, and every watch is served from it. Texture paths are upper-cased once and remembered (`FBHealBox_UpperTex`), which also removes the same allocation from the unit buff scan. +- **Range and line of sight in raid mode are spread over several ticks.** `FBRaid_Tick` used to check up to 40 cells in one frame every half second. It now walks a quarter of the groups per tick at a quarter of the interval (`FBRAID_TICK_SLICES`), so a full sweep still takes the same half second while the peak load per frame drops to a quarter. +- **`pcall` per sweep instead of per unit.** Range, distance and line of sight went through a protected call for every single unit, up to 160 a second in a full raid. The first successful call now marks the form as safe and the individual calls go straight through, while the sweep as a whole stays protected: one `pcall` per pass in `FBHealBox_CheckRangeAll`, `FBHealBox_CheckLOSAll` and `FBRaid_Tick`. A client that throws on the very first call keeps the old per-call protection and its fallback path. +- **Self-healing if an API throws late.** Should a sweep throw after the form had already proven itself, `FBHealBox_ApiFailed()` puts all three queries back to the protected single call, where the error is caught and the fallback takes over as before. The frame is never torn open, and at worst one pass is skipped. +- **Raid health text no longer builds strings it throws away.** The percentage and deficit texts were formatted before the change check, so every `UNIT_HEALTH` in a 40 man raid produced a string that was usually discarded immediately. The cell now compares the number and only formats on a real change, the way the plates always did. +- **Cheap pre-test before the combat log patterns.** Up to four anchored patterns ran on every message from twenty registered events. A plain substring test now filters first, with the words cut out of the client's own message templates so it works in every language. The tick test deliberately comes from the self template, because "You gain" and "%s gains" differ in the verb but share what follows. +- **Equipment bonus read once instead of per candidate.** With heal chains a single Smart Healing click asked the API for the bonus up to fifteen times. The value is now remembered and dropped again on `UNIT_INVENTORY_CHANGED` or a spellbook rebuild. +- **Cast time comes free with the spell scan.** `FBHealBox_SpellCastSeconds` used to build its own tooltip on a cache miss, which could mean a dozen tooltip builds in the middle of combat on the first click. `FBPredict_TooltipText` now picks the cast time off the right-hand side while the tooltip is standing anyway and fills the cache; the standalone scan remains only as a fallback. The cast time is deliberately kept out of the parsed text, because a number like 2.5 would confuse the shield duration and heal amount patterns. +- **Smaller things.** The range spell of the first assigned button is remembered instead of being searched over ten slots on every range check (invalidated on rebinding and on a spellbook rebuild). The ticker redraws on a fixed 0.03 second step instead of every frame. Blizzard's party frames are re-evaluated in the same collection point as the name updates instead of on every event of a burst. Two `or {}` fallbacks in loops no longer allocate a throwaway table. + +### Deutsch + +**Neu & Geaendert** + +- **Blizzards Gruppenfenster ausblenden.** Neuer Schalter im Reiter *Allgemein*, der `PartyMemberFrame1` bis `4` versteckt, solange du in einer Gruppe bist, sodass nur die Plaketten der Heal Box uebrig bleiben. Standardmaessig aus. Blizzard zeigt diese Frames bei jeder Gruppenaenderung neu an, deshalb haengt sich das Addon einmalig in deren `OnShow` statt sie nur einmal zu verstecken; ein Frame, der wieder auftaucht, verschwindet im selben Moment erneut. +- **Schliesst sich mit dem Anheftmodus aus.** *Standard-Gruppenfenster* haengt die Plaketten genau an diese Frames, beide Optionen koennen also nie zusammen an sein. Was angehakt ist, sperrt und graut das andere aus (`FBHealBox_UpdatePartyExclusion`), und `FBHealBox_HideBlizzPartyActive()` prueft dieselbe Regel im Code noch einmal nach, fuer von Hand bearbeitete gespeicherte Werte. Ausschalten holt die Frames sofort zurueck, ebenso die Klassensperre: Ein Addon, das fuer Krieger, Schurken und Jaeger schlaeft, gibt die Frames mit zurueck. +- **Wut, Energie und Fokus im Ressourcenbalken.** Der schmale Streifen unter dem Lebensbalken war dem Mana vorbehalten und blieb bei allen anderen leer. Der neue Schalter *Wut, Energie, Fokus zeigen* im Reiter *Allgemein* gibt jeder Einheit ihre eigene Ressource in der gewohnten Farbe, aus der neuen Tabelle `FBPOWER_COLORS`: Mana blau, Wut rot, Fokus orange, Energie gelb. `FBUnitMana()` liefert die Energieart jetzt als vierten Rueckgabewert, Plaketten und Raidzellen faerben ihren Balken danach. Standardmaessig aus, und der Manabalken muss weiterhin an sein. +- **Heilbonus der Ausruestung in der Vorhersage.** Vanilla-Tooltips zeigen den nackten Grundwert, der Bonus der Ausruestung fehlt darin. Gelernte Werte aus dem Combatlog tragen ihn von selbst, die Luecke betraf also nur Raenge, die nie gewirkt wurden. `FBHealBox_ProbeHealBonus()` sucht jetzt nach `GetSpellBonusHealing`, `GetSpellBonusHeal` und `GetHealingBonus`, erst als globale Funktion, dann in einer `ClassicAPI`-Tabelle, und `FBPredict_ExpectedDirect()` schlaegt den Anteil auf, der auf den Zauber entfaellt. +- **Gewichtet wie in Vanilla ueblich.** Der Bonus zaehlt mit Zauberzeit geteilt durch 3,5, gedeckelt bei 3,5 Sekunden, Instants rechnen mit 1,5 und eine unlesbare Zauberzeit mit 2,5. Die Zauberzeit wird einmal je Zauberbuchplatz aus dem Tooltip gelesen (`FBHealBox_SpellCastSeconds`, rechte Seite der zweiten Zeile). Eine Grosse Heilung mit 3 Sekunden nimmt von +700 Heilung also 600 mit, eine Blitzheilung mit 1,5 Sekunden 300. Ohne passende API bleibt der Bonus 0 und am bisherigen Verhalten aendert sich nichts. +- **Neuer Befehl `/fbp healbonus`.** Schaltet den Ausruestungsbonus aus und wieder ein, gespeichert je Charakter in `HealBox.HealBonus` (Standard an). `/fbp` nennt den gefundenen Wert und ob er angewandt wird; der Befehl sagt es, wenn ueberhaupt keine API einen Bonus liefert. + +**Leistung (ohne Funktionsaenderung)** + +- **Eigene Bufflaufzeiten einmal je Frame.** `FBPredict_PlayerBuffTimeLeft` lief bisher je ueberwachtem Buff einmal durch die gesamte Buffliste und rief dabei fuer jede Textur `strupper` auf: bei sechs Wachen und dreissig Buffs rund 200 API-Aufrufe und 200 Wegwerf-Strings je Sekunde. Die Liste wird jetzt einmal je Frame in eine Tabelle Textur nach Sekunden gelesen (`FBPredict_ScanPlayerBuffTimes`), genau wie es `FBHealBox_UnitBuffs` schon vormacht, und alle Wachen bedienen sich daraus. Texturpfade werden einmal gross geschrieben und gemerkt (`FBHealBox_UpperTex`), was dieselbe Zuteilung auch aus dem Buffscan der Einheiten nimmt. +- **Reichweite und Sichtlinie im Raid auf mehrere Ticks verteilt.** `FBRaid_Tick` pruefte alle halbe Sekunde bis zu 40 Zellen in einem einzigen Frame. Jetzt kommt je Tick ein Viertel der Gruppen dran, dafuer viermal so oft (`FBRAID_TICK_SLICES`): Der volle Durchlauf dauert weiterhin eine halbe Sekunde, die Spitzenlast je Frame sinkt auf ein Viertel. +- **`pcall` je Durchlauf statt je Einheit.** Reichweite, Abstand und Sichtlinie liefen fuer jede einzelne Einheit durch einen geschuetzten Aufruf, im Vierzigerraid bis zu 160 je Sekunde. Der erste erfolgreiche Aufruf erklaert die Form jetzt fuer sicher und die Einzelaufrufe gehen direkt hinein, der Durchlauf als Ganzes bleibt aber geschuetzt: ein `pcall` je Durchgang in `FBHealBox_CheckRangeAll`, `FBHealBox_CheckLOSAll` und `FBRaid_Tick`. Ein Client, der schon beim ersten Aufruf wirft, behaelt den geschuetzten Einzelaufruf samt Rueckfallweg. +- **Selbstheilung, wenn eine API doch spaet wirft.** Wirft ein Durchlauf, obwohl die Form sich vorher bewaehrt hatte, stellt `FBHealBox_ApiFailed()` alle drei Abfragen auf den geschuetzten Einzelaufruf zurueck, wo der Fehler abgefangen wird und der Rueckfall wie frueher greift. Der Frame wird nie aufgerissen, schlimmstenfalls faellt ein Durchgang aus. +- **Kein HP-Text im Raid, der gleich wieder weggeworfen wird.** Prozent- und Deficit-Text wurden vor dem Vergleich gebaut, jedes `UNIT_HEALTH` im Vierzigerraid erzeugte also einen String, der meist sofort verfiel. Die Zelle vergleicht jetzt die Zahl und formatiert nur bei echter Aenderung, so wie es die Plaketten immer schon tun. +- **Billiger Vortest vor den Combatlog-Mustern.** Bei jeder Meldung aus zwanzig registrierten Ereignissen liefen bis zu vier verankerte Muster. Ein einfacher Textvergleich filtert jetzt vorweg, mit Wortstuecken, die aus den Meldungsvorlagen des Clients geschnitten werden und damit in jeder Sprache passen. Der Tick-Vortest kommt bewusst aus der Selbst-Vorlage, weil sich "You gain" und "%s gains" im Verb unterscheiden, im Rest aber gleich sind. +- **Ausruestungsbonus einmal statt je Kandidat.** Mit Heilketten fragte ein einziger Smart-Healing-Klick die API bis zu fuenfzehnmal nach dem Bonus. Der Wert wird jetzt gemerkt und bei `UNIT_INVENTORY_CHANGED` oder einem Neuaufbau des Zauberbuchs wieder verworfen. +- **Zauberzeit faellt beim Auslesen der Zauber mit ab.** `FBHealBox_SpellCastSeconds` baute bei einem Fehlgriff einen eigenen Tooltip, was beim ersten Klick mitten im Kampf ein Dutzend Tooltip-Aufbauten bedeuten konnte. `FBPredict_TooltipText` liest die Zauberzeit jetzt nebenbei von der rechten Seite ab, solange der Tooltip ohnehin steht, und fuellt den Zwischenspeicher; der eigene Scan bleibt nur als Rueckfall. Der Wert kommt bewusst nicht in den ausgewerteten Text, weil eine Zahl wie 2.5 die Muster fuer Schilddauer und Heilbetrag durcheinanderbraechte. +- **Kleinigkeiten.** Der Reichweiten-Zauber des ersten belegten Buttons wird gemerkt, statt bei jeder Pruefung ueber zehn Plaetze zu suchen (verworfen beim Umbelegen und beim Neuaufbau des Zauberbuchs). Der Ticker zeichnet in einem festen Takt von 0,03 Sekunden statt in jedem Frame. Blizzards Gruppenfenster werden im selben Sammelpunkt wie die Namen neu bewertet statt bei jedem Ereignis einer Salve. Zwei `or {}`-Rueckfaelle in Schleifen legen keine Wegwerf-Tabelle mehr an. + + ## 1.4.4.3 (2026-09-08) Class gate, heal chains and a Smart Healing fix. The addon no longer loads its display for warriors, rogues and hunters, Smart Healing keeps its hands off heal over time spells, and downranking may now switch spell inside a heal chain. diff --git a/FBHealBox.lua b/FBHealBox.lua index b211b93..706aea2 100644 --- a/FBHealBox.lua +++ b/FBHealBox.lua @@ -34,6 +34,9 @@ -- Smart Healing rangt HoTs aller Klassen nicht mehr ab. -- Smart Healing rangt in Heilketten auch ueber Zaubergrenzen -- ab (Grosse Heilung -> Geringes Heilen), abschaltbar mit /fbp smartcross. +-- * v1.4.5: Blizzards Gruppenfenster ausblendbar (schliesst sich mit dem +-- Anheftmodus gegenseitig aus), Wut, Energie und Fokus im Balken, +-- +Heilung der Ausruestung ueber ClassicAPI in der Vorhersage. -- -- Ehre wem Ehre gebuehrt: Aufbau, Namensplaketten und Grundidee stammen -- aus dem Original. @@ -120,6 +123,9 @@ HealBox = { ButtonSpacing = 2, -- px zwischen den Buttons (1..20) RowSpacing = 4, -- px zwischen den Plaketten (1..20) ManaBar = 1, -- Manabalken im Lebensbalken anzeigen + PowerBar = 0, -- auch Wut, Energie und Fokus im Balken zeigen + HideBlizzParty = 0, -- Blizzards Gruppenfenster verstecken + HealBonus = 1, -- +Heilung der Ausruestung in die Vorhersage rechnen ShowPets = 1, -- Begleiter als eigene Plaketten anzeigen ClassColors = 1, -- Namen in Klassenfarbe RangeFade = 1, -- Plakette ausgrauen, wenn ausser Reichweite @@ -145,7 +151,7 @@ HealBox = { -- feuert ADDON_LOADED fuer uns. FBADDON_NAME = "Heal Box Vanilla"; FBADDON_FOLDER = "FBHealBox"; -HealBoxVersion = "|cFFFFFF00v1.4.4.3|r"; +HealBoxVersion = "|cFFFFFF00v1.4.5|r"; -- ========================================================================== -- [ Lokalisierung / Localization ] @@ -279,7 +285,7 @@ FBLocale["enUS"] = { FBP_INCOMING = "Incoming", FBP_DEBUG = "Debug:", FBP_RESET = "Learned values discarded.", - FBP_COMMANDS = "Commands: /fbp config (options window), /fbp test (test mode), /fbp buffs (buff diagnostics), /fbp forceload (show for warrior/rogue/hunter), /fbp smartcross (downranking across spells), /fbp debug, /fbp reset", + FBP_COMMANDS = "Commands: /fbp config (options window), /fbp test (test mode), /fbp buffs (buff diagnostics), /fbp forceload (show for warrior/rogue/hunter), /fbp smartcross (downranking across spells), /fbp healbonus (equipment bonus), /fbp debug, /fbp reset", FBP_SMART = "Smart Healing: %s (safety margin %d %%)", DBG_HOT = "HoT %s on %s: %d per tick, %ds", @@ -293,6 +299,14 @@ FBLocale["enUS"] = { SMART_CROSS_ON = "Smart Healing may now switch spell within a heal chain (e.g. Greater Heal to Lesser Heal). |cFF00FF00On|r.", SMART_CROSS_OFF = "Smart Healing now stays within the assigned spell and only lowers its rank. |cFFFF0000Cross-spell off|r.", SMART_CROSS_NEEDS = "Note: Smart Healing itself is off, so this has no effect yet. Turn it on in the options or with the Smart Healing switch on the Buttons tab.", + HIDEPARTY = "Hide Blizzard party frames", + HIDEPARTY_TIP = "Hides Blizzard's party frames while you are in a group, so only the Heal Box plates remain. Cannot be combined with 'Attach to party frames', because that mode docks the plates onto exactly those frames; whichever one is on greys the other out. Switching it off brings the frames back at once, and so does a class the addon does not load for.", + POWERBAR = "Show rage, energy, focus", + POWERBAR_TIP = "The thin bar under the health bar normally shows mana and stays hidden for everyone else. With this on, warriors, rogues and pets get their own resource in its usual colour: rage red, energy yellow, focus orange. Needs the mana bar to be on.", + HEALBONUS_ON = "Equipment bonus of +%d healing is now included in the prediction. |cFF00FF00On|r.", + HEALBONUS_OFF = "Equipment bonus is no longer included, spell tooltips count as they are. |cFFFF0000Off|r.", + HEALBONUS_NA = "No API delivers a healing bonus. ClassicAPI (or anything else with GetSpellBonusHealing) enables this; without it the learned values from the combat log carry the gear anyway.", + FBP_HEALBONUS = "Equipment: +%d healing, applied to unlearned ranks: %s", CLASS_BLOCKED = "not shown for %s: this addon is made for healing classes. Warriors, rogues and hunters have no heals, no heal prediction and nothing to gain from the mana ticker.", CLASS_BLOCKED_HINT = "Type /fbp forceload to show it anyway. The setting is kept for this character.", @@ -419,7 +433,7 @@ FBLocale["deDE"] = { FBP_INCOMING = "Fremdheilung", FBP_DEBUG = "Debug:", FBP_RESET = "Gelernte Werte verworfen.", - FBP_COMMANDS = "Befehle: /fbp config (Optionsfenster), /fbp test (Testmodus), /fbp buffs (Buff-Diagnose), /fbp forceload (Anzeige fuer Krieger/Schurke/Jaeger), /fbp smartcross (Abrangen ueber Zaubergrenzen), /fbp debug, /fbp reset", + FBP_COMMANDS = "Befehle: /fbp config (Optionsfenster), /fbp test (Testmodus), /fbp buffs (Buff-Diagnose), /fbp forceload (Anzeige fuer Krieger/Schurke/Jaeger), /fbp smartcross (Abrangen ueber Zaubergrenzen), /fbp healbonus (Ausruestungsbonus), /fbp debug, /fbp reset", FBP_SMART = "Smart Healing: %s (Sicherheitsaufschlag %d %%)", DBG_HOT = "HoT %s auf %s: %d pro Tick, %ds", @@ -433,6 +447,14 @@ FBLocale["deDE"] = { SMART_CROSS_ON = "Smart Healing darf den Zauber innerhalb einer Heilkette wechseln (z. B. Grosse Heilung zu Geringem Heilen). |cFF00FF00An|r.", SMART_CROSS_OFF = "Smart Healing bleibt beim belegten Zauber und senkt nur dessen Rang. |cFFFF0000Kettenwechsel aus|r.", SMART_CROSS_NEEDS = "Hinweis: Smart Healing selbst ist aus, das wirkt also noch nicht. Einschalten im Optionsfenster ueber den Schalter Smart Healing im Reiter Buttons.", + HIDEPARTY = "Blizzard-Gruppenfenster aus", + HIDEPARTY_TIP = "Versteckt Blizzards Gruppenfenster, solange du in einer Gruppe bist, sodass nur die Plaketten der Heal Box uebrig bleiben. Nicht zusammen mit 'An Gruppenfenster anheften' nutzbar, weil dieser Modus die Plaketten genau an diese Frames haengt; was gerade an ist, graut das andere aus. Ausschalten holt die Frames sofort zurueck, ebenso eine Klasse, fuer die das Addon nicht laedt.", + POWERBAR = "Wut, Energie, Fokus zeigen", + POWERBAR_TIP = "Der schmale Streifen unter dem Lebensbalken zeigt normalerweise Mana und bleibt bei allen anderen leer. Eingeschaltet bekommen Krieger, Schurken und Begleiter ihre eigene Ressource in der gewohnten Farbe: Wut rot, Energie gelb, Fokus orange. Setzt den Manabalken voraus.", + HEALBONUS_ON = "Ausruestungsbonus von +%d Heilung wird jetzt eingerechnet. |cFF00FF00An|r.", + HEALBONUS_OFF = "Ausruestungsbonus wird nicht mehr eingerechnet, es zaehlt der nackte Tooltip. |cFFFF0000Aus|r.", + HEALBONUS_NA = "Keine API liefert einen Heilbonus. Mit ClassicAPI (oder etwas anderem mit GetSpellBonusHealing) geht das; ohne sie tragen die gelernten Werte aus dem Combatlog die Ausruestung ohnehin mit.", + FBP_HEALBONUS = "Ausruestung: +%d Heilung, auf ungelernte Raenge angewandt: %s", CLASS_BLOCKED = "wird fuer %s nicht angezeigt: Das Addon ist fuer Heilerklassen gemacht. Krieger, Schurken und Jaeger haben keine Heilzauber, keine Heilvorhersage und keinen Nutzen vom Mana-Ticker.", CLASS_BLOCKED_HINT = "Mit /fbp forceload trotzdem anzeigen. Die Einstellung bleibt fuer diesen Charakter gespeichert.", @@ -579,7 +601,7 @@ FBLocale["esES"] = { FBP_INCOMING = "Entrante", FBP_DEBUG = "Depuración:", FBP_RESET = "Valores aprendidos descartados.", - FBP_COMMANDS = "Comandos: /fbp config (ventana de opciones), /fbp test (modo de prueba), /fbp buffs (diagnóstico de beneficios), /fbp forceload (mostrar para guerrero/pícaro/cazador), /fbp smartcross (reducción entre hechizos), /fbp debug, /fbp reset", + FBP_COMMANDS = "Comandos: /fbp config (ventana de opciones), /fbp test (modo de prueba), /fbp buffs (diagnóstico de beneficios), /fbp forceload (mostrar para guerrero/pícaro/cazador), /fbp smartcross (reducción entre hechizos), /fbp healbonus (bono de equipo), /fbp debug, /fbp reset", FBP_SMART = "Smart Healing: %s (margen de seguridad %d %%)", DBG_HOT = "HoT %s sobre %s: %d por pulso, %ds", DBG_SHIELD = "Escudo %s sobre %s: %d absorción, %ds", @@ -592,6 +614,14 @@ FBLocale["esES"] = { SMART_CROSS_ON = "Smart Healing puede cambiar de hechizo dentro de una cadena de curación (p. ej. Curar más y Curar menos). |cFF00FF00Activado|r.", SMART_CROSS_OFF = "Smart Healing se queda en el hechizo asignado y solo baja su rango. |cFFFF0000Cambio de hechizo desactivado|r.", SMART_CROSS_NEEDS = "Nota: Smart Healing está desactivado, así que esto aún no tiene efecto. Actívalo en la pestaña Botones.", + HIDEPARTY = "Ocultar marcos de grupo", + HIDEPARTY_TIP = "Oculta los marcos de grupo de Blizzard mientras estás en un grupo, dejando solo las placas de Heal Box. No se puede combinar con 'Anclar a los marcos de grupo', porque ese modo ancla las placas justo en esos marcos; el que esté activo desactiva el otro.", + POWERBAR = "Mostrar ira, energía, enfoque", + POWERBAR_TIP = "La barra fina bajo la salud muestra normalmente maná y queda vacía para los demás. Activada, guerreros, pícaros y mascotas muestran su recurso en su color habitual: ira roja, energía amarilla, enfoque naranja. Requiere la barra de maná.", + HEALBONUS_ON = "El bono de equipo de +%d de sanación ya se incluye en la predicción. |cFF00FF00Activado|r.", + HEALBONUS_OFF = "El bono de equipo ya no se incluye, cuenta el tooltip puro. |cFFFF0000Desactivado|r.", + HEALBONUS_NA = "Ninguna API ofrece un bono de sanación. ClassicAPI (u otra con GetSpellBonusHealing) lo habilita; sin ella, los valores aprendidos del registro de combate ya incluyen el equipo.", + FBP_HEALBONUS = "Equipo: +%d de sanación, aplicado a rangos no aprendidos: %s", CLASS_BLOCKED = "no se muestra para %s: este addon está hecho para clases sanadoras. Guerreros, pícaros y cazadores no tienen curaciones, ni predicción de curación, ni provecho del marcador de maná.", CLASS_BLOCKED_HINT = "Escribe /fbp forceload para mostrarlo igualmente. El ajuste se guarda para este personaje.", @@ -710,7 +740,7 @@ FBLocale["frFR"] = { FBP_INCOMING = "Entrant", FBP_DEBUG = "Débogage :", FBP_RESET = "Valeurs apprises effacées.", - FBP_COMMANDS = "Commandes : /fbp config (fenêtre des options), /fbp test (mode test), /fbp buffs (diagnostic des buffs), /fbp forceload (afficher pour guerrier/voleur/chasseur), /fbp smartcross (rang abaissé entre sorts), /fbp debug, /fbp reset", + FBP_COMMANDS = "Commandes : /fbp config (fenêtre des options), /fbp test (mode test), /fbp buffs (diagnostic des buffs), /fbp forceload (afficher pour guerrier/voleur/chasseur), /fbp smartcross (rang abaissé entre sorts), /fbp healbonus (bonus d'équipement), /fbp debug, /fbp reset", FBP_SMART = "Smart Healing : %s (marge de sécurité %d %%)", DBG_HOT = "HoT %s sur %s : %d par tick, %ds", DBG_SHIELD = "Bouclier %s sur %s : %d absorption, %ds", @@ -723,6 +753,14 @@ FBLocale["frFR"] = { SMART_CROSS_ON = "Smart Healing peut changer de sort dans une chaîne de soins (p. ex. Soins supérieurs vers Soins inférieurs). |cFF00FF00Activé|r.", SMART_CROSS_OFF = "Smart Healing reste sur le sort assigné et n'abaisse que son rang. |cFFFF0000Changement de sort désactivé|r.", SMART_CROSS_NEEDS = "Note : Smart Healing lui-même est désactivé, ceci n'a donc pas encore d'effet. Activez-le dans l'onglet Boutons.", + HIDEPARTY = "Masquer les cadres de groupe", + HIDEPARTY_TIP = "Masque les cadres de groupe de Blizzard tant que vous êtes en groupe, il ne reste que les plaques de Heal Box. Incompatible avec 'Ancrer aux cadres de groupe', car ce mode ancre les plaques précisément sur ces cadres ; celui qui est actif grise l'autre.", + POWERBAR = "Afficher rage, énergie, focus", + POWERBAR_TIP = "La fine barre sous la santé montre normalement le mana et reste vide pour les autres. Activée, les guerriers, voleurs et familiers affichent leur ressource dans sa couleur habituelle : rage rouge, énergie jaune, focus orange. Nécessite la barre de mana.", + HEALBONUS_ON = "Le bonus d'équipement de +%d soins est désormais pris en compte. |cFF00FF00Activé|r.", + HEALBONUS_OFF = "Le bonus d'équipement n'est plus pris en compte, seul l'infobulle compte. |cFFFF0000Désactivé|r.", + HEALBONUS_NA = "Aucune API ne fournit de bonus aux soins. ClassicAPI (ou autre avec GetSpellBonusHealing) l'active ; sans elle, les valeurs apprises du journal de combat portent déjà l'équipement.", + FBP_HEALBONUS = "Équipement : +%d soins, appliqué aux rangs non appris : %s", CLASS_BLOCKED = "non affiché pour %s : cet addon est fait pour les classes soigneuses. Les guerriers, voleurs et chasseurs n'ont pas de soins, pas de prévision de soins et aucun usage du compteur de mana.", CLASS_BLOCKED_HINT = "Tapez /fbp forceload pour l'afficher quand même. Le réglage est conservé pour ce personnage.", @@ -841,7 +879,7 @@ FBLocale["itIT"] = { FBP_INCOMING = "In arrivo", FBP_DEBUG = "Debug:", FBP_RESET = "Valori appresi scartati.", - FBP_COMMANDS = "Comandi: /fbp config (finestra opzioni), /fbp test (modalità test), /fbp buffs (diagnostica benefici), /fbp forceload (mostra per guerriero/ladro/cacciatore), /fbp smartcross (riduzione tra incantesimi), /fbp debug, /fbp reset", + FBP_COMMANDS = "Comandi: /fbp config (finestra opzioni), /fbp test (modalità test), /fbp buffs (diagnostica benefici), /fbp forceload (mostra per guerriero/ladro/cacciatore), /fbp smartcross (riduzione tra incantesimi), /fbp healbonus (bonus equipaggiamento), /fbp debug, /fbp reset", FBP_SMART = "Smart Healing: %s (margine di sicurezza %d %%)", DBG_HOT = "HoT %s su %s: %d per tick, %ds", DBG_SHIELD = "Scudo %s su %s: %d assorbimento, %ds", @@ -854,6 +892,14 @@ FBLocale["itIT"] = { SMART_CROSS_ON = "Smart Healing può cambiare incantesimo all'interno di una catena di cure (p. es. da Cura Superiore a Cura Inferiore). |cFF00FF00Attivo|r.", SMART_CROSS_OFF = "Smart Healing resta sull'incantesimo assegnato e ne abbassa solo il rango. |cFFFF0000Cambio incantesimo disattivato|r.", SMART_CROSS_NEEDS = "Nota: Smart Healing stesso è disattivato, quindi questo non ha ancora effetto. Attivalo nella scheda Pulsanti.", + HIDEPARTY = "Nascondi i riquadri gruppo", + HIDEPARTY_TIP = "Nasconde i riquadri del gruppo di Blizzard finché sei in gruppo, restano solo le targhette di Heal Box. Non combinabile con 'Aggancia ai riquadri gruppo', perché quella modalità aggancia le targhette proprio a quei riquadri; quella attiva disattiva l'altra.", + POWERBAR = "Mostra ira, energia, focus", + POWERBAR_TIP = "La barra sottile sotto la salute mostra di norma il mana e resta vuota per gli altri. Attiva, guerrieri, ladri e famigli mostrano la loro risorsa nel colore consueto: ira rossa, energia gialla, focus arancione. Richiede la barra del mana.", + HEALBONUS_ON = "Il bonus dell'equipaggiamento di +%d cura viene ora incluso. |cFF00FF00Attivo|r.", + HEALBONUS_OFF = "Il bonus dell'equipaggiamento non viene più incluso, conta il tooltip nudo. |cFFFF0000Disattivo|r.", + HEALBONUS_NA = "Nessuna API fornisce un bonus alle cure. ClassicAPI (o altro con GetSpellBonusHealing) lo abilita; senza, i valori appresi dal registro di combattimento includono già l'equipaggiamento.", + FBP_HEALBONUS = "Equipaggiamento: +%d cura, applicato ai ranghi non appresi: %s", CLASS_BLOCKED = "non mostrato per %s: questo addon è pensato per le classi curatrici. Guerrieri, ladri e cacciatori non hanno cure, né previsione delle cure, né vantaggi dal contatore del mana.", CLASS_BLOCKED_HINT = "Scrivi /fbp forceload per mostrarlo comunque. L'impostazione resta salvata per questo personaggio.", @@ -902,6 +948,14 @@ NamePlateHeight = 28; -- Manabalken: "Balken im Balken" am unteren Rand des Lebensbalkens FBMANA_BAR_HEIGHT = 5; -- px FBMANA_BAR_COLOR = { 0.15, 0.40, 1.00, 1 }; -- blau +-- Farben je Energieart, wie sie UnitPowerType liefert. Nur Mana faerbt den +-- Balken standardmaessig; die uebrigen erscheinen erst mit HealBox.PowerBar. +FBPOWER_COLORS = { + [0] = { 0.15, 0.40, 1.00, 1 }, -- Mana, blau + [1] = { 0.85, 0.20, 0.20, 1 }, -- Wut, rot + [2] = { 1.00, 0.55, 0.20, 1 }, -- Fokus, orange + [3] = { 1.00, 0.85, 0.10, 1 }, -- Energie, gelb +}; FBMANA_BG_ALPHA = 0.35; -- dunkler Streifen hinter dem Mana (0 = aus) -- Begleiter-Plaketten: Namensfarbe, Einrueckung unter dem Besitzer (die @@ -1054,7 +1108,7 @@ FBTestMode = false; -- los = true zeigt das Sichtlinien-Abzeichen, aggro = true den roten Rahmen, -- hotLeft/shieldLeft Restsekunden auf Button 1 bzw. 2. FBTestGhosts = { - ["party1"] = { name = "Brynn", class = "WARRIOR", hpMax = 3400, hp = 0.90, swing = 0.08, hasMana = false, buffMissing = true, state = "dead" }, + ["party1"] = { name = "Brynn", class = "WARRIOR", hpMax = 3400, hp = 0.90, swing = 0.08, hasMana = false, power = 1, mpMax = 100, mp = 0.45, buffMissing = true, state = "dead" }, ["party2"] = { name = "Cerys", class = "WARLOCK", hpMax = 2300, hp = 0.55, swing = 0.10, hasMana = true, mpMax = 3100, mp = 0.65, shield = 450, los = true }, ["party3"] = { name = "Dorn", class = "HUNTER", hpMax = 2900, hp = 0.30, swing = 0.15, hasMana = true, mpMax = 2400, mp = 0.35, inc = 700, aggro = true, hotLeft = 9, shieldLeft = 21, -- sechs Buffs: Seelenstaerke, Willen, Schattenschutz, Furchtzauberschutz, Mal der Wildnis (fremd, ohne Uhr), Koenige @@ -1065,9 +1119,9 @@ FBTestGhosts = { { tex = "Interface\\Icons\\Spell_Nature_Regeneration", dur = 1800 }, { tex = "Interface\\Icons\\Spell_Magic_MageArmor", left = 1750, dur = 1800 } } }, ["party4"] = { name = "Elowen", class = "DRUID", hpMax = 2700, hp = 0.95, swing = 0.04, hasMana = true, mpMax = 3600, mp = 0.90, debuff = true, debuffTex = "Interface\\Icons\\Spell_Shadow_ShadowWordPain", debuffCount = 3, outOfRange = true }, - ["pet"] = { name = "Fang", hpMax = 1900, hp = 0.75, swing = 0.12, hasMana = false }, + ["pet"] = { name = "Fang", hpMax = 1900, hp = 0.75, swing = 0.12, hasMana = false, power = 3, mpMax = 100, mp = 0.80 }, ["partypet2"] = { name = "Zorbek", hpMax = 1200, hp = 0.60, swing = 0.10, hasMana = true, mpMax = 900, mp = 0.50 }, - ["partypet3"] = { name = "Bramble", hpMax = 2100, hp = 0.40, swing = 0.14, hasMana = false, inc = 300, + ["partypet3"] = { name = "Bramble", hpMax = 2100, hp = 0.40, swing = 0.14, hasMana = false, power = 2, mpMax = 100, mp = 0.60, inc = 300, debuffType = "Disease", debuffTex = "Interface\\Icons\\Spell_Nature_NullifyDisease", debuffCount = 1 }, }; @@ -1127,18 +1181,26 @@ end -- liefert mp, mpMax, hasMana. hasMana ist nur wahr, wenn die Einheit -- tatsaechlich Mana nutzt (Powertyp 0). Krieger, Schurken, Druiden in -- Gestalt und Jaegerbegleiter bekommen keinen Manabalken. +-- Energie einer Einheit: Wert, Maximum, anzeigen?, Energieart (0 = Mana). +-- Ohne HealBox.PowerBar bleibt es beim Mana, Wut, Energie und Fokus liefern +-- dann wie frueher "nichts anzuzeigen" und der Lebensbalken bleibt voll hoch. function FBUnitMana(unit) local g = FBTest_Ghost(unit); if (g) then - if (not g.hasMana) then return 0, 0, false; end - return math.floor(g.mpMax * g.mp), g.mpMax, true; + if (not g.hasMana) then + if (HealBox.PowerBar == 1) and (g.power) and (g.mpMax) then + return math.floor(g.mpMax * (g.mp or 0)), g.mpMax, true, g.power; + end + return 0, 0, false, nil; + end + return math.floor(g.mpMax * g.mp), g.mpMax, true, 0; end local ptype = 0; if (UnitPowerType) then ptype = UnitPowerType(unit); end - if (ptype ~= 0) then return 0, 0, false; end + if (ptype ~= 0) and (HealBox.PowerBar ~= 1) then return 0, 0, false, nil; end local mp, mpMax = UnitMana(unit), UnitManaMax(unit); - if (not mpMax) or (mpMax <= 0) then return 0, 0, false; end - return (mp or 0), mpMax, true; + if (not mpMax) or (mpMax <= 0) then return 0, 0, false, nil; end + return (mp or 0), mpMax, true, ptype; end -- nil (lebt), "dead", "ghost" oder "offline" @@ -1329,6 +1391,7 @@ function FBHealBox_HideAll() end if (panel) then panel:Hide(); end if (MMButton) then MMButton:Hide(); end + FBHealBox_ApplyBlizzParty(); -- Blizzards Gruppenfenster zurueckgeben FBHealBox_RunHook("Suppress"); end @@ -1367,6 +1430,7 @@ function FBHealBox_StartUp() FBHealBox_SyncOptions(); HealBoxAttachMode(HealBox.AttachMode); FBHealBox_ApplyButtonSpacing(); + FBHealBox_ApplyBlizzParty(); if (MMButton) then MMButton:Show(); end FBUpdateNames(); end @@ -1389,6 +1453,7 @@ function FBHealBox_OnLoad() this:RegisterEvent("UNIT_MANA"); this:RegisterEvent("UNIT_MAXMANA"); this:RegisterEvent("UNIT_DISPLAYPOWER"); + this:RegisterEvent("UNIT_INVENTORY_CHANGED"); end -- Fehlende Schluessel in den geladenen SavedVariables nachziehen @@ -1404,6 +1469,9 @@ function FBHealBox_ApplyDefaults() if (HealBox.ButtonSpacing == nil) then HealBox.ButtonSpacing = 2; end if (HealBox.RowSpacing == nil) then HealBox.RowSpacing = 4; end if (HealBox.ManaBar == nil) then HealBox.ManaBar = 1; end + if (HealBox.PowerBar == nil) then HealBox.PowerBar = 0; end + if (HealBox.HideBlizzParty == nil) then HealBox.HideBlizzParty = 0; end + if (HealBox.HealBonus == nil) then HealBox.HealBonus = 1; end if (HealBox.ShowPets == nil) then HealBox.ShowPets = 1; end if (HealBox.ClassColors == nil) then HealBox.ClassColors = 1; end if (HealBox.RangeFade == nil) then HealBox.RangeFade = 1; end @@ -1434,6 +1502,8 @@ function FBHealBox_SyncOptions() if (AttachModeCheck) then AttachModeCheck:SetChecked(HealBox.AttachMode == 1); end if (HealCommCheck) then HealCommCheck:SetChecked(HealBox.HealComm == 1); end if (ManaBarCheck) then ManaBarCheck:SetChecked(HealBox.ManaBar == 1); end + if (PowerBarCheck) then PowerBarCheck:SetChecked(HealBox.PowerBar == 1); end + if (HidePartyCheck) then HidePartyCheck:SetChecked(HealBox.HideBlizzParty == 1); end if (ShowPetsCheck) then ShowPetsCheck:SetChecked(HealBox.ShowPets == 1); end if (TestModeCheck) then TestModeCheck:SetChecked(FBTestMode); end if (ClassColorsCheck) then ClassColorsCheck:SetChecked(HealBox.ClassColors == 1); end @@ -1453,6 +1523,7 @@ function FBHealBox_SyncOptions() FBHealBox_UpdatePlateActionLabels(); FBHealBox_ApplyRightClickLayout(); FBHealBox_UpdateSmartCrossState(); + FBHealBox_UpdatePartyExclusion(); FBHealBox_RunHook("SyncOptions"); end @@ -1472,6 +1543,26 @@ function FBHealBox_UpdateSmartCrossState() end end +-- Anheftmodus und das Verstecken der Blizzard-Frames schliessen sich aus: +-- was gerade nicht geht, wird gesperrt und ausgegraut. +function FBHealBox_UpdatePartyExclusion() + local pairsList = { + { box = AttachModeCheck, blocked = (HealBox.HideBlizzParty == 1) }, + { box = HidePartyCheck, blocked = (HealBox.AttachMode == 1) }, + }; + for _, e in ipairs(pairsList) do + if (e.box) then + if (e.blocked) then + e.box:Disable(); + if (e.box.Text) then e.box.Text:SetTextColor(0.5, 0.5, 0.5, 1); end + else + e.box:Enable(); + if (e.box.Text) then e.box.Text:SetTextColor(1, 0.82, 0, 1); end + end + end + end +end + function FBUnitGUID(unit) if (FBHasSuperWoW and UnitExists(unit)) then local exists, guid = UnitExists(unit); @@ -1528,6 +1619,8 @@ function FBLoadSpellData() -- Tooltips auswerten: welche dieser Zauber sind HoTs bzw. Absorb-Schilde? FBPredict_BuildWatch(); FBSpellNameCache = {}; + FBSpellCastCache = {}; + FBHealBox_InvalidateRangeSpell(); FBHealBox_ProbeAPIs(); -- Doppelt gezaehlte Absorb-Lernwerte (Versionen vor 1.4.2) verwerfen FBPredict_SanitizeMemory(); @@ -1551,6 +1644,7 @@ end -- side = "L" (Standard) oder "R" (Rechtsklick) function FBApplySpellChoice(i, castString, side) if type(castString) == "number" then return; end + FBHealBox_InvalidateRangeSpell(); -- Belegung geaendert local names, icons, ids, fields = FBChoiceTables(side); names[i] = castString; @@ -2075,6 +2169,12 @@ function FBHealBox_OnEvent(event, arg1) -- im naechsten Frame zusammengefasst. if (event == "PARTY_MEMBERS_CHANGED" or event == "UNIT_PET") then FBNamesDirty = true; + FBBlizzPartyDirty = true; -- neue Mitglieder gleich mit verstecken + end + + -- Ausruestung gewechselt: gemerkten +Heilung-Wert verwerfen + if (event == "UNIT_INVENTORY_CHANGED") and (arg1 == "player") then + FBHealBox_InvalidateHealBonus(); end if (event == "UNIT_NAME_UPDATE") then @@ -2543,7 +2643,7 @@ function FBHealBox_DirectAmount(spellName, sd) if (not sd) then return nil; end local info = FBPredict_GetSpellInfo(sd.id, spellName); if (not info) or (not info.direct) or info.shield or info.hot or (not info.isHeal) then return nil; end - return FBPredict_Remembered("direct", spellName, sd.rank) or info.direct; + return FBPredict_ExpectedDirect(spellName, sd.rank, info, sd.id); end -- Alle Zauber, die als kleinere Ausgabe von base gelten (base immer dabei) @@ -2915,6 +3015,18 @@ end -- Buff-Texturen einer Einheit, je Frame nur einmal gelesen: Vorhersage-Scan -- und Buff-Wache brauchen dieselbe Liste im selben Event. +-- Grossschreibung von Texturpfaden einmal merken. Die Pfade wiederholen sich +-- staendig, strupper legt sonst bei jedem Aufruf einen neuen String an, der +-- gleich wieder Muell ist. Die Tabelle bleibt klein: es gibt nur so viele +-- Eintraege wie unterschiedliche Buff-Texturen. +FBTexUpperMemo = {}; +function FBHealBox_UpperTex(tex) + if (not tex) then return nil; end + local u = FBTexUpperMemo[tex]; + if (not u) then u = strupper(tex); FBTexUpperMemo[tex] = u; end + return u; +end + FBBuffScanMemo = {}; -- [unit] = { t, tex = {}, list = {}, n } function FBHealBox_UnitBuffs(unit) local now = GetTime(); @@ -2926,7 +3038,7 @@ function FBHealBox_UnitBuffs(unit) while (i <= 32) do local tex = UnitBuff(unit, i); if (not tex) then break; end - tex = strupper(tex); + tex = FBHealBox_UpperTex(tex); m.tex[tex] = true; m.list[i] = tex; i = i + 1; @@ -3211,10 +3323,24 @@ function FBLOS_HasUnitXP() end -- UnitXP sicher abfragen: true / false, nil wenn nicht verfuegbar +-- Ein Durchlauf hat geworfen, obwohl die Form sich vorher bewaehrt hatte. +-- Dann gehen alle drei Abfragen wieder in den geschuetzten Einzelaufruf: der +-- faengt den Fehler ab, liefert nil und der Rueckfallweg greift wie frueher. +-- Das Addon heilt sich also selbst, statt dauerhaft Fehler zu werfen. +function FBHealBox_ApiFailed() + FBAPI_RangeDirect = false; + FBAPI_DistDirect = false; + FBAPI_SightDirect = false; +end + +-- Wie bei der Reichweite: der erste Aufruf geschuetzt, danach direkt. +FBAPI_SightDirect = false; function FBLOS_QueryUnitXP(unit) if (not UnitXP) then return nil; end + if (FBAPI_SightDirect) then return UnitXP("inSight", "player", unit); end local ok, res = pcall(UnitXP, "inSight", "player", unit); if (not ok) then return nil; end + FBAPI_SightDirect = true; return res; end @@ -3254,6 +3380,10 @@ function FBLOS_Clear(name) end function FBHealBox_CheckLOSAll() + if (not pcall(FBHealBox_CheckLOSSweep)) then FBHealBox_ApiFailed(); end +end + +function FBHealBox_CheckLOSSweep() for p = 1, FBSlotCount do local f = FBPartyFrame[p]; if (f and f.LOSIcon) then @@ -3309,6 +3439,9 @@ function FBHealBox_ProbeAPIs() FBAPI_Probed = true; FBAPI_SpellRange = false; FBAPI_RangeForm = nil; FBAPI_UsableSpell = false; FBAPI_UsableForm = nil; + -- Direktaufruf erst wieder erlauben, wenn eine Form sich bewaehrt hat + FBAPI_RangeDirect = false; FBAPI_DistDirect = false; FBAPI_SightDirect = false; + FBHealBox_ProbeHealBonus(); if (type(IsSpellInRange) == "function") then local ok = pcall(IsSpellInRange, 1, BOOKTYPE_SPELL, "player"); if (ok) then @@ -3329,6 +3462,120 @@ function FBHealBox_ProbeAPIs() end end +-- ========================================================================== +-- [ +Heilung aus der Ausruestung ] +-- +-- Vanilla-Tooltips zeigen den Heilbonus der Ausruestung nicht, der Tooltip +-- eines Zaubers nennt also immer den nackten Grundwert. Gelernte Werte aus +-- dem Combatlog enthalten den Bonus dagegen von selbst, deshalb greift die +-- Rechnung hier nur bei Raengen, die noch nie gewirkt wurden. +-- +-- Den Bonus liefert eine Zusatz-API, wenn eine da ist (ClassicAPI bringt +-- GetSpellBonusHealing mit). Gewichtet wird er wie in Vanilla ueblich mit +-- Zauberzeit geteilt durch 3,5, gedeckelt bei 3,5 Sekunden; Instants rechnen +-- mit 1,5 Sekunden. Ohne API bleibt der Bonus 0 und alles laeuft wie bisher. +-- ========================================================================== + +FBAPI_HealBonusFn = nil; -- gefundene Funktion oder nil +FBSpellCastCache = {}; -- [bookID] = Zauberzeit in Sekunden oder false + +function FBHealBox_ProbeHealBonus() + FBAPI_HealBonusFn = nil; + FBHealBonusValue = nil; + local names = { "GetSpellBonusHealing", "GetSpellBonusHeal", "GetHealingBonus" }; + for _, n in ipairs(names) do + local fn = getglobal(n); + if (type(fn) == "function") then + local ok, v = pcall(fn); + if (ok) and (type(v) == "number") then FBAPI_HealBonusFn = fn; return; end + end + end + -- ClassicAPI legt seine Nachbauten je nach Fassung in eine eigene Tabelle + if (type(ClassicAPI) == "table") then + local t = ClassicAPI.API or ClassicAPI; + for _, n in ipairs(names) do + local fn = t[n]; + if (type(fn) == "function") then + local ok, v = pcall(fn); + if (ok) and (type(v) == "number") then FBAPI_HealBonusFn = fn; return; end + end + end + end +end + +-- Aktueller +Heilung-Wert oder 0. +-- +-- Der Wert aendert sich nur beim Ausruestungswechsel, wird aber bei einem +-- Smart-Healing-Klick fuer jeden Kandidaten gebraucht (mit Heilketten bis zu +-- fuenfzehn). Deshalb einmal lesen und merken; UNIT_INVENTORY_CHANGED und +-- der Zauberbuch-Neuaufbau werfen den Wert weg. +FBHealBonusValue = nil; + +function FBHealBox_InvalidateHealBonus() + FBHealBonusValue = nil; +end + +function FBHealBox_HealingBonus() + if (not FBAPI_Probed) then FBHealBox_ProbeAPIs(); end + if (HealBox.HealBonus ~= 1) or (not FBAPI_HealBonusFn) then return 0; end + if (FBHealBonusValue ~= nil) then return FBHealBonusValue; end + local v; + local ok, res = pcall(FBAPI_HealBonusFn); + if (ok) and (type(res) == "number") and (res > 0) then v = res; else v = 0; end + FBHealBonusValue = v; + return v; +end + +-- Zauberzeit aus dem Tooltip. Sie steht rechts in der zweiten Zeile +-- ("2.5 sec cast"), Instants stehen dort als "Instant cast". 0 = Instant. +function FBHealBox_SpellCastSeconds(id) + if (not id) then return nil; end + local c = FBSpellCastCache[id]; + if (c ~= nil) then return c or nil; end + -- Regelfall ist der Treffer oben: FBPredict_TooltipText fuellt den + -- Zwischenspeicher beim Auslesen der Zauberdaten mit. Hierher kommt nur, + -- wer noch nie durch die Tooltip-Auswertung gelaufen ist. + FBPredictTip:SetOwner(UIParent, "ANCHOR_NONE"); + FBPredictTip:ClearLines(); + FBPredictTip:SetSpell(id, BOOKTYPE_SPELL); + local secs = nil; + for i = 1, 6 do + for _, side in ipairs({ "Left", "Right" }) do + local fs = getglobal("FBHealBoxScanTipText"..side..i); + if (fs and fs:IsShown()) then + local t = fs:GetText(); + if (t) then + local _, _, v = string.find(t, "([%d%.]+)%s+[Ss]ec%s+cast"); + if (v) then secs = tonumber(v); end + if (not secs) and string.find(t, "[Ii]nstant") then secs = 0; end + end + end + end + end + FBSpellCastCache[id] = secs or false; + return secs; +end + +-- Anteil des +Heilung-Werts, der auf diesen Zauber entfaellt +function FBHealBox_HealBonusFor(bookID) + local bonus = FBHealBox_HealingBonus(); + if (bonus <= 0) then return 0; end + local secs = FBHealBox_SpellCastSeconds(bookID); + if (not secs) then secs = 2.5; end -- Zauberzeit unlesbar: mittlerer Wert + if (secs <= 0) then secs = 1.5; end -- Instants rechnen mit 1,5 Sekunden + if (secs > 3.5) then secs = 3.5; end + return bonus * (secs / 3.5); +end + +-- Erwartete Sofortheilung eines Rangs: gelernter Wert, sonst Tooltip plus +-- anteiliger Ausruestungsbonus +function FBPredict_ExpectedDirect(spellName, rank, info, bookID) + local learned = FBPredict_Remembered("direct", spellName, rank); + if (learned) then return learned; end + if (not info) or (not info.direct) then return nil; end + return info.direct + FBHealBox_HealBonusFor(bookID); +end + -- Reichweite eines Zaubers aus dem Tooltip (Meter) oder nil function FBHealBox_SpellRangeYards(id) local c = FBSpellRangeCache[id]; @@ -3358,12 +3605,23 @@ function FBHealBox_SpellInRange(id, unit) if (not FBAPI_Probed) then FBHealBox_ProbeAPIs(); end if (FBAPI_SpellRange) then local ok, r; - if (FBAPI_RangeForm == 3) then + -- Der erste Aufruf je Form laeuft geschuetzt. Danach ist bewiesen, + -- dass der Client sie versteht, und pcall faellt weg: im Vierzigerraid + -- waren das 80 geschuetzte Aufrufe je Sekunde nur fuer die Reichweite. + if (FBAPI_RangeDirect) then + ok = true; + if (FBAPI_RangeForm == 3) then + r = IsSpellInRange(id, BOOKTYPE_SPELL, unit); + else + r = IsSpellInRange(FBHealBox_SpellNameOf(id), unit); + end + elseif (FBAPI_RangeForm == 3) then ok, r = pcall(IsSpellInRange, id, BOOKTYPE_SPELL, unit); else ok, r = pcall(IsSpellInRange, FBHealBox_SpellNameOf(id), unit); end if (ok) then + FBAPI_RangeDirect = true; if (r == 1 or r == true) then return 1; end if (r == 0 or r == false) then return 0; end return nil; @@ -3373,8 +3631,14 @@ function FBHealBox_SpellInRange(id, unit) end local range = FBHealBox_SpellRangeYards(id); if (UnitXP and range) then - local ok, d = pcall(UnitXP, "distanceBetween", "player", unit); + local ok, d; + if (FBAPI_DistDirect) then + ok = true; d = UnitXP("distanceBetween", "player", unit); + else + ok, d = pcall(UnitXP, "distanceBetween", "player", unit); + end if (ok and type(d) == "number") then + FBAPI_DistDirect = true; if (d <= range) then return 1; else return 0; end end end @@ -3419,11 +3683,23 @@ end FBRangeAccum = 0; +-- Erster belegter Button: Zauber fuer die Reichweitenpruefung. Wird je +-- Einheit und Durchlauf gebraucht, im Vierzigerraid also hundertfach je +-- Sekunde, aendert sich aber nur beim Umbelegen. Also merken. +FBRangeSpellCached = nil; -- false = keiner belegt, nil = neu suchen + +function FBHealBox_InvalidateRangeSpell() + FBRangeSpellCached = nil; +end + function FBHealBox_RangeSpellID() + if (FBRangeSpellCached ~= nil) then return FBRangeSpellCached or nil; end + local found = false; for i = 1, MaxButtonCount do - if (FBActiveSpellIDs[i]) then return FBActiveSpellIDs[i]; end + if (FBActiveSpellIDs[i]) then found = FBActiveSpellIDs[i]; break; end end - return nil; + FBRangeSpellCached = found; + return found or nil; end function FBHealBox_UnitInRange(unit) @@ -3443,7 +3719,14 @@ function FBHealBox_UnitInRange(unit) return true; end -function FBHealBox_CheckRangeAll() +-- Der Durchlauf selbst laeuft geschuetzt: ein pcall je Durchlauf statt eines +-- je Einheit. Das kostet praktisch nichts und haelt einen spaeten Fehler aus +-- den Reichweiten-Abfragen trotzdem vom Frame fern. +function FBHealBox_CheckRangeAll() + if (not pcall(FBHealBox_CheckRangeSweep)) then FBHealBox_ApiFailed(); end +end + +function FBHealBox_CheckRangeSweep() for p = 1, FBSlotCount do local f = FBPartyFrame[p]; if (f) then @@ -3550,6 +3833,15 @@ function FBHealBox_ApplyLocale() SmartCrossCheck.Text:SetText(FBT("SMARTCROSS")); SmartCrossCheck.tooltipText = FBT("SMARTCROSS_TIP"); end + if (HidePartyCheck) then + HidePartyCheck.Text:SetText(FBT("HIDEPARTY")); + HidePartyCheck.tooltipText = FBT("HIDEPARTY_TIP"); + end + if (PowerBarCheck) then + PowerBarCheck.Text:SetText(FBT("POWERBAR")); + PowerBarCheck.tooltipText = FBT("POWERBAR_TIP"); + end + FBHealBox_UpdatePartyExclusion(); FBUpdateSmartMarginText(); FBHealBox_UpdateSmartCrossState(); if (CooldownsCheck) then @@ -3954,6 +4246,10 @@ function FBHealBoxCreateAddonOptionFrame() local cy = gy - 95; AttachModeCheck = FBHealBox_CreateCheck("$parentCheckButton", tabGeneral, 40, cy, "ATTACH", "ATTACH_TIP", function() HealBox.AttachMode = AttachModeCheck:GetChecked() and 1 or 0; + -- Anheften und Verstecken schliessen sich aus + if (HealBox.AttachMode == 1) then HealBox.HideBlizzParty = 0; end + if (HidePartyCheck) then HidePartyCheck:SetChecked(HealBox.HideBlizzParty == 1); end + FBHealBox_UpdatePartyExclusion(); HealBoxAttachMode(HealBox.AttachMode); FBUpdateNames(); end); @@ -4039,8 +4335,29 @@ function FBHealBoxCreateAddonOptionFrame() end); BuffIconsCheck:SetChecked(1); + HidePartyCheck = FBHealBox_CreateCheck("FBHealBoxHidePartyCheck", tabGeneral, 40, cy - 210, "HIDEPARTY", "HIDEPARTY_TIP", function() + HealBox.HideBlizzParty = HidePartyCheck:GetChecked() and 1 or 0; + if (HealBox.HideBlizzParty == 1) then + HealBox.AttachMode = 0; + if (AttachModeCheck) then AttachModeCheck:SetChecked(nil); end + HealBoxAttachMode(0); + FBUpdateNames(); + end + FBHealBox_UpdatePartyExclusion(); + FBHealBox_ApplyBlizzParty(); + end); + HidePartyCheck:SetChecked(nil); + + PowerBarCheck = FBHealBox_CreateCheck("FBHealBoxPowerBarCheck", tabGeneral, 250, cy - 210, "POWERBAR", "POWERBAR_TIP", function() + HealBox.PowerBar = PowerBarCheck:GetChecked() and 1 or 0; + FBHealBox_InvalidateUnitCaches(); + FBHealBox_RefreshAllBars(); + FBHealBox_RunHook("SyncOptions"); + end); + PowerBarCheck:SetChecked(nil); + -- [ Sprache und Buff-Wache ] ------------------------------------------- - local py = cy - 215; + local py = cy - 245; FBLangBtn = FBHealBox_CreatePickButton("FBHealBoxLangBtn", tabGeneral, 35, py, 180, 26, 18); FBLangBtn.icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01"); FBLangBtn:SetScript("OnEnter", function() @@ -4185,6 +4502,7 @@ function FBHealBoxButtons() end function FBHealBoxButtonsChanged() + FBHealBox_InvalidateRangeSpell(); local rightOn = (HealBox.RightClick == 1); for p = 1, FBSlotCount do local unit = FBPartyUnit[p]; @@ -4462,6 +4780,46 @@ function FBHealBox_BlizzAnchor(p) return nil; end +-- ========================================================================== +-- [ Blizzards Gruppenfenster ] +-- +-- Der Anheftmodus haengt die Plaketten genau an PartyMemberFrame1 bis 4. +-- Beides gleichzeitig ergibt keinen Sinn, deshalb sperren sich die zwei +-- Optionen im Optionsfenster gegenseitig, und diese Pruefung hier ist der +-- Sicherheitsgurt fuer alte gespeicherte Werte. Im Ruhezustand der +-- Klassensperre gibt das Addon die Frames ebenfalls wieder frei. +-- ========================================================================== + +function FBHealBox_HideBlizzPartyActive() + if (FBAddonSuppressed) then return false; end + if (HealBox.AttachMode == 1) then return false; end + return (HealBox.HideBlizzParty == 1); +end + +function FBHealBox_ApplyBlizzParty() + local hide = FBHealBox_HideBlizzPartyActive(); + for i = 1, 4 do + local f = getglobal("PartyMemberFrame"..i); + if (f) then + -- Blizzard zeigt die Frames bei jeder Gruppenaenderung neu an, + -- deshalb einmalig OnShow abfangen statt nur einmal zu verstecken + if (not f.fbShowHooked) then + f.fbShowHooked = true; + f.fbOldOnShow = f:GetScript("OnShow"); + f:SetScript("OnShow", function() + if (this.fbOldOnShow) then this.fbOldOnShow(); end + if (FBHealBox_HideBlizzPartyActive()) then this:Hide(); end + end); + end + if (hide) then + f:Hide(); + elseif (UnitExists("party"..i)) then + f:Show(); + end + end + end +end + function HealBoxAttachMode(mode) if (not FBHealBox1) then return; end @@ -4509,6 +4867,7 @@ function HealBoxAttachMode(mode) end FBHealBox_Layout(); end + FBHealBox_ApplyBlizzParty(); end -- ========================================================================== @@ -4588,14 +4947,34 @@ function FBPredict_TooltipText(bookID) FBPredictTip:SetSpell(bookID, BOOKTYPE_SPELL); local txt = ""; + local secs = nil; local i = 1; while (i <= 30) do local fs = getglobal("FBHealBoxScanTipTextLeft"..i); if (not fs or not fs:IsShown()) then break; end local line = fs:GetText(); if (line) then txt = txt.." "..line; end + -- Die Zauberzeit steht rechts ("2.5 sec cast", "Instant cast"). Sie + -- kommt bewusst nicht in txt: eine Zahl wie 2.5 wuerde die Muster + -- fuer Schilddauer und Heilbetrag durcheinanderbringen. Hier wird sie + -- nur nebenbei gelesen, solange der Tooltip ohnehin steht, und + -- gemerkt. Der Ausruestungsbonus braucht dann keinen eigenen Scan. + if (secs == nil) then + local rs = getglobal("FBHealBoxScanTipTextRight"..i); + if (rs and rs:IsShown()) then + local rline = rs:GetText(); + if (rline) then + local _, _, v = string.find(rline, "([%d%.]+)%s+[Ss]ec%s+cast"); + if (v) then secs = tonumber(v); end + if (secs == nil) and string.find(rline, "[Ii]nstant") then secs = 0; end + end + end + end i = i + 1; end + if (FBSpellCastCache[bookID] == nil) then + FBSpellCastCache[bookID] = secs or false; + end return txt; end @@ -4823,22 +5202,47 @@ function FBPredict_StartBuff(unitName, spellName, secs) FBBuffIconsDirty = true; end --- Restlaufzeit eines eigenen Buffs (Textur) ueber die Spielerbuff-API -function FBPredict_PlayerBuffTimeLeft(tex) - if (not GetPlayerBuff) or (not GetPlayerBuffTexture) or (not GetPlayerBuffTimeLeft) then return nil; end +-- Restlaufzeiten aller eigenen Buffs, [TEXTUR] = Sekunden. +-- +-- Frueher lief je gesuchtem Buff eine eigene Schleife ueber die gesamte +-- Buffliste. Bei sechs Wachen und dreissig Buffs waren das rund 200 Aufrufe +-- je Sekunde. Jetzt wird die Liste einmal je Frame gelesen und alle Wachen +-- bedienen sich daraus; der Zeitstempel arbeitet wie bei FBHealBox_UnitBuffs. +FBPlayerBuffLeft = {}; +FBPlayerBuffStamp = nil; + +function FBPredict_ScanPlayerBuffTimes() + local now = GetTime(); + if (FBPlayerBuffStamp == now) then return FBPlayerBuffLeft; end + FBPlayerBuffStamp = now; + for k in pairs(FBPlayerBuffLeft) do FBPlayerBuffLeft[k] = nil; end + if (not GetPlayerBuff) or (not GetPlayerBuffTexture) or (not GetPlayerBuffTimeLeft) then + return FBPlayerBuffLeft; + end local i = 0; while true do local id = GetPlayerBuff(i, "HELPFUL"); if (not id) or (id < 0) then break; end local t = GetPlayerBuffTexture(id); - if (t and strupper(t) == tex) then + if (t) then local left = GetPlayerBuffTimeLeft(id); - if (left and left > 0) then return left; end - return nil; + if (left and left > 0) then + local up = FBHealBox_UpperTex(t); + -- gleiche Textur mehrfach (etwa gestapelte Buffs): laengste Zeit + if (not FBPlayerBuffLeft[up]) or (left > FBPlayerBuffLeft[up]) then + FBPlayerBuffLeft[up] = left; + end + end end i = i + 1; end - return nil; + return FBPlayerBuffLeft; +end + +-- Restlaufzeit eines eigenen Buffs (Textur, gross geschrieben) oder nil +function FBPredict_PlayerBuffTimeLeft(tex) + if (not tex) then return nil; end + return FBPredict_ScanPlayerBuffTimes()[tex]; end -- [ Lernspeicher (wandert in die SavedVariables) ] -------------------------- @@ -5056,7 +5460,7 @@ function FBPredict_CastStart(spellName, castMs) local info = FBPredict_GetSpellInfo(bookID, spellName); if (not info) or (not info.direct) then return; end - local amount = FBPredict_Remembered("direct", spellName, rank) or info.direct; + local amount = FBPredict_ExpectedDirect(spellName, rank, info, bookID) or info.direct; FBLOS_Clear(FBPredict_ResolveTarget()); -- Cast laeuft an: Sichtlinie ist da FBPredictDirect = { @@ -5196,6 +5600,22 @@ function FBPredict_ToPattern(gs, anchor) return p; end +-- Feststehendes Wortstueck aus einer Client-Vorlage schneiden: alles vor dem +-- ersten Platzhalter faellt weg, alles ab dem naechsten ebenfalls, uebrig +-- bleibt ein Stueck reiner Text fuer den billigen Vortest. Bei "Your %s heals +-- %s for %d." ist das " heals ". Klappt das nicht, greift der Rueckfall. +function FBPredict_PlainPart(template, fallback) + if (not template) or (type(template) ~= "string") then return fallback; end + local s1, e1 = string.find(template, "%%%a"); + if (not s1) then return fallback; end + local rest = string.sub(template, e1 + 1); + local s2 = string.find(rest, "%%%a"); + if (s2) then rest = string.sub(rest, 1, s2 - 1); end + -- Satzzeichen am Ende stoeren nicht, zu kurze Stuecke taugen aber nichts + if (string.len(rest) < 4) then return fallback; end + return rest; +end + function FBPredict_InitPatterns() -- "%s gains %d health from your %s." FBPredictPatHoTOther = FBPredict_ToPattern(PERIODICAURAHEALOTHERSELF, true) @@ -5213,6 +5633,13 @@ function FBPredict_InitPatterns() -- " (%d absorbed)" FBPredictPatAbsorb = FBPredict_ToPattern(ABSORB_TRAILER, false) or "%((%d+) absorbed%)"; + -- Vortests: " heals " und " health from your ". + -- Beim Tick wird bewusst die Selbst-Vorlage genommen: "You gain ..." und + -- "%s gains ..." unterscheiden sich im Verb, das Stueck dahinter ist in + -- beiden gleich. Ein aus der Fremd-Vorlage geschnittenes " gains " wuerde + -- die eigenen Ticks aussperren. + FBPRED_WORD_HEAL = FBPredict_PlainPart(HEALEDSELFOTHER, " heals "); + FBPRED_WORD_TICK = FBPredict_PlainPart(PERIODICAURAHEALSELFSELF, " health from your "); end -- Events, bei denen das Opfer immer der Spieler selbst ist @@ -5343,32 +5770,40 @@ function FBPredict_ParseCombat(event, msg) return; end - -- Direktheilung auf jemand anderen: "Your Flash Heal heals Bob for 1240." - local _, _, spell, who, amt = string.find(msg, FBPredictPatHealOther); - if (spell and who and amt) then - FBPredict_OnDirectHeal(spell, who, tonumber(amt)); - return; + -- Vor den verankerten Mustern ein billiger Textvergleich (viertes + -- Argument true = einfache Suche ohne Musterlogik). Fremde Heilungen und + -- fremde Ticks fallen so nach einem Vergleich raus statt nach vier + -- Musterlaeufen; im Raid sind das die meisten Meldungen. + if (string.find(msg, FBPRED_WORD_HEAL, 1, true)) then + -- Direktheilung auf jemand anderen: "Your Flash Heal heals Bob for 1240." + local _, _, spell, who, amt = string.find(msg, FBPredictPatHealOther); + if (spell and who and amt) then + FBPredict_OnDirectHeal(spell, who, tonumber(amt)); + return; + end + + -- Direktheilung auf mich: "Your Flash Heal heals you for 1240." + local _, _, spell2, amt2 = string.find(msg, FBPredictPatHealSelf); + if (spell2 and amt2) then + FBPredict_OnDirectHeal(spell2, UnitName("player"), tonumber(amt2)); + return; + end end - -- Direktheilung auf mich: "Your Flash Heal heals you for 1240." - local _, _, spell2, amt2 = string.find(msg, FBPredictPatHealSelf); - if (spell2 and amt2) then - FBPredict_OnDirectHeal(spell2, UnitName("player"), tonumber(amt2)); - return; - end + if (string.find(msg, FBPRED_WORD_TICK, 1, true)) then + -- HoT-Tick auf jemand anderen: "Bob gains 194 health from your Renew." + local _, _, who3, amt3, spell3 = string.find(msg, FBPredictPatHoTOther); + if (who3 and amt3 and spell3) then + FBPredict_OnTick(who3, tonumber(amt3), spell3); + return; + end - -- HoT-Tick auf jemand anderen: "Bob gains 194 health from your Renew." - local _, _, who3, amt3, spell3 = string.find(msg, FBPredictPatHoTOther); - if (who3 and amt3 and spell3) then - FBPredict_OnTick(who3, tonumber(amt3), spell3); - return; - end - - -- HoT-Tick auf mich: "You gain 194 health from your Renew." - local _, _, amt4, spell4 = string.find(msg, FBPredictPatHoTSelf); - if (amt4 and spell4) then - FBPredict_OnTick(UnitName("player"), tonumber(amt4), spell4); - return; + -- HoT-Tick auf mich: "You gain 194 health from your Renew." + local _, _, amt4, spell4 = string.find(msg, FBPredictPatHoTSelf); + if (amt4 and spell4) then + FBPredict_OnTick(UnitName("player"), tonumber(amt4), spell4); + return; + end end end @@ -5456,6 +5891,7 @@ FBPredictFrame:SetScript("OnUpdate", function() end); FBNamesDirty = false; +FBBlizzPartyDirty = false; FBBtnStatesDirty = nil; FBBuffIconsDirty = true; FBBuffIconsAccum = 0; @@ -5466,6 +5902,12 @@ function FBPredict_OnUpdate(elapsed) FBNamesDirty = false; FBUpdateNames(); end + -- Blizzards Gruppenfenster im selben Sammelpunkt, statt bei jedem + -- einzelnen Ereignis einer Salve + if (FBBlizzPartyDirty) then + FBBlizzPartyDirty = false; + FBHealBox_ApplyBlizzParty(); + end -- Aufgeschobene Button-Zustaende (USABLE schlaegt COOLDOWN, da es beides tut) if (FBBtnStatesDirty) then local ev = FBBtnStatesDirty; @@ -5806,6 +6248,21 @@ SlashCmdList["FBHEALPREDICT"] = function(msg) return; end + -- Ausruestungsbonus in der Vorhersage an/aus + if (msg == "healbonus" or msg == "bonus") then + if (not FBAPI_Probed) then FBHealBox_ProbeAPIs(); end + if (not FBAPI_HealBonusFn) then + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r "..FBT("HEALBONUS_NA")); + return; + end + if (HealBox.HealBonus == 1) then HealBox.HealBonus = 0; else HealBox.HealBonus = 1; end + local key = "HEALBONUS_OFF"; + if (HealBox.HealBonus == 1) then key = "HEALBONUS_ON"; end + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00"..FBADDON_NAME..":|r " + ..format(FBT(key), math.floor(FBHealBox_HealingBonus() + 0.5))); + return; + end + -- Abrangen ueber Zaubergrenzen (Heilketten) an/aus if (msg == "smartcross" or msg == "cross") then if (HealBox.SmartCross == 1) then HealBox.SmartCross = 0; else HealBox.SmartCross = 1; end @@ -5858,6 +6315,14 @@ SlashCmdList["FBHEALPREDICT"] = function(msg) return; end + if (not FBAPI_Probed) then FBHealBox_ProbeAPIs(); end + if (FBAPI_HealBonusFn) then + local state = FBT("FBP_STATE_OFF"); + if (HealBox.HealBonus == 1) then state = FBT("FBP_STATE_ON"); end + DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..format(FBT("FBP_HEALBONUS"), + math.floor(FBHealBox_HealingBonus() + 0.5), state)); + end + DEFAULT_CHAT_FRAME:AddMessage("|cFF00FFFF[FBP]|r "..FBT("FBP_WATCHED")); for spellName, w in pairs(FBPredictWatch) do local info = FBPredict_GetSpellInfo(w.bookID, spellName); @@ -6136,7 +6601,8 @@ end function FBHealBox_BuffIconList(unitName, g, out) local n = 0; if (g) then - for _, b in ipairs(g.buffs or {}) do + if (not g.buffs) then return 0; end + for _, b in ipairs(g.buffs) do n = n + 1; local e = out[n] or {}; out[n] = e; e.tex = b.tex; e.expires = b.left and (GetTime() + b.left); e.duration = b.dur; e.name = "Test"; @@ -6254,19 +6720,31 @@ function FBHealBox_UpdateAllBuffIcons() FBHealBox_RunHook("BuffIcons"); end +-- Balkenfarbe der Energieart anpassen. Der Zwischenspeicher am Rahmen +-- verhindert, dass die Farbe bei jedem Durchlauf neu gesetzt wird. +function FBHealBox_SetPowerColor(bar, cache, ptype) + if (not bar) then return; end + local t = ptype or 0; + if (cache) and (cache.powerType == t) then return; end + if (cache) then cache.powerType = t; end + local c = FBPOWER_COLORS[t] or FBMANA_BAR_COLOR; + bar:SetStatusBarColor(c[1], c[2], c[3], c[4]); +end + -- Manabalken: nur wenn eingeschaltet, die Plakette sichtbar ist und die -- Einheit tatsaechlich Mana nutzt. Sonst weg, dann ist der Lebensbalken -- wieder auf voller Hoehe zu sehen. function FBHealBox_UpdateMana(unit, frame) if (not frame.ManaBar) then return; end - local mp, mpMax, hasMana = 0, 0, false; + local mp, mpMax, hasMana, ptype = 0, 0, false, nil; if (HealBox.ManaBar == 1) and (not frame.plateHidden) then - mp, mpMax, hasMana = FBUnitMana(unit); + mp, mpMax, hasMana, ptype = FBUnitMana(unit); end if (not hasMana) then if (frame.manaShown ~= false) then frame.manaShown = false; frame.ManaBar:Hide(); end return; end + FBHealBox_SetPowerColor(frame.ManaBar, frame, ptype); if (frame.lastMpMax ~= mpMax) then frame.lastMpMax = mpMax; frame.ManaBar:SetMinMaxValues(0, mpMax); @@ -6279,7 +6757,7 @@ end function FBHealBox_InvalidateUnitCaches() for p = 1, FBSlotCount do local f = FBPartyFrame[p]; - if (f) then f.dispelKnown = nil; f.lastText = nil; f.lastMax = nil; f.colorKey = nil; f.manaShown = nil; f.lastMpMax = nil; f.vHp = nil; f.vShield = nil; f.vInc = nil; f.vMp = nil; end + if (f) then f.dispelKnown = nil; f.lastText = nil; f.lastMax = nil; f.colorKey = nil; f.manaShown = nil; f.lastMpMax = nil; f.powerType = nil; f.vHp = nil; f.vShield = nil; f.vInc = nil; f.vMp = nil; end end end diff --git a/FBHealBox.toc b/FBHealBox.toc index 6cf5094..840abb8 100644 --- a/FBHealBox.toc +++ b/FBHealBox.toc @@ -1,9 +1,9 @@ ## Interface: 11200 -## Title: Heal Box Vanilla |cFFFFFF00v1.4.4.3|r +## Title: Heal Box Vanilla |cFFFFFF00v1.4.5|r ## Notes: Heal/raid frame addon like Healium, with quick-cast buttons, heal prediction, mana ticker and more. ## Notes-deDE: Heal/Raidframe Addon aehnlich Healium, mit Schnellzugriff Buttons, Heal Prediction und Mana Ticker und mehr. ## Author: Dourd (Argent Dawn EU) | Vanilla-Port & Erweiterung 09/2026: Mquadrat -## Version: 1.4.4.3 +## Version: 1.4.5 ## SavedVariablesPerCharacter: HealBox ## OptionalDeps: SuperWoW diff --git a/FBHealBox_Raid.lua b/FBHealBox_Raid.lua index 66f881d..c04dfe2 100644 --- a/FBHealBox_Raid.lua +++ b/FBHealBox_Raid.lua @@ -28,7 +28,8 @@ FBRAID_MAX = 40; FBRAID_GROUPS = 8; FBRAID_PER_GROUP = 5; FBRAID_MAX_BUTTONS = 4; -FBRAID_TICK = 0.5; -- Sek.: Reichweite und Sichtlinie +FBRAID_TICK = 0.5; -- Sek.: voller Durchlauf Reichweite und Sichtlinie +FBRAID_TICK_SLICES = 4; -- in so viele Haeppchen zerlegt (je 2 Gruppen) FBRAID_MANA_H = 3; -- px Manastreifen in der Zelle FBRAID_LOS_SIZE = 10; -- px Sichtlinien-Auge in der Zelle FBRAID_HEADER_H = 12; -- px Gruppenkopf @@ -437,8 +438,14 @@ end function FBRaid_Mana(cell) local g = cell.ghost; if (g) then - if (not g.hasMana) then return 0, 0, false; end - return math.floor(g.mpMax * g.mp), g.mpMax, true; + if (not g.hasMana) then + -- Wut, Energie und Fokus nur, wenn der Kern sie zeigen soll + if (HealBox.PowerBar == 1) and (g.power) and (g.mpMax) then + return math.floor(g.mpMax * (g.mp or 0)), g.mpMax, true, g.power; + end + return 0, 0, false, nil; + end + return math.floor(g.mpMax * g.mp), g.mpMax, true, 0; end return FBUnitMana(cell.unit); end @@ -738,7 +745,7 @@ function FBRaid_UpdateRoster() if (FBRaidCells[g] and FBRaidCells[g][pos]) then local c = FBRaidCells[g][pos]; c.unit = nil; c.name = nil; c.ghost = nil; c.index = nil; - c.dispelKnown = nil; c.lastText = nil; c.lastMax = nil; c.colorKey = nil; c.manaShown = nil; c.lastMpMax = nil; + c.dispelKnown = nil; c.lastText = nil; c.lastPct = nil; c.lastDef = nil; c.lastMax = nil; c.colorKey = nil; c.manaShown = nil; c.lastMpMax = nil; c.powerType = nil; c.vHp = nil; c.vShield = nil; c.vInc = nil; c.vMp = nil; c:Hide(); end @@ -940,6 +947,7 @@ function FBRaid_UpdateCell(c, auraChanged) if (state == "ghost") then key = "STATE_GHOST"; end if (state == "offline") then key = "STATE_OFFLINE"; end FBRaid_SetText(c, FBT(key)); + c.lastPct = nil; c.lastDef = nil; -- Zahlenspeicher gilt nicht mehr FBRaid_SetMax(c, hpMax); FBRaid_SetValues(c, 0, 0, 0); FBRaid_SetColor(c, "state", 0.5, 0.5, 0.5, 1); @@ -956,19 +964,29 @@ function FBRaid_UpdateCell(c, auraChanged) if (incTop > hpMax) then incTop = hpMax; end FBRaid_SetValues(c, hp, shieldTop, incTop); - -- HP-Text + -- HP-Text. Verglichen wird die Zahl, nicht der fertige Text: sonst baut + -- jedes UNIT_HEALTH einen String, der meist sofort wieder verworfen wird. if (cfg.HPText == "percent") then - FBRaid_SetText(c, math.floor(hp / hpMax * 100).."%"); + local pct = math.floor(hp / hpMax * 100); + if (c.lastPct ~= pct) then + c.lastPct = pct; c.lastDef = nil; + FBRaid_SetText(c, pct.."%"); + end elseif (cfg.HPText == "deficit") then local def = hpMax - hp; - if (def > 0) then FBRaid_SetText(c, "-"..def); else FBRaid_SetText(c, ""); end + if (c.lastDef ~= def) then + c.lastDef = def; c.lastPct = nil; + if (def > 0) then FBRaid_SetText(c, "-"..def); else FBRaid_SetText(c, ""); end + end else + c.lastPct = nil; c.lastDef = nil; FBRaid_SetText(c, false); end -- Mana - local mp, mpMax, hasMana = FBRaid_Mana(c); + local mp, mpMax, hasMana, ptype = FBRaid_Mana(c); if (cfg.ManaBar == 1 and hasMana and mpMax > 0) then + FBHealBox_SetPowerColor(c.ManaBar, c, ptype); if (c.lastMpMax ~= mpMax) then c.lastMpMax = mpMax; c.ManaBar:SetMinMaxValues(0, mpMax); end if (c.vMp ~= mp) then c.vMp = mp; c.ManaBar:SetValue(mp); end if (c.manaShown ~= true) then c.manaShown = true; c.ManaBar:Show(); end @@ -1199,10 +1217,30 @@ function FBRaid_SyncButtons() end -- Reichweite, Sichtlinie und Geister-Atmung im Takt +-- Reichweite und Sichtlinie kosten je Zelle einen Aufruf ins Spiel hinein. +-- Bei acht Gruppen waeren das in einem Rutsch bis zu 80 Abfragen in einem +-- einzigen Frame. Deshalb wandert der Durchlauf in Haeppchen: je Tick nur +-- ein Teil der Gruppen, nach FBRAID_TICK_SLICES Ticks sind alle einmal dran. +-- Die Taktdauer ist entsprechend kuerzer, der volle Durchlauf dauert also +-- weiterhin FBRAID_TICK Sekunden. force = true macht alles auf einmal. +FBRaidTickSlice = 0; + +-- Der Durchlauf laeuft geschuetzt, aber nur einmal je Tick statt einmal je +-- Zelle: im Vierzigerraid ein pcall alle 0,125 Sekunden statt achtzig je +-- Sekunde. Wirft eine Abfrage doch, gehen die Direktaufrufe zurueck in den +-- geschuetzten Einzelaufruf und der Rueckfallweg greift wieder. function FBRaid_Tick(force) if (not FBRaid_IsActive()) then return; end + local slice = FBRaidTickSlice; + FBRaidTickSlice = math.mod(FBRaidTickSlice + 1, FBRAID_TICK_SLICES); + if (not pcall(FBRaid_TickSweep, slice, force)) then + if (FBHealBox_ApiFailed) then FBHealBox_ApiFailed(); end + end +end + +function FBRaid_TickSweep(slice, force) for g = 1, FBRAID_GROUPS do - if (FBRaidCells[g]) then + if (FBRaidCells[g]) and (force or (math.mod(g - 1, FBRAID_TICK_SLICES) == slice)) then for pos = 1, FBRAID_PER_GROUP do local c = FBRaidCells[g][pos]; if (c and c.unit and c:IsShown()) then @@ -1264,7 +1302,7 @@ FBRaidEventFrame:SetScript("OnUpdate", function() FBRaid_RefreshBuffBorders(); end FBRaidTickAccum = FBRaidTickAccum + (arg1 or 0); - if (FBRaidTickAccum < FBRAID_TICK) then return; end + if (FBRaidTickAccum < (FBRAID_TICK / FBRAID_TICK_SLICES)) then return; end FBRaidTickAccum = 0; FBRaid_Tick(false); end); diff --git a/FBHealBox_Ticker.lua b/FBHealBox_Ticker.lua index 8d3c265..78d7b9d 100644 --- a/FBHealBox_Ticker.lua +++ b/FBHealBox_Ticker.lua @@ -422,8 +422,19 @@ FBTickerFrame:SetScript("OnEvent", function() end FBTicker_UpdateGate(); end); +-- Der Funke wandert in fuenf Sekunden ueber den Balken, ein Neuzeichnen in +-- jedem Frame bringt dabei kein sichtbar weicheres Bild. Ein fester Takt von +-- 0,03 Sekunden (rund 33 Bilder je Sekunde) spart auf schnellen Rechnern die +-- Haelfte der Durchlaeufe samt der Breiten- und Hoehenabfragen je Balken. +FBTickerDrawAccum = 0; +FBTICK_DRAW_STEP = 0.03; + FBTickerFrame:SetScript("OnUpdate", function() if (FBAddonSuppressed) then return; end + if (not FBTicker.gate) then return; end + FBTickerDrawAccum = FBTickerDrawAccum + (arg1 or 0); + if (FBTickerDrawAccum < FBTICK_DRAW_STEP) then return; end + FBTickerDrawAccum = 0; FBTicker_Draw(); end); @@ -529,7 +540,7 @@ FBHealBox_RegisterHook("Loaded", function() end); -- Klassensperre des Kerns: Funken ausknipsen FBHealBox_RegisterHook("Suppress", function() - for _, e in ipairs(FBTicker.sparks or {}) do + for _, e in ipairs(FBTicker.sparks) do if (e.spark) then e.spark:Hide(); end end return true;