Add files via upload

This commit is contained in:
QbiXibQ
2026-09-05 20:53:40 +02:00
committed by GitHub
parent 6f5ad76d63
commit de4daab039
5 changed files with 2690 additions and 74 deletions
+1221 -55
View File
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -1,12 +1,14 @@
## Interface: 11200
## Title: Heal Box Vanilla |cFFFFFF00v1.4.2|r
## Notes: Party, pet and self heal display with quick-cast buttons for healers. Ported to client 1.12.1 (optional SuperWoW). Up to 10 buttons per row, mana bar inside the health bar, class colours, buff watch, range fading, adjustable spacing, test mode with ghost players, optional raid mode for 20 and 40 players. Heal prediction for direct heals, HoT ticks and absorb shields, self-correcting from the combat log. HealComm sync with Puppeteer, pfUI and others. German/English. Original by Dourd, ported to Vanilla and extended 09/2026 by Mquadrat.
## Notes-deDE: Party-, Begleiter- und Selbst-Heilanzeige mit Schnellzugriff-Buttons fuer Heiler. Portiert fuer Client 1.12.1 (optional SuperWoW). Bis zu 10 Buttons pro Reihe, Manabalken im Lebensbalken, Klassenfarben, Buff-Wache, Reichweiten-Fading, einstellbare Abstaende, Testmodus mit Geisterspielern, optionaler Raidmodus fuer 20 und 40 Spieler. Heal-Prediction fuer Direktheilung, HoT-Ticks und Absorb-Schilde, selbstkorrigierend ueber den Combatlog. HealComm-Sync mit Puppeteer, pfUI und Co. Deutsch/Englisch. Original von Dourd, Vanilla-Portierung und Erweiterung 09/2026 von Mquadrat.
## Title: Heal Box Vanilla |cFFFFFF00v1.4.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.2
## Version: 1.4.3
## SavedVariablesPerCharacter: HealBox
## OptionalDeps: SuperWoW
FBHealBox.lua
FBHealBox_Raid.lua
FBHealBox_Ticker.lua
FBHealBox_Damage.lua
FBHealBox.xml
+617
View File
@@ -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();
+297 -15
View File
@@ -33,6 +33,10 @@ FBRAID_MANA_H = 3; -- px Manastreifen in der Zelle
FBRAID_LOS_SIZE = 10; -- px Sichtlinien-Auge in der Zelle
FBRAID_HEADER_H = 12; -- px Gruppenkopf
FBRAID_TITLE_H = 14; -- px Titelleiste (Griff zum Verschieben)
FBRAID_BUFFICON_SIZE = 6; -- px Buff-Icons aussen links an der Zelle
FBRAID_BUFFICON_ROWS = 3; -- Stapelhoehe (3 x 4 = 12 Plaetze)
FBRAID_BUFFICON_COLS = 4; -- so viele Spalten bekommen Platz zwischen den Gruppen
FBRAID_BUFFICON_MAX = 12;
-- Vorgaben aller Raid-Einstellungen. Leben in HealBox.Raid.
FBRaidDefaults = {
@@ -52,6 +56,7 @@ FBRaidDefaults = {
HPText = "percent",-- none | percent | deficit
HideEmpty = 1, -- leere Gruppen ausblenden
ShowTitle = 1, -- Titelleiste anzeigen
BuffIcons = 1, -- Buff-Icons links an den Zellen (aus: kein Platz reserviert)
PosX = nil, PosY = nil, -- Position der linken oberen Ecke (Bildschirmpixel)
};
@@ -74,6 +79,8 @@ FBLocale["enUS"].RAID_HIDEEMPTY = "Hide empty groups";
FBLocale["enUS"].RAID_HIDEEMPTY_TIP = "Groups without members take no space. A 20-player raid then fills a single row.";
FBLocale["enUS"].RAID_SHOWTITLE = "Title bar";
FBLocale["enUS"].RAID_SHOWTITLE_TIP = "A thin title bar above the grid that can be dragged. Without it, hold Shift and drag any cell.";
FBLocale["enUS"].RAID_BUFFICONS = "Show buffs";
FBLocale["enUS"].RAID_BUFFICONS_TIP = "Buff icons with clock on the left of each cell, as on the party plates. Off: no icons and no room reserved for them, the groups move closer together.";
FBLocale["enUS"].RAID_HPTEXT = "HP text";
FBLocale["enUS"].RAID_HPTEXT_TIP = "What the right side of a cell shows: nothing, the health percentage, or the missing health (deficit).";
FBLocale["enUS"].RAID_HP_NONE = "none";
@@ -114,6 +121,8 @@ FBLocale["deDE"].RAID_HIDEEMPTY = "Leere Gruppen ausblenden";
FBLocale["deDE"].RAID_HIDEEMPTY_TIP = "Gruppen ohne Mitglieder brauchen keinen Platz. Ein 20er-Raid fuellt dann eine einzige Zeile.";
FBLocale["deDE"].RAID_SHOWTITLE = "Titelleiste";
FBLocale["deDE"].RAID_SHOWTITLE_TIP = "Eine schmale Titelleiste ueber dem Raster, an der man ziehen kann. Ohne sie: Shift halten und eine beliebige Zelle ziehen.";
FBLocale["deDE"].RAID_BUFFICONS = "Buffs anzeigen";
FBLocale["deDE"].RAID_BUFFICONS_TIP = "Buff-Icons mit Uhr links an jeder Zelle, wie auf den Gruppenplaketten. Aus: keine Icons und kein reservierter Platz, die Gruppen ruecken enger zusammen.";
FBLocale["deDE"].RAID_HPTEXT = "HP-Text";
FBLocale["deDE"].RAID_HPTEXT_TIP = "Was die rechte Seite einer Zelle zeigt: nichts, die Lebensprozente oder die fehlenden Lebenspunkte (Defizit).";
FBLocale["deDE"].RAID_HP_NONE = "keiner";
@@ -137,6 +146,134 @@ FBLocale["deDE"].RAID_INFO = "Die Mini-Buttons nutzen die Zauber von But
FBLocale["deDE"].RAID_MOVE_TIP = "Ziehen verschiebt das Raid-Raster.";
FBLocale["deDE"].FBP_RAID = "Raidmodus: %s, %d Mitglieder in %d Gruppen";
FBLocale["deDE"].FBP_RAID_CMDS = "Raid: /fbp raid (an/aus), /fbp raidtest 20|40|off";
-- Weitere Sprachen (Spanisch, Franzoesisch, Italienisch)
FBLocale["esES"].TAB_RAID = "Modo banda";
FBLocale["esES"].RAID_TITLE = "Banda";
FBLocale["esES"].RAID_ENABLED = "Modo banda";
FBLocale["esES"].RAID_ENABLED_TIP = "Interruptor principal de la rejilla de banda compacta: una celda por miembro, agrupadas por grupo de banda, con minibotones de curación. La rejilla aparece sola cuando tu banda tiene al menos el número de jugadores fijado abajo; las bandas más pequeñas conservan la vista de grupo.";
FBLocale["esES"].RAID_MINPLAYERS = "Vista de banda a partir de |cFFFFFFFF%s|r jugadores";
FBLocale["esES"].RAID_HIDEPARTY = "Ocultar placas de grupo";
FBLocale["esES"].RAID_HIDEPARTY_TIP = "Mientras se muestra la rejilla de banda, las cinco placas de grupo se ocultan. Desactívalo para conservar ambas.";
FBLocale["esES"].RAID_HEADERS = "Cabeceras de grupo";
FBLocale["esES"].RAID_HEADERS_TIP = "Un pequeño número de grupo sobre cada bloque.";
FBLocale["esES"].RAID_MANABAR = "Franja de maná";
FBLocale["esES"].RAID_MANABAR_TIP = "Una franja de maná de 3 px en la parte inferior de cada celda, solo para usuarios de maná.";
FBLocale["esES"].RAID_HIDEEMPTY = "Ocultar grupos vacíos";
FBLocale["esES"].RAID_HIDEEMPTY_TIP = "Los grupos sin miembros no ocupan espacio. Una banda de 20 llena entonces una sola fila.";
FBLocale["esES"].RAID_SHOWTITLE = "Barra de título";
FBLocale["esES"].RAID_SHOWTITLE_TIP = "Una fina barra de título sobre la rejilla que se puede arrastrar. Sin ella, mantén Mayús y arrastra cualquier celda.";
FBLocale["esES"].RAID_BUFFICONS = "Mostrar beneficios";
FBLocale["esES"].RAID_BUFFICONS_TIP = "Iconos de beneficios con reloj a la izquierda de cada celda, como en las placas de grupo. Desactivado: sin iconos ni espacio reservado, los grupos se juntan más.";
FBLocale["esES"].RAID_HPTEXT = "Texto de vida";
FBLocale["esES"].RAID_HPTEXT_TIP = "Qué muestra el lado derecho de una celda: nada, el porcentaje de vida o la vida que falta (déficit).";
FBLocale["esES"].RAID_HP_NONE = "nada";
FBLocale["esES"].RAID_HP_PERCENT = "porcentaje";
FBLocale["esES"].RAID_HP_DEFICIT = "déficit";
FBLocale["esES"].RAID_GROUPSPERROW = "Grupos por fila: |cFFFFFFFF%s";
FBLocale["esES"].RAID_SCALE = "Escala de banda: |cFFFFFFFF%s";
FBLocale["esES"].RAID_CELLW = "Ancho de celda: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_CELLH = "Alto de celda: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_BUTTONS = "Botones por unidad: |cFFFFFFFF%s";
FBLocale["esES"].RAID_BTNSIZE = "Tamaño de botón: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_CELLGAP = "Separación de celdas: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_GROUPGAP = "Separación de grupos: |cFFFFFFFF%s px";
FBLocale["esES"].RAID_TEST = "Prueba de banda";
FBLocale["esES"].RAID_TEST_TIP = "Rellena la rejilla de banda con 20 o 40 jugadores fantasma para organizarla sin banda. No se guarda.";
FBLocale["esES"].RAID_TEST_OFF = "desactivada";
FBLocale["esES"].RAID_TEST_20 = "20 jugadores";
FBLocale["esES"].RAID_TEST_40 = "40 jugadores";
FBLocale["esES"].RAID_TEST_CLICK = "Prueba de banda: sin lanzamiento sobre un jugador fantasma.";
FBLocale["esES"].RAID_INFO = "Los minibotones usan los hechizos de los botones 1 a N de la pestaña Botones, incluidos los del clic derecho.";
FBLocale["esES"].RAID_MOVE_TIP = "Arrastra para mover la rejilla de banda.";
FBLocale["esES"].FBP_RAID = "Modo banda: %s, %d miembros en %d grupos";
FBLocale["esES"].FBP_RAID_CMDS = "Banda: /fbp raid (alternar), /fbp raidtest 20|40|off";
FBLocale["esES"].RAID_LOADED = "Módulo de modo banda cargado: pestaña |cFFFFFFFFModo banda|r en las opciones, /fbp raid, /fbp raidtest 20|40";
FBLocale["frFR"].TAB_RAID = "Mode raid";
FBLocale["frFR"].RAID_TITLE = "Raid";
FBLocale["frFR"].RAID_ENABLED = "Mode raid";
FBLocale["frFR"].RAID_ENABLED_TIP = "Interrupteur principal de la grille de raid compacte : une cellule par membre, regroupées par groupe de raid, avec des mini-boutons de soins. La grille apparaît d'elle-même dès que votre raid compte au moins le nombre de joueurs réglé ci-dessous ; les raids plus petits gardent l'affichage de groupe.";
FBLocale["frFR"].RAID_MINPLAYERS = "Vue raid à partir de |cFFFFFFFF%s|r joueurs";
FBLocale["frFR"].RAID_HIDEPARTY = "Masquer les plaques de groupe";
FBLocale["frFR"].RAID_HIDEPARTY_TIP = "Tant que la grille de raid est affichée, les cinq plaques de groupe sont masquées. Désactivez pour garder les deux.";
FBLocale["frFR"].RAID_HEADERS = "En-têtes de groupe";
FBLocale["frFR"].RAID_HEADERS_TIP = "Un petit numéro de groupe au-dessus de chaque bloc.";
FBLocale["frFR"].RAID_MANABAR = "Bande de mana";
FBLocale["frFR"].RAID_MANABAR_TIP = "Une bande de mana de 3 px en bas de chaque cellule, uniquement pour les utilisateurs de mana.";
FBLocale["frFR"].RAID_HIDEEMPTY = "Masquer les groupes vides";
FBLocale["frFR"].RAID_HIDEEMPTY_TIP = "Les groupes sans membres ne prennent pas de place. Un raid de 20 tient alors sur une seule ligne.";
FBLocale["frFR"].RAID_SHOWTITLE = "Barre de titre";
FBLocale["frFR"].RAID_SHOWTITLE_TIP = "Une fine barre de titre au-dessus de la grille, que l'on peut faire glisser. Sans elle, maintenez Maj et faites glisser n'importe quelle cellule.";
FBLocale["frFR"].RAID_BUFFICONS = "Afficher les buffs";
FBLocale["frFR"].RAID_BUFFICONS_TIP = "Icônes de buffs avec horloge à gauche de chaque cellule, comme sur les plaques de groupe. Désactivé : pas d'icônes ni d'espace réservé, les groupes se rapprochent.";
FBLocale["frFR"].RAID_HPTEXT = "Texte de vie";
FBLocale["frFR"].RAID_HPTEXT_TIP = "Ce qu'affiche le côté droit d'une cellule : rien, le pourcentage de vie ou la vie manquante (déficit).";
FBLocale["frFR"].RAID_HP_NONE = "rien";
FBLocale["frFR"].RAID_HP_PERCENT = "pourcentage";
FBLocale["frFR"].RAID_HP_DEFICIT = "déficit";
FBLocale["frFR"].RAID_GROUPSPERROW = "Groupes par ligne : |cFFFFFFFF%s";
FBLocale["frFR"].RAID_SCALE = "Échelle du raid : |cFFFFFFFF%s";
FBLocale["frFR"].RAID_CELLW = "Largeur de cellule : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_CELLH = "Hauteur de cellule : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_BUTTONS = "Boutons par unité : |cFFFFFFFF%s";
FBLocale["frFR"].RAID_BTNSIZE = "Taille des boutons : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_CELLGAP = "Espacement des cellules : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_GROUPGAP = "Espacement des groupes : |cFFFFFFFF%s px";
FBLocale["frFR"].RAID_TEST = "Test de raid";
FBLocale["frFR"].RAID_TEST_TIP = "Remplit la grille de raid avec 20 ou 40 joueurs fantômes pour la disposer sans raid. Non sauvegardé.";
FBLocale["frFR"].RAID_TEST_OFF = "désactivé";
FBLocale["frFR"].RAID_TEST_20 = "20 joueurs";
FBLocale["frFR"].RAID_TEST_40 = "40 joueurs";
FBLocale["frFR"].RAID_TEST_CLICK = "Test de raid : aucun lancement sur un joueur fantôme.";
FBLocale["frFR"].RAID_INFO = "Les mini-boutons utilisent les sorts des boutons 1 à N de l'onglet Boutons, sorts du clic droit compris.";
FBLocale["frFR"].RAID_MOVE_TIP = "Faites glisser pour déplacer la grille de raid.";
FBLocale["frFR"].FBP_RAID = "Mode raid : %s, %d membres dans %d groupes";
FBLocale["frFR"].FBP_RAID_CMDS = "Raid : /fbp raid (basculer), /fbp raidtest 20|40|off";
FBLocale["frFR"].RAID_LOADED = "Module mode raid chargé : onglet |cFFFFFFFFMode raid|r dans les options, /fbp raid, /fbp raidtest 20|40";
FBLocale["itIT"].TAB_RAID = "Incursione";
FBLocale["itIT"].RAID_TITLE = "Incursione";
FBLocale["itIT"].RAID_ENABLED = "Modalità incursione";
FBLocale["itIT"].RAID_ENABLED_TIP = "Interruttore principale della griglia compatta d'incursione: una cella per membro, raggruppate per gruppo, con mini pulsanti di cura. La griglia compare da sola quando l'incursione ha almeno il numero di giocatori impostato sotto; le incursioni più piccole mantengono la vista di gruppo.";
FBLocale["itIT"].RAID_MINPLAYERS = "Vista incursione da |cFFFFFFFF%s|r giocatori";
FBLocale["itIT"].RAID_HIDEPARTY = "Nascondi targhette di gruppo";
FBLocale["itIT"].RAID_HIDEPARTY_TIP = "Finché la griglia d'incursione è visibile, le cinque targhette di gruppo sono nascoste. Disattiva per tenerle entrambe.";
FBLocale["itIT"].RAID_HEADERS = "Intestazioni di gruppo";
FBLocale["itIT"].RAID_HEADERS_TIP = "Un piccolo numero di gruppo sopra ogni blocco.";
FBLocale["itIT"].RAID_MANABAR = "Striscia del mana";
FBLocale["itIT"].RAID_MANABAR_TIP = "Una striscia del mana di 3 px in fondo a ogni cella, solo per chi usa mana.";
FBLocale["itIT"].RAID_HIDEEMPTY = "Nascondi gruppi vuoti";
FBLocale["itIT"].RAID_HIDEEMPTY_TIP = "I gruppi senza membri non occupano spazio. Un'incursione da 20 riempie allora una sola riga.";
FBLocale["itIT"].RAID_SHOWTITLE = "Barra del titolo";
FBLocale["itIT"].RAID_SHOWTITLE_TIP = "Una sottile barra del titolo sopra la griglia che si può trascinare. Senza, tieni premuto Maiusc e trascina una cella qualsiasi.";
FBLocale["itIT"].RAID_BUFFICONS = "Mostra benefici";
FBLocale["itIT"].RAID_BUFFICONS_TIP = "Icone dei benefici con orologio a sinistra di ogni cella, come sulle targhette di gruppo. Disattivato: niente icone né spazio riservato, i gruppi si avvicinano.";
FBLocale["itIT"].RAID_HPTEXT = "Testo salute";
FBLocale["itIT"].RAID_HPTEXT_TIP = "Cosa mostra il lato destro di una cella: niente, la percentuale di salute o la salute mancante (deficit).";
FBLocale["itIT"].RAID_HP_NONE = "niente";
FBLocale["itIT"].RAID_HP_PERCENT = "percentuale";
FBLocale["itIT"].RAID_HP_DEFICIT = "deficit";
FBLocale["itIT"].RAID_GROUPSPERROW = "Gruppi per riga: |cFFFFFFFF%s";
FBLocale["itIT"].RAID_SCALE = "Scala dell'incursione: |cFFFFFFFF%s";
FBLocale["itIT"].RAID_CELLW = "Larghezza cella: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_CELLH = "Altezza cella: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_BUTTONS = "Pulsanti per unità: |cFFFFFFFF%s";
FBLocale["itIT"].RAID_BTNSIZE = "Dimensione pulsanti: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_CELLGAP = "Spaziatura celle: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_GROUPGAP = "Spaziatura gruppi: |cFFFFFFFF%s px";
FBLocale["itIT"].RAID_TEST = "Test incursione";
FBLocale["itIT"].RAID_TEST_TIP = "Riempie la griglia d'incursione con 20 o 40 giocatori fantasma per sistemarla senza incursione. Non viene salvato.";
FBLocale["itIT"].RAID_TEST_OFF = "disattivato";
FBLocale["itIT"].RAID_TEST_20 = "20 giocatori";
FBLocale["itIT"].RAID_TEST_40 = "40 giocatori";
FBLocale["itIT"].RAID_TEST_CLICK = "Test incursione: nessun lancio su un giocatore fantasma.";
FBLocale["itIT"].RAID_INFO = "I mini pulsanti usano gli incantesimi dei pulsanti da 1 a N della scheda Pulsanti, compresi quelli del clic destro.";
FBLocale["itIT"].RAID_MOVE_TIP = "Trascina per spostare la griglia d'incursione.";
FBLocale["itIT"].FBP_RAID = "Modalità incursione: %s, %d membri in %d gruppi";
FBLocale["itIT"].FBP_RAID_CMDS = "Incursione: /fbp raid (attiva/disattiva), /fbp raidtest 20|40|off";
FBLocale["itIT"].RAID_LOADED = "Modulo modalità incursione caricato: scheda |cFFFFFFFFModalità incursione|r nelle opzioni, /fbp raid, /fbp raidtest 20|40";
FBLocale["deDE"].RAID_LOADED = "Raidmodus-Modul geladen: Reiter |cFFFFFFFFRaidmodus|r in den Optionen, /fbp raid, /fbp raidtest 20|40";
-- ==========================================================================
@@ -206,11 +343,38 @@ function FBRaid_BuildTestGhosts(count)
if (i == 13) then g.state = "offline"; end
if (i == 18) then g.state = "ghost"; end
if (i == 4 or i == 22) then g.debuff = true; end
if (i == 27) then g.debuffType = "Disease"; end
if (i == 9 or i == 31) then g.outOfRange = true; end
if (i == 11) then g.los = true; end
if (i == 2 or i == 26) then g.buffMissing = true; end
if (i == 15) then g.shield = 500; end
if (i == 16 or i == 33) then g.inc = 800; end
if (i == 16) then g.aggro = true; g.hotLeft = 7; end
if (i == 15) then g.shieldLeft = 18; end
-- Buff-Icons: jeder dritte Geist traegt Seelenstaerke mit anderer Restzeit,
-- einer davon dazu Goettlichen Willen, einer einen fremden Buff ohne Zeit
if (math.mod(i, 3) == 0) then
local left = math.mod(i * 7, 18) * 100; -- 0..1700 s von 1800
if (left < 60) then left = 60; end
g.buffs = { { tex = "Interface\\Icons\\Spell_Holy_WordFortitude", left = left, dur = 1800 } };
if (i == 6) then table.insert(g.buffs, { tex = "Interface\\Icons\\Spell_Holy_DivineSpirit", left = 1500, dur = 1800 }); end
if (i == 9) then g.buffs[1].left = nil; end
if (i == 12) then
-- zwoelf Buffs auf einem Geist (3 x 4), um die Darstellung zu pruefen
g.buffs = { { tex = "Interface\\Icons\\Spell_Holy_WordFortitude", left = 540, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Holy_DivineSpirit", left = 1500, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Shadow_AntiShadow", left = 300, dur = 600 },
{ tex = "Interface\\Icons\\Spell_Holy_Excorcism", left = 45, dur = 180 },
{ tex = "Interface\\Icons\\Spell_Nature_Regeneration", dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Magic_MageArmor", left = 1750, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Holy_SealOfWisdom", left = 200, dur = 300 },
{ tex = "Interface\\Icons\\Spell_Holy_FistOfJustice", left = 100, dur = 300 },
{ tex = "Interface\\Icons\\Spell_Nature_Thorns", left = 500, dur = 600 },
{ tex = "Interface\\Icons\\Spell_Holy_InnerFire", left = 1200, dur = 600 },
{ tex = "Interface\\Icons\\Spell_Holy_SealOfSalvation", left = 900, dur = 1800 },
{ tex = "Interface\\Icons\\Spell_Fire_FireArmor", dur = 1800 } };
end
end
FBRaidTestGhosts[i] = g;
end
end
@@ -287,6 +451,10 @@ end
function FBRaid_Dispel(cell)
local g = cell.ghost;
if (g) then
if (g.debuffType) then
if (FBDispelColors[g.debuffType]) then return g.debuffType; end
return nil;
end
if (g.debuff) then
for _, dtype in ipairs({ "Magic", "Poison", "Disease", "Curse" }) do
if (FBDispelColors[dtype]) then return dtype; end
@@ -449,17 +617,24 @@ function FBRaid_CreateCell(g, pos)
c.ManaBar.bg:SetTexture(0, 0, 0, FBMANA_BG_ALPHA);
c.ManaBar:Hide();
-- Texte
c.NameText = c:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
-- Texte liegen auf einer eigenen Ebene ueber den Balken (die Balken sind
-- Kind-Frames mit hoeherer Ebene und wuerden Texte der Zelle verdecken)
c.Overlay = CreateFrame("Frame", nil, c);
c.Overlay:SetAllPoints(c);
c.Overlay:SetFrameStrata("MEDIUM");
c.Overlay:SetFrameLevel(base + 6);
c.NameText = c.Overlay:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
c.NameText:SetPoint("LEFT", c, "LEFT", 4, 0);
c.NameText:SetHeight(FBNAME_HEIGHT);
c.NameText:SetJustifyH("LEFT");
c.HPText = c:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall");
c.NameText:SetJustifyV("MIDDLE");
c.HPText = c.Overlay:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall");
c.HPText:SetPoint("RIGHT", c, "RIGHT", -3, 0);
c.HPText:SetJustifyH("RIGHT");
c.HPText:SetTextColor(1, 1, 1, 1);
-- Sichtlinie: kleines Auge oben links
c.LOSIcon = c:CreateTexture(nil, "OVERLAY");
c.LOSIcon = c.Overlay:CreateTexture(nil, "OVERLAY");
c.LOSIcon:SetWidth(FBRAID_LOS_SIZE);
c.LOSIcon:SetHeight(FBRAID_LOS_SIZE);
c.LOSIcon:SetPoint("TOPLEFT", c, "TOPLEFT", -3, 3);
@@ -605,9 +780,9 @@ end
-- [ Layout ]
-- ==========================================================================
-- Breite einer Zelle samt ihrer Buttons
-- Breite einer Zelle samt Buff-Streifen links und Buttons rechts
function FBRaid_RowWidth(cfg)
local w = cfg.CellW;
local w = FBRaid_BuffStrip(cfg) + cfg.CellW;
if (cfg.Buttons > 0) then w = w + cfg.Buttons * (cfg.ButtonSize + 1); end
return w;
end
@@ -668,12 +843,13 @@ function FBRaid_LayoutAll()
col * (rowW + cfg.GroupGap),
-(titleH + row * (blockH + cfg.GroupGap)));
if (cfg.Headers == 1) then f.header:Show(); else f.header:Hide(); end
-- Zellen im Block
-- Zellen im Block, links davon der Streifen fuer die Buff-Icons
local strip = FBRaid_BuffStrip(cfg);
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaid_GetCell(g, pos);
FBRaid_ApplyCellSize(c, cfg);
c:ClearAllPoints();
c:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -(headH + (pos - 1) * (cfg.CellH + cfg.CellGap)));
c:SetPoint("TOPLEFT", f, "TOPLEFT", strip, -(headH + (pos - 1) * (cfg.CellH + cfg.CellGap)));
end
f:Show();
col = col + 1;
@@ -789,7 +965,14 @@ function FBRaid_UpdateCell(c)
end
end
-- Rahmen: Buff-Wache (orange) oder normal
-- Rahmen: Angegriffener (rot) vor Buff-Wache (orange) vor normal
function FBRaid_ApplyBorder(c)
local col = FBBUFF_NORMAL_COLOR;
if (c.buffMissing) then col = FBBUFF_MISSING_COLOR; end
if (c.underAttack) then col = FBAGGRO_COLOR; end
c:SetBackdropBorderColor(col[1], col[2], col[3], col[4]);
end
function FBRaid_UpdateBuffBorder(c)
if (not c) or (not c.unit) then return; end
local missing = false;
@@ -798,9 +981,96 @@ function FBRaid_UpdateBuffBorder(c)
end
if (c.buffMissing ~= missing) then
c.buffMissing = missing;
local col = FBBUFF_NORMAL_COLOR;
if (missing) then col = FBBUFF_MISSING_COLOR; end
c:SetBackdropBorderColor(col[1], col[2], col[3], col[4]);
FBRaid_ApplyBorder(c);
end
end
-- Wer wird angegriffen (Hook "Aggro" des Kerns, tt = "targettarget" oder nil)
function FBRaid_CheckAggro(tt)
if (not FBRaid_IsActive()) then return; end
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c and c.unit) then
local flag = false;
if (HealBox.AggroMark == 1 and c:IsShown()) then
if (c.ghost) then flag = (c.ghost.aggro == true);
elseif (tt) then flag = FBHealBox_UnitIsAggro(c.unit, tt); end
end
if (c.underAttack ~= flag) then
c.underAttack = flag;
FBRaid_ApplyBorder(c);
end
end
end
end
end
end
-- Platz links neben jeder Zelle fuer die Buff-Icons (aussen links)
function FBRaid_BuffStrip(cfg)
if (HealBox.BuffIcons ~= 1) or (cfg.BuffIcons ~= 1) then return 0; end
return -FBBUFFICON_XOFF + FBRAID_BUFFICON_COLS * (FBRAID_BUFFICON_SIZE + FBBUFFICON_GAP);
end
-- Buff-Icons mit Uhr aussen links an den Zellen (Hook "BuffIcons")
function FBRaid_UpdateBuffIcons()
if (not FBRaid_IsActive()) then return; end
local on = (FBRaid_Cfg().BuffIcons == 1);
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c) then
if (on and c.unit and c:IsShown()) then
FBHealBox_UpdateBuffIcons(c, c.name, c.ghost, FBRAID_BUFFICON_SIZE, FBRAID_BUFFICON_ROWS, FBRAID_BUFFICON_MAX);
else
FBHealBox_UpdateBuffIcons(c, nil, nil, FBRAID_BUFFICON_SIZE, FBRAID_BUFFICON_ROWS, FBRAID_BUFFICON_MAX);
end
end
end
end
end
end
-- HoT-/Schild-Timer auf den Mini-Buttons (Hook "SpellTimers")
function FBRaid_UpdateSpellTimers(now)
local on = (HealBox.SpellTimers == 1) and FBRaid_IsActive();
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c) then
local shown = on and c.unit and c:IsShown();
for i = 1, FBRAID_MAX_BUTTONS do
local b = c.buttons[i];
if (shown and b:IsShown()) then
local text, color = FBHealBox_SpellTimerFor(b.spellName, c.name, now, c.ghost, i);
if (not text and b.spellNameR) then
text, color = FBHealBox_SpellTimerFor(b.spellNameR, c.name, now, nil, i);
end
FBHealBox_SetButtonTimer(b, text, color);
else
FBHealBox_SetButtonTimer(b, nil);
end
end
end
end
end
end
end
function FBRaid_UpdateCooldowns()
for g = 1, FBRAID_GROUPS do
if (FBRaidCells[g]) then
for pos = 1, FBRAID_PER_GROUP do
local c = FBRaidCells[g][pos];
if (c) then
for i = 1, FBRAID_MAX_BUTTONS do FBHealBox_UpdateButtonCooldown(c.buttons[i]); end
end
end
end
end
end
@@ -860,6 +1130,7 @@ function FBRaid_SyncButtons()
b.subIcon:Hide();
end
end
FBHealBox_UpdateButtonCooldown(b);
end
end
end
@@ -1053,9 +1324,14 @@ function FBRaid_BuildOptions()
FBRaid_Cfg().ShowTitle = FBRaidTitleCheck:GetChecked() and 1 or 0;
FBRaid_LayoutAll();
end);
FBRaidBuffIconsCheck = FBHealBox_CreateCheck("FBHealBoxRaidBuffIconsCheck", tab, 40, y - 90, "RAID_BUFFICONS", "RAID_BUFFICONS_TIP", function()
FBRaid_Cfg().BuffIcons = FBRaidBuffIconsCheck:GetChecked() and 1 or 0;
FBRaid_LayoutAll();
FBRaid_UpdateBuffIcons();
end);
-- HP-Text: Auswahlknopf mit Kaskadenmenue
FBRaidHPTextBtn = FBHealBox_CreatePickButton("FBHealBoxRaidHPTextBtn", tab, 35, y - 96, 180, 26, 18);
FBRaidHPTextBtn = FBHealBox_CreatePickButton("FBHealBoxRaidHPTextBtn", tab, 35, y - 126, 180, 26, 18);
FBRaidHPTextBtn.icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01");
FBRaidHPTextBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
@@ -1085,7 +1361,7 @@ function FBRaid_BuildOptions()
end);
-- Raid-Test: aus / 20 / 40
FBRaidTestBtn = FBHealBox_CreatePickButton("FBHealBoxRaidTestBtn", tab, 245, y - 96, 180, 26, 18);
FBRaidTestBtn = FBHealBox_CreatePickButton("FBHealBoxRaidTestBtn", tab, 245, y - 126, 180, 26, 18);
FBRaidTestBtn.icon:SetTexture("Interface\\Icons\\Spell_Shadow_Twilight");
FBRaidTestBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
@@ -1112,7 +1388,7 @@ function FBRaid_BuildOptions()
end);
-- Schieberegler (Beschriftung sitzt ueber dem Regler)
local sy = y - 150;
local sy = y - 180;
FBRaid_CreateSlider("FBRaidGroupsPerRowSlider", tab, 75, sy, "RAID_GROUPSPERROW", "GroupsPerRow", 1, 8, 1, false, FBRaid_LayoutAll);
FBRaid_CreateSlider("FBRaidScaleSlider", tab, 260, sy, "RAID_SCALE", "Scale", 0.5, 1.5, 0.1, true, FBRaid_LayoutAll);
FBRaid_CreateSlider("FBRaidCellWSlider", tab, 75, sy - 50, "RAID_CELLW", "CellW", 50, 120, 1, false, FBRaid_LayoutAll);
@@ -1142,6 +1418,7 @@ function FBRaid_SyncOptions()
if (FBRaidManaCheck) then FBRaidManaCheck:SetChecked(cfg.ManaBar == 1); end
if (FBRaidHideEmptyCheck) then FBRaidHideEmptyCheck:SetChecked(cfg.HideEmpty == 1); end
if (FBRaidTitleCheck) then FBRaidTitleCheck:SetChecked(cfg.ShowTitle == 1); end
if (FBRaidBuffIconsCheck) then FBRaidBuffIconsCheck:SetChecked(cfg.BuffIcons == 1); end
for key, s in pairs(FBRaidSliders) do
if (cfg[key] ~= nil) then s:SetValue(cfg[key]); end
end
@@ -1157,6 +1434,7 @@ function FBRaid_ApplyLocale()
if (FBRaidManaCheck) then FBRaidManaCheck.Text:SetText(FBT("RAID_MANABAR")); FBRaidManaCheck.tooltipText = FBT("RAID_MANABAR_TIP"); end
if (FBRaidHideEmptyCheck) then FBRaidHideEmptyCheck.Text:SetText(FBT("RAID_HIDEEMPTY")); FBRaidHideEmptyCheck.tooltipText = FBT("RAID_HIDEEMPTY_TIP"); end
if (FBRaidTitleCheck) then FBRaidTitleCheck.Text:SetText(FBT("RAID_SHOWTITLE")); FBRaidTitleCheck.tooltipText = FBT("RAID_SHOWTITLE_TIP"); end
if (FBRaidBuffIconsCheck) then FBRaidBuffIconsCheck.Text:SetText(FBT("RAID_BUFFICONS")); FBRaidBuffIconsCheck.tooltipText = FBT("RAID_BUFFICONS_TIP"); end
if (FBRaidInfoText) then FBRaidInfoText:SetText(FBT("RAID_INFO")); end
for _, s in pairs(FBRaidSliders) do FBRaid_SliderText(s); end
FBRaid_UpdateHPTextLabel();
@@ -1198,6 +1476,10 @@ FBHealBox_RegisterHook("ButtonsChanged", function()
FBRaid_SyncButtons();
return true;
end);
FBHealBox_RegisterHook("Aggro", function(tt) FBRaid_CheckAggro(tt); return true; end);
FBHealBox_RegisterHook("SpellTimers", function(now) FBRaid_UpdateSpellTimers(now); return true; end);
FBHealBox_RegisterHook("Cooldowns", function() FBRaid_UpdateCooldowns(); return true; end);
FBHealBox_RegisterHook("BuffIcons", function() FBRaid_UpdateBuffIcons(); return true; end);
FBHealBox_RegisterHook("Status", function()
local cfg = FBRaid_Cfg();
local state = FBT("FBP_STATE_OFF");
+549
View File
@@ -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();