Wechsel auf UNIT_COMBAT Engine (WoW Forever Beta Interface 16001)
- Entferne deaktiviertes COMBAT_LOG_EVENT_UNFILTERED - Implementiere UNIT_COMBAT mit Anti-Triplet Filter (unit == 'target') - Doppel-Auslösungs-Schutz bei Selbstheilung - Mapping von school auf lokalisierte Schadensschulen (SCHOOL_NAMES) - Parameter-Validierung mit issecretvalue - AGENT_GUIDE.md aktualisiert
This commit is contained in:
@@ -74,45 +74,71 @@ CritTrackerDB = {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 4. Logik- & Entscheidungsfluss (Core Engine)
|
## 4. Logik- & Entscheidungsfluss (Core Engine)
|
||||||
|
|
||||||
### 4.1. Combat Log Filterung (`COMBAT_LOG_EVENT_UNFILTERED`)
|
### 4.1. Warum `UNIT_COMBAT` statt `COMBAT_LOG_EVENT_UNFILTERED`? (Root Cause)
|
||||||
|
Im "WoW Forever"-Client (Interface 16001 / 12.x Engine) ist die sogenannte Addon-Apokalypse aktiv:
|
||||||
|
- `COMBAT_LOG_EVENT_UNFILTERED` wurde von Blizzard für Addons **vollständig deaktiviert** und feuert niemals.
|
||||||
|
- Kritische Treffer und Heilungen müssen zwingend über das Event `UNIT_COMBAT` abgefangen werden.
|
||||||
|
|
||||||
Die Engine verwendet `CombatLogGetCurrentEventInfo()` mit folgenden Regeln:
|
### 4.2. Filterregeln & Mehrfachauslösungs-Schutz
|
||||||
|
`UNIT_COMBAT` feuert mit den Argumenten `(unit, action, modifier, amount, school)`.
|
||||||
|
|
||||||
1. **Source-Check:** Nur Events mit `sourceGUID == UnitGUID("player")` werden verarbeitet (Spieler-Treffer).
|
1. **Forever Secret Values Schutz:**
|
||||||
2. **Sub-Events:**
|
Parameter werden mit `issecretvalue(val)` geprüft. Handelt es sich um ein geschütztes Geheimnis oder `nil`, wird das Event verworfen.
|
||||||
- **Schaden:** `SWING_DAMAGE`, `SPELL_DAMAGE`, `RANGE_DAMAGE`, `SPELL_PERIODIC_DAMAGE`, `SPELL_BUILDING_DAMAGE`.
|
2. **Krit-Filter:**
|
||||||
- Effektiver Schaden: `amount + absorbed` (Absorbe werden dem Treffer zugerechnet).
|
Nur Events mit `modifier == "CRITICAL"` und numerischem `amount > 0` werden akzeptiert.
|
||||||
- Bedingung: `critical == true`.
|
3. **Schaden-Filter (Crucial Anti-Triplet):**
|
||||||
- **Heilung:** `SPELL_HEAL`, `SPELL_PERIODIC_HEAL`, `SPELL_BUILDING_HEAL`.
|
- Es wird ausschließlich `action == "WOUND"` und `unit == "target"` ausgewertet.
|
||||||
- Effektive Heilung: `amount + overhealing` (Overheal wird berücksichtigt, damit große Heiler-Crits bei voller HP nicht fälschlich als "Low Crit" eingestuft werden).
|
- Alle Pseudo-Einheiten wie `nameplate*`, `softenemy` etc. werden verworfen, da der Client denselben Treffer sonst 3-fach meldet.
|
||||||
- Bedingung: `critical == true`.
|
4. **Heilung-Filter (Anti-Selfheal-Duplicate):**
|
||||||
|
- Akzeptiert `action == "HEAL"` für `unit == "target"` oder `unit == "player"`.
|
||||||
|
- Bei Selbstheilung mit sich selbst im Ziel (`UnitIsUnit("player", "target")`) wird die Auswertung auf `unit == "target"` beschränkt, um doppelte Auslösungen zu verhindern.
|
||||||
|
5. **Schadensschulen-Mapping (`SCHOOL_NAMES`):**
|
||||||
|
Da `UNIT_COMBAT` keine konkreten Zaubernamen liefert, wird das Argument `school` auf die jeweilige Schadensart gemappt:
|
||||||
|
- `0` / `1` = Physisch / Physical
|
||||||
|
- `2` = Heilig / Holy
|
||||||
|
- `4` = Feuer / Fire
|
||||||
|
- `8` = Natur / Nature
|
||||||
|
- `16` = Frost / Frost
|
||||||
|
- `32` = Schatten / Shadow
|
||||||
|
- `64` = Arkan / Arcane
|
||||||
|
|
||||||
### 4.2. Ablaufdiagramm der Crit-Verarbeitung
|
### 4.3. Ablaufdiagramm der Crit-Verarbeitung
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A["Combat Log Crit Event (Player)"] --> B{"Addon aktiviert?"}
|
A["Event: UNIT_COMBAT(unit, action, modifier, amount, school)"] --> B{"IsPublic? modifier == 'CRITICAL'?"}
|
||||||
B -- Nein --> End[Abbruch]
|
B -- Nein --> End[Abbruch]
|
||||||
B -- Ja --> C["Hole bisherigen Rekord (Schaden/Heilung)"]
|
B -- Ja --> C{"action & unit Check"}
|
||||||
|
|
||||||
C --> D{"amount > currentRecord?"}
|
C -- "WOUND & unit == 'target'" --> D["isDamage = true"]
|
||||||
D -- Ja --> E["Neuer Rekord! Aktualisiere charData (maxRecord = amount)"]
|
C -- "HEAL & (target oder player ohne Self-Duplicate)" --> E["isHeal = true"]
|
||||||
D -- Nein --> F{"enableLowCrit UND currentRecord > 0 UND amount <= (currentRecord * threshold)?"}
|
C -- Andere Units/Actions --> End
|
||||||
|
|
||||||
F -- Ja --> G["Als Low-Crit markieren (Sound: low.mp3)"]
|
D --> F["Löse school via SCHOOL_NAMES auf"]
|
||||||
F -- Nein --> H["Als normaler Crit markieren (Sound: critX / healX)"]
|
E --> F
|
||||||
|
|
||||||
E --> I{"GetTime() - lastOutputTime < cooldown?"}
|
F --> G["Hole bisherigen Rekord (charData.maxDamage / maxHeal)"]
|
||||||
G --> I
|
G --> H{"amount > currentRecord?"}
|
||||||
H --> I
|
|
||||||
|
|
||||||
I -- Ja (im Cooldown) --> J["Ausgabe unterdrückt (Anti-Spam)"]
|
H -- Ja --> I["Neuer Rekord! Aktualisiere charData (maxRecord = amount)"]
|
||||||
I -- Nein (Cooldown frei) --> K["lastOutputTime = GetTime()"]
|
H -- Nein --> J{"enableLowCrit UND currentRecord > 0 UND amount <= threshold?"}
|
||||||
|
|
||||||
K --> L["PlaySoundFile(soundPath, soundChannel)"]
|
J -- Ja --> K["Als Low-Crit markieren (Sound: low.mp3)"]
|
||||||
K --> M["DEFAULT_CHAT_FRAME:AddMessage(formatierte Nachricht)"]
|
J -- Nein --> L["Als normaler Crit markieren (Sound: critX / healX)"]
|
||||||
|
|
||||||
|
I --> M{"GetTime() - lastOutputTime < cooldown?"}
|
||||||
|
K --> M
|
||||||
|
L --> M
|
||||||
|
|
||||||
|
M -- Ja (im Cooldown) --> N["Ausgabe unterdrückt (Anti-Spam)"]
|
||||||
|
M -- Nein (Cooldown frei) --> O["lastOutputTime = GetTime()"]
|
||||||
|
|
||||||
|
O --> P["PlaySoundFile(soundPath, soundChannel)"]
|
||||||
|
O --> Q["DEFAULT_CHAT_FRAME:AddMessage(formatierte Nachricht)"]
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
182
CritTracker.lua
182
CritTracker.lua
@@ -1,6 +1,6 @@
|
|||||||
--[[
|
--[[
|
||||||
CritTracker - Core Engine
|
CritTracker - Core Engine (WoW Forever Edition)
|
||||||
World of Warcraft - Forever (12.x Beta / Interface 16001)
|
Optimiert für Interface 16001 / UNIT_COMBAT Engine
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local addonName, addonTable = ...
|
local addonName, addonTable = ...
|
||||||
@@ -12,23 +12,45 @@ _G["CritTracker"] = CritTracker
|
|||||||
-- Timing & State
|
-- Timing & State
|
||||||
CritTracker.lastOutputTime = 0
|
CritTracker.lastOutputTime = 0
|
||||||
|
|
||||||
-- Defaults
|
-- Standardeinstellungen
|
||||||
local defaultSettings = {
|
local defaultSettings = {
|
||||||
enabled = true,
|
enabled = true,
|
||||||
enableChat = true,
|
enableChat = true,
|
||||||
enableSound = true,
|
enableSound = true,
|
||||||
enableLowCrit = true,
|
enableLowCrit = true,
|
||||||
lowThreshold = 30, -- 30% of highest recorded crit
|
lowThreshold = 30, -- 30% des Rekords
|
||||||
cooldown = 5, -- 5 seconds output cooldown (1-30s)
|
cooldown = 5, -- Cooldown in Sekunden
|
||||||
damageSound = "crit1", -- crit1, crit2, crit3, crit4, none
|
damageSound = "crit1", -- crit1, crit2, crit3, crit4, none
|
||||||
healSound = "heal1", -- heal1, heal2, heal3, heal4, none
|
healSound = "heal1", -- heal1, heal2, heal3, heal4, none
|
||||||
lowSound = "low", -- low, none
|
lowSound = "low", -- low, none
|
||||||
soundChannel = "Master", -- Master, Dialog, SFX
|
soundChannel = "Master", -- Master, Dialog, SFX
|
||||||
language = "auto", -- "auto", "German", "English"
|
language = "auto", -- "auto", "German", "English"
|
||||||
announceMode = "all", -- "all" (subject to cooldown) or "records_only"
|
announceMode = "all", -- "all" oder "records_only"
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Dynamic Localization Proxy
|
-- Schadensschulen-Übersetzung für UNIT_COMBAT
|
||||||
|
local SCHOOL_NAMES = {
|
||||||
|
[0] = { deDE = "Physisch", enUS = "Physical" },
|
||||||
|
[1] = { deDE = "Physisch", enUS = "Physical" },
|
||||||
|
[2] = { deDE = "Heilig", enUS = "Holy" },
|
||||||
|
[4] = { deDE = "Feuer", enUS = "Fire" },
|
||||||
|
[8] = { deDE = "Natur", enUS = "Nature" },
|
||||||
|
[16] = { deDE = "Frost", enUS = "Frost" },
|
||||||
|
[32] = { deDE = "Schatten", enUS = "Shadow" },
|
||||||
|
[64] = { deDE = "Arkan", enUS = "Arcane" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Schutz vor Forever Secret Values
|
||||||
|
local function IsPublic(val)
|
||||||
|
if type(val) == "nil" then return false end
|
||||||
|
if type(issecretvalue) == "function" then
|
||||||
|
local ok, secret = pcall(issecretvalue, val)
|
||||||
|
if ok and secret == true then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Lokalisierungs-Proxy
|
||||||
CritTracker.L = setmetatable({}, {
|
CritTracker.L = setmetatable({}, {
|
||||||
__index = function(_, key)
|
__index = function(_, key)
|
||||||
local lang = CritTracker:GetActiveLanguage()
|
local lang = CritTracker:GetActiveLanguage()
|
||||||
@@ -44,7 +66,6 @@ CritTracker.L = setmetatable({}, {
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Language Resolution
|
|
||||||
function CritTracker:GetActiveLanguage()
|
function CritTracker:GetActiveLanguage()
|
||||||
local setting = (self.db and self.db.language) or "auto"
|
local setting = (self.db and self.db.language) or "auto"
|
||||||
if setting == "German" then
|
if setting == "German" then
|
||||||
@@ -52,25 +73,18 @@ function CritTracker:GetActiveLanguage()
|
|||||||
elseif setting == "English" then
|
elseif setting == "English" then
|
||||||
return "English"
|
return "English"
|
||||||
else
|
else
|
||||||
-- Auto: German client -> German, all others -> English
|
|
||||||
local locale = GetLocale()
|
local locale = GetLocale()
|
||||||
if locale == "deDE" then
|
return (locale == "deDE") and "German" or "English"
|
||||||
return "German"
|
|
||||||
else
|
|
||||||
return "English"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sound File Path
|
|
||||||
function CritTracker:GetSoundFilePath(soundKey)
|
function CritTracker:GetSoundFilePath(soundKey)
|
||||||
local lang = self:GetActiveLanguage()
|
local lang = self:GetActiveLanguage()
|
||||||
return "Interface\\AddOns\\CritTracker\\sounds\\" .. lang .. "\\" .. soundKey .. ".mp3"
|
return "Interface\\AddOns\\CritTracker\\sounds\\" .. lang .. "\\" .. soundKey .. ".mp3"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Play Sound
|
|
||||||
function CritTracker:PlaySound(soundKey)
|
function CritTracker:PlaySound(soundKey)
|
||||||
if not self.db.enableSound or not soundKey or soundKey == "none" then
|
if not self.db or not self.db.enableSound or not soundKey or soundKey == "none" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local path = self:GetSoundFilePath(soundKey)
|
local path = self:GetSoundFilePath(soundKey)
|
||||||
@@ -78,7 +92,6 @@ function CritTracker:PlaySound(soundKey)
|
|||||||
PlaySoundFile(path, channel)
|
PlaySoundFile(path, channel)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Print Message (Visible strictly to the local player)
|
|
||||||
function CritTracker:Print(msg)
|
function CritTracker:Print(msg)
|
||||||
if not msg then return end
|
if not msg then return end
|
||||||
if DEFAULT_CHAT_FRAME then
|
if DEFAULT_CHAT_FRAME then
|
||||||
@@ -88,7 +101,6 @@ function CritTracker:Print(msg)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Format Numbers with commas / dots
|
|
||||||
function CritTracker:FormatNumber(n)
|
function CritTracker:FormatNumber(n)
|
||||||
if not n then return "0" end
|
if not n then return "0" end
|
||||||
if BreakUpLargeNumbers then
|
if BreakUpLargeNumbers then
|
||||||
@@ -99,7 +111,6 @@ function CritTracker:FormatNumber(n)
|
|||||||
return left .. (num:reverse():gsub('(%d%d%d)', '%1.'):reverse()) .. right
|
return left .. (num:reverse():gsub('(%d%d%d)', '%1.'):reverse()) .. right
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Character Data Accessor (strictly per character & realm)
|
|
||||||
function CritTracker:GetCharKey()
|
function CritTracker:GetCharKey()
|
||||||
local name = UnitName("player") or "Unknown"
|
local name = UnitName("player") or "Unknown"
|
||||||
local realm = GetRealmName() or "UnknownRealm"
|
local realm = GetRealmName() or "UnknownRealm"
|
||||||
@@ -107,12 +118,8 @@ function CritTracker:GetCharKey()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function CritTracker:GetCharData()
|
function CritTracker:GetCharData()
|
||||||
if not CritTrackerDB then
|
if not CritTrackerDB then CritTrackerDB = {} end
|
||||||
CritTrackerDB = {}
|
if not CritTrackerDB.chars then CritTrackerDB.chars = {} end
|
||||||
end
|
|
||||||
if not CritTrackerDB.chars then
|
|
||||||
CritTrackerDB.chars = {}
|
|
||||||
end
|
|
||||||
local key = self:GetCharKey()
|
local key = self:GetCharKey()
|
||||||
if not CritTrackerDB.chars[key] then
|
if not CritTrackerDB.chars[key] then
|
||||||
CritTrackerDB.chars[key] = {
|
CritTrackerDB.chars[key] = {
|
||||||
@@ -126,7 +133,6 @@ function CritTracker:GetCharData()
|
|||||||
return CritTrackerDB.chars[key]
|
return CritTrackerDB.chars[key]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Reset Records for Current Character
|
|
||||||
function CritTracker:ResetRecords()
|
function CritTracker:ResetRecords()
|
||||||
local charData = self:GetCharData()
|
local charData = self:GetCharData()
|
||||||
charData.maxDamage = 0
|
charData.maxDamage = 0
|
||||||
@@ -143,7 +149,6 @@ function CritTracker:ResetRecords()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Level Up Handler
|
|
||||||
function CritTracker:OnLevelUp(newLevel)
|
function CritTracker:OnLevelUp(newLevel)
|
||||||
local charData = self:GetCharData()
|
local charData = self:GetCharData()
|
||||||
charData.maxDamage = 0
|
charData.maxDamage = 0
|
||||||
@@ -159,14 +164,9 @@ function CritTracker:OnLevelUp(newLevel)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Initialization
|
|
||||||
function CritTracker:OnInitialize()
|
function CritTracker:OnInitialize()
|
||||||
if not CritTrackerDB then
|
if not CritTrackerDB then CritTrackerDB = {} end
|
||||||
CritTrackerDB = {}
|
if not CritTrackerDB.settings then CritTrackerDB.settings = {} end
|
||||||
end
|
|
||||||
if not CritTrackerDB.settings then
|
|
||||||
CritTrackerDB.settings = {}
|
|
||||||
end
|
|
||||||
for k, v in pairs(defaultSettings) do
|
for k, v in pairs(defaultSettings) do
|
||||||
if CritTrackerDB.settings[k] == nil then
|
if CritTrackerDB.settings[k] == nil then
|
||||||
CritTrackerDB.settings[k] = v
|
CritTrackerDB.settings[k] = v
|
||||||
@@ -174,11 +174,9 @@ function CritTracker:OnInitialize()
|
|||||||
end
|
end
|
||||||
self.db = CritTrackerDB.settings
|
self.db = CritTrackerDB.settings
|
||||||
|
|
||||||
-- Initialize Character
|
|
||||||
local charData = self:GetCharData()
|
local charData = self:GetCharData()
|
||||||
local curLevel = UnitLevel("player")
|
local curLevel = UnitLevel("player")
|
||||||
if charData.level and charData.level > 0 and curLevel > charData.level then
|
if charData.level and charData.level > 0 and curLevel > charData.level then
|
||||||
-- Leveled up while offline / addon disabled
|
|
||||||
self:OnLevelUp(curLevel)
|
self:OnLevelUp(curLevel)
|
||||||
else
|
else
|
||||||
charData.level = curLevel
|
charData.level = curLevel
|
||||||
@@ -189,8 +187,7 @@ function CritTracker:OnInitialize()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Main Crit Processing
|
function CritTracker:ProcessCrit(isHeal, spellOrSchool, amount, destName)
|
||||||
function CritTracker:ProcessCrit(isHeal, spellName, amount, destName)
|
|
||||||
if not self.db or not self.db.enabled or not amount or amount <= 0 then
|
if not self.db or not self.db.enabled or not amount or amount <= 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -203,19 +200,17 @@ function CritTracker:ProcessCrit(isHeal, spellName, amount, destName)
|
|||||||
local thresholdPercent = self.db.lowThreshold or 30
|
local thresholdPercent = self.db.lowThreshold or 30
|
||||||
local thresholdValue = math.floor(currentRecord * (thresholdPercent / 100))
|
local thresholdValue = math.floor(currentRecord * (thresholdPercent / 100))
|
||||||
|
|
||||||
-- Check for Low Crit
|
|
||||||
if self.db.enableLowCrit and currentRecord > 0 and not isNewRecord and amount <= thresholdValue then
|
if self.db.enableLowCrit and currentRecord > 0 and not isNewRecord and amount <= thresholdValue then
|
||||||
isLowCrit = true
|
isLowCrit = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update Record if higher
|
|
||||||
if isNewRecord then
|
if isNewRecord then
|
||||||
if isHeal then
|
if isHeal then
|
||||||
charData.maxHeal = amount
|
charData.maxHeal = amount
|
||||||
charData.maxHealSpell = spellName or ""
|
charData.maxHealSpell = spellOrSchool or ""
|
||||||
else
|
else
|
||||||
charData.maxDamage = amount
|
charData.maxDamage = amount
|
||||||
charData.maxDamageSpell = spellName or ""
|
charData.maxDamageSpell = spellOrSchool or ""
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.UpdateOptionsDisplay then
|
if self.UpdateOptionsDisplay then
|
||||||
@@ -223,19 +218,16 @@ function CritTracker:ProcessCrit(isHeal, spellName, amount, destName)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Cooldown Check for Audio and Text Output
|
|
||||||
local now = GetTime()
|
local now = GetTime()
|
||||||
local cooldown = self.db.cooldown or 5
|
local cooldown = self.db.cooldown or 5
|
||||||
if (now - self.lastOutputTime) < cooldown then
|
if (now - self.lastOutputTime) < cooldown then
|
||||||
-- Suppressed by Cooldown (Records are already preserved)
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Determine Sound and Text
|
|
||||||
local soundKey = nil
|
local soundKey = nil
|
||||||
local chatMsg = nil
|
local chatMsg = nil
|
||||||
local destStr = (destName and destName ~= "") and string.format(self.L["TARGET_ON"], destName) or ""
|
local destStr = (destName and destName ~= "") and string.format(self.L["TARGET_ON"], destName) or ""
|
||||||
local spellStr = (spellName and spellName ~= "") and spellName or self.L["UNKNOWN"]
|
local spellStr = (spellOrSchool and spellOrSchool ~= "") and spellOrSchool or (isHeal and "Heilung" or "Treffer")
|
||||||
local amountStr = self:FormatNumber(amount)
|
local amountStr = self:FormatNumber(amount)
|
||||||
local recordStr = self:FormatNumber(currentRecord)
|
local recordStr = self:FormatNumber(currentRecord)
|
||||||
|
|
||||||
@@ -248,7 +240,6 @@ function CritTracker:ProcessCrit(isHeal, spellName, amount, destName)
|
|||||||
local formatStr = isHeal and self.L["NEW_RECORD_HEAL"] or self.L["NEW_RECORD_DAMAGE"]
|
local formatStr = isHeal and self.L["NEW_RECORD_HEAL"] or self.L["NEW_RECORD_DAMAGE"]
|
||||||
chatMsg = string.format(formatStr, destStr, spellStr, amountStr)
|
chatMsg = string.format(formatStr, destStr, spellStr, amountStr)
|
||||||
else
|
else
|
||||||
-- Normal Crit (between threshold and current record)
|
|
||||||
if self.db.announceMode == "all" then
|
if self.db.announceMode == "all" then
|
||||||
soundKey = isHeal and (self.db.healSound or "heal1") or (self.db.damageSound or "crit1")
|
soundKey = isHeal and (self.db.healSound or "heal1") or (self.db.damageSound or "crit1")
|
||||||
local formatStr = isHeal and self.L["NORMAL_CRIT_HEAL"] or self.L["NORMAL_CRIT_DAMAGE"]
|
local formatStr = isHeal and self.L["NORMAL_CRIT_HEAL"] or self.L["NORMAL_CRIT_DAMAGE"]
|
||||||
@@ -256,7 +247,6 @@ function CritTracker:ProcessCrit(isHeal, spellName, amount, destName)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If something triggered, apply cooldown and output
|
|
||||||
if soundKey or chatMsg then
|
if soundKey or chatMsg then
|
||||||
self.lastOutputTime = now
|
self.lastOutputTime = now
|
||||||
|
|
||||||
@@ -270,74 +260,56 @@ function CritTracker:ProcessCrit(isHeal, spellName, amount, destName)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Combat Log Parser
|
-- UNIT_COMBAT Parser
|
||||||
function CritTracker:OnCombatLogEvent()
|
function CritTracker:OnUnitCombat(unit, action, modifier, amount, school)
|
||||||
local timestamp, subevent, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags,
|
if not IsPublic(unit) or not IsPublic(action) or not IsPublic(modifier) or not IsPublic(amount) then
|
||||||
destGUID, destName, destFlags, destRaidFlags,
|
|
||||||
arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20,
|
|
||||||
arg21, arg22, arg23, arg24 = CombatLogGetCurrentEventInfo()
|
|
||||||
|
|
||||||
if not sourceGUID or sourceGUID ~= UnitGUID("player") then
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if subevent == "SWING_DAMAGE" then
|
if modifier ~= "CRITICAL" or type(amount) ~= "number" or amount <= 0 then
|
||||||
local amount = arg12
|
return
|
||||||
local overkill = arg13
|
|
||||||
local school = arg14
|
|
||||||
local resisted = arg15
|
|
||||||
local blocked = arg16
|
|
||||||
local absorbed = arg17
|
|
||||||
local critical = arg18
|
|
||||||
|
|
||||||
if critical and critical ~= 0 then
|
|
||||||
local totalDamage = (amount or 0) + (absorbed or 0)
|
|
||||||
self:ProcessCrit(false, self.L["MELEE_ATTACK"], totalDamage, destName)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif subevent == "SPELL_DAMAGE" or subevent == "RANGE_DAMAGE"
|
local isDamage = false
|
||||||
or subevent == "SPELL_PERIODIC_DAMAGE" or subevent == "SPELL_BUILDING_DAMAGE" then
|
local isHeal = false
|
||||||
local spellId = arg12
|
|
||||||
local spellName = arg13
|
|
||||||
local spellSchool = arg14
|
|
||||||
local amount = arg15
|
|
||||||
local overkill = arg16
|
|
||||||
local school = arg17
|
|
||||||
local resisted = arg18
|
|
||||||
local blocked = arg19
|
|
||||||
local absorbed = arg20
|
|
||||||
local critical = arg21
|
|
||||||
|
|
||||||
if critical and critical ~= 0 then
|
-- Schaden: Nur 'target' werten (verhindert Alias-Triplets durch nameplate1/softenemy)
|
||||||
local totalDamage = (amount or 0) + (absorbed or 0)
|
if action == "WOUND" and unit == "target" then
|
||||||
self:ProcessCrit(false, spellName, totalDamage, destName)
|
isDamage = true
|
||||||
end
|
|
||||||
|
|
||||||
elseif subevent == "SPELL_HEAL" or subevent == "SPELL_PERIODIC_HEAL" or subevent == "SPELL_BUILDING_HEAL" then
|
-- Heilung: 'target' oder 'player'
|
||||||
local spellId = arg12
|
elseif action == "HEAL" then
|
||||||
local spellName = arg13
|
if unit == "target" then
|
||||||
local spellSchool = arg14
|
isHeal = true
|
||||||
local amount = arg15
|
elseif unit == "player" then
|
||||||
local overhealing = arg16
|
-- Verhindert doppelte Auslösung bei Ziel auf sich selbst
|
||||||
local absorbed = arg17
|
if not UnitIsUnit("player", "target") then
|
||||||
local critical = arg18
|
isHeal = true
|
||||||
|
|
||||||
if critical and critical ~= 0 then
|
|
||||||
local totalHeal = (amount or 0) + (overhealing or 0)
|
|
||||||
self:ProcessCrit(true, spellName, totalHeal, destName)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Event Registration
|
if not isDamage and not isHeal then return end
|
||||||
|
|
||||||
|
local targetName = UnitName(unit) or (isDamage and "Ziel" or "Spieler")
|
||||||
|
|
||||||
|
-- Schadensart / Schule auflösen
|
||||||
|
local langKey = (self:GetActiveLanguage() == "German") and "deDE" or "enUS"
|
||||||
|
local schoolName = (school and SCHOOL_NAMES[school] and SCHOOL_NAMES[school][langKey])
|
||||||
|
or (isHeal and (langKey == "deDE" and "Heilung" or "Heal") or (langKey == "deDE" and "Angriff" or "Attack"))
|
||||||
|
|
||||||
|
self:ProcessCrit(isHeal, schoolName, amount, targetName)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Event Registrierung
|
||||||
CritTracker:RegisterEvent("ADDON_LOADED")
|
CritTracker:RegisterEvent("ADDON_LOADED")
|
||||||
CritTracker:RegisterEvent("PLAYER_LOGIN")
|
CritTracker:RegisterEvent("PLAYER_LOGIN")
|
||||||
CritTracker:RegisterEvent("PLAYER_LEVEL_UP")
|
CritTracker:RegisterEvent("PLAYER_LEVEL_UP")
|
||||||
CritTracker:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
|
CritTracker:RegisterEvent("UNIT_COMBAT")
|
||||||
|
|
||||||
CritTracker:SetScript("OnEvent", function(self, event, ...)
|
CritTracker:SetScript("OnEvent", function(self, event, ...)
|
||||||
if event == "COMBAT_LOG_EVENT_UNFILTERED" then
|
if event == "UNIT_COMBAT" then
|
||||||
self:OnCombatLogEvent()
|
self:OnUnitCombat(...)
|
||||||
elseif event == "PLAYER_LEVEL_UP" then
|
elseif event == "PLAYER_LEVEL_UP" then
|
||||||
local newLevel = ...
|
local newLevel = ...
|
||||||
self:OnLevelUp(newLevel)
|
self:OnLevelUp(newLevel)
|
||||||
@@ -357,7 +329,7 @@ CritTracker:SetScript("OnEvent", function(self, event, ...)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Slash Commands
|
-- Slash Commands (/ctr & /crittracker)
|
||||||
SLASH_CRITTRACKER1 = "/crittracker"
|
SLASH_CRITTRACKER1 = "/crittracker"
|
||||||
SLASH_CRITTRACKER2 = "/ctr"
|
SLASH_CRITTRACKER2 = "/ctr"
|
||||||
SlashCmdList["CRITTRACKER"] = function(msg)
|
SlashCmdList["CRITTRACKER"] = function(msg)
|
||||||
@@ -380,13 +352,13 @@ SlashCmdList["CRITTRACKER"] = function(msg)
|
|||||||
elseif cmd == "test" then
|
elseif cmd == "test" then
|
||||||
if arg == "heal" then
|
if arg == "heal" then
|
||||||
CritTracker:PlaySound(CritTracker.db.healSound or "heal1")
|
CritTracker:PlaySound(CritTracker.db.healSound or "heal1")
|
||||||
CritTracker:Print(string.format(CritTracker.L["NORMAL_CRIT_HEAL"], "", "Test Heal", CritTracker:FormatNumber(50000)))
|
CritTracker:Print(string.format(CritTracker.L["NORMAL_CRIT_HEAL"], "", "Heilig", CritTracker:FormatNumber(50000)))
|
||||||
elseif arg == "low" then
|
elseif arg == "low" then
|
||||||
CritTracker:PlaySound("low")
|
CritTracker:PlaySound("low")
|
||||||
CritTracker:Print(string.format(CritTracker.L["LOW_CRIT_DAMAGE"], "", "Test Low Hit", CritTracker:FormatNumber(150), CritTracker.db.lowThreshold, CritTracker:FormatNumber(1000)))
|
CritTracker:Print(string.format(CritTracker.L["LOW_CRIT_DAMAGE"], "", "Physisch", CritTracker:FormatNumber(150), CritTracker.db.lowThreshold, CritTracker:FormatNumber(1000)))
|
||||||
else
|
else
|
||||||
CritTracker:PlaySound(CritTracker.db.damageSound or "crit1")
|
CritTracker:PlaySound(CritTracker.db.damageSound or "crit1")
|
||||||
CritTracker:Print(string.format(CritTracker.L["NORMAL_CRIT_DAMAGE"], "", "Test Strike", CritTracker:FormatNumber(125000)))
|
CritTracker:Print(string.format(CritTracker.L["NORMAL_CRIT_DAMAGE"], "", "Feuer", CritTracker:FormatNumber(125000)))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
CritTracker:Print(CritTracker.L["SLASH_HELP_HEADER"])
|
CritTracker:Print(CritTracker.L["SLASH_HELP_HEADER"])
|
||||||
|
|||||||
Reference in New Issue
Block a user