Addon CritTracker implementiert (WoW Forever 12.x Beta, Interface 16001)

- Getrennte Erfassung für Schaden und Heilung
- Erkennung schwacher Crits mit einstellbarem Schwellenwert (Standard 30%)
- Audio-Feedback (crit1-4, heal1-4, low.mp3) für Deutsch und Englisch
- Einstellungsmenü mit Cooldown-Slider (1-30s), Sound-Auswahl und Test-Buttons
- Automatischer Reset der Rekorde bei Stufenaufstieg (PLAYER_LEVEL_UP)
- Lokale Chat-Benachrichtigungen (nur für Spieler sichtbar)
- Slash-Befehle /ctr und /crittracker
- Entwickler- & Agenten-Leitfaden (AGENT_GUIDE.md) hinzugefügt
- sounds/English/low.mp3 repariert
This commit is contained in:
2026-09-30 09:41:35 +02:00
parent 370ae9b0be
commit edc52fdd86
7 changed files with 1285 additions and 0 deletions

185
AGENT_GUIDE.md Normal file
View File

@@ -0,0 +1,185 @@
# CritTracker – Entwickler- & Agenten-Leitfaden (Developer & Agent Guide)
Dieses Dokument dient als technische Referenz für Entwickler und KI-Agenten, die das Addon **CritTracker** nachvollziehen, erweitern oder in einer vergleichbaren Umgebung reproduzieren möchten.
---
## 1. System- & Zielumgebung
- **Spiel / Client:** World of Warcraft - Forever (Beta)
- **Engine-Basis:** WoW Retail 12.x Engine
- **TOC Interface-Version:** `16001` (`## Interface: 16001`)
- **Addon-Verzeichnis:** `Interface/AddOns/CritTracker/`
- **Slash-Befehle:** `/ctr`, `/crittracker` (Hinweis: `/ct` wurde bewusst vermieden, um Namenskonflikte mit anderen Addons wie CTMod auszuschließen).
---
## 2. Projektstruktur
```
CritTracker/
├── CritTracker.toc # Addon-Manifest für Interface 16001
├── Localization.lua # Lokalisierungs-Strings (deDE und enUS)
├── CritTracker.lua # Core Engine: Event-Handling, Combat-Log, Cooldown, Reset
├── Config.lua # UI: 2-Spalten Einstellungsmenü (Modern Settings API + Standalone)
├── README.md # Benutzerdokumentation
├── AGENT_GUIDE.md # Diese technische Spezifikation für Agenten
└── sounds/
├── German/ # Deutsche Audio-Dateien
│ ├── crit1.mp3 .. crit4.mp3
│ ├── heal1.mp3 .. heal4.mp3
│ └── low.mp3
└── English/ # Englische Audio-Dateien
├── crit1.mp3 .. crit4.mp3
├── heal1.mp3 .. heal4.mp3
└── low.mp3
```
> [!IMPORTANT]
> **Audio-Asset-Korrektur:** Die ursprüngliche Datei `sounds/English/low.mp3` war eine fehlerhafte 1.024-Byte-Null-Datei (Stille). Sie wurde durch die funktionierende Audiodatei aus `sounds/German/low.mp3` ersetzt, damit englische Clients nicht stumm bleiben.
---
## 3. Datenarchitektur (`SavedVariables: CritTrackerDB`)
Die Daten werden in `CritTrackerDB` gespeichert. Die Konfiguration ist accountweit geteilt, während die Rekorde strikt pro Charakter und Realm isoliert sind.
```lua
CritTrackerDB = {
settings = {
enabled = true, -- Addon an/aus
enableChat = true, -- Lokale Chat-Ausgabe an/aus
enableSound = true, -- Soundausgabe an/aus
enableLowCrit = true, -- Erkennung schwacher Crits an/aus
lowThreshold = 30, -- Grenzwert in Prozent (Standard: 30%)
cooldown = 5, -- Cooldown in Sekunden (1-30s, Schieberegler)
damageSound = "crit1", -- "crit1", "crit2", "crit3", "crit4", "none"
healSound = "heal1", -- "heal1", "heal2", "heal3", "heal4", "none"
lowSound = "low", -- "low", "none"
soundChannel = "Master", -- "Master", "Dialog", "SFX"
language = "auto", -- "auto", "German", "English"
announceMode = "all", -- "all" oder "records_only"
},
chars = {
["CharakterName - RealmName"] = {
level = 80,
maxDamage = 152000,
maxDamageSpell = "Feuerball",
maxHeal = 89000,
maxHealSpell = "Lichtblitz",
}
}
}
```
---
## 4. Logik- & Entscheidungsfluss (Core Engine)
### 4.1. Combat Log Filterung (`COMBAT_LOG_EVENT_UNFILTERED`)
Die Engine verwendet `CombatLogGetCurrentEventInfo()` mit folgenden Regeln:
1. **Source-Check:** Nur Events mit `sourceGUID == UnitGUID("player")` werden verarbeitet (Spieler-Treffer).
2. **Sub-Events:**
- **Schaden:** `SWING_DAMAGE`, `SPELL_DAMAGE`, `RANGE_DAMAGE`, `SPELL_PERIODIC_DAMAGE`, `SPELL_BUILDING_DAMAGE`.
- Effektiver Schaden: `amount + absorbed` (Absorbe werden dem Treffer zugerechnet).
- Bedingung: `critical == true`.
- **Heilung:** `SPELL_HEAL`, `SPELL_PERIODIC_HEAL`, `SPELL_BUILDING_HEAL`.
- Effektive Heilung: `amount + overhealing` (Overheal wird berücksichtigt, damit große Heiler-Crits bei voller HP nicht fälschlich als "Low Crit" eingestuft werden).
- Bedingung: `critical == true`.
### 4.2. Ablaufdiagramm der Crit-Verarbeitung
```mermaid
flowchart TD
A["Combat Log Crit Event (Player)"] --> B{"Addon aktiviert?"}
B -- Nein --> End[Abbruch]
B -- Ja --> C["Hole bisherigen Rekord (Schaden/Heilung)"]
C --> D{"amount > currentRecord?"}
D -- Ja --> E["Neuer Rekord! Aktualisiere charData (maxRecord = amount)"]
D -- Nein --> F{"enableLowCrit UND currentRecord > 0 UND amount <= (currentRecord * threshold)?"}
F -- Ja --> G["Als Low-Crit markieren (Sound: low.mp3)"]
F -- Nein --> H["Als normaler Crit markieren (Sound: critX / healX)"]
E --> I{"GetTime() - lastOutputTime < cooldown?"}
G --> I
H --> I
I -- Ja (im Cooldown) --> J["Ausgabe unterdrückt (Anti-Spam)"]
I -- Nein (Cooldown frei) --> K["lastOutputTime = GetTime()"]
K --> L["PlaySoundFile(soundPath, soundChannel)"]
K --> M["DEFAULT_CHAT_FRAME:AddMessage(formatierte Nachricht)"]
```
---
## 5. Kernfunktionen im Detail
### 5.1. Level-Up Reset
- **Event:** `PLAYER_LEVEL_UP`
- **Sicherheits-Check:** Beim Login (`PLAYER_LOGIN`) wird geprüft, ob `UnitLevel("player") > charData.level`. Falls der Spieler offline oder bei deaktiviertem Addon aufgestiegen ist, wird der Reset nachträglich durchgeführt.
- **Wirkung:** `maxDamage = 0`, `maxHeal = 0`, `level = newLevel`.
- **Chat-Meldung:** Gibt lokalen Hinweis aus: `[CritTracker] Stufenaufstieg! Krit-Rekorde wurden auf 0 zurückgesetzt.`
### 5.2. Cooldown für Audio- & Chat-Ausgabe
- Einstellbar von **1 bis 30 Sekunden** über den Schieberegler in den Optionen.
- **Wichtig für Agenten:** Der Cooldown blockiert **nicht** das Speichern neuer Rekorde! Trifft ein Spieler während des Cooldowns einen neuen Rekordwert, wird `charData.maxDamage` sofort aktualisiert. Lediglich die Soundwiedergabe und der Textchat werden gedrosselt, um Tonaussetzer und Chatüberflutung zu verhindern.
### 5.3. Lokale Chat-Ausgabe
- Alle Chat-Meldungen verwenden `DEFAULT_CHAT_FRAME:AddMessage(msg)` oder `print(msg)`.
- Es werden **keine** öffentlichen Chatkanäle (`SAY`, `PARTY`, `RAID`) angesprochen. Die Ausgabe ist garantiert nur für den lokalen Client sichtbar.
- Abschaltbar über `CritTrackerDB.settings.enableChat`.
### 5.4. Zweisprachigkeit (Localization Architecture)
- Lokalisierungs-Proxy via Lua-Metatable in `CritTracker.L`:
```lua
CritTracker.L = setmetatable({}, {
__index = function(_, key)
local lang = CritTracker:GetActiveLanguage()
local locKey = (lang == "German") and "deDE" or "enUS"
return addonTable.Locales[locKey][key] or addonTable.Locales["enUS"][key] or key
end
})
```
- **Sprachauflösung:**
- `auto`: Prüft `GetLocale()`. Bei `deDE` wird Deutsch gewählt, bei allen anderen Sprachen Englisch.
- `German`: Erzwingt Deutsch.
- `English`: Erzwingt Englisch.
- **Audio-Pfad:**
`Interface\AddOns\CritTracker\sounds\<Language>\<SoundKey>.mp3`
---
## 6. UI-Architektur (`Config.lua`)
Das Einstellungsmenü ist für maximale Kompatibilität mit modernem Retail WoW (Dragonflight, War Within, Midnight / 12.x) ausgelegt:
1. **Modern Settings API:**
- Registrierung über `Settings.RegisterCanvasLayoutCategory(panel, "CritTracker")` und `Settings.RegisterAddOnCategory(category)`.
- Öffnen über `Settings.OpenToCategory(category:GetID())`.
2. **Standalone-Fallback:**
- Sollte das Settings-Menü im Betastatus nicht ansprechbar sein oder während des Kampfes aufgerufen werden, öffnet sich das Panel automatisch als bewegliches Standalone-Fenster (`BackdropTemplateMixin`, Close-Button, Dragging).
3. **2-Spalten-Layout:**
- **Linke Spalte:** Checkboxen (Addon, Chat, Sound, Low-Crit), Cooldown-Slider (1–30s), Schwellenwert-Slider (5–100%), Charakter-Rekordanzeige und Reset-Button.
- **Rechte Spalte:** Sound-Auswahl (Schaden, Heilung, Low, Kanal), Sprachauswahl und Test-Buttons (`►`).
---
## 7. Anleitung zur Nachbildung / Testen
1. **Dateien anlegen:** Die Dateien `CritTracker.toc`, `Localization.lua`, `CritTracker.lua` und `Config.lua` wie spezifiziert erstellen.
2. **Sounds ablegen:**
- `sounds/German/`: `crit1.mp3` bis `crit4.mp3`, `heal1.mp3` bis `heal4.mp3`, `low.mp3`.
- `sounds/English/`: `crit1.mp3` bis `crit4.mp3`, `heal1.mp3` bis `heal4.mp3`, `low.mp3`.
3. **Im Spiel testen:**
- `/ctr` eingeben -> Einstellungsfenster muss sich öffnen.
- Auf die `►`-Buttons klicken -> Die jeweiligen MP3-Dateien müssen hörbar abgespielt werden.
- `/ctr test crit` -> Testet Schadensmeldung und Audio.
- `/ctr test heal` -> Testet Heilungsmeldung und Audio.
- `/ctr test low` -> Testet Low-Crit-Meldung und `low.mp3`.
- Schieberegler verstellen -> Werte in `CritTrackerDB.settings` müssen persistieren.
- `/ctr reset` -> Rekorde werden auf 0 gesetzt.

488
Config.lua Normal file
View File

@@ -0,0 +1,488 @@
--[[
CritTracker - Configuration & Options UI
Supports Modern WoW 12.x / Retail Settings API and Standalone UI
]]
local addonName, addonTable = ...
local CritTracker = addonTable.CritTracker
local panel = CreateFrame("Frame", "CritTrackerOptionsPanel", UIParent)
CritTracker.optionsPanel = panel
panel.name = "CritTracker"
-- Standalone Window Styling (Close Button & Dragging)
panel:SetSize(660, 560)
panel:SetMovable(true)
panel:EnableMouse(true)
panel:RegisterForDrag("LeftButton")
panel:SetScript("OnDragStart", panel.StartMoving)
panel:SetScript("OnDragStop", panel.StopMovingOrSizing)
-- Close Button for Standalone Mode
local closeBtn = CreateFrame("Button", nil, panel, "UIPanelCloseButton")
closeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -6, -6)
closeBtn:SetScript("OnClick", function()
panel:Hide()
end)
-- UI Element References for Language Updating
local uiLabels = {}
local function RegisterLabel(fontString, locKey)
table.insert(uiLabels, { element = fontString, key = locKey, isText = true })
fontString:SetText(CritTracker.L[locKey] or locKey)
end
-- Controls State Update Helper
local function UpdateSegmentedButtons(buttons, selectedKey)
for key, btn in pairs(buttons) do
if key == selectedKey then
btn:LockHighlight()
btn:SetText("|cffffd100" .. (btn.displayLabel or key) .. "|r")
else
btn:UnlockHighlight()
btn:SetText(btn.displayLabel or key)
end
end
end
-- Refresh Panel Displays
function CritTracker:UpdateOptionsDisplay()
if not self.db then return end
-- Checkboxes
if self.ui_cbEnable then self.ui_cbEnable:SetChecked(self.db.enabled) end
if self.ui_cbChat then self.ui_cbChat:SetChecked(self.db.enableChat) end
if self.ui_cbSound then self.ui_cbSound:SetChecked(self.db.enableSound) end
if self.ui_cbLow then self.ui_cbLow:SetChecked(self.db.enableLowCrit) end
-- Sliders
if self.ui_sliderCooldown then
self.ui_sliderCooldown:SetValue(self.db.cooldown or 5)
self.ui_lblCooldown:SetText(string.format(self.L["OPT_COOLDOWN_TITLE"], self.db.cooldown or 5))
end
if self.ui_sliderThreshold then
self.ui_sliderThreshold:SetValue(self.db.lowThreshold or 30)
self.ui_lblThreshold:SetText(string.format(self.L["OPT_THRESHOLD_TITLE"], self.db.lowThreshold or 30))
end
-- Audio Buttons
if self.ui_damageButtons then
UpdateSegmentedButtons(self.ui_damageButtons, self.db.damageSound or "crit1")
end
if self.ui_healButtons then
UpdateSegmentedButtons(self.ui_healButtons, self.db.healSound or "heal1")
end
if self.ui_lowButtons then
UpdateSegmentedButtons(self.ui_lowButtons, self.db.lowSound or "low")
end
if self.ui_channelButtons then
UpdateSegmentedButtons(self.ui_channelButtons, self.db.soundChannel or "Master")
end
if self.ui_langButtons then
UpdateSegmentedButtons(self.ui_langButtons, self.db.language or "auto")
end
-- Character Stats
local charData = self:GetCharData()
local dmgNum = self:FormatNumber(charData.maxDamage)
local dmgSpell = (charData.maxDamageSpell ~= "") and charData.maxDamageSpell or self.L["OPT_STATS_NONE"]
local healNum = self:FormatNumber(charData.maxHeal)
local healSpell = (charData.maxHealSpell ~= "") and charData.maxHealSpell or self.L["OPT_STATS_NONE"]
if self.ui_lblDamageRecord then
self.ui_lblDamageRecord:SetText(string.format(self.L["OPT_STATS_DAMAGE"], "|cffff4444" .. dmgNum .. "|r", dmgSpell))
end
if self.ui_lblHealRecord then
self.ui_lblHealRecord:SetText(string.format(self.L["OPT_STATS_HEAL"], "|cff44ff44" .. healNum .. "|r", healSpell))
end
-- Dynamic Language Labels
for _, item in ipairs(uiLabels) do
if item.isText and item.element and item.key then
item.element:SetText(self.L[item.key] or item.key)
end
end
end
-- Initialize UI Components
function CritTracker:InitConfig()
-- Title & Header
local title = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
title:SetPoint("TOPLEFT", panel, "TOPLEFT", 20, -18)
title:SetText("|cff00e5ffCritTracker|r")
local subtitle = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
subtitle:SetText(self.L["ADDON_SUBTITLE"] .. " |cff888888v1.0.0 (TOC 16001)|r")
-------------------------------------------------------------------------
-- LEFT COLUMN: General Options, Cooldown/Threshold Sliders, Character Stats
-------------------------------------------------------------------------
local colLeftX = 20
local curY = -70
-- Section: General
local secGeneral = panel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
secGeneral:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
RegisterLabel(secGeneral, "OPT_SECTION_GENERAL")
curY = curY - 24
-- Checkbox: Enable Addon
local cbEnable = CreateFrame("CheckButton", "CritTrackerCB_Enable", panel, "UICheckButtonTemplate")
cbEnable:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
cbEnable.Text:SetText(self.L["OPT_ENABLE"])
RegisterLabel(cbEnable.Text, "OPT_ENABLE")
cbEnable:SetScript("OnClick", function(selfBtn)
CritTracker.db.enabled = selfBtn:GetChecked()
end)
self.ui_cbEnable = cbEnable
curY = curY - 28
-- Checkbox: Enable Chat
local cbChat = CreateFrame("CheckButton", "CritTrackerCB_Chat", panel, "UICheckButtonTemplate")
cbChat:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
cbChat.Text:SetText(self.L["OPT_CHAT"])
RegisterLabel(cbChat.Text, "OPT_CHAT")
cbChat:SetScript("OnClick", function(selfBtn)
CritTracker.db.enableChat = selfBtn:GetChecked()
end)
self.ui_cbChat = cbChat
curY = curY - 28
-- Checkbox: Enable Sound
local cbSound = CreateFrame("CheckButton", "CritTrackerCB_Sound", panel, "UICheckButtonTemplate")
cbSound:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
cbSound.Text:SetText(self.L["OPT_SOUND"])
RegisterLabel(cbSound.Text, "OPT_SOUND")
cbSound:SetScript("OnClick", function(selfBtn)
CritTracker.db.enableSound = selfBtn:GetChecked()
end)
self.ui_cbSound = cbSound
curY = curY - 28
-- Checkbox: Enable Low Crit
local cbLow = CreateFrame("CheckButton", "CritTrackerCB_Low", panel, "UICheckButtonTemplate")
cbLow:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
cbLow.Text:SetText(self.L["OPT_LOW"])
RegisterLabel(cbLow.Text, "OPT_LOW")
cbLow:SetScript("OnClick", function(selfBtn)
CritTracker.db.enableLowCrit = selfBtn:GetChecked()
end)
self.ui_cbLow = cbLow
curY = curY - 36
-- Section: Cooldown & Threshold
local secCooldown = panel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
secCooldown:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
RegisterLabel(secCooldown, "OPT_SECTION_COOLDOWN_THRESHOLD")
curY = curY - 22
-- Slider: Cooldown (1 - 30 seconds)
local lblCooldown = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblCooldown:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX + 4, curY)
self.ui_lblCooldown = lblCooldown
curY = curY - 16
local sliderCooldown = CreateFrame("Slider", "CritTrackerSlider_Cooldown", panel, "OptionsSliderTemplate")
sliderCooldown:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX + 6, curY)
sliderCooldown:SetWidth(270)
sliderCooldown:SetMinMaxValues(1, 30)
sliderCooldown:SetValueStep(1)
sliderCooldown:SetObeyStepOnDrag(true)
_G[sliderCooldown:GetName() .. "Low"]:SetText("1s")
_G[sliderCooldown:GetName() .. "High"]:SetText("30s")
_G[sliderCooldown:GetName() .. "Text"]:SetText("")
sliderCooldown:SetScript("OnValueChanged", function(selfSlider, val)
val = math.floor(val + 0.5)
CritTracker.db.cooldown = val
lblCooldown:SetText(string.format(CritTracker.L["OPT_COOLDOWN_TITLE"], val))
end)
self.ui_sliderCooldown = sliderCooldown
curY = curY - 42
-- Slider: Low Threshold (5% - 100%, default 30%)
local lblThreshold = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblThreshold:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX + 4, curY)
self.ui_lblThreshold = lblThreshold
curY = curY - 16
local sliderThreshold = CreateFrame("Slider", "CritTrackerSlider_Threshold", panel, "OptionsSliderTemplate")
sliderThreshold:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX + 6, curY)
sliderThreshold:SetWidth(270)
sliderThreshold:SetMinMaxValues(5, 100)
sliderThreshold:SetValueStep(5)
sliderThreshold:SetObeyStepOnDrag(true)
_G[sliderThreshold:GetName() .. "Low"]:SetText("5%")
_G[sliderThreshold:GetName() .. "High"]:SetText("100%")
_G[sliderThreshold:GetName() .. "Text"]:SetText("")
sliderThreshold:SetScript("OnValueChanged", function(selfSlider, val)
val = math.floor(val + 0.5)
CritTracker.db.lowThreshold = val
lblThreshold:SetText(string.format(CritTracker.L["OPT_THRESHOLD_TITLE"], val))
end)
self.ui_sliderThreshold = sliderThreshold
curY = curY - 44
-- Section: Character Records
local secRecords = panel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
secRecords:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
RegisterLabel(secRecords, "OPT_SECTION_RECORDS")
curY = curY - 20
-- Box for Records
local recordBox = CreateFrame("Frame", nil, panel)
recordBox:SetPoint("TOPLEFT", panel, "TOPLEFT", colLeftX, curY)
recordBox:SetSize(280, 80)
local bgTex = recordBox:CreateTexture(nil, "BACKGROUND")
bgTex:SetAllPoints()
bgTex:SetColorTexture(0, 0, 0, 0.35)
local lblCharName = recordBox:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblCharName:SetPoint("TOPLEFT", recordBox, "TOPLEFT", 8, -8)
lblCharName:SetText("|cff00e5ff" .. CritTracker:GetCharKey() .. "|r")
local lblDmg = recordBox:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblDmg:SetPoint("TOPLEFT", lblCharName, "BOTTOMLEFT", 0, -6)
self.ui_lblDamageRecord = lblDmg
local lblHeal = recordBox:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblHeal:SetPoint("TOPLEFT", lblDmg, "BOTTOMLEFT", 0, -4)
self.ui_lblHealRecord = lblHeal
-- Reset Button
local resetBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
resetBtn:SetPoint("TOPLEFT", recordBox, "BOTTOMLEFT", 0, -8)
resetBtn:SetSize(160, 24)
resetBtn:SetText(self.L["OPT_RESET_BUTTON"])
RegisterLabel(resetBtn:GetFontString(), "OPT_RESET_BUTTON")
resetBtn:SetScript("OnClick", function()
CritTracker:ResetRecords()
end)
-------------------------------------------------------------------------
-- RIGHT COLUMN: Audio Selection, Test Buttons, Language
-------------------------------------------------------------------------
local colRightX = 330
local curRightY = -70
-- Section: Audio
local secAudio = panel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
secAudio:SetPoint("TOPLEFT", panel, "TOPLEFT", colRightX, curRightY)
RegisterLabel(secAudio, "OPT_SECTION_AUDIO")
curRightY = curRightY - 24
-- Damage Sound Row
local lblDmgSound = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblDmgSound:SetPoint("TOPLEFT", panel, "TOPLEFT", colRightX, curRightY)
RegisterLabel(lblDmgSound, "OPT_DAMAGE_SOUND")
curRightY = curRightY - 18
self.ui_damageButtons = {}
local dmgOptions = { "crit1", "crit2", "crit3", "crit4", "none" }
local btnX = colRightX
for _, opt in ipairs(dmgOptions) do
local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
btn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX, curRightY)
btn:SetSize((opt == "none") and 42 or 46, 22)
btn.displayLabel = (opt == "none") and (self.L["OPT_SOUND_NONE"]) or opt
btn:SetText(btn.displayLabel)
btn:SetScript("OnClick", function()
CritTracker.db.damageSound = opt
UpdateSegmentedButtons(CritTracker.ui_damageButtons, opt)
end)
self.ui_damageButtons[opt] = btn
btnX = btnX + ((opt == "none") and 44 or 48)
end
local testDmgBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
testDmgBtn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX + 4, curRightY)
testDmgBtn:SetSize(52, 22)
testDmgBtn:SetText("►")
testDmgBtn:SetScript("OnClick", function()
CritTracker:PlaySound(CritTracker.db.damageSound or "crit1")
end)
curRightY = curRightY - 34
-- Heal Sound Row
local lblHealSound = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblHealSound:SetPoint("TOPLEFT", panel, "TOPLEFT", colRightX, curRightY)
RegisterLabel(lblHealSound, "OPT_HEAL_SOUND")
curRightY = curRightY - 18
self.ui_healButtons = {}
local healOptions = { "heal1", "heal2", "heal3", "heal4", "none" }
btnX = colRightX
for _, opt in ipairs(healOptions) do
local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
btn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX, curRightY)
btn:SetSize((opt == "none") and 42 or 46, 22)
btn.displayLabel = (opt == "none") and (self.L["OPT_SOUND_NONE"]) or opt
btn:SetText(btn.displayLabel)
btn:SetScript("OnClick", function()
CritTracker.db.healSound = opt
UpdateSegmentedButtons(CritTracker.ui_healButtons, opt)
end)
self.ui_healButtons[opt] = btn
btnX = btnX + ((opt == "none") and 44 or 48)
end
local testHealBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
testHealBtn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX + 4, curRightY)
testHealBtn:SetSize(52, 22)
testHealBtn:SetText("►")
testHealBtn:SetScript("OnClick", function()
CritTracker:PlaySound(CritTracker.db.healSound or "heal1")
end)
curRightY = curRightY - 34
-- Low Sound Row
local lblLowSound = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblLowSound:SetPoint("TOPLEFT", panel, "TOPLEFT", colRightX, curRightY)
RegisterLabel(lblLowSound, "OPT_LOW_SOUND")
curRightY = curRightY - 18
self.ui_lowButtons = {}
local lowOptions = { "low", "none" }
btnX = colRightX
for _, opt in ipairs(lowOptions) do
local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
btn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX, curRightY)
btn:SetSize((opt == "none") and 42 or 52, 22)
btn.displayLabel = (opt == "none") and (self.L["OPT_SOUND_NONE"]) or opt
btn:SetText(btn.displayLabel)
btn:SetScript("OnClick", function()
CritTracker.db.lowSound = opt
UpdateSegmentedButtons(CritTracker.ui_lowButtons, opt)
end)
self.ui_lowButtons[opt] = btn
btnX = btnX + ((opt == "none") and 44 or 54)
end
local testLowBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
testLowBtn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX + 4, curRightY)
testLowBtn:SetSize(52, 22)
testLowBtn:SetText("►")
testLowBtn:SetScript("OnClick", function()
CritTracker:PlaySound(CritTracker.db.lowSound or "low")
end)
curRightY = curRightY - 36
-- Sound Channel Row
local lblChannel = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblChannel:SetPoint("TOPLEFT", panel, "TOPLEFT", colRightX, curRightY)
RegisterLabel(lblChannel, "OPT_SOUND_CHANNEL")
curRightY = curRightY - 18
self.ui_channelButtons = {}
local chanOptions = { "Master", "Dialog", "SFX" }
btnX = colRightX
for _, opt in ipairs(chanOptions) do
local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
btn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX, curRightY)
btn:SetSize(62, 22)
btn.displayLabel = opt
btn:SetText(opt)
btn:SetScript("OnClick", function()
CritTracker.db.soundChannel = opt
UpdateSegmentedButtons(CritTracker.ui_channelButtons, opt)
end)
self.ui_channelButtons[opt] = btn
btnX = btnX + 66
end
curRightY = curRightY - 38
-- Section: Language
local secLang = panel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
secLang:SetPoint("TOPLEFT", panel, "TOPLEFT", colRightX, curRightY)
RegisterLabel(secLang, "OPT_SECTION_LANGUAGE")
curRightY = curRightY - 22
local lblLang = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
lblLang:SetPoint("TOPLEFT", panel, "TOPLEFT", colRightX, curRightY)
RegisterLabel(lblLang, "OPT_LANGUAGE")
curRightY = curRightY - 18
self.ui_langButtons = {}
local langOptions = {
{ id = "auto", label = "OPT_LANG_AUTO", width = 85 },
{ id = "German", label = "OPT_LANG_DE", width = 85 },
{ id = "English", label = "OPT_LANG_EN", width = 85 },
}
btnX = colRightX
for _, item in ipairs(langOptions) do
local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
btn:SetPoint("TOPLEFT", panel, "TOPLEFT", btnX, curRightY)
btn:SetSize(item.width, 22)
btn.displayLabel = self.L[item.label] or item.id
btn:SetText(btn.displayLabel)
btn:SetScript("OnClick", function()
CritTracker.db.language = item.id
CritTracker:UpdateOptionsDisplay()
end)
self.ui_langButtons[item.id] = btn
btnX = btnX + item.width + 4
end
-- Initial display refresh
panel:SetScript("OnShow", function()
CritTracker:UpdateOptionsDisplay()
end)
-- Register with Modern Settings API if available
if Settings and Settings.RegisterCanvasLayoutCategory then
local category = Settings.RegisterCanvasLayoutCategory(panel, "CritTracker")
Settings.RegisterAddOnCategory(category)
CritTracker.settingsCategory = category
elseif InterfaceOptions_AddCategory then
InterfaceOptions_AddCategory(panel)
end
end
-- Open Options Method
function CritTracker:OpenOptions()
if InCombatLockdown and InCombatLockdown() then
self:Print("|cffff4444[CritTracker]|r Einstellungen können während des Kampfes nicht geöffnet werden.")
return
end
local opened = false
if Settings and Settings.OpenToCategory and self.settingsCategory then
local ok = pcall(function()
Settings.OpenToCategory(self.settingsCategory:GetID())
end)
if ok then
opened = true
end
end
if not opened and InterfaceOptionsFrame_OpenToCategory and panel then
local ok = pcall(function()
InterfaceOptionsFrame_OpenToCategory(panel)
end)
if ok then
opened = true
end
end
-- Fallback to Standalone Movable Dialog Window
if not opened and panel then
if panel:GetParent() ~= UIParent then
panel:SetParent(UIParent)
end
if not panel.hasBackdrop and BackdropTemplateMixin then
Mixin(panel, BackdropTemplateMixin)
panel:SetBackdrop({
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true, tileSize = 32, edgeSize = 32,
insets = { left = 8, right = 8, top = 8, bottom = 8 },
})
panel.hasBackdrop = true
end
panel:ClearAllPoints()
panel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
panel:Show()
self:UpdateOptionsDisplay()
end
end

398
CritTracker.lua Normal file
View File

@@ -0,0 +1,398 @@
--[[
CritTracker - Core Engine
World of Warcraft - Forever (12.x Beta / Interface 16001)
]]
local addonName, addonTable = ...
local CritTracker = CreateFrame("Frame", "CritTrackerFrame")
addonTable.CritTracker = CritTracker
_G["CritTracker"] = CritTracker
-- Timing & State
CritTracker.lastOutputTime = 0
-- Defaults
local defaultSettings = {
enabled = true,
enableChat = true,
enableSound = true,
enableLowCrit = true,
lowThreshold = 30, -- 30% of highest recorded crit
cooldown = 5, -- 5 seconds output cooldown (1-30s)
damageSound = "crit1", -- crit1, crit2, crit3, crit4, none
healSound = "heal1", -- heal1, heal2, heal3, heal4, none
lowSound = "low", -- low, none
soundChannel = "Master", -- Master, Dialog, SFX
language = "auto", -- "auto", "German", "English"
announceMode = "all", -- "all" (subject to cooldown) or "records_only"
}
-- Dynamic Localization Proxy
CritTracker.L = setmetatable({}, {
__index = function(_, key)
local lang = CritTracker:GetActiveLanguage()
local locKey = (lang == "German") and "deDE" or "enUS"
local locTable = (addonTable.Locales and addonTable.Locales[locKey]) or (addonTable.Locales and addonTable.Locales["enUS"])
if locTable and locTable[key] then
return locTable[key]
end
if addonTable.Locales and addonTable.Locales["enUS"] and addonTable.Locales["enUS"][key] then
return addonTable.Locales["enUS"][key]
end
return key
end
})
-- Language Resolution
function CritTracker:GetActiveLanguage()
local setting = (self.db and self.db.language) or "auto"
if setting == "German" then
return "German"
elseif setting == "English" then
return "English"
else
-- Auto: German client -> German, all others -> English
local locale = GetLocale()
if locale == "deDE" then
return "German"
else
return "English"
end
end
end
-- Sound File Path
function CritTracker:GetSoundFilePath(soundKey)
local lang = self:GetActiveLanguage()
return "Interface\\AddOns\\CritTracker\\sounds\\" .. lang .. "\\" .. soundKey .. ".mp3"
end
-- Play Sound
function CritTracker:PlaySound(soundKey)
if not self.db.enableSound or not soundKey or soundKey == "none" then
return
end
local path = self:GetSoundFilePath(soundKey)
local channel = self.db.soundChannel or "Master"
PlaySoundFile(path, channel)
end
-- Print Message (Visible strictly to the local player)
function CritTracker:Print(msg)
if not msg then return end
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage(msg)
else
print(msg)
end
end
-- Format Numbers with commas / dots
function CritTracker:FormatNumber(n)
if not n then return "0" end
if BreakUpLargeNumbers then
return BreakUpLargeNumbers(n)
end
local left, num, right = string.match(tostring(n), '^([^%d]*%d)(%d*)(.-)$')
if not left then return tostring(n) end
return left .. (num:reverse():gsub('(%d%d%d)', '%1.'):reverse()) .. right
end
-- Character Data Accessor (strictly per character & realm)
function CritTracker:GetCharKey()
local name = UnitName("player") or "Unknown"
local realm = GetRealmName() or "UnknownRealm"
return name .. " - " .. realm
end
function CritTracker:GetCharData()
if not CritTrackerDB then
CritTrackerDB = {}
end
if not CritTrackerDB.chars then
CritTrackerDB.chars = {}
end
local key = self:GetCharKey()
if not CritTrackerDB.chars[key] then
CritTrackerDB.chars[key] = {
level = UnitLevel("player") or 1,
maxDamage = 0,
maxDamageSpell = "",
maxHeal = 0,
maxHealSpell = "",
}
end
return CritTrackerDB.chars[key]
end
-- Reset Records for Current Character
function CritTracker:ResetRecords()
local charData = self:GetCharData()
charData.maxDamage = 0
charData.maxDamageSpell = ""
charData.maxHeal = 0
charData.maxHealSpell = ""
charData.level = UnitLevel("player") or 1
local charName = UnitName("player") or "Player"
self:Print(string.format(self.L["RESET_SUCCESS"], charName))
if self.UpdateOptionsDisplay then
self:UpdateOptionsDisplay()
end
end
-- Level Up Handler
function CritTracker:OnLevelUp(newLevel)
local charData = self:GetCharData()
charData.maxDamage = 0
charData.maxDamageSpell = ""
charData.maxHeal = 0
charData.maxHealSpell = ""
charData.level = newLevel or UnitLevel("player")
self:Print(string.format(self.L["LEVEL_UP_RESET"], charData.level))
if self.UpdateOptionsDisplay then
self:UpdateOptionsDisplay()
end
end
-- Initialization
function CritTracker:OnInitialize()
if not CritTrackerDB then
CritTrackerDB = {}
end
if not CritTrackerDB.settings then
CritTrackerDB.settings = {}
end
for k, v in pairs(defaultSettings) do
if CritTrackerDB.settings[k] == nil then
CritTrackerDB.settings[k] = v
end
end
self.db = CritTrackerDB.settings
-- Initialize Character
local charData = self:GetCharData()
local curLevel = UnitLevel("player")
if charData.level and charData.level > 0 and curLevel > charData.level then
-- Leveled up while offline / addon disabled
self:OnLevelUp(curLevel)
else
charData.level = curLevel
end
if self.InitConfig then
self:InitConfig()
end
end
-- Main Crit Processing
function CritTracker:ProcessCrit(isHeal, spellName, amount, destName)
if not self.db or not self.db.enabled or not amount or amount <= 0 then
return
end
local charData = self:GetCharData()
local currentRecord = isHeal and charData.maxHeal or charData.maxDamage
local isNewRecord = (amount > currentRecord)
local isLowCrit = false
local thresholdPercent = self.db.lowThreshold or 30
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
isLowCrit = true
end
-- Update Record if higher
if isNewRecord then
if isHeal then
charData.maxHeal = amount
charData.maxHealSpell = spellName or ""
else
charData.maxDamage = amount
charData.maxDamageSpell = spellName or ""
end
if self.UpdateOptionsDisplay then
self:UpdateOptionsDisplay()
end
end
-- Cooldown Check for Audio and Text Output
local now = GetTime()
local cooldown = self.db.cooldown or 5
if (now - self.lastOutputTime) < cooldown then
-- Suppressed by Cooldown (Records are already preserved)
return
end
-- Determine Sound and Text
local soundKey = nil
local chatMsg = nil
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 amountStr = self:FormatNumber(amount)
local recordStr = self:FormatNumber(currentRecord)
if isLowCrit then
soundKey = self.db.lowSound or "low"
local formatStr = isHeal and self.L["LOW_CRIT_HEAL"] or self.L["LOW_CRIT_DAMAGE"]
chatMsg = string.format(formatStr, destStr, spellStr, amountStr, thresholdPercent, recordStr)
elseif isNewRecord then
soundKey = isHeal and (self.db.healSound or "heal1") or (self.db.damageSound or "crit1")
local formatStr = isHeal and self.L["NEW_RECORD_HEAL"] or self.L["NEW_RECORD_DAMAGE"]
chatMsg = string.format(formatStr, destStr, spellStr, amountStr)
else
-- Normal Crit (between threshold and current record)
if self.db.announceMode == "all" then
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"]
chatMsg = string.format(formatStr, destStr, spellStr, amountStr)
end
end
-- If something triggered, apply cooldown and output
if soundKey or chatMsg then
self.lastOutputTime = now
if self.db.enableSound and soundKey and soundKey ~= "none" then
self:PlaySound(soundKey)
end
if self.db.enableChat and chatMsg then
self:Print(chatMsg)
end
end
end
-- Combat Log Parser
function CritTracker:OnCombatLogEvent()
local timestamp, subevent, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags,
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
end
if subevent == "SWING_DAMAGE" then
local amount = arg12
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
elseif subevent == "SPELL_DAMAGE" or subevent == "RANGE_DAMAGE"
or subevent == "SPELL_PERIODIC_DAMAGE" or subevent == "SPELL_BUILDING_DAMAGE" then
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
local totalDamage = (amount or 0) + (absorbed or 0)
self:ProcessCrit(false, spellName, totalDamage, destName)
end
elseif subevent == "SPELL_HEAL" or subevent == "SPELL_PERIODIC_HEAL" or subevent == "SPELL_BUILDING_HEAL" then
local spellId = arg12
local spellName = arg13
local spellSchool = arg14
local amount = arg15
local overhealing = arg16
local absorbed = arg17
local critical = arg18
if critical and critical ~= 0 then
local totalHeal = (amount or 0) + (overhealing or 0)
self:ProcessCrit(true, spellName, totalHeal, destName)
end
end
end
-- Event Registration
CritTracker:RegisterEvent("ADDON_LOADED")
CritTracker:RegisterEvent("PLAYER_LOGIN")
CritTracker:RegisterEvent("PLAYER_LEVEL_UP")
CritTracker:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
CritTracker:SetScript("OnEvent", function(self, event, ...)
if event == "COMBAT_LOG_EVENT_UNFILTERED" then
self:OnCombatLogEvent()
elseif event == "PLAYER_LEVEL_UP" then
local newLevel = ...
self:OnLevelUp(newLevel)
elseif event == "ADDON_LOADED" then
local name = ...
if name == addonName then
self:OnInitialize()
end
elseif event == "PLAYER_LOGIN" then
local charData = self:GetCharData()
local curLevel = UnitLevel("player")
if charData.level and charData.level > 0 and curLevel > charData.level then
self:OnLevelUp(curLevel)
else
charData.level = curLevel
end
end
end)
-- Slash Commands
SLASH_CRITTRACKER1 = "/crittracker"
SLASH_CRITTRACKER2 = "/ctr"
SlashCmdList["CRITTRACKER"] = function(msg)
local cmd, arg = string.match((msg or ""):lower():trim(), "^(%S*)%s*(.-)$")
if cmd == "" or cmd == "config" or cmd == "options" or cmd == "menu" then
if CritTracker.OpenOptions then
CritTracker:OpenOptions()
end
elseif cmd == "reset" then
CritTracker:ResetRecords()
elseif cmd == "stats" then
local charData = CritTracker:GetCharData()
local dmgStr = CritTracker:FormatNumber(charData.maxDamage)
local healStr = CritTracker:FormatNumber(charData.maxHeal)
local dmgSpell = (charData.maxDamageSpell ~= "") and charData.maxDamageSpell or CritTracker.L["OPT_STATS_NONE"]
local healSpell = (charData.maxHealSpell ~= "") and charData.maxHealSpell or CritTracker.L["OPT_STATS_NONE"]
local name = UnitName("player") or "Player"
CritTracker:Print(string.format(CritTracker.L["CURRENT_STATS"], name, charData.level or 1, dmgStr, dmgSpell, healStr, healSpell))
elseif cmd == "test" then
if arg == "heal" then
CritTracker:PlaySound(CritTracker.db.healSound or "heal1")
CritTracker:Print(string.format(CritTracker.L["NORMAL_CRIT_HEAL"], "", "Test Heal", CritTracker:FormatNumber(50000)))
elseif arg == "low" then
CritTracker:PlaySound("low")
CritTracker:Print(string.format(CritTracker.L["LOW_CRIT_DAMAGE"], "", "Test Low Hit", CritTracker:FormatNumber(150), CritTracker.db.lowThreshold, CritTracker:FormatNumber(1000)))
else
CritTracker:PlaySound(CritTracker.db.damageSound or "crit1")
CritTracker:Print(string.format(CritTracker.L["NORMAL_CRIT_DAMAGE"], "", "Test Strike", CritTracker:FormatNumber(125000)))
end
else
CritTracker:Print(CritTracker.L["SLASH_HELP_HEADER"])
CritTracker:Print(CritTracker.L["SLASH_HELP_CONFIG"])
CritTracker:Print(CritTracker.L["SLASH_HELP_STATS"])
CritTracker:Print(CritTracker.L["SLASH_HELP_RESET"])
CritTracker:Print(CritTracker.L["SLASH_HELP_TEST"])
end
end

13
CritTracker.toc Normal file
View File

@@ -0,0 +1,13 @@
## Interface: 16001
## Title: CritTracker
## Title-deDE: CritTracker
## Notes: Tracks critical strikes and heals with sound alerts and chat announcements.
## Notes-deDE: Zeichnet kritische Treffer und Heilungen getrennt auf, mit Sound- und Chat-Benachrichtigungen.
## Author: Sarah
## Version: 1.0.0
## SavedVariables: CritTrackerDB
## IconTexture: Interface\Icons\Spell_Nature_BloodLust
Localization.lua
CritTracker.lua
Config.lua

157
Localization.lua Normal file
View File

@@ -0,0 +1,157 @@
--[[
CritTracker Localization
Supports German (deDE) and English (default / enUS)
]]
local _, addonTable = ...
local L_enUS = {
-- General / Branding
["ADDON_TITLE"] = "CritTracker",
["ADDON_SUBTITLE"] = "World of Warcraft - Forever (12.x Beta)",
["MELEE_ATTACK"] = "Melee Attack",
["UNKNOWN"] = "Unknown",
["TARGET_ON"] = "on %s",
-- Chat Announcements (Player only)
["NEW_RECORD_DAMAGE"] = "|cff00e5ff[CritTracker]|r |cffffd100★ NEW DAMAGE RECORD! ★|r %s with %s: |cffff4444%s|r!",
["NEW_RECORD_HEAL"] = "|cff00e5ff[CritTracker]|r |cffffd100★ NEW HEAL RECORD! ★|r %s with %s: |cff44ff44%s|r!",
["NORMAL_CRIT_DAMAGE"] = "|cff00e5ff[CritTracker]|r |cffff8800Critical Strike:|r %s with %s: |cffffffff%s|r",
["NORMAL_CRIT_HEAL"] = "|cff00e5ff[CritTracker]|r |cff00ff88Critical Heal:|r %s with %s: |cffffffff%s|r",
["LOW_CRIT_DAMAGE"] = "|cff00e5ff[CritTracker]|r |cff999999Low Crit (Damage):|r %s with %s: |cffff8888%s|r (|cffaaaaaa<= %d%% of record %s|r)",
["LOW_CRIT_HEAL"] = "|cff00e5ff[CritTracker]|r |cff999999Low Crit (Heal):|r %s with %s: |cff88ff88%s|r (|cffaaaaaa<= %d%% of record %s|r)",
["LEVEL_UP_RESET"] = "|cff00e5ff[CritTracker]|r Level Up (%d)! Crit records have been reset to 0.",
["RESET_SUCCESS"] = "|cff00e5ff[CritTracker]|r Crit records for %s have been reset to 0.",
["CURRENT_STATS"] = "|cff00e5ff[CritTracker]|r Current Records for %s (Level %d):\n - Damage: |cffff4444%s|r (%s)\n - Healing: |cff44ff44%s|r (%s)",
-- Options UI
["OPT_SECTION_GENERAL"] = "General Options",
["OPT_ENABLE"] = "Enable Addon",
["OPT_ENABLE_DESC"] = "Enable or disable CritTracker completely.",
["OPT_CHAT"] = "Enable Chat Output",
["OPT_CHAT_DESC"] = "Show critical strikes and heals in your chat window (visible to you only).",
["OPT_SOUND"] = "Enable Audio Alerts",
["OPT_SOUND_DESC"] = "Play audio cues for critical strikes, heals, and low crits.",
["OPT_LOW"] = "Enable Low Crit Alerts",
["OPT_LOW_DESC"] = "Trigger special alert and sound when a critical hit/heal falls below your threshold.",
["OPT_SECTION_COOLDOWN_THRESHOLD"] = "Cooldown & Threshold",
["OPT_COOLDOWN_TITLE"] = "Alert Cooldown: %d sec.",
["OPT_COOLDOWN_DESC"] = "Minimum time between chat and audio announcements (1-30 seconds). Prevents audio overlapping and chat spam.",
["OPT_THRESHOLD_TITLE"] = "Low Crit Threshold: %d%%",
["OPT_THRESHOLD_DESC"] = "Percentage of your current record. Crits at or below this value are considered low crits (Default: 30%%).",
["OPT_SECTION_AUDIO"] = "Audio Settings",
["OPT_DAMAGE_SOUND"] = "Damage Crit Sound:",
["OPT_HEAL_SOUND"] = "Heal Crit Sound:",
["OPT_LOW_SOUND"] = "Low Crit Sound:",
["OPT_SOUND_CHANNEL"] = "Sound Channel:",
["OPT_CHANNEL_MASTER"] = "Master",
["OPT_CHANNEL_DIALOG"] = "Dialog",
["OPT_CHANNEL_SFX"] = "SFX",
["OPT_SOUND_NONE"] = "None",
["OPT_SECTION_LANGUAGE"] = "Language / Sprache",
["OPT_LANGUAGE"] = "Voice & Text Language:",
["OPT_LANG_AUTO"] = "Auto (Client)",
["OPT_LANG_DE"] = "German (Deutsch)",
["OPT_LANG_EN"] = "English",
["OPT_TEST_BUTTON"] = "Play Test",
["OPT_TEST_DAMAGE"] = "Test Damage",
["OPT_TEST_HEAL"] = "Test Heal",
["OPT_TEST_LOW"] = "Test Low",
["OPT_SECTION_RECORDS"] = "Character Records",
["OPT_STATS_HEADER"] = "Current character records:",
["OPT_STATS_DAMAGE"] = "Highest Damage: %s (%s)",
["OPT_STATS_HEAL"] = "Highest Heal: %s (%s)",
["OPT_STATS_NONE"] = "None recorded yet",
["OPT_RESET_BUTTON"] = "Reset Records",
["OPT_RESET_TOOLTIP"] = "Reset highest damage and healing records for this character to 0.",
-- Slash Command Help
["SLASH_HELP_HEADER"] = "|cff00e5ffCritTracker Commands:|r",
["SLASH_HELP_CONFIG"] = " |cffffd100/ctr|r or |cffffd100/crittracker|r - Open settings panel",
["SLASH_HELP_STATS"] = " |cffffd100/ctr stats|r - Show current records",
["SLASH_HELP_RESET"] = " |cffffd100/ctr reset|r - Reset current character records",
["SLASH_HELP_TEST"] = " |cffffd100/ctr test [crit|heal|low]|r - Test sound and chat alert",
}
local L_deDE = {
-- General / Branding
["ADDON_TITLE"] = "CritTracker",
["ADDON_SUBTITLE"] = "World of Warcraft - Forever (12.x Beta)",
["MELEE_ATTACK"] = "Nahkampfangriff",
["UNKNOWN"] = "Unbekannt",
["TARGET_ON"] = "an %s",
-- Chat Announcements (Player only)
["NEW_RECORD_DAMAGE"] = "|cff00e5ff[CritTracker]|r |cffffd100★ NEUER SCHADENS-REKORD! ★|r %s mit %s: |cffff4444%s|r!",
["NEW_RECORD_HEAL"] = "|cff00e5ff[CritTracker]|r |cffffd100★ NEUER HEILUNGS-REKORD! ★|r %s mit %s: |cff44ff44%s|r!",
["NORMAL_CRIT_DAMAGE"] = "|cff00e5ff[CritTracker]|r |cffff8800Kritischer Treffer:|r %s mit %s: |cffffffff%s|r",
["NORMAL_CRIT_HEAL"] = "|cff00e5ff[CritTracker]|r |cff00ff88Kritische Heilung:|r %s mit %s: |cffffffff%s|r",
["LOW_CRIT_DAMAGE"] = "|cff00e5ff[CritTracker]|r |cff999999Schwacher Krit (Schaden):|r %s mit %s: |cffff8888%s|r (|cffaaaaaa<= %d%% von Rekord %s|r)",
["LOW_CRIT_HEAL"] = "|cff00e5ff[CritTracker]|r |cff999999Schwache kritische Heilung:|r %s mit %s: |cff88ff88%s|r (|cffaaaaaa<= %d%% von Rekord %s|r)",
["LEVEL_UP_RESET"] = "|cff00e5ff[CritTracker]|r Stufenaufstieg (%d)! Krit-Rekorde wurden auf 0 zurückgesetzt.",
["RESET_SUCCESS"] = "|cff00e5ff[CritTracker]|r Krit-Rekorde für %s wurden auf 0 zurückgesetzt.",
["CURRENT_STATS"] = "|cff00e5ff[CritTracker]|r Aktuelle Rekorde für %s (Stufe %d):\n - Schaden: |cffff4444%s|r (%s)\n - Heilung: |cff44ff44%s|r (%s)",
-- Options UI
["OPT_SECTION_GENERAL"] = "Allgemeine Optionen",
["OPT_ENABLE"] = "Addon aktivieren",
["OPT_ENABLE_DESC"] = "Aktiviert oder deaktiviert CritTracker vollständig.",
["OPT_CHAT"] = "Chat-Ausgabe aktivieren",
["OPT_CHAT_DESC"] = "Zeigt kritische Treffer und Heilungen im Chat an (nur für dich sichtbar).",
["OPT_SOUND"] = "Sound-Ausgabe aktivieren",
["OPT_SOUND_DESC"] = "Spielt Sounds bei kritischen Treffern, Heilungen und schwachen Crits ab.",
["OPT_LOW"] = "Schwache Crits erkennen & melden",
["OPT_LOW_DESC"] = "Meldet kritische Treffer oder Heilungen, die unter dem Grenzwert deines Rekords liegen.",
["OPT_SECTION_COOLDOWN_THRESHOLD"] = "Cooldown & Grenzwert",
["OPT_COOLDOWN_TITLE"] = "Ausgabe-Cooldown: %d Sek.",
["OPT_COOLDOWN_DESC"] = "Mindestabstand zwischen Chat- und Sound-Ausgaben (1-30 Sekunden). Verhindert Audio-Überlagerungen und Chat-Spam bei schnellen Crits.",
["OPT_THRESHOLD_TITLE"] = "Grenzwert für schwache Crits: %d%%",
["OPT_THRESHOLD_DESC"] = "Prozentsatz deines aktuellen Rekords. Ein Krit bei oder unter diesem Wert gilt als schwacher Krit (Standard: 30%%).",
["OPT_SECTION_AUDIO"] = "Audio-Einstellungen",
["OPT_DAMAGE_SOUND"] = "Sound für Schaden-Crits:",
["OPT_HEAL_SOUND"] = "Sound für Heilung-Crits:",
["OPT_LOW_SOUND"] = "Sound für schwache Crits:",
["OPT_SOUND_CHANNEL"] = "Sound-Kanal:",
["OPT_CHANNEL_MASTER"] = "Master",
["OPT_CHANNEL_DIALOG"] = "Dialog",
["OPT_CHANNEL_SFX"] = "SFX",
["OPT_SOUND_NONE"] = "Aus",
["OPT_SECTION_LANGUAGE"] = "Sprache / Language",
["OPT_LANGUAGE"] = "Audio- & Textsprache:",
["OPT_LANG_AUTO"] = "Automatisch (Client)",
["OPT_LANG_DE"] = "Deutsch (German)",
["OPT_LANG_EN"] = "English",
["OPT_TEST_BUTTON"] = "Test abspielen",
["OPT_TEST_DAMAGE"] = "Test Schaden",
["OPT_TEST_HEAL"] = "Test Heilung",
["OPT_TEST_LOW"] = "Test Low",
["OPT_SECTION_RECORDS"] = "Charakter-Rekorde",
["OPT_STATS_HEADER"] = "Aktuelle Rekorde dieses Charakters:",
["OPT_STATS_DAMAGE"] = "Höchster Schaden: %s (%s)",
["OPT_STATS_HEAL"] = "Höchste Heilung: %s (%s)",
["OPT_STATS_NONE"] = "Noch keine Rekorde erfasst",
["OPT_RESET_BUTTON"] = "Rekorde zurücksetzen",
["OPT_RESET_TOOLTIP"] = "Setzt den höchsten Schaden- und Heilungswert dieses Charakters auf 0 zurück.",
-- Slash Command Help
["SLASH_HELP_HEADER"] = "|cff00e5ffCritTracker Befehle:|r",
["SLASH_HELP_CONFIG"] = " |cffffd100/ctr|r oder |cffffd100/crittracker|r - Einstellungsmenü öffnen",
["SLASH_HELP_STATS"] = " |cffffd100/ctr stats|r - Aktuelle Rekorde anzeigen",
["SLASH_HELP_RESET"] = " |cffffd100/ctr reset|r - Rekorde dieses Charakters auf 0 zurücksetzen",
["SLASH_HELP_TEST"] = " |cffffd100/ctr test [crit|heal|low]|r - Test-Sound und Chat-Meldung auslösen",
}
addonTable.Locales = {
["enUS"] = L_enUS,
["deDE"] = L_deDE,
}

View File

@@ -1,2 +1,46 @@
# CritTracker # CritTracker
CritTracker ist ein World of Warcraft Addon für **World of Warcraft - Forever (12.x Beta)** (TOC Interface `16001`), das kritische Treffer und Heilungen getrennt erfasst und mit anpassbarem Audio- und lokalem Chat-Feedback versieht.
---
## Features
- **Getrennte Erfassung für Schaden & Heilung:**
- Protokolliert höchste kritische Treffer (Rekorde) für Schaden und Heilung getrennt nach Zauber/Angriff.
- Nur für den Spieler sichtbare Chat-Ausgabe (`AddMessage`), die in den Einstellungen vollständig deaktiviert werden kann.
- **Erkennung schwacher kritischer Treffer ("Low Crits"):**
- Einstellbarer Schwellenwert (standardmäßig **30%** des aktuellen Rekords).
- Spielt bei schwachen Crits die Sound-Datei `low.mp3` ab und gibt eine dezent gefärbte Nachricht aus.
- **Sicherheits-Mechanismen:**
- **Level-Up Reset:** Bei jedem Stufenaufstieg (`PLAYER_LEVEL_UP`) sowie beim ersten Login nach einem Level-Up werden die Rekorde dieses Charakters automatisch auf 0 zurückgesetzt.
- **Ausgabe-Cooldown:** Ein Schieberegler (1–30 Sekunden, Standard 5s) verhindert Audio-Überlagerungen und Chat-Spam bei schnellen aufeinanderfolgenden Crits.
- **Charakterspezifische Datenspeicherung:** Rekorde werden getrennt nach Charakter & Realm gespeichert, sodass Twinks nicht gegen Endgame-Zahlen antreten.
- **Audio-Feedback:**
- Schadens-Crits: Auswahl zwischen `crit1` bis `crit4` (oder deaktiviert).
- Heilungs-Crits: Auswahl zwischen `heal1` bis `heal4` (oder deaktiviert).
- Schwache Crits: `low.mp3` (oder deaktiviert).
- Wählbarer Sound-Kanal (`Master`, `Dialog`, `SFX`).
- **Zweisprachigkeit:**
- Deutsche Clients (`deDE`): Deutsche Benutzeroberfläche und deutsche Sounds (`sounds/German/`).
- Alle anderen Clients: Englische Benutzeroberfläche und englische Sounds (`sounds/English/`).
- Optionale manuelle Sprachauswahl in den Einstellungen.
---
## Befehle (Slash Commands)
- `/ctr` oder `/crittracker` – Öffnet das Einstellungsmenü.
- `/ctr stats` – Zeigt die aktuellen Rekorde des Charakters im Chat an.
- `/ctr reset` – Setzt die Rekorde des aktuellen Charakters manuell auf 0 zurück.
- `/ctr test [crit|heal|low]` – Löst einen Test-Sound und eine Chat-Nachricht aus.
- `/ctr test crit` (Schadens-Crit Test)
- `/ctr test heal` (Heilungs-Crit Test)
- `/ctr test low` (Schwacher Treffer Test)
---
## Installation
Kopiere den Ordner `CritTracker` in dein Addon-Verzeichnis:
`World of Warcraft\_retail_\Interface\AddOns\CritTracker`

Binary file not shown.