diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 25f537e3..4a287405 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ ko_fi: enderneko -custom: "https://afdian.com/@enderneko" \ No newline at end of file +custom: "https://afdian.com/@enderneko" diff --git a/.gitignore b/.gitignore index 5c499c57..cd5e2008 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .utils/*.csv Libs/* + # excluded !.release/*.sh !Libs/CallbackHandler.lua @@ -16,4 +17,12 @@ Libs/* !Libs/LibBadWords.lua !Libs/LoadLibs.xml !Libs/LoadLibs_Classic.xml -!Libs/LibTranslit-1.0 \ No newline at end of file +!Libs/LibTranslit-1.0 + + +.DS_Store +.codex +Cell-r*-release-*.zip + +# local tooling / references +skills/ diff --git a/.release/validate_package.sh b/.release/validate_package.sh new file mode 100755 index 00000000..762569a2 --- /dev/null +++ b/.release/validate_package.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: .release/validate_package.sh + +Validates whether an extracted Cell package looks like a real installable addon +package or a source archive from GitHub. +EOF +} + +if [[ $# -ne 1 ]]; then + usage >&2 + exit 2 +fi + +input_path="$1" + +if [[ ! -d "$input_path" ]]; then + echo "ERROR: path does not exist or is not a directory: $input_path" >&2 + exit 2 +fi + +addon_root="$input_path" + +# GitHub source archives often unpack into a parent folder that contains the +# actual addon root as a nested "Cell" directory. +if [[ -d "$input_path/Cell" && -f "$input_path/Cell/Cell.toc" ]]; then + addon_root="$input_path/Cell" +fi + +if [[ ! -f "$addon_root/Cell.toc" ]]; then + echo "ERROR: could not find Cell.toc under: $addon_root" >&2 + exit 2 +fi + +required_lib_paths=( + "Libs/LibStub/LibStub.lua" + "Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml" + "Libs/AceComm-3.0/AceComm-3.0.xml" + "Libs/LibSerialize/lib.xml" + "Libs/LibCustomGlow-1.0/LibCustomGlow-1.0.xml" + "Libs/LibSharedMedia-3.0/lib.xml" + "Libs/LibDeflate/lib.xml" +) + +source_markers=( + ".gitignore" + ".gitattributes" + ".pkgmeta" + ".github" + ".release" +) + +missing=() +markers_found=() + +for path in "${required_lib_paths[@]}"; do + if [[ ! -e "$addon_root/$path" ]]; then + missing+=("$path") + fi +done + +for path in "${source_markers[@]}"; do + if [[ -e "$addon_root/$path" ]]; then + markers_found+=("$path") + fi +done + +echo "Inspecting package root: $addon_root" +echo + +if [[ ${#missing[@]} -eq 0 ]]; then + echo "Required embedded libraries: OK" +else + echo "Required embedded libraries: MISSING" + for path in "${missing[@]}"; do + echo " - $path" + done +fi + +echo + +if [[ ${#markers_found[@]} -eq 0 ]]; then + echo "Source archive markers: none detected" +else + echo "Source archive markers detected:" + for path in "${markers_found[@]}"; do + echo " - $path" + done +fi + +echo + +if [[ ${#missing[@]} -gt 0 ]]; then + echo "RESULT: INVALID INSTALL PACKAGE" + echo "This looks like a source archive or an incomplete release." + echo "Install the packaged release asset instead of GitHub 'Source code'." + exit 1 +fi + +if [[ ${#markers_found[@]} -gt 0 ]]; then + echo "RESULT: SUSPICIOUS PACKAGE" + echo "Libraries are present, but repository-only files were included." + echo "Double-check which asset was uploaded or downloaded." + exit 1 +fi + +echo "RESULT: PACKAGE LOOKS INSTALLABLE" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2a7519..8962aa42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,68 +1,4 @@ -# r275.5 Added Midnight Raid Debuffs +[View Full Changelog](https://github.com/enderneko/Cell/compare/r276-beta...f35daad0c620b95f475dee61945fbab7cbee2fcb) -## Raid Debuffs -- Added initial Midnight expansion raid debuffs for all 12 instances (6 raids, 6 dungeons) and 41 bosses. -- Boss ability spell IDs sourced from the Encounter Journal via wago.tools DB2 tables. -- General (trash mob) debuffs still need to be collected in-game and added in a future update. -- Spells may need further in-game curation to filter out non-debuff abilities. - -# r275-release — WoW 12.0.0 (Midnight) Compatibility - -Comprehensive compatibility update for WoW Patch 12.0.0 (Midnight), addressing the removal of `COMBAT_LOG_EVENT_UNFILTERED`, the introduction of Secret Values, blocked addon communications during restricted contexts, and spell/API removals. Interface bumped to 120001. - -## Secret Values (12.0.0+) -- Add `Cell.isMidnight` detection flag and `F.IsSecretValue()`, `F.IsAuraRestricted()`, `F.IsCooldownRestricted()` utility functions -- Add per-aura `F.IsAuraNonSecret()`, `F.IsSpellAuraNonSecret()`, `F.IsValueNonSecret()` helpers — non-secret (whitelisted) auras now get real countdown timers, source detection, and duration display; secret auras gracefully degrade -- UnitButton: major dual-path refactor — Midnight uses `UnitHealPredictionCalculator`, `C_CurveUtil.CreateCurve()`, and StatusBar overlays for health/prediction/shields; pre-Midnight retains arithmetic-based paths -- Appearance: IncomingHeal widget uses `SetStatusBarTexture` on Midnight (StatusBar) vs `SetTexture` pre-Midnight (Texture) -- Indicator_Defaults: local `DebuffTypeColor` fallback for when the WoW global is removed -- Per-field `F.IsValueNonSecret()` guards before every arithmetic operation on temporal aura fields (`expirationTime`, `duration`, `applications`, and cached `old*` variants) - -## CLEU Removal -- AoEHealing: disabled on Midnight (CLEU unavailable); frame still exists for potential future non-CLEU API -- StatusIcon: soulstone/resurrection tracking switches to `UNIT_AURA` + `UNIT_HEALTH` on Midnight -- NPCFrame: boss6-8 health/aura tracking switches to unit events on Midnight -- DeathReport: full refactor — Midnight uses `UNIT_HEALTH` + `UnitIsDeadOrGhost()` for death detection -- UnitButton: removed `CombatLogGetCurrentEventInfo` dependency and `CheckCLEURequired` -- General: removed `useCleuHealthUpdater` checkbox (CLEU health updater obsolete) -- Revise: r275 migration removes `useCleuHealthUpdater` from saved variables - -## Comm Restrictions -- Comm: `IsCommRestricted()` detects encounters/M+/PvP; all `SendCommMessage` calls guarded; pending queue with flush on `ENCOUNTER_END` -- Nicknames: all nickname sync sends guarded with `F.IsCommRestricted()` - -## Heal Prediction & Health Bar Fixes -- Created a dedicated `healPredictionCalculator` separate from the shared `healthCalculator` — the heal prediction function's `SetIncomingHealClampMode(0)` and `SetIncomingHealOverflowPercent(1.0)` were persisting on the shared calculator and corrupting health/absorb reads -- Incoming heal bar is now a StatusBar (instead of Texture) anchored to the health fill texture edge -- Fixed health bar loss color stuck on white/full-health — `self.states.healthPercent` was never set on the Midnight path; now populated from `calculator:GetCurrentHealthPercent()` with a secret-safe fallback -- Dispels now show correctly because `HandleDebuff` completes to the dispel detection code (string/boolean fields, not temporal arithmetic) - -## Spell & Default Updates -- Removed: Engulf, Renew, Power Word: Life, Void Shift, Shadow Covenant, Divine Star, Cloudburst Totem, Minor Cenarion Ward, Premonition of Solace -- Added: Plea (200829, Disc Priest) -- Added missing healing spells to default indicator list (Evoker, Monk, Paladin, Priest) -- Moved: Prayer of Mending from class-wide to Holy spec only -- Fixed: Shaman Poison dispel node IDs (103609 → 103599) - -## Defensive Nil Guards & Fixes -- MainFrame: nil guards for `currentLayoutTable` and `tooltipPoint` -- HideBlizzard: guards for `PartyMemberFramePool`, `CompactPartyFrame`, `PartyMemberBackground` -- RaidDebuffs: nil guard for encounter journal expansion data -- TargetedSpells: skip enemy spell tracking during restricted periods -- BuffTracker: guard `GetAuraDataBySpellName` when auras are restricted; per-aura `sourceUnit` check -- QuickCast: skip only secret auras in `ForEachAura` -- Custom indicators: per-aura secret check for duration/start -- Appearance: ticker nil guard in preview `OnHide` - -## Infrastructure -- All 22 XML files updated from `FrameXML/UI_shared.xsd` → `Blizzard_SharedXML/UI.xsd` -- Core: version constants bumped to 275, `GetBattlegroundInfo` guard added - ---- - -# r274-release - -[View Full Changelog](https://github.com/enderneko/Cell/compare/r273-release...c376c32494926a90b93cc63bfc564234fb6e5cd6) - -- Update Molten Core debuffs -- Fix boss unit button mapping +- feat: 添加决战奥格瑞玛团本减益 (#488, thanks @Follen) +- feat: 添加 Classic ZG 团本减益数据 (#487, thanks @Follen) diff --git a/Cell.toc b/Cell.toc index 9fe590cd..3989448d 100644 --- a/Cell.toc +++ b/Cell.toc @@ -1,11 +1,25 @@ -## Interface: 120001 +## Interface: 120100 ## Title: Cell -## Version: r275-beta -## Author: enderneko +## Version: r277.9.7.8 +## Author: enderneko, Krysio, NeRgY ## X-Flavor: Mainline ## SavedVariables: CellDB, CellDBBackup +## LoadSavedVariablesFirst: 1 ## AddonCompartmentFunc: Cell_OnAddonCompartmentClick ## IconTexture: Interface\AddOns\Cell\Media\icon.tga +## Notes: Beautiful and customizable raid/party frames +## Notes-deDE: Schone und anpassbare Raid-/Gruppenfenster +## Category-enUS: Unit Frames +## Category-deDE: Einheitenfenster +## Category-esES: Marcos de unidades +## Category-esMX: Marcos de unidades +## Category-frFR: Portraits d'unités +## Category-itIT: Riquadri unità +## Category-koKR: 유닛 프레임 +## Category-ptBR: Quadros de unidade +## Category-ruRU: Рамки юнитов +## Category-zhCN: 单位框体 +## Category-zhTW: 單位框架 ## X-Curse-Project-ID: 409666 ## X-Wago-ID: qv63LLKb @@ -13,12 +27,29 @@ Locales\LoadLocales.xml -Libs\LoadLibs.xml +Libs\utf8.lua +Libs\CallbackHandler.lua +Libs\PixelPerfect.lua +Libs\FAIAP.lua +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua +Libs\LibSharedMedia-3.0\LibSharedMedia-3.0.lua +Libs\LibCustomGlow-1.0\LibCustomGlow-1.0.lua +Libs\AceComm-3.0\ChatThrottleLib.lua +Libs\AceComm-3.0\AceComm-3.0.lua +Libs\LibSerialize\LibSerialize.lua +Libs\LibDeflate\LibDeflate.lua +Libs\LibSpecData.lua +Libs\LibGroupInfo.lua +Libs\LibBadWords.lua +Libs\LibTranslit-1.0\LibTranslit-1.0.lua Core.lua Utils.lua +Utils_Auras.lua Revise.lua Comm\LoadComm.xml +Widgets\Fonts.xml Widgets\LoadWidgets.xml Defaults\LoadDefaults.xml @@ -34,13 +65,18 @@ Indicators\Actions.lua Indicators\Custom.lua Indicators\Supporter.lua +RaidFrames\HealersAuraDisplay.lua +RaidFrames\CustomAuraDisplay.lua +RaidFrames\CombatAuraDisplay.lua RaidFrames\MainFrame.lua Modules\LoadModules.xml Utilities\LoadUtilities.xml RaidDebuffs\LoadRaidDebuffs.xml +RaidFrames\SecretAuraFingerprint.lua RaidFrames\UnitButton.lua +RaidFrames\HandleBuff.lua RaidFrames\UnitButton.xml RaidFrames\Groups\SoloFrame.lua RaidFrames\Groups\PartyFrame.lua @@ -49,4 +85,4 @@ RaidFrames\Groups\PetFrame.lua RaidFrames\Groups\NPCFrame.lua RaidFrames\Groups\SpotlightFrame.lua -Supporters.lua \ No newline at end of file +Supporters.lua diff --git a/Cell_Cata.toc b/Cell_Cata.toc index 13c6ab21..df2eacce 100644 --- a/Cell_Cata.toc +++ b/Cell_Cata.toc @@ -1,10 +1,26 @@ ## Interface: 40402 ## Title: Cell -## Version: r275-beta -## Author: enderneko +## Version: r277.9.7.8 +## Author: enderneko, Krysio, NeRgY ## X-Flavor: Cata ## SavedVariables: CellDB, CellDBBackup +## LoadSavedVariablesFirst: 1 ## SavedVariablesPerCharacter: CellCharacterDB +## AddonCompartmentFunc: Cell_OnAddonCompartmentClick +## IconTexture: Interface\AddOns\Cell\Media\icon.tga +## Notes: Beautiful and customizable raid/party frames +## Notes-deDE: Schone und anpassbare Raid-/Gruppenfenster +## Category-enUS: Unit Frames +## Category-deDE: Einheitenfenster +## Category-esES: Marcos de unidades +## Category-esMX: Marcos de unidades +## Category-frFR: Portraits d'unités +## Category-itIT: Riquadri unità +## Category-koKR: 유닛 프레임 +## Category-ptBR: Quadros de unidade +## Category-ruRU: Рамки юнитов +## Category-zhCN: 单位框体 +## Category-zhTW: 單位框架 ## X-Curse-Project-ID: 409666 ## X-Wago-ID: qv63LLKb @@ -12,16 +28,31 @@ Locales\LoadLocales.xml -Libs\LoadLibs_Classic.xml +Libs\utf8.lua +Libs\CallbackHandler.lua +Libs\PixelPerfect.lua +Libs\FAIAP.lua +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua +Libs\LibSharedMedia-3.0\LibSharedMedia-3.0.lua +Libs\LibCustomGlow-1.0\LibCustomGlow-1.0.lua +Libs\AceComm-3.0\ChatThrottleLib.lua +Libs\AceComm-3.0\AceComm-3.0.lua +Libs\LibSerialize\LibSerialize.lua +Libs\LibDeflate\LibDeflate.lua +Libs\LibBadWords.lua +Libs\LibTranslit-1.0\LibTranslit-1.0.lua Core_Cata.lua Utils.lua +Utils_Auras.lua Revise.lua Comm\LoadComm.xml +Widgets\Fonts.xml Widgets\LoadWidgets_Classic.xml Defaults\LoadDefaults_Cata.xml -HideBlizzard.lua +HideBlizzard_Classic.lua Indicators\Base.lua Indicators\Built-in.lua @@ -41,6 +72,7 @@ Utilities\LoadUtilities_Classic.xml RaidDebuffs\LoadRaidDebuffs_Cata.xml RaidFrames\UnitButton_Cata_Wrath.lua +RaidFrames\HandleBuff.lua RaidFrames\UnitButton.xml RaidFrames\Groups\SoloFrame.lua RaidFrames\Groups\PartyFrame.lua diff --git a/Cell_Mists.toc b/Cell_Mists.toc index 63c1e5b3..825b9207 100644 --- a/Cell_Mists.toc +++ b/Cell_Mists.toc @@ -1,9 +1,25 @@ -## Interface: 50503 +## Interface: 50504 ## Title: Cell -## Version: r275-beta -## Author: enderneko +## Version: r277.9.7.8 +## Author: enderneko, Krysio, NeRgY ## X-Flavor: Mists ## SavedVariables: CellDB, CellDBBackup +## LoadSavedVariablesFirst: 1 +## AddonCompartmentFunc: Cell_OnAddonCompartmentClick +## IconTexture: Interface\AddOns\Cell\Media\icon.tga +## Notes: Beautiful and customizable raid/party frames +## Notes-deDE: Schone und anpassbare Raid-/Gruppenfenster +## Category-enUS: Unit Frames +## Category-deDE: Einheitenfenster +## Category-esES: Marcos de unidades +## Category-esMX: Marcos de unidades +## Category-frFR: Portraits d'unités +## Category-itIT: Riquadri unità +## Category-koKR: 유닛 프레임 +## Category-ptBR: Quadros de unidade +## Category-ruRU: Рамки юнитов +## Category-zhCN: 单位框体 +## Category-zhTW: 單位框架 ## X-Curse-Project-ID: 409666 ## X-Wago-ID: qv63LLKb @@ -11,16 +27,33 @@ Locales\LoadLocales.xml -Libs\LoadLibs.xml +Libs\utf8.lua +Libs\CallbackHandler.lua +Libs\PixelPerfect.lua +Libs\FAIAP.lua +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua +Libs\LibSharedMedia-3.0\LibSharedMedia-3.0.lua +Libs\LibCustomGlow-1.0\LibCustomGlow-1.0.lua +Libs\AceComm-3.0\ChatThrottleLib.lua +Libs\AceComm-3.0\AceComm-3.0.lua +Libs\LibSerialize\LibSerialize.lua +Libs\LibDeflate\LibDeflate.lua +Libs\LibSpecData.lua +Libs\LibGroupInfo.lua +Libs\LibBadWords.lua +Libs\LibTranslit-1.0\LibTranslit-1.0.lua Core_Mists.lua Utils.lua +Utils_Auras.lua Revise.lua Comm\LoadComm.xml +Widgets\Fonts.xml Widgets\LoadWidgets_Classic.xml Defaults\LoadDefaults_Mists.xml -HideBlizzard.lua +HideBlizzard_Mists.lua Indicators\Base.lua Indicators\Built-in.lua @@ -39,6 +72,7 @@ Utilities\LoadUtilities_Mists.xml RaidDebuffs\LoadRaidDebuffs.xml RaidFrames\UnitButton_Mists.lua +RaidFrames\HandleBuff.lua RaidFrames\UnitButton.xml RaidFrames\Groups\SoloFrame.lua RaidFrames\Groups\PartyFrame.lua diff --git a/Cell_TBC.toc b/Cell_TBC.toc index 87a61566..0c0eca9c 100644 --- a/Cell_TBC.toc +++ b/Cell_TBC.toc @@ -1,10 +1,26 @@ -## Interface: 20505 +## Interface: 20506 ## Title: Cell -## Version: r275-beta -## Author: enderneko -## X-Flavor: Vanilla +## Version: r277.9.7.8 +## Author: enderneko, Krysio, NeRgY +## X-Flavor: TBC ## SavedVariables: CellDB, CellDBBackup +## LoadSavedVariablesFirst: 1 ## SavedVariablesPerCharacter: CellCharacterDB +## AddonCompartmentFunc: Cell_OnAddonCompartmentClick +## IconTexture: Interface\AddOns\Cell\Media\icon.tga +## Notes: Beautiful and customizable raid/party frames +## Notes-deDE: Schone und anpassbare Raid-/Gruppenfenster +## Category-enUS: Unit Frames +## Category-deDE: Einheitenfenster +## Category-esES: Marcos de unidades +## Category-esMX: Marcos de unidades +## Category-frFR: Portraits d'unités +## Category-itIT: Riquadri unità +## Category-koKR: 유닛 프레임 +## Category-ptBR: Quadros de unidade +## Category-ruRU: Рамки юнитов +## Category-zhCN: 单位框体 +## Category-zhTW: 單位框架 ## X-Curse-Project-ID: 409666 ## X-Wago-ID: qv63LLKb @@ -12,16 +28,31 @@ Locales\LoadLocales.xml -Libs\LoadLibs_Classic.xml +Libs\utf8.lua +Libs\CallbackHandler.lua +Libs\PixelPerfect.lua +Libs\FAIAP.lua +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua +Libs\LibSharedMedia-3.0\LibSharedMedia-3.0.lua +Libs\LibCustomGlow-1.0\LibCustomGlow-1.0.lua +Libs\AceComm-3.0\ChatThrottleLib.lua +Libs\AceComm-3.0\AceComm-3.0.lua +Libs\LibSerialize\LibSerialize.lua +Libs\LibDeflate\LibDeflate.lua +Libs\LibBadWords.lua +Libs\LibTranslit-1.0\LibTranslit-1.0.lua Core_Vanilla.lua Utils.lua +Utils_Auras.lua Revise.lua Comm\LoadComm.xml +Widgets\Fonts.xml Widgets\LoadWidgets_Classic.xml Defaults\LoadDefaults_TBC.xml -HideBlizzard.lua +HideBlizzard_Classic.lua Indicators\Base.lua Indicators\Built-in.lua @@ -41,6 +72,7 @@ Utilities\LoadUtilities_Classic.xml RaidDebuffs\LoadRaidDebuffs_TBC.xml RaidFrames\UnitButton_Vanilla.lua +RaidFrames\HandleBuff.lua RaidFrames\UnitButton.xml RaidFrames\Groups\SoloFrame.lua RaidFrames\Groups\PartyFrame.lua diff --git a/Cell_Vanilla.toc b/Cell_Vanilla.toc index bfdc4e58..4c601329 100644 --- a/Cell_Vanilla.toc +++ b/Cell_Vanilla.toc @@ -1,10 +1,26 @@ -## Interface: 11508 +## Interface: 11508, 11509 ## Title: Cell -## Version: r275-beta -## Author: enderneko +## Version: r277.9.7.8 +## Author: enderneko, Krysio, NeRgY ## X-Flavor: Vanilla ## SavedVariables: CellDB, CellDBBackup +## LoadSavedVariablesFirst: 1 ## SavedVariablesPerCharacter: CellCharacterDB +## AddonCompartmentFunc: Cell_OnAddonCompartmentClick +## IconTexture: Interface\AddOns\Cell\Media\icon.tga +## Notes: Beautiful and customizable raid/party frames +## Notes-deDE: Schone und anpassbare Raid-/Gruppenfenster +## Category-enUS: Unit Frames +## Category-deDE: Einheitenfenster +## Category-esES: Marcos de unidades +## Category-esMX: Marcos de unidades +## Category-frFR: Portraits d'unités +## Category-itIT: Riquadri unità +## Category-koKR: 유닛 프레임 +## Category-ptBR: Quadros de unidade +## Category-ruRU: Рамки юнитов +## Category-zhCN: 单位框体 +## Category-zhTW: 單位框架 ## X-Curse-Project-ID: 409666 ## X-Wago-ID: qv63LLKb @@ -12,16 +28,31 @@ Locales\LoadLocales.xml -Libs\LoadLibs_Classic.xml +Libs\utf8.lua +Libs\CallbackHandler.lua +Libs\PixelPerfect.lua +Libs\FAIAP.lua +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua +Libs\LibSharedMedia-3.0\LibSharedMedia-3.0.lua +Libs\LibCustomGlow-1.0\LibCustomGlow-1.0.lua +Libs\AceComm-3.0\ChatThrottleLib.lua +Libs\AceComm-3.0\AceComm-3.0.lua +Libs\LibSerialize\LibSerialize.lua +Libs\LibDeflate\LibDeflate.lua +Libs\LibBadWords.lua +Libs\LibTranslit-1.0\LibTranslit-1.0.lua Core_Vanilla.lua Utils.lua +Utils_Auras.lua Revise.lua Comm\LoadComm.xml +Widgets\Fonts.xml Widgets\LoadWidgets_Classic.xml Defaults\LoadDefaults_Vanilla.xml -HideBlizzard.lua +HideBlizzard_Classic.lua Indicators\Base.lua Indicators\Built-in.lua @@ -41,6 +72,7 @@ Utilities\LoadUtilities_Classic.xml RaidDebuffs\LoadRaidDebuffs_Vanilla.xml RaidFrames\UnitButton_Vanilla.lua +RaidFrames\HandleBuff.lua RaidFrames\UnitButton.xml RaidFrames\Groups\SoloFrame.lua RaidFrames\Groups\PartyFrame.lua @@ -49,4 +81,4 @@ RaidFrames\Groups\PetFrame.lua RaidFrames\Groups\NPCFrame.lua RaidFrames\Groups\SpotlightFrame.lua -Supporters.lua \ No newline at end of file +Supporters.lua diff --git a/Cell_Wrath.toc b/Cell_Wrath.toc index ff84af1b..471ebc7c 100644 --- a/Cell_Wrath.toc +++ b/Cell_Wrath.toc @@ -1,10 +1,26 @@ -## Interface: 38000 +## Interface: 38001 ## Title: Cell -## Version: r275-beta -## Author: enderneko +## Version: r277.9.7.8 +## Author: enderneko, Krysio, NeRgY ## X-Flavor: Wrath ## SavedVariables: CellDB, CellDBBackup +## LoadSavedVariablesFirst: 1 ## SavedVariablesPerCharacter: CellCharacterDB +## AddonCompartmentFunc: Cell_OnAddonCompartmentClick +## IconTexture: Interface\AddOns\Cell\Media\icon.tga +## Notes: Beautiful and customizable raid/party frames +## Notes-deDE: Schone und anpassbare Raid-/Gruppenfenster +## Category-enUS: Unit Frames +## Category-deDE: Einheitenfenster +## Category-esES: Marcos de unidades +## Category-esMX: Marcos de unidades +## Category-frFR: Portraits d'unités +## Category-itIT: Riquadri unità +## Category-koKR: 유닛 프레임 +## Category-ptBR: Quadros de unidade +## Category-ruRU: Рамки юнитов +## Category-zhCN: 单位框体 +## Category-zhTW: 單位框架 ## X-Curse-Project-ID: 409666 ## X-Wago-ID: qv63LLKb @@ -12,16 +28,31 @@ Locales\LoadLocales.xml -Libs\LoadLibs_Classic.xml +Libs\utf8.lua +Libs\CallbackHandler.lua +Libs\PixelPerfect.lua +Libs\FAIAP.lua +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua +Libs\LibSharedMedia-3.0\LibSharedMedia-3.0.lua +Libs\LibCustomGlow-1.0\LibCustomGlow-1.0.lua +Libs\AceComm-3.0\ChatThrottleLib.lua +Libs\AceComm-3.0\AceComm-3.0.lua +Libs\LibSerialize\LibSerialize.lua +Libs\LibDeflate\LibDeflate.lua +Libs\LibBadWords.lua +Libs\LibTranslit-1.0\LibTranslit-1.0.lua Core_Wrath.lua Utils.lua +Utils_Auras.lua Revise.lua Comm\LoadComm.xml +Widgets\Fonts.xml Widgets\LoadWidgets_Classic.xml Defaults\LoadDefaults_Wrath.xml -HideBlizzard.lua +HideBlizzard_Classic.lua Indicators\Base.lua Indicators\Built-in.lua @@ -41,6 +72,7 @@ Utilities\LoadUtilities_Classic.xml RaidDebuffs\LoadRaidDebuffs_Wrath.xml RaidFrames\UnitButton_Cata_Wrath.lua +RaidFrames\HandleBuff.lua RaidFrames\UnitButton.xml RaidFrames\Groups\SoloFrame.lua RaidFrames\Groups\PartyFrame.lua diff --git a/Comm/Comm.lua b/Comm/Comm.lua index 65ff64af..9af689e2 100644 --- a/Comm/Comm.lua +++ b/Comm/Comm.lua @@ -6,6 +6,48 @@ local LibDeflate = LibStub:GetLibrary("LibDeflate") local deflateConfig = {level = 9} local Serializer = LibStub:GetLibrary("LibSerialize") local Comm = LibStub:GetLibrary("AceComm-3.0") +local midnightDiagnostics = Cell.vars.midnightDiagnostics or { + versions = {}, + lastVersionRequestAt = 0, + lastVersionBroadcastAt = 0, + commQueue = { + lastQueuedAt = 0, + lastFlushAt = 0, + }, +} +Cell.vars.midnightDiagnostics = midnightDiagnostics + +local function FireMidnightDiagnosticsChanged(reason) + Cell.Fire("MidnightDiagnosticsUpdated", reason) +end + +local function NormalizeSenderName(name) + if not name or name == "" then return name end + if not strfind(name, "-") then + name = name .. "-" .. GetNormalizedRealmName() + end + return name +end + +local function UpdateVersionDiagnostic(sender, version, channel) + if not sender or not version then return end + + sender = NormalizeSenderName(sender) + midnightDiagnostics.versions[sender] = { + version = version, + versionNum = tonumber(string.match(version, "%d+")) or 0, + channel = channel, + receivedAt = time(), + } + + FireMidnightDiagnosticsChanged("version") +end + +local function RecordSelfVersion(channel) + local selfName = Cell.vars.playerNameFull or F.UnitFullName("player") + if not selfName or not Cell.version then return end + UpdateVersionDiagnostic(selfName, Cell.version, channel or "SELF") +end local function Serialize(data) local serialized = Serializer:Serialize(data) -- serialize @@ -29,48 +71,197 @@ local function Deserialize(encoded) end ----------------------------------------- --- Comm restriction (Midnight 12.0.0+) --- Addon communications are blocked during active encounters, M+ keys, and PvP matches. +-- Comm queue and wrappers ----------------------------------------- -local function IsCommRestricted() - if not Cell.isMidnight then return false end - -- Check encounter - if IsEncounterInProgress and IsEncounterInProgress() then return true end - -- Check M+ - if C_MythicPlus and C_MythicPlus.IsRunActive and C_MythicPlus.IsRunActive() then return true end - -- Check PvP - if C_PvP and C_PvP.IsActiveBattlefield and C_PvP.IsActiveBattlefield() then return true end - return false +local commQueue = {} +local commQueueByKey = {} +local commFlushTicker + +local function StopCommFlushTicker() + if commFlushTicker then + commFlushTicker:Cancel() + commFlushTicker = nil + end end --- Export for use in other Comm files (e.g. Nicknames.lua) -function F.IsCommRestricted() - return IsCommRestricted() +local function StartCommFlushTicker() + if not Cell.isMidnight or commFlushTicker then return end + commFlushTicker = C_Timer.NewTicker(2, function() + if #commQueue == 0 then + StopCommFlushTicker() + return + end + if not F.IsCommRestricted() then + F.FlushCommQueue() + end + end) +end + +local function BuildCommQueueKey(prefix, message, distribution, target, priority) + return table.concat({ + tostring(prefix or ""), + tostring(distribution or ""), + tostring(target or ""), + tostring(priority or ""), + tostring(message or ""), + }, "\031") end --- Simple queue for deferred sends (used when comms are restricted) -local pendingComms = {} +local function CanSendCommDistribution(distribution, target) + if distribution == "INSTANCE_CHAT" then + return IsInGroup(LE_PARTY_CATEGORY_INSTANCE) + elseif distribution == "RAID" then + return IsInRaid() + elseif distribution == "PARTY" then + return IsInGroup() + elseif distribution == "GUILD" or distribution == "OFFICER" then + return IsInGuild() + elseif distribution == "WHISPER" then + return target and target ~= "" + end + return true +end -local function QueueComm(prefix, message, channel, target, priority) - tinsert(pendingComms, {prefix=prefix, message=message, channel=channel, target=target, priority=priority}) +local function QueueCommMessage(prefix, message, distribution, target, priority, callbackFn, queueKey) + local key = queueKey or BuildCommQueueKey(prefix, message, distribution, target, priority) + local queued = commQueueByKey[key] + if queued then + queued.message = message + queued.target = target + queued.distribution = distribution + queued.priority = priority + queued.callbackFn = callbackFn + else + queued = { + key = key, + prefix = prefix, + message = message, + distribution = distribution, + target = target, + priority = priority, + callbackFn = callbackFn, + } + tinsert(commQueue, queued) + commQueueByKey[key] = queued + end + midnightDiagnostics.commQueue.lastQueuedAt = time() + StartCommFlushTicker() + F.Debug("Cell: Comm queued - restricted context ("..tostring(prefix)..")") + FireMidnightDiagnosticsChanged("queue") end -local function FlushPendingComms() - if IsCommRestricted() then return end - if #pendingComms == 0 then return end - local toSend = pendingComms - pendingComms = {} - for _, msg in ipairs(toSend) do - Comm:SendCommMessage(msg.prefix, msg.message, msg.channel, msg.target, msg.priority or "NORMAL") +function F.FlushCommQueue() + if F.IsCommRestricted() then + StartCommFlushTicker() + return false + end + + if #commQueue == 0 then + StopCommFlushTicker() + return true + end + + local pending = commQueue + commQueue = {} + commQueueByKey = {} + StopCommFlushTicker() + midnightDiagnostics.commQueue.lastFlushAt = time() + + for _, queued in ipairs(pending) do + if CanSendCommDistribution(queued.distribution, queued.target) then + Comm:SendCommMessage(queued.prefix, queued.message, queued.distribution, queued.target, queued.priority, queued.callbackFn) + else + F.Debug("Cell: Comm dropped - invalid distribution after queue ("..tostring(queued.prefix)..")") + end end + + FireMidnightDiagnosticsChanged("queue") + return true end -local commFrame = CreateFrame("Frame") -commFrame:RegisterEvent("ENCOUNTER_END") -commFrame:RegisterEvent("PLAYER_LEAVING_WORLD") -commFrame:SetScript("OnEvent", function() - C_Timer.After(1, FlushPendingComms) -end) +function F.TrySendCommMessage(prefix, message, distribution, target, priority, callbackFn, options) + if not prefix or not message or not distribution then return false end + + options = options or {} + if Cell.isMidnight and F.IsCommRestricted() then + if options.queue == false then + F.Debug("Cell: Comm suppressed - restricted context ("..tostring(prefix)..")") + return false + end + QueueCommMessage(prefix, message, distribution, target, priority, callbackFn, options.queueKey) + return false, "queued" + end + + if not CanSendCommDistribution(distribution, target) then + F.Debug("Cell: Comm suppressed - invalid distribution ("..tostring(prefix)..")") + return false + end + + Comm:SendCommMessage(prefix, message, distribution, target, priority, callbackFn) + return true +end + +function F.GetCommQueueSnapshot() + local snapshot = { + size = #commQueue, + lastQueuedAt = midnightDiagnostics.commQueue.lastQueuedAt, + lastFlushAt = midnightDiagnostics.commQueue.lastFlushAt, + entries = {}, + prefixCounts = {}, + } + + for _, queued in ipairs(commQueue) do + tinsert(snapshot.entries, { + prefix = queued.prefix, + distribution = queued.distribution, + target = queued.target, + priority = queued.priority, + }) + snapshot.prefixCounts[queued.prefix] = (snapshot.prefixCounts[queued.prefix] or 0) + 1 + end + + return snapshot +end + +function F.GetVersionDiagnosticsSnapshot() + local snapshot = { + entries = {}, + lastVersionRequestAt = midnightDiagnostics.lastVersionRequestAt, + lastVersionBroadcastAt = midnightDiagnostics.lastVersionBroadcastAt, + } + + for sender, info in pairs(midnightDiagnostics.versions) do + snapshot.entries[sender] = { + version = info.version, + versionNum = info.versionNum, + channel = info.channel, + receivedAt = info.receivedAt, + } + end + + return snapshot +end + +function F.RequestVersionDiagnostics() + RecordSelfVersion("SELF") + + local sendChannel = F.GetGroupCommChannel() + if not sendChannel then + FireMidnightDiagnosticsChanged("version") + return false + end + + midnightDiagnostics.lastVersionRequestAt = time() + midnightDiagnostics.lastVersionBroadcastAt = time() + F.TrySendCommMessage("CELL_VERSION_REQ", "req", sendChannel, nil, "ALERT", nil, { + queueKey = "CELL_VERSION_REQ:" .. sendChannel, + }) + F.TrySendCommMessage("CELL_VERSION", Cell.version, sendChannel, nil, "NORMAL", nil, { + queueKey = "CELL_VERSION:" .. sendChannel, + }) + FireMidnightDiagnosticsChanged("version") + return true +end ----------------------------------------- -- for WA @@ -81,20 +272,6 @@ function F.Notify(type, ...) end end ------------------------------------------ --- shared ------------------------------------------ -local sendChannel -local function UpdateSendChannel() - if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then - sendChannel = "INSTANCE_CHAT" - elseif IsInRaid() then - sendChannel = "RAID" - else - sendChannel = "PARTY" - end -end - ----------------------------------------- -- Check Version ----------------------------------------- @@ -107,30 +284,26 @@ eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE") function eventFrame:GROUP_ROSTER_UPDATE() if IsInGroup() then eventFrame:UnregisterEvent("GROUP_ROSTER_UPDATE") - UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ - if IsCommRestricted() then - F.Debug("Cell: Comm suppressed - restricted context (CELL_VERSION group)") - return + local sendChannel = F.GetGroupCommChannel() + if sendChannel then + midnightDiagnostics.lastVersionBroadcastAt = time() + F.TrySendCommMessage("CELL_VERSION", Cell.version, sendChannel, nil, "NORMAL") end - Comm:SendCommMessage("CELL_VERSION", Cell.version, sendChannel, nil, "NORMAL") end end eventFrame:RegisterEvent("PLAYER_LOGIN") function eventFrame:PLAYER_LOGIN() + RecordSelfVersion("SELF") if IsInGuild() then - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ - if IsCommRestricted() then - F.Debug("Cell: Comm suppressed - restricted context (CELL_VERSION guild)") - return - end - Comm:SendCommMessage("CELL_VERSION", Cell.version, "GUILD", nil, "NORMAL") + midnightDiagnostics.lastVersionBroadcastAt = time() + F.TrySendCommMessage("CELL_VERSION", Cell.version, "GUILD", nil, "NORMAL") end end Comm:RegisterComm("CELL_VERSION", function(prefix, message, channel, sender) if sender == UnitName("player") then return end + UpdateVersionDiagnostic(sender, message, channel) local version = tonumber(string.match(message, "%d+")) local myVersion = tonumber(string.match(Cell.version, "%d+")) if (not CellDB["lastVersionCheck"] or time()-CellDB["lastVersionCheck"]>=25200) and version and myVersion and myVersion < version then @@ -139,6 +312,17 @@ Comm:RegisterComm("CELL_VERSION", function(prefix, message, channel, sender) end end) +Comm:RegisterComm("CELL_VERSION_REQ", function(prefix, message, channel, sender) + if sender == UnitName("player") then return end + if not channel then return end + + local target = channel == "WHISPER" and sender or nil + midnightDiagnostics.lastVersionBroadcastAt = time() + F.TrySendCommMessage("CELL_VERSION", Cell.version, channel, target, "NORMAL", nil, { + queueKey = "CELL_VERSION_REPLY:" .. tostring(channel) .. ":" .. tostring(target or ""), + }) +end) + ----------------------------------------- -- Notify Marks ----------------------------------------- @@ -160,26 +344,20 @@ function F.NotifyMarkLock(mark, name, class) name = F.GetClassColorStr(class)..name.."|r" F.Print(L["%s lock %s on %s."]:format(L["You"], F.GetMarkEscapeSequence(mark), name)) - UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ - if IsCommRestricted() then - F.Debug("Cell: Comm suppressed - restricted context (CELL_MARKS lock)") - return + local sendChannel = F.GetGroupCommChannel() + if sendChannel then + F.TrySendCommMessage("CELL_MARKS", Serialize({true, mark, name}), sendChannel, nil, "ALERT") end - Comm:SendCommMessage("CELL_MARKS", Serialize({true, mark, name}), sendChannel, nil, "ALERT") end function F.NotifyMarkUnlock(mark, name, class) name = F.GetClassColorStr(class)..name.."|r" F.Print(L["%s unlock %s from %s."]:format(L["You"], F.GetMarkEscapeSequence(mark), name)) - UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ - if IsCommRestricted() then - F.Debug("Cell: Comm suppressed - restricted context (CELL_MARKS unlock)") - return + local sendChannel = F.GetGroupCommChannel() + if sendChannel then + F.TrySendCommMessage("CELL_MARKS", Serialize({false, mark, name}), sendChannel, nil, "ALERT") end - Comm:SendCommMessage("CELL_MARKS", Serialize({false, mark, name}), sendChannel, nil, "ALERT") end ----------------------------------------- @@ -230,13 +408,10 @@ function F.CheckPriority() UpdatePriority() -- NOTE: needs time to calc myPriority C_Timer.After(1, function() - UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ - if IsCommRestricted() then - F.Debug("Cell: Comm suppressed - restricted context (CELL_CPRIO chk)") - return + local sendChannel = F.GetGroupCommChannel() + if sendChannel then + F.TrySendCommMessage("CELL_CPRIO", "chk", sendChannel, nil, "ALERT") end - Comm:SendCommMessage("CELL_CPRIO", "chk", sendChannel, nil, "ALERT") end) -- if t_check then t_check:Cancel() end -- t_check = C_Timer.NewTimer(2, function() @@ -252,13 +427,10 @@ Comm:RegisterComm("CELL_CPRIO", function(prefix, message, channel, sender) -- NOTE: wait for check requests if t_send then t_send:Cancel() end t_send = C_Timer.NewTimer(2, function() - UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ - if IsCommRestricted() then - F.Debug("Cell: Comm suppressed - restricted context (CELL_PRIO)") - return + local sendChannel = F.GetGroupCommChannel() + if sendChannel then + F.TrySendCommMessage("CELL_PRIO", tostring(myPriority), sendChannel, nil, "ALERT") end - Comm:SendCommMessage("CELL_PRIO", tostring(myPriority), sendChannel, nil, "ALERT") end) end) @@ -282,19 +454,14 @@ end) -- cross realm send ----------------------------------------- local function CrossRealmSendCommMessage(prefix, message, playerName, priority, callbackFn) - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ - if IsCommRestricted() then - F.Debug("Cell: Comm suppressed - restricted context (CrossRealm:", prefix, ")") - return - end -- NOTE: unit needs to be in your group, or it will always return true if UnitIsSameServer(playerName) then - Comm:SendCommMessage(prefix, message, "WHISPER", playerName, priority, callbackFn) + F.TrySendCommMessage(prefix, message, "WHISPER", playerName, priority, callbackFn) else if UnitInParty(playerName) then - Comm:SendCommMessage(prefix, playerName..":"..message, "PARTY", nil, priority, callbackFn) + F.TrySendCommMessage(prefix, playerName..":"..message, "PARTY", nil, priority, callbackFn) elseif UnitInRaid(playerName) then - Comm:SendCommMessage(prefix, playerName..":"..message, "RAID", nil, priority, callbackFn) + F.TrySendCommMessage(prefix, playerName..":"..message, "RAID", nil, priority, callbackFn) end end end @@ -486,4 +653,4 @@ hooksecurefunc("SetItemRef", function(link, text) local layoutName, playerName = text:match("|Hgarrmission:cell%-layout|h|cFFFF0066%[.+: (.+) %- ([^%s]+%-[^%s]+)%]|h|r") ShowReceivingFrame("Layout", playerName, layoutName) end -end) \ No newline at end of file +end) diff --git a/Comm/Nicknames.lua b/Comm/Nicknames.lua index 1ce50419..97c40401 100644 --- a/Comm/Nicknames.lua +++ b/Comm/Nicknames.lua @@ -2,8 +2,8 @@ local _, Cell = ... local L = Cell.L local F = Cell.funcs -local LBW = LibStub:GetLibrary("LibBadWords") -local Comm = LibStub:GetLibrary("AceComm-3.0") +local LBW = LibStub and LibStub:GetLibrary("LibBadWords", true) +local Comm = LibStub and LibStub:GetLibrary("AceComm-3.0", true) ----------------------------------------- -- shared @@ -73,7 +73,6 @@ local function CheckNicknames() if nic_check then nic_check:Cancel() end nic_check = C_Timer.NewTimer(random(3), function() UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ if Cell.isMidnight and F.IsCommRestricted and F.IsCommRestricted() then F.Debug("Cell: Comm suppressed - restricted context (CELL_CNIC)") return @@ -165,7 +164,6 @@ local function UpdateNicknames(which, value1, value2) if nic_check then nic_check:Cancel() end -- disabled, notify others UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ if Cell.isMidnight and F.IsCommRestricted and F.IsCommRestricted() then F.Debug("Cell: Comm suppressed - restricted context (CELL_NIC sync-off)") else @@ -188,7 +186,6 @@ local function UpdateNicknames(which, value1, value2) -- notify others if IsInGroup() and CellDB["nicknames"]["sync"] then UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ if Cell.isMidnight and F.IsCommRestricted and F.IsCommRestricted() then F.Debug("Cell: Comm suppressed - restricted context (CELL_NIC mine)") else @@ -226,6 +223,7 @@ end Cell.RegisterCallback("UpdateNicknames", "UpdateNicknames", UpdateNicknames) -- check nickname received +if Comm then Comm:RegisterComm("CELL_CNIC", function(prefix, message, channel, sender) -- others send chk before you, no need to send chk again if nic_check then nic_check:Cancel() end @@ -233,7 +231,6 @@ Comm:RegisterComm("CELL_CNIC", function(prefix, message, channel, sender) if nic_send then nic_send:Cancel() end nic_send = C_Timer.NewTimer(3, function() UpdateSendChannel() - -- Addon comms blocked during encounters/M+/PvP on Midnight 12.0.0+ if Cell.isMidnight and F.IsCommRestricted and F.IsCommRestricted() then F.Debug("Cell: Comm suppressed - restricted context (CELL_NIC nic_send)") return @@ -255,7 +252,7 @@ Comm:RegisterComm("CELL_NIC", function(prefix, message, channel, sender) sender = sender .. "-" .. GetNormalizedRealmName() end - if message == "CELL_NONE" or Cell.vars.nicknameBlacklist[sender] or LBW.ContainsBadWords(message) then + if message == "CELL_NONE" or Cell.vars.nicknameBlacklist[sender] or (LBW and LBW.ContainsBadWords(message)) then Cell.vars.nicknames[sender] = nil else Cell.vars.nicknames[sender] = message @@ -263,6 +260,7 @@ Comm:RegisterComm("CELL_NIC", function(prefix, message, channel, sender) UpdateName(sender) end end) +end ----------------------------------------- -- NickTag diff --git a/Core.lua b/Core.lua index 224cf9ac..1cfc8b9d 100644 --- a/Core.lua +++ b/Core.lua @@ -23,6 +23,68 @@ Cell.bFuncs = {} Cell.uFuncs = {} Cell.animations = {} +-- Provide safe accent-color fallbacks before Widgets.lua initializes its +-- richer helpers. This keeps later files from exploding if a mixed install +-- loads newer callers before the real widget helpers are available. +if not Cell.GetAccentColorRGB then + local fallbackAccentColor = {0.7, 0.7, 0.7} + local fallbackAccentColorString = "|cFFB2B2B2" + + local function ClampColorComponent(value) + value = tonumber(value) or 0 + if value < 0 then return 0 end + if value > 1 then return 1 end + return value + end + + local function UpdateFallbackAccentColorString() + fallbackAccentColorString = ("|cFF%02X%02X%02X"):format( + floor(ClampColorComponent(fallbackAccentColor[1]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[2]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[3]) * 255 + 0.5) + ) + end + + function Cell.OverrideAccentColor(cTable) + if type(cTable) ~= "table" then return end + + fallbackAccentColor[1] = ClampColorComponent(cTable[1] or fallbackAccentColor[1]) + fallbackAccentColor[2] = ClampColorComponent(cTable[2] or fallbackAccentColor[2]) + fallbackAccentColor[3] = ClampColorComponent(cTable[3] or fallbackAccentColor[3]) + UpdateFallbackAccentColorString() + end + + function Cell.GetAccentColorRGB() + return unpack(fallbackAccentColor) + end + + function Cell.GetAccentColorTable(alpha) + if alpha then + return {fallbackAccentColor[1], fallbackAccentColor[2], fallbackAccentColor[3], alpha} + end + + return fallbackAccentColor + end + + function Cell.GetAccentColorString() + return fallbackAccentColorString + end + + function Cell.ColorFontStringWithAccentColor(fs) + if fs and fs.SetTextColor then + fs:SetTextColor(unpack(fallbackAccentColor)) + end + end + + function Cell.WrapTextInAccentColor(text) + if WrapTextInColorCode then + return WrapTextInColorCode(text, fallbackAccentColorString) + end + + return fallbackAccentColorString .. text .. "|r" + end +end + ---@class CellFuncs local F = Cell.funcs local I = Cell.iFuncs @@ -38,16 +100,7 @@ Cell.MIN_INDICATORS_VERSION = 275 Cell.MIN_DEBUFFS_VERSION = 275 Cell.MIN_QUICKASSIST_VERSION = 275 --- Patch 12.0.0+ Secret Value Testing CVars (use in-game to force restrictions): --- /run SetCVar("secretCombatRestrictionsForced", 1) --- /run SetCVar("secretEncounterRestrictionsForced", 1) --- /run SetCVar("secretChallengeModeRestrictionsForced", 1) --- /run SetCVar("secretPvPMatchRestrictionsForced", 1) --- Reset: /run SetCVar("secretCombatRestrictionsForced", 0) ---@debug@ -local debugMode = true ---@end-debug@ function F.Debug(arg, ...) if debugMode then if type(arg) == "string" or type(arg) == "number" then @@ -122,7 +175,7 @@ function F.UpdateLayout(layoutGroupType) end local bgMaxPlayers = { - [2197] = 40, -- 科尔拉克的复仇 + [2197] = 40, -- 科尔拉克的复仇 } -- layout auto switch @@ -219,6 +272,34 @@ function eventFrame:ADDON_LOADED(arg1) if type(CellDB["snippets"]) ~= "table" then CellDB["snippets"] = {} end if not CellDB["snippets"][0] then CellDB["snippets"][0] = F.GetDefaultSnippet() end + if type(CellDB["midnightTools"]) ~= "table" then + CellDB["midnightTools"] = { + ["showQueueIndicator"] = true, + } + end + if CellDB["midnightTools"]["showQueueIndicator"] == nil then + CellDB["midnightTools"]["showQueueIndicator"] = true + end + if type(CellDB["systemTools"]) ~= "table" then + CellDB["systemTools"] = { + ["autoBackupsEnabled"] = true, + ["maxAutoBackups"] = 12, + ["maxNotifications"] = 40, + } + end + if CellDB["systemTools"]["autoBackupsEnabled"] == nil then + CellDB["systemTools"]["autoBackupsEnabled"] = true + end + if CellDB["systemTools"]["maxAutoBackups"] == nil then + CellDB["systemTools"]["maxAutoBackups"] = 12 + end + if CellDB["systemTools"]["maxNotifications"] == nil then + CellDB["systemTools"]["maxNotifications"] = 40 + end + if type(CellDB["addonNotifications"]) ~= "table" then + CellDB["addonNotifications"] = {} + end + Cell.vars.playerClass, Cell.vars.playerClassID = UnitClassBase("player") -- general -------------------------------------------------------------------------------- @@ -241,8 +322,10 @@ function eventFrame:ADDON_LOADED(arg1) }, ["useCleuHealthUpdater"] = false, ["translit"] = false, + ["localeOverride"] = "auto", } end + if CellDB["general"]["localeOverride"] == nil then CellDB["general"]["localeOverride"] = "auto" end Cell.vars.alwaysUpdateAuras = CellDB["general"]["alwaysUpdateAuras"] -- nicknames ------------------------------------------------------------------------------ @@ -497,9 +580,27 @@ function eventFrame:ADDON_LOADED(arg1) -- defensives/externals ------------------------------------------------------------------- if type(CellDB["defensives"]) ~= "table" then CellDB["defensives"] = {["disabled"]={}, ["custom"]={}} end if type(CellDB["externals"]) ~= "table" then CellDB["externals"] = {["disabled"]={}, ["custom"]={}} end + if type(CellDB["offensives"]) ~= "table" then CellDB["offensives"] = {["disabled"]={}, ["custom"]={}} end + + -- auraBlacklist -------------------------------------------------------------------------- + if type(CellDB["auraBlacklist"]) ~= "table" then CellDB["auraBlacklist"] = {["buffs"]={}, ["debuffs"]={}} end + + if Cell.isRetail and type(CellDB["debuffBlacklist"]) == "table" then + local target = CellDB["auraBlacklist"]["debuffs"] + for _, sid in ipairs(CellDB["debuffBlacklist"]) do + if type(sid) == "number" and not target[sid] then + target[sid] = { combat = true, ooc = true } + end + end + -- Clear old array after migration: on Retail, auraBlacklist.debuffs is the source of truth. + -- Without this, every login re-adds entries the user already removed from the AuraBlacklist UI. + CellDB["debuffBlacklist"] = {} + Cell.vars.debuffBlacklist = {} + end -- raid debuffs --------------------------------------------------------------------------- if type(CellDB["raidDebuffs"]) ~= "table" then CellDB["raidDebuffs"] = {} end + if type(CellDB["raidDebuffsCuration"]) ~= "table" then CellDB["raidDebuffsCuration"] = {} end -- CellDB["raidDebuffs"] = { -- [instanceId] = { -- ["general"] = { @@ -526,6 +627,9 @@ function eventFrame:ADDON_LOADED(arg1) if type(CellDB["targetedSpellsGlow"]) ~= "table" then CellDB["targetedSpellsGlow"] = I.GetDefaultTargetedSpellsGlow() + elseif CellDB["targetedSpellsGlow"][1] == "None" then + -- Glow Type panel removed; "None" would make Border/Both look broken. + CellDB["targetedSpellsGlow"] = I.GetDefaultTargetedSpellsGlow() end Cell.vars.targetedSpellsGlow = CellDB["targetedSpellsGlow"] @@ -536,13 +640,21 @@ function eventFrame:ADDON_LOADED(arg1) if type(CellDB["actions"]) ~= "table" then CellDB["actions"] = I.GetDefaultActions() end - Cell.vars.actions = I.ConvertActions(CellDB["actions"]) -- misc ----------------------------------------------------------------------------------- - Cell.version = GetAddOnMetadata("Cell", "version") - Cell.versionNum = tonumber(string.match(Cell.version, "%d+")) + F.InitAddonVersion() if not CellDB["revise"] then CellDB["firstRun"] = true end + + -- krysio: warn non-Midnight users ------------------------------------------------ + if not Cell.isMidnight and Cell.version and strfind(Cell.version, "krysio") then + F.Print("|cffFFAA00WARNING|r This is a custom Cell build focused on WoW Retail Midnight (12.0).") + F.Print("If you play on MoP, Cata, Wrath, TBC or Vanilla, use the official |cff00FF00r276-beta|r release by Enderneko instead.") + F.Print("Download: |cff00BFFFhttps://www.curseforge.com/wow/addons/cell|r") + F.Print("You can also update via CurseForge or Wago.") + end + F.Revise() + Cell.vars.actions = I.ConvertActions(CellDB["actions"]) F.CheckWhatsNew() F.RunSnippets() @@ -825,7 +937,6 @@ function eventFrame:PLAYER_LOGIN() Cell.vars.playerNameFull = F.UnitFullName("player") --! init bgMaxPlayers - -- Midnight 12.0.0 removed GetBattlegroundInfo; guard for compatibility if GetBattlegroundInfo then for i = 1, GetNumBattlegroundTypes() do local bgName, _, _, _, _, _, bgId, maxPlayers = GetBattlegroundInfo(i) @@ -872,8 +983,14 @@ function eventFrame:PLAYER_LOGIN() Cell.Fire("UpdateCLEU") -- update builtIns and customs I.UpdateAoEHealings(CellDB["aoeHealings"]) + if I.ApplyDefaultDisabledCooldowns then + I.ApplyDefaultDisabledCooldowns() + end I.UpdateDefensives(CellDB["defensives"]) I.UpdateExternals(CellDB["externals"]) + if I.UpdateOffensives then + I.UpdateOffensives(CellDB["offensives"]) + end I.UpdateCrowdControls(CellDB["crowdControls"]) -- update pixel perfect Cell.Fire("UpdatePixelPerfect") @@ -1035,6 +1152,63 @@ function SlashCmdList.CELL(msg, editbox) F.Print(L["A 0-40 integer is required."]) end + elseif command == "midnight" then + if not Cell.isMidnight then + F.Print("Midnight tools are only available on Midnight builds.") + elseif rest == "print" or rest == "status" then + if F.PrintMidnightDiagnostics then + F.PrintMidnightDiagnostics() + end + elseif rest == "refresh" or rest == "versions" then + if F.RequestVersionDiagnostics and F.RequestVersionDiagnostics() then + F.Print("Midnight sync diagnostics refresh requested.") + else + F.Print("No group channel is available for Midnight sync diagnostics.") + end + elseif rest == "flush" then + if F.FlushCommQueue and F.FlushCommQueue() then + F.Print("Midnight comm queue flushed.") + else + F.Print("Midnight comm queue is still blocked by restrictions.") + end + elseif rest == "cvars" or rest == "test" then + if F.ShowMidnightTestCVars then + F.ShowMidnightTestCVars() + end + elseif F.ShowMidnightTools then + F.ShowMidnightTools() + end + + elseif command == "restrictions" then + if not Cell.isMidnight then + F.Print("Restriction diagnostics are only available on Midnight builds.") + elseif F.PrintMidnightDiagnostics then + F.PrintMidnightDiagnostics() + end + + elseif command == "syncdiag" then + if not Cell.isMidnight then + F.Print("Sync diagnostics are only available on Midnight builds.") + elseif rest == "refresh" then + if F.RequestVersionDiagnostics and F.RequestVersionDiagnostics() then + F.Print("Midnight sync diagnostics refresh requested.") + else + F.Print("No group channel is available for Midnight sync diagnostics.") + end + elseif rest == "flush" then + if F.FlushCommQueue and F.FlushCommQueue() then + F.Print("Midnight comm queue flushed.") + else + F.Print("Midnight comm queue is still blocked by restrictions.") + end + elseif rest == "print" then + if F.PrintMidnightDiagnostics then + F.PrintMidnightDiagnostics() + end + elseif F.ShowMidnightTools then + F.ShowMidnightTools() + end + -- elseif command == "buff" then -- rest = tonumber(rest:format("%d")) -- if rest and rest > 0 then @@ -1046,6 +1220,16 @@ function SlashCmdList.CELL(msg, editbox) -- end else + local midnightHelp = "" + if Cell.isMidnight then + midnightHelp = "\n".. + "|cFFFFB5C5/cell midnight|r: open Midnight Tools.\n".. + "|cFFFFB5C5/cell midnight cvars|r: show Midnight restriction test CVars.\n".. + "|cFFFFB5C5/cell syncdiag refresh|r: request version and sync diagnostics.\n".. + "|cFFFFB5C5/cell syncdiag flush|r: flush the queued Midnight comm messages.\n".. + "|cFFFFB5C5/cell restrictions|r: print the current Midnight restriction state." + end + F.Print(L["Available slash commands"]..":\n".. "|cFFFFB5C5/cell options|r, |cFFFFB5C5/cell opt|r: "..L["show Cell options frame"]..".\n".. "|cFFFFB5C5/cell healers|r: "..L["create a \"Healers\" indicator"]..".\n".. @@ -1057,11 +1241,13 @@ function SlashCmdList.CELL(msg, editbox) "|cFFFFB5C5/cell reset raiddebuffs|r: "..L["reset all Raid Debuffs"]..".\n".. "|cFFFFB5C5/cell reset snippets|r: "..L["reset all Code Snippets"]..".\n".. "|cFFFFB5C5/cell reset quickassist|r: "..L["reset Quick Assist for current spec"]..".\n".. - "|cFFFFB5C5/cell reset all|r: "..L["reset all Cell settings"].."." + "|cFFFFB5C5/cell reset all|r: "..L["reset all Cell settings"]..".".. + midnightHelp ) end end function Cell_OnAddonCompartmentClick() F.ShowOptionsFrame() -end \ No newline at end of file +end + diff --git a/Core_Cata.lua b/Core_Cata.lua index 617f611a..a31269f6 100644 --- a/Core_Cata.lua +++ b/Core_Cata.lua @@ -1,4 +1,4 @@ ----@class Cell +---@class Cell local Cell = select(2, ...) _G.Cell = Cell @@ -23,6 +23,68 @@ Cell.bFuncs = {} Cell.uFuncs = {} Cell.animations = {} +-- Provide safe accent-color fallbacks before Widgets.lua initializes its +-- richer helpers. This keeps later files from exploding if a mixed install +-- loads newer callers before the real widget helpers are available. +if not Cell.GetAccentColorRGB then + local fallbackAccentColor = {0.7, 0.7, 0.7} + local fallbackAccentColorString = "|cFFB2B2B2" + + local function ClampColorComponent(value) + value = tonumber(value) or 0 + if value < 0 then return 0 end + if value > 1 then return 1 end + return value + end + + local function UpdateFallbackAccentColorString() + fallbackAccentColorString = ("|cFF%02X%02X%02X"):format( + floor(ClampColorComponent(fallbackAccentColor[1]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[2]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[3]) * 255 + 0.5) + ) + end + + function Cell.OverrideAccentColor(cTable) + if type(cTable) ~= "table" then return end + + fallbackAccentColor[1] = ClampColorComponent(cTable[1] or fallbackAccentColor[1]) + fallbackAccentColor[2] = ClampColorComponent(cTable[2] or fallbackAccentColor[2]) + fallbackAccentColor[3] = ClampColorComponent(cTable[3] or fallbackAccentColor[3]) + UpdateFallbackAccentColorString() + end + + function Cell.GetAccentColorRGB() + return unpack(fallbackAccentColor) + end + + function Cell.GetAccentColorTable(alpha) + if alpha then + return {fallbackAccentColor[1], fallbackAccentColor[2], fallbackAccentColor[3], alpha} + end + + return fallbackAccentColor + end + + function Cell.GetAccentColorString() + return fallbackAccentColorString + end + + function Cell.ColorFontStringWithAccentColor(fs) + if fs and fs.SetTextColor then + fs:SetTextColor(unpack(fallbackAccentColor)) + end + end + + function Cell.WrapTextInAccentColor(text) + if WrapTextInColorCode then + return WrapTextInColorCode(text, fallbackAccentColorString) + end + + return fallbackAccentColorString .. text .. "|r" + end +end + local F = Cell.funcs local I = Cell.iFuncs local P = Cell.pixelPerfectFuncs @@ -35,9 +97,6 @@ Cell.MIN_LAYOUTS_VERSION = 246 Cell.MIN_INDICATORS_VERSION = 246 Cell.MIN_DEBUFFS_VERSION = 246 ---@debug@ -local debugMode = true ---@end-debug@ function F.Debug(arg, ...) if debugMode then if type(arg) == "string" or type(arg) == "number" then @@ -106,7 +165,7 @@ function F.UpdateLayout(layoutGroupType) end local bgMaxPlayers = { - [2197] = 40, -- 科尔拉克的复仇 + [2197] = 40, -- 科尔拉克的复仇 } -- layout auto switch @@ -211,8 +270,10 @@ function eventFrame:ADDON_LOADED(arg1) }, ["useCleuHealthUpdater"] = false, ["translit"] = false, + ["localeOverride"] = "auto", } end + if CellDB["general"]["localeOverride"] == nil then CellDB["general"]["localeOverride"] = "auto" end -- nicknames ------------------------------------------------------------------------------ if type(CellDB["nicknames"]) ~= "table" then @@ -463,9 +524,17 @@ function eventFrame:ADDON_LOADED(arg1) Cell.vars.actions = I.ConvertActions(CellDB["actions"]) -- misc ----------------------------------------------------------------------------------- - Cell.version = GetAddOnMetadata("Cell", "version") - Cell.versionNum = tonumber(string.match(Cell.version, "%d+")) + F.InitAddonVersion() if not CellDB["revise"] then CellDB["firstRun"] = true end + + -- krysio: warn non-Midnight users ------------------------------------------------ + if not Cell.isMidnight and Cell.version and strfind(Cell.version, "krysio") then + F.Print("|cffFFAA00WARNING|r This is a custom Cell build focused on WoW Retail Midnight (12.0).") + F.Print("If you play on MoP, Cata, Wrath, TBC or Vanilla, use the official |cff00FF00r276-beta|r release by Enderneko instead.") + F.Print("Download: |cff00BFFFhttps://www.curseforge.com/wow/addons/cell|r") + F.Print("You can also update via CurseForge or Wago.") + end + F.Revise() F.CheckWhatsNew() F.RunSnippets() @@ -907,4 +976,5 @@ function SlashCmdList.CELL(msg, editbox) "|cFFFFB5C5/cell reset all|r: "..L["reset all Cell settings"].."." ) end -end \ No newline at end of file +end + diff --git a/Core_Mists.lua b/Core_Mists.lua index 09e59c86..2d0b92b3 100644 --- a/Core_Mists.lua +++ b/Core_Mists.lua @@ -23,6 +23,80 @@ Cell.bFuncs = {} Cell.uFuncs = {} Cell.animations = {} +-- Some MoP / Classic builds omit the Blizzard DebuffTypeColor global. +if type(_G.DebuffTypeColor) ~= "table" then + _G.DebuffTypeColor = { + none = { r = 0.80, g = 0.00, b = 0.00 }, + Magic = { r = 0.20, g = 0.60, b = 1.00 }, + Curse = { r = 0.60, g = 0.00, b = 1.00 }, + Disease = { r = 0.60, g = 0.40, b = 0.00 }, + Poison = { r = 0.00, g = 0.60, b = 0.00 }, + } + _G.DebuffTypeColor[""] = _G.DebuffTypeColor.none +end + +-- Provide safe accent-color fallbacks before Widgets.lua initializes its +-- richer helpers. This keeps later files from exploding if a mixed install +-- loads newer callers before the real widget helpers are available. +if not Cell.GetAccentColorRGB then + local fallbackAccentColor = {0.7, 0.7, 0.7} + local fallbackAccentColorString = "|cFFB2B2B2" + + local function ClampColorComponent(value) + value = tonumber(value) or 0 + if value < 0 then return 0 end + if value > 1 then return 1 end + return value + end + + local function UpdateFallbackAccentColorString() + fallbackAccentColorString = ("|cFF%02X%02X%02X"):format( + floor(ClampColorComponent(fallbackAccentColor[1]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[2]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[3]) * 255 + 0.5) + ) + end + + function Cell.OverrideAccentColor(cTable) + if type(cTable) ~= "table" then return end + + fallbackAccentColor[1] = ClampColorComponent(cTable[1] or fallbackAccentColor[1]) + fallbackAccentColor[2] = ClampColorComponent(cTable[2] or fallbackAccentColor[2]) + fallbackAccentColor[3] = ClampColorComponent(cTable[3] or fallbackAccentColor[3]) + UpdateFallbackAccentColorString() + end + + function Cell.GetAccentColorRGB() + return unpack(fallbackAccentColor) + end + + function Cell.GetAccentColorTable(alpha) + if alpha then + return {fallbackAccentColor[1], fallbackAccentColor[2], fallbackAccentColor[3], alpha} + end + + return fallbackAccentColor + end + + function Cell.GetAccentColorString() + return fallbackAccentColorString + end + + function Cell.ColorFontStringWithAccentColor(fs) + if fs and fs.SetTextColor then + fs:SetTextColor(unpack(fallbackAccentColor)) + end + end + + function Cell.WrapTextInAccentColor(text) + if WrapTextInColorCode then + return WrapTextInColorCode(text, fallbackAccentColorString) + end + + return fallbackAccentColorString .. text .. "|r" + end +end + ---@class CellFuncs local F = Cell.funcs local I = Cell.iFuncs @@ -37,9 +111,6 @@ Cell.MIN_LAYOUTS_VERSION = 246 Cell.MIN_INDICATORS_VERSION = 246 Cell.MIN_DEBUFFS_VERSION = 246 ---@debug@ -local debugMode = true ---@end-debug@ function F.Debug(arg, ...) if debugMode then if type(arg) == "string" or type(arg) == "number" then @@ -114,7 +185,7 @@ function F.UpdateLayout(layoutGroupType) end local bgMaxPlayers = { - [2197] = 40, -- 科尔拉克的复仇 + [2197] = 40, -- 科尔拉克的复仇 } -- layout auto switch @@ -234,8 +305,10 @@ function eventFrame:ADDON_LOADED(arg1) }, ["useCleuHealthUpdater"] = false, ["translit"] = false, + ["localeOverride"] = "auto", } end + if CellDB["general"]["localeOverride"] == nil then CellDB["general"]["localeOverride"] = "auto" end Cell.vars.alwaysUpdateAuras = CellDB["general"]["alwaysUpdateAuras"] -- nicknames ------------------------------------------------------------------------------ @@ -502,9 +575,17 @@ function eventFrame:ADDON_LOADED(arg1) Cell.vars.actions = I.ConvertActions(CellDB["actions"]) -- misc ----------------------------------------------------------------------------------- - Cell.version = GetAddOnMetadata("Cell", "version") - Cell.versionNum = tonumber(string.match(Cell.version, "%d+")) + F.InitAddonVersion() if not CellDB["revise"] then CellDB["firstRun"] = true end + + -- krysio: warn non-Midnight users ------------------------------------------------ + if not Cell.isMidnight and Cell.version and strfind(Cell.version, "krysio") then + F.Print("|cffFFAA00WARNING|r This is a custom Cell build focused on WoW Retail Midnight (12.0).") + F.Print("If you play on MoP, Cata, Wrath, TBC or Vanilla, use the official |cff00FF00r276-beta|r release by Enderneko instead.") + F.Print("Download: |cff00BFFFhttps://www.curseforge.com/wow/addons/cell|r") + F.Print("You can also update via CurseForge or Wago.") + end + F.Revise() F.CheckWhatsNew() F.RunSnippets() @@ -760,6 +841,17 @@ function eventFrame:PLAYER_LOGIN() Cell.vars.playerGUID = UnitGUID("player") + -- Aura icon duration options can be nil before Appearance applies (MoP). + if type(Cell.vars.iconDurationDecimal) ~= "number" then + local opts = CellDB and CellDB.appearance and CellDB.appearance.auraIconOptions + local d = opts and opts.durationDecimal + Cell.vars.iconDurationDecimal = (type(d) == "number") and d or 0 + end + if Cell.vars.iconDurationRoundUp == nil then + local opts = CellDB and CellDB.appearance and CellDB.appearance.auraIconOptions + Cell.vars.iconDurationRoundUp = opts and opts.durationRoundUp and true or false + end + -- update spec vars UpdateSpecVars() @@ -971,4 +1063,5 @@ end function Cell_OnAddonCompartmentClick() F.ShowOptionsFrame() -end \ No newline at end of file +end + diff --git a/Core_Vanilla.lua b/Core_Vanilla.lua index 5740e21b..d3bd3ebc 100644 --- a/Core_Vanilla.lua +++ b/Core_Vanilla.lua @@ -1,4 +1,4 @@ ----@class Cell +---@class Cell local Cell = select(2, ...) _G.Cell = Cell @@ -23,6 +23,68 @@ Cell.bFuncs = {} Cell.uFuncs = {} Cell.animations = {} +-- Provide safe accent-color fallbacks before Widgets.lua initializes its +-- richer helpers. This keeps later files from exploding if a mixed install +-- loads newer callers before the real widget helpers are available. +if not Cell.GetAccentColorRGB then + local fallbackAccentColor = {0.7, 0.7, 0.7} + local fallbackAccentColorString = "|cFFB2B2B2" + + local function ClampColorComponent(value) + value = tonumber(value) or 0 + if value < 0 then return 0 end + if value > 1 then return 1 end + return value + end + + local function UpdateFallbackAccentColorString() + fallbackAccentColorString = ("|cFF%02X%02X%02X"):format( + floor(ClampColorComponent(fallbackAccentColor[1]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[2]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[3]) * 255 + 0.5) + ) + end + + function Cell.OverrideAccentColor(cTable) + if type(cTable) ~= "table" then return end + + fallbackAccentColor[1] = ClampColorComponent(cTable[1] or fallbackAccentColor[1]) + fallbackAccentColor[2] = ClampColorComponent(cTable[2] or fallbackAccentColor[2]) + fallbackAccentColor[3] = ClampColorComponent(cTable[3] or fallbackAccentColor[3]) + UpdateFallbackAccentColorString() + end + + function Cell.GetAccentColorRGB() + return unpack(fallbackAccentColor) + end + + function Cell.GetAccentColorTable(alpha) + if alpha then + return {fallbackAccentColor[1], fallbackAccentColor[2], fallbackAccentColor[3], alpha} + end + + return fallbackAccentColor + end + + function Cell.GetAccentColorString() + return fallbackAccentColorString + end + + function Cell.ColorFontStringWithAccentColor(fs) + if fs and fs.SetTextColor then + fs:SetTextColor(unpack(fallbackAccentColor)) + end + end + + function Cell.WrapTextInAccentColor(text) + if WrapTextInColorCode then + return WrapTextInColorCode(text, fallbackAccentColorString) + end + + return fallbackAccentColorString .. text .. "|r" + end +end + local F = Cell.funcs local I = Cell.iFuncs local P = Cell.pixelPerfectFuncs @@ -35,9 +97,6 @@ Cell.MIN_LAYOUTS_VERSION = 246 Cell.MIN_INDICATORS_VERSION = 246 Cell.MIN_DEBUFFS_VERSION = 246 ---@debug@ -local debugMode = true ---@end-debug@ function F.Debug(arg, ...) if debugMode then if type(arg) == "string" or type(arg) == "number" then @@ -86,6 +145,9 @@ function F.UpdateLayout(layoutGroupType) local layout = Cell.vars.layoutAutoSwitch[layoutGroupType] Cell.vars.layoutGroupType = layoutGroupType + local prevLayout = Cell.vars.currentLayout + local prevHidden = Cell.vars.isHidden + if layout == "hide" then Cell.vars.isHidden = true Cell.vars.currentLayout = "default" @@ -96,9 +158,11 @@ function F.UpdateLayout(layoutGroupType) Cell.vars.currentLayoutTable = CellDB["layouts"][layout] end - F.IterateAllUnitButtons(function(b) - b._indicatorsReady = nil - end, true) + if prevLayout ~= Cell.vars.currentLayout or prevHidden ~= Cell.vars.isHidden then + F.IterateAllUnitButtons(function(b) + b._indicatorsReady = nil + end, true) + end Cell.Fire("UpdateLayout", layout) Cell.Fire("UpdateIndicators") @@ -196,8 +260,10 @@ function eventFrame:ADDON_LOADED(arg1) }, ["useCleuHealthUpdater"] = false, ["translit"] = false, + ["localeOverride"] = "auto", } end + if CellDB["general"]["localeOverride"] == nil then CellDB["general"]["localeOverride"] = "auto" end -- nicknames ------------------------------------------------------------------------------ if type(CellDB["nicknames"]) ~= "table" then @@ -448,9 +514,17 @@ function eventFrame:ADDON_LOADED(arg1) Cell.vars.actions = I.ConvertActions(CellDB["actions"]) -- misc ----------------------------------------------------------------------------------- - Cell.version = GetAddOnMetadata("Cell", "version") - Cell.versionNum = tonumber(string.match(Cell.version, "%d+")) + F.InitAddonVersion() if not CellDB["revise"] then CellDB["firstRun"] = true end + + -- krysio: warn non-Midnight users ------------------------------------------------ + if not Cell.isMidnight and Cell.version and strfind(Cell.version, "krysio") then + F.Print("|cffFFAA00WARNING|r This is a custom Cell build focused on WoW Retail Midnight (12.0).") + F.Print("If you play on MoP, Cata, Wrath, TBC or Vanilla, use the official |cff00FF00r276-beta|r release by Enderneko instead.") + F.Print("Download: |cff00BFFFhttps://www.curseforge.com/wow/addons/cell|r") + F.Print("You can also update via CurseForge or Wago.") + end + F.Revise() F.CheckWhatsNew() F.RunSnippets() @@ -837,3 +911,4 @@ function SlashCmdList.CELL(msg, editbox) ) end end + diff --git a/Core_Wrath.lua b/Core_Wrath.lua index be4663f5..07708642 100644 --- a/Core_Wrath.lua +++ b/Core_Wrath.lua @@ -1,4 +1,4 @@ ----@class Cell +---@class Cell local Cell = select(2, ...) _G.Cell = Cell @@ -23,6 +23,68 @@ Cell.bFuncs = {} Cell.uFuncs = {} Cell.animations = {} +-- Provide safe accent-color fallbacks before Widgets.lua initializes its +-- richer helpers. This keeps later files from exploding if a mixed install +-- loads newer callers before the real widget helpers are available. +if not Cell.GetAccentColorRGB then + local fallbackAccentColor = {0.7, 0.7, 0.7} + local fallbackAccentColorString = "|cFFB2B2B2" + + local function ClampColorComponent(value) + value = tonumber(value) or 0 + if value < 0 then return 0 end + if value > 1 then return 1 end + return value + end + + local function UpdateFallbackAccentColorString() + fallbackAccentColorString = ("|cFF%02X%02X%02X"):format( + floor(ClampColorComponent(fallbackAccentColor[1]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[2]) * 255 + 0.5), + floor(ClampColorComponent(fallbackAccentColor[3]) * 255 + 0.5) + ) + end + + function Cell.OverrideAccentColor(cTable) + if type(cTable) ~= "table" then return end + + fallbackAccentColor[1] = ClampColorComponent(cTable[1] or fallbackAccentColor[1]) + fallbackAccentColor[2] = ClampColorComponent(cTable[2] or fallbackAccentColor[2]) + fallbackAccentColor[3] = ClampColorComponent(cTable[3] or fallbackAccentColor[3]) + UpdateFallbackAccentColorString() + end + + function Cell.GetAccentColorRGB() + return unpack(fallbackAccentColor) + end + + function Cell.GetAccentColorTable(alpha) + if alpha then + return {fallbackAccentColor[1], fallbackAccentColor[2], fallbackAccentColor[3], alpha} + end + + return fallbackAccentColor + end + + function Cell.GetAccentColorString() + return fallbackAccentColorString + end + + function Cell.ColorFontStringWithAccentColor(fs) + if fs and fs.SetTextColor then + fs:SetTextColor(unpack(fallbackAccentColor)) + end + end + + function Cell.WrapTextInAccentColor(text) + if WrapTextInColorCode then + return WrapTextInColorCode(text, fallbackAccentColorString) + end + + return fallbackAccentColorString .. text .. "|r" + end +end + local F = Cell.funcs local I = Cell.iFuncs local P = Cell.pixelPerfectFuncs @@ -35,9 +97,6 @@ Cell.MIN_LAYOUTS_VERSION = 246 Cell.MIN_INDICATORS_VERSION = 246 Cell.MIN_DEBUFFS_VERSION = 246 ---@debug@ -local debugMode = true ---@end-debug@ function F.Debug(arg, ...) if debugMode then if type(arg) == "string" or type(arg) == "number" then @@ -106,7 +165,7 @@ function F.UpdateLayout(layoutGroupType) end local bgMaxPlayers = { - [2197] = 40, -- 科尔拉克的复仇 + [2197] = 40, -- 科尔拉克的复仇 } -- layout auto switch @@ -211,8 +270,10 @@ function eventFrame:ADDON_LOADED(arg1) }, ["useCleuHealthUpdater"] = false, ["translit"] = false, + ["localeOverride"] = "auto", } end + if CellDB["general"]["localeOverride"] == nil then CellDB["general"]["localeOverride"] = "auto" end -- nicknames ------------------------------------------------------------------------------ if type(CellDB["nicknames"]) ~= "table" then @@ -463,9 +524,17 @@ function eventFrame:ADDON_LOADED(arg1) Cell.vars.actions = I.ConvertActions(CellDB["actions"]) -- misc ----------------------------------------------------------------------------------- - Cell.version = GetAddOnMetadata("Cell", "version") - Cell.versionNum = tonumber(string.match(Cell.version, "%d+")) + F.InitAddonVersion() if not CellDB["revise"] then CellDB["firstRun"] = true end + + -- krysio: warn non-Midnight users ------------------------------------------------ + if not Cell.isMidnight and Cell.version and strfind(Cell.version, "krysio") then + F.Print("|cffFFAA00WARNING|r This is a custom Cell build focused on WoW Retail Midnight (12.0).") + F.Print("If you play on MoP, Cata, Wrath, TBC or Vanilla, use the official |cff00FF00r276-beta|r release by Enderneko instead.") + F.Print("Download: |cff00BFFFhttps://www.curseforge.com/wow/addons/cell|r") + F.Print("You can also update via CurseForge or Wago.") + end + F.Revise() F.CheckWhatsNew() F.RunSnippets() @@ -902,4 +971,5 @@ function SlashCmdList.CELL(msg, editbox) "|cFFFFB5C5/cell reset all|r: "..L["reset all Cell settings"].."." ) end -end \ No newline at end of file +end + diff --git a/Defaults/Appearance_Defaults.lua b/Defaults/Appearance_Defaults.lua index f0220988..d8473120 100644 --- a/Defaults/Appearance_Defaults.lua +++ b/Defaults/Appearance_Defaults.lua @@ -16,7 +16,7 @@ Cell.defaults.appearance = { ["barAlpha"] = 1, ["lossAlpha"] = 1, ["bgAlpha"] = 1, - ["barAnimation"] = "Flash", + ["barAnimation"] = "Smooth", ["colorThresholds"] = {{1,0,0}, {1,0.7,0}, {0.7,1,0}, 0.05, 0.95, true}, ["colorThresholdsLoss"] = {{1,0,0}, {1,0.7,0}, {0.7,1,0}, 0.05, 0.95, true}, ["auraIconOptions"] = { @@ -26,6 +26,7 @@ Cell.defaults.appearance = { ["durationColorEnabled"] = false, ["durationColors"] = {{0,1,0}, {1,1,0,0.5}, {1,0,0,3}}, }, + ["cooldownStyle"] = "VERTICAL", ["targetColor"] = {1, 0.31, 0.31, 1}, ["mouseoverColor"] = {1, 1, 1, 0.6}, ["highlightSize"] = 1, @@ -51,6 +52,7 @@ local buttonStyleIndices = { "colorThresholds", "colorThresholdsLoss", "auraIconOptions", + "cooldownStyle", "targetColor", "mouseoverColor", "highlightSize", @@ -71,4 +73,4 @@ function F.ResetButtonStyle() CellDB["appearance"][index] = Cell.defaults.appearance[index] end end -end \ No newline at end of file +end diff --git a/Defaults/Indicator_Bleeds.lua b/Defaults/Indicator_Bleeds.lua index 46c556cc..a5bd2c2c 100644 --- a/Defaults/Indicator_Bleeds.lua +++ b/Defaults/Indicator_Bleeds.lua @@ -4,7 +4,6 @@ local I = Cell.iFuncs local bleedList function I.CheckDebuffType(debuffType, spellId) - -- Midnight 12.0.0+: debuffType and spellId may be secret — can't compare or use as table key if issecretvalue and (issecretvalue(spellId) or issecretvalue(debuffType)) then return debuffType end diff --git a/Defaults/Indicator_DefaultSpells.lua b/Defaults/Indicator_DefaultSpells.lua index 29988548..77e98740 100644 --- a/Defaults/Indicator_DefaultSpells.lua +++ b/Defaults/Indicator_DefaultSpells.lua @@ -184,8 +184,13 @@ function I.UpdateAoEHealings(t) end function I.IsAoEHealing(name, id) - if issecretvalue and (issecretvalue(name) or issecretvalue(id)) then return end - return builtInAoEHealings[name] or builtInAoEHealings[id] or customAoEHealings[id] + if F.IsValueNonSecret(name) and builtInAoEHealings[name] then + return true + end + + if F.IsValueNonSecret(id) then + return builtInAoEHealings[id] or customAoEHealings[id] + end end local summonDuration = { @@ -217,75 +222,107 @@ end ------------------------------------------------- local externals = { -- true: track by name, false: track by id ["DEATHKNIGHT"] = { - [51052] = true, -- 反魔法领域 - Anti-Magic Zone + [51052] = { -- 反魔法领域 - Anti-Magic Zone + [145629] = true, + }, + [454863] = true, -- Lesser Anti-Magic Shell }, ["DEMONHUNTER"] = { - [196718] = true, -- 黑暗 - Darkness + [196718] = { -- 黑暗 - Darkness + [209426] = true, + }, }, ["DRUID"] = { [102342] = true, -- 铁木树皮 - Ironbark + [33891] = true, -- 化身:生命之树 - Incarnation: Tree of Life + [740] = { -- 宁静 - Tranquility + [157982] = true, + [1264623] = true, + }, }, ["EVOKER"] = { [374227] = true, -- 微风 - Zephyr [357170] = true, -- 时间膨胀 - Time Dilation - [378441] = true, -- 时间停止 - Time Stop (pvp) - [374348] = true, -- 新生光焰 - Renewing blaze + [363534] = true, -- 回溯 - Rewind + [410651] = true, -- Molten Blood + [359816] = { -- 梦境飞行 - Dream Flight + [362361] = true, + [363502] = true, + }, }, - ["MAGE"] = { - [198158] = true, -- 群体隐形 - Mass Invisibility - [414660] = { -- 群体屏障 - Mass Barrier - [414661] = false, -- 寒冰护体 - Ice Barrier - [414662] = false, -- 烈焰护体 - Blazing Barrier - [414663] = false, -- 棱光护体 - Prismatic Barrier - -- [11426] = false, -- 寒冰护体 (self) - -- [235313] = false, -- 烈焰护体 (self) - -- [235450] = false, -- 棱光护体 (self) - }, + ["HUNTER"] = { + [53480] = true, -- 牺牲咆哮 - Roar of Sacrifice }, ["MONK"] = { [116849] = true, -- 作茧缚命 - Life Cocoon - [202248] = false, -- 偏转冥想 - Guided Meditation + [322118] = true, -- 青龙下凡 - Invoke Yu'lon + [325197] = true, -- 朱鹤下凡 - Invoke Chi-Ji + [443028] = { -- 天神御身 - Celestial Conduit + [1248992] = true, + }, + [1260681] = { -- Chi Cocoon + [406139] = true, + [406220] = true, + [451299] = true, + [432772] = true, + }, }, ["PALADIN"] = { - [1022] = true, -- 保护祝福 - Blessing of Protection + [1022] = { -- 保护祝福 - Blessing of Protection + [1309794] = true, + }, [6940] = true, -- 牺牲祝福 - Blessing of Sacrifice [204018] = true, -- 破咒祝福 - Blessing of Spellwarding - [31821] = true, -- 光环掌握 - Aura Mastery - [210256] = true, -- 庇护祝福 - Blessing of Sanctuary - [228050] = false, -- 圣盾术 (被遗忘的女王护卫) - Divine Shield - -- [211210] = true, -- 提尔的保护 - -- [216328] = true, -- 光之优雅 + [387804] = true, -- Echoing Protection + [387792] = true, -- Empyreal Ward + [461499] = true, -- Overflowing Light + [211210] = true, -- Protection of Tyr + [200652] = { -- 提尔的拯救 - Tyr's Deliverance + [200654] = true, + }, + [31821] = { -- 光环掌握 - Aura Mastery + [317929] = true, + }, + [31884] = { -- 复仇之怒 - Avenging Wrath + [454351] = true, + }, + [216331] = true, -- 复仇十字军 - Avenging Crusader }, ["PRIEST"] = { [33206] = true, -- 痛苦压制 - Pain Suppression [47788] = true, -- 守护之魂 - Guardian Spirit - [62618] = true, -- 真言术:障 - Power Word: Barrier - [213610] = true, -- 神圣守卫 - Holy Ward - [197268] = true, -- 希望之光 - Ray of Hope - }, - - ["ROGUE"] = { - [114018] = true, -- 潜伏帷幕 - Shroud of Concealment + [10060] = true, -- 能量灌注 - Power Infusion + [62618] = { -- 真言术:障 - Power Word: Barrier + [81782] = true, + }, + [64843] = { -- 神圣赞美诗 - Divine Hymn + [64844] = true, + }, + [472433] = true, -- Evangelism + [200183] = true, -- 神圣化身 - Apotheosis + [15286] = true, -- 吸血鬼的拥抱 - Vampiric Embrace + [421453] = true, -- Ultimate Penitence }, ["SHAMAN"] = { - [98008] = true, -- 灵魂链接图腾 - Spirit Link Totem - [201633] = true, -- 大地之墙图腾 - Earthen Wall - [8178] = true, -- 根基图腾 - Grounding Totem - [383018] = true, -- 石肤图腾 - Stoneskin + [98008] = { -- 灵魂链接图腾 - Spirit Link Totem + [325174] = true, + }, + [114052] = true, -- 升腾 - Ascendance + [462568] = true, -- Elemental Resistance }, ["WARRIOR"] = { - [97462] = true, -- 集结呐喊 - Rallying Cry - [3411] = true, -- 援护 - Intervene - [213871] = true, -- 护卫 - Bodyguard + [97462] = { -- 集结呐喊 - Rallying Cry + [97463] = true, + }, }, } @@ -302,9 +339,10 @@ local function UpdateExternals(id, trackByName) if name then builtInExternals[name] = true end - else - builtInExternals[id] = true end + -- Also store by ID (in addition to name when trackByName is true) + -- so IsExternalCooldown/IsDefensiveCooldown can match by ID directly + builtInExternals[id] = true end function I.UpdateExternals(t) @@ -334,21 +372,31 @@ function I.UpdateExternals(t) -- end customExternals[id] = true end + Cell.vars.builtInExternals = builtInExternals + Cell.vars.customExternals = customExternals end local UnitIsUnit = UnitIsUnit local bos = F.GetSpellInfo(6940) -- 牺牲祝福 function I.IsExternalCooldown(name, id, source, target) - if issecretvalue and (issecretvalue(name) or issecretvalue(id)) then return end - if name == bos then + local nameIsReadable = F.IsValueNonSecret(name) + local idIsReadable = F.IsValueNonSecret(id) + + if nameIsReadable and name == bos then if source and target then -- NOTE: hide bos on caster return not UnitIsUnit(source, target) else return true end - else - return builtInExternals[name] or builtInExternals[id] or customExternals[id] + end + + if nameIsReadable and builtInExternals[name] then + return true + end + + if idIsReadable then + return builtInExternals[id] or customExternals[id] end end @@ -357,65 +405,108 @@ end ------------------------------------------------- local defensives = { -- true: track by name, false: track by id ["DEATHKNIGHT"] = { - [48707] = true, -- 反魔法护罩 - Anti-Magic Shell + [48707] = { -- 反魔法护罩 - Anti-Magic Shell + [444741] = true, + [410358] = true, + }, [48792] = true, -- 冰封之韧 - Icebound Fortitude - [49028] = true, -- 符文刃舞 - Dancing Rune Weapon - [55233] = true, -- 吸血鬼之血 - Vampiric Blood [49039] = false, -- 巫妖之躯 - Lichborne - [194679] = true, -- 符文分流 - Rune Tap + [55233] = true, -- 吸血鬼之血 - Vampiric Blood + [49028] = { -- 符文刃舞 - Dancing Rune Weapon + [81256] = true, + }, + [434107] = { -- Vampiric Aura + [434105] = true, + }, }, ["DEMONHUNTER"] = { - [196555] = true, -- 虚空行走 - Netherwalk - [198589] = true, -- 疾影 - Blur - [187827] = false, -- 恶魔变形 162264(DPS) - Metamorphosis + [198589] = { -- 疾影 - Blur + [212800] = true, + }, + [187827] = true, -- 恶魔变形 - Metamorphosis + [263648] = true, -- 灵魂壁障 - Soul Barrier + [204021] = { -- 烈火烙印 - Fiery Brand + [207771] = true, + }, + [442715] = true, -- Blade Ward + [1266616] = { -- Demon Muzzle + [394933] = true, + }, + [427912] = true, -- Infernal Armor }, ["DRUID"] = { [22812] = true, -- 树皮术 - Barkskin [61336] = true, -- 生存本能 - Survival Instincts - [200851] = true, -- 沉睡者之怒 - Rage of the Sleeper - [102558] = true, -- 化身:乌索克的守护者 - Incarnation: Guardian of Ursoc [22842] = true, -- 狂暴回复 - Frenzied Regeneration + [192081] = true, -- 铁鬃 - Ironfur + [102558] = true, -- 化身:乌索克的守护者 - Incarnation: Guardian of Ursoc + [1253799] = true, -- Sundering Roar + [393903] = true, -- Ursine Vigor + [5487] = true, -- 熊形态 - Bear Form }, ["EVOKER"] = { [363916] = true, -- 黑曜鳞片 - Obsidian Scales - [374348] = true, -- 新生光焰 - Renewing Blaze - [370960] = true, -- 翡翠交融 - Emerald Communion - [431872] = false, -- 瞬息之隔 - Temporality (Chronowarden Hero Talent) - [377088] = false, -- 活力迸射 - Rush of Vitality + [374348] = { -- 新生光焰 - Renewing Blaze + [374349] = true, + }, + [404381] = true, -- Defy Fate }, ["HUNTER"] = { [186265] = true, -- 灵龟守护 - Aspect of the Turtle [264735] = true, -- 优胜劣汰 - Survival of the Fittest + [109304] = true, -- 意气风发 - Exhilaration }, ["MAGE"] = { [45438] = true, -- 寒冰屏障 - Ice Block - [414658] = true, -- 深寒凝冰 - Ice Cold - [113862] = false, -- 强化隐形术 - Greater Invisibility - [55342] = false, -- 镜像(使用 CLEU 而非 UNIT_AURA) - Mirror Image + [414658] = { -- 深寒凝冰 - Ice Cold + [414659] = true, + }, [342246] = true, -- 操控时间 - Alter Time + [11426] = true, -- 寒冰护体 - Ice Barrier + [235313] = true, -- 烈焰护体 - Blazing Barrier + [235450] = true, -- 棱光护体 - Prismatic Barrier + [66] = true, -- 隐形术 - Invisibility + [110960] = { -- 强化隐形 - Greater Invisibility + [113862] = true, + }, + [55342] = true, -- 镜像 - Mirror Image }, ["MONK"] = { - [115176] = false, -- 禅悟冥想 - Zen Meditation - [115203] = true, -- 壮胆酒 - Fortifying Brew - [122278] = true, -- 躯不坏 - Dampen Harm + [115203] = { -- 壮胆酒 - Fortifying Brew + [120954] = true, + }, [122783] = true, -- 散魔功 - Diffuse Magic - [125174] = true, -- 业报之触 - Touch of Karma + [322507] = { -- 天神酒 - Celestial Brew + [425965] = true, + }, + [1241059] = true, -- Celestial Infusion + [132578] = true, -- 召唤玄牛雕像 - Invoke Niuzao, the Black Ox + [122470] = { -- 业报之触 - Touch of Karma + [125174] = true, + }, + [455179] = true, -- Elixir of Determination }, ["PALADIN"] = { [498] = true, -- 圣佑术 - Divine Protection + [403876] = true, -- Divine Protection [642] = true, -- 圣盾术 - Divine Shield - [31850] = true, -- 炽热防御者 - Ardent Defender - [212641] = true, -- 远古列王守卫 - Guardian of Ancient Kings - [205191] = true, -- 以眼还眼 - Eye for an Eye - [389539] = true, -- 戒卫 - Sentinel [184662] = true, -- 复仇之盾 - Shield of Vengeance + [31850] = true, -- 炽热防御者 - Ardent Defender + [86659] = { -- 远古列王守卫 - Guardian of Ancient Kings + [393108] = true, + }, + [389539] = true, -- Sentinel + [461867] = true, -- Sacrosanct Crusade + [209388] = { -- Bulwark of Order + [453043] = true, + }, }, ["PRIEST"] = { @@ -423,33 +514,52 @@ local defensives = { -- true: track by name, false: track by id [19236] = true, -- 绝望祷言 - Desperate Prayer [586] = true, -- 渐隐术 -- TODO: 373446 通透影像 - Fade [193065] = true, -- 防护圣光 - Protective Light - [27827] = true, -- 救赎之魂 - Spirit of Redemption + [114216] = { -- Angelic Bulwark + [114214] = true, + }, + [45242] = { -- Focused Will + [426401] = true, + }, }, ["ROGUE"] = { [1966] = true, -- 佯攻 - Feint [5277] = true, -- 闪避 - Evasion [31224] = false, -- 暗影斗篷 - Cloak of Shadows + [185311] = true, -- 猩红之瓶 - Crimson Vial }, ["SHAMAN"] = { [108271] = true, -- 星界转移 - Astral Shift - [409293] = true, -- 掘地三尺 - Burrow (PVP) - [114893] = true, -- 石壁 - Stone Bulwark + [457387] = true, -- Wind Barrier + [381755] = true, -- Primordial Bond }, ["WARLOCK"] = { [104773] = true, -- 不灭决心 - Unending Resolve - [212295] = true, -- 虚空守卫 - Nether Ward (PVP) [108416] = true, -- 黑暗契约 - Dark Pact + [387847] = true, -- 邪甲术 - Fel Armor + [108366] = true, -- 灵魂榨取 - Soul Leech }, ["WARRIOR"] = { - [871] = true, -- 盾墙 - Shield Wall - [12975] = true, -- 破釜沉舟 - Last Stand - [23920] = true, -- 法术反射 - Spell Reflection + [23920] = { -- 法术反射 - Spell Reflection + [385391] = true, + }, [118038] = true, -- 剑在人在 - Die by the Sword [184364] = true, -- 狂怒回复 - Enraged Regeneration + [190456] = { -- 无视痛苦 - Ignore Pain + [1277297] = true, + }, + [871] = true, -- 盾墙 - Shield Wall + [12975] = true, -- 破釜沉舟 - Last Stand + [3411] = { -- 援护 - Intervene + [147833] = true, + }, + [386208] = { -- 防御姿态 - Defensive Stance + [1261776] = true, + [1243856] = true, + }, }, } @@ -460,19 +570,29 @@ end local builtInDefensives = {} local customDefensives = {} +local function UpdateDefensive(id, trackByName) + if trackByName then + local name = F.GetSpellInfo(id) + if name then + builtInDefensives[name] = true + end + end + builtInDefensives[id] = true +end + function I.UpdateDefensives(t) -- user disabled wipe(builtInDefensives) for class, spells in pairs(defensives) do - for id, trackByName in pairs(spells) do + for id, v in pairs(spells) do if not t["disabled"][id] then -- not disabled - if trackByName then - local name = F.GetSpellInfo(id) - if name then - builtInDefensives[name] = true + if type(v) == "table" then + builtInDefensives[id] = true + for subId, subTrackByName in pairs(v) do + UpdateDefensive(subId, subTrackByName) end else - builtInDefensives[id] = true + UpdateDefensive(id, v) end end end @@ -487,11 +607,212 @@ function I.UpdateDefensives(t) -- end customDefensives[id] = true end + Cell.vars.builtInDefensives = builtInDefensives + Cell.vars.customDefensives = customDefensives end function I.IsDefensiveCooldown(name, id) - if issecretvalue and (issecretvalue(name) or issecretvalue(id)) then return end - return builtInDefensives[name] or builtInDefensives[id] or customDefensives[id] + if F.IsValueNonSecret(name) and builtInDefensives[name] then + return true + end + + if F.IsValueNonSecret(id) then + return builtInDefensives[id] or customDefensives[id] + end +end + +------------------------------------------------- +-- offensiveCooldowns +------------------------------------------------- +local offensives = { + ["DEATHKNIGHT"] = { + [42650] = true, -- 亡者大军 - Army of the Dead + [1249658] = { -- Breath of Sindragosa + [152279] = true, + }, + [51271] = true, -- 冰霜之柱 - Pillar of Frost + }, + ["DEMONHUNTER"] = { + [191427] = { -- 恶魔变形 - Metamorphosis + [162264] = true, + }, + [1225789] = { -- Void Metamorphosis + [1217607] = true, + }, + [1241937] = true, -- Soul Immolation + [370965] = { -- The Hunt + [1246167] = true, + [1259431] = true, + }, + }, + ["DRUID"] = { + [194223] = true, -- 超凡之盟 - Celestial Alignment + [106951] = true, -- 狂暴 - Berserk + [102560] = true, -- 化身:艾露恩之眷 - Incarnation: Chosen of Elune + [391528] = true, -- 万灵之召 - Convoke the Spirits + [202770] = true, -- 艾露恩之怒 - Fury of Elune + [204066] = true, -- Lunar Beam + }, + ["EVOKER"] = { + [375087] = true, -- 龙怒 - Dragonrage + [442204] = { -- 亘古吐息 - Breath of Eons + [403631] = true, + }, + [357210] = { -- 深呼吸 - Deep Breath + [433874] = true, + }, + }, + ["HUNTER"] = { + [288613] = true, -- 百发百中 - Trueshot + [1250646] = true, -- Takedown + [1265063] = true, -- Bloody Frenzy + [459808] = true, -- Wailing Arrow + [1261193] = true, -- Boomstick + [1258344] = { -- Stampede + [1258345] = true, + }, + }, + ["MAGE"] = { + [190319] = true, -- 燃烧 - Combustion + [365350] = { -- 奥术涌动 - Arcane Surge + [365362] = true, + }, + }, + ["MONK"] = { + [1249625] = true, -- 天顶 - Zenith + [325153] = true, -- Exploding Keg + }, + ["PALADIN"] = { + [1234189] = true, -- Execution Sentence + }, + ["PRIEST"] = { + [194249] = true, -- 虚空形态 - Voidform + }, + ["ROGUE"] = { + [13750] = true, -- 冲动 - Adrenaline Rush + [121471] = true, -- 暗影之刃 - Shadow Blades + [185422] = true, -- 暗影之舞 - Shadow Dance + [51690] = true, -- 影舞步 - Killing Spree + [394095] = { -- Kingsbane + [385627] = true, + }, + [13877] = true, -- 剑刃乱舞 - Blade Flurry + }, + ["SHAMAN"] = { + [114050] = { -- 升腾 - Ascendance + [114051] = true, + [1219480] = true, + }, + [466772] = true, -- Doom Winds + [191634] = true, -- 风暴守护者 - Stormkeeper + }, + ["WARLOCK"] = { + [265187] = true, -- 召唤恶魔暴君 - Summon Demonic Tyrant + [205180] = true, -- 召唤暗眼 - Summon Darkglare + [111685] = true, -- 召唤地狱火 - Summon Infernal + [442726] = true, -- Malevolence + [1257052] = true, -- Dark Harvest + }, + ["WARRIOR"] = { + [107574] = true, -- 天神下凡 - Avatar + [1719] = true, -- 鲁莽 - Recklessness + [446035] = { -- 剑刃风暴 - Bladestorm + [227847] = true, + }, + }, +} + +function I.GetOffensives() + return offensives +end + +local builtInOffensives = {} +local customOffensives = {} + +local function UpdateOffensive(id, trackByName) + if trackByName then + local name = F.GetSpellInfo(id) + if name then + builtInOffensives[name] = true + end + end + builtInOffensives[id] = true +end + +function I.UpdateOffensives(t) + wipe(builtInOffensives) + if type(t) ~= "table" then + t = {["disabled"] = {}, ["custom"] = {}} + end + t["disabled"] = t["disabled"] or {} + t["custom"] = t["custom"] or {} + for class, spells in pairs(offensives) do + for id, v in pairs(spells) do + if not t["disabled"][id] then + if type(v) == "table" then + builtInOffensives[id] = true + for subId, subTrackByName in pairs(v) do + UpdateOffensive(subId, subTrackByName) + end + else + UpdateOffensive(id, v) + end + end + end + end + + wipe(customOffensives) + for _, id in pairs(t["custom"]) do + customOffensives[id] = true + end + Cell.vars.builtInOffensives = builtInOffensives + Cell.vars.customOffensives = customOffensives +end + +function I.IsOffensiveCooldown(name, id) + if F.IsValueNonSecret(name) and builtInOffensives[name] then + return true + end + + if F.IsValueNonSecret(id) then + return builtInOffensives[id] or customOffensives[id] + end +end + +local defaultDisabledDefensives = { + 192081, 386208, 461867, 209388, 45242, 193065, 393903, 5487, 387847, 108366, 427912, 434107, +} + +local defaultDisabledExternals = { + 200183, 15286, 421453, 31884, 216331, 114052, 740, 325197, 462568, +} + +local defaultDisabledOffensives = { + 370965, 391528, 202770, 442204, 357210, 1261193, 191634, 1257052, 1258344, 204066, 325153, +} + +local function SeedDisabledSpells(dbKey, ids) + if type(CellDB[dbKey]) ~= "table" then + CellDB[dbKey] = {["disabled"] = {}, ["custom"] = {}} + end + CellDB[dbKey]["disabled"] = CellDB[dbKey]["disabled"] or {} + for _, id in ipairs(ids) do + CellDB[dbKey]["disabled"][id] = true + end +end + +function I.ApplyDefaultDisabledCooldowns() + if type(CellDB) ~= "table" then return end + if type(CellDB["compatibility"]) ~= "table" then + CellDB["compatibility"] = {} + end + if CellDB["compatibility"].optionalCdSeed == 2 then return end + + SeedDisabledSpells("defensives", defaultDisabledDefensives) + SeedDisabledSpells("externals", defaultDisabledExternals) + SeedDisabledSpells("offensives", defaultDisabledOffensives) + + CellDB["compatibility"].optionalCdSeed = 2 end ------------------------------------------------- @@ -549,7 +870,7 @@ do end function I.IsTankActiveMitigation(spellId) - if issecretvalue and issecretvalue(spellId) then return end + if not F.IsValueNonSecret(spellId) then return end return tankActiveMitigations[spellId] end @@ -737,7 +1058,7 @@ do end function I.IsDrinking(name) - if issecretvalue and issecretvalue(name) then return end + if not F.IsValueNonSecret(name) then return end return drinks[name] end @@ -758,6 +1079,8 @@ local spells = { 145205, -- 百花齐放 - Efflorescence 383193, -- 林地护理 - Grove Tending 439530, -- 共生绽华 - Symbiotic Blooms + 474754, -- 共生关系 - Symbiotic Relationship + 29166, -- 激活 - Innervate -- 429224, -- 次级塞纳里奥结界 - Minor Cenarion Ward (removed in 12.0, Durability of Nature redesigned) -- evoker @@ -779,6 +1102,8 @@ local spells = { 406789, -- 空间悖论 - Spatial Paradox 445740, -- 纵焰 - Enkindle 409895, -- 精神之花 - Spiritbloom (Reverberations, Chronowarden Hero Talent) + 409678, -- Chrono Ward + 1291636, -- Temporal Barrier 410263, -- 炼狱祝福 - Inferno's Blessing 410686, -- 共生绽放 - Symbiotic Bloom 413984, -- 流沙 - Shifting Sands @@ -793,6 +1118,7 @@ local spells = { 450805, -- 净化之魂 - Purified Spirit 467281, -- 金创药 - Healing Elixir 115175, -- 抚慰之雾 - Soothing Mist + 1292922, -- Coalescence -- paladin 53563, -- 圣光道标 - Beacon of Light @@ -803,15 +1129,19 @@ local spells = { 287280, -- 圣光闪烁 - Glimmer of Light 156322, -- 永恒之火 - Eternal Flame 431381, -- 晨光 - Dawnlight - 388013, -- 阳春祝福 - Blessing of Spring - 388007, -- 仲夏祝福 - Blessing of Summer - 388010, -- 暮秋祝福 - Blessing of Autumn - 388011, -- 凛冬祝福 - Blessing of Winter + -- 388013, -- 阳春祝福 - Blessing of Spring, removed in 12.0 + -- 388007, -- 仲夏祝福 - Blessing of Summer, removed in 12.0 + -- 388010, -- 暮秋祝福 - Blessing of Autumn, removed in 12.0 + -- 388011, -- 凛冬祝福 - Blessing of Winter, removed in 12.0 200654, -- 提尔的拯救 - Tyr's Deliverance 1244893, -- 救世主道标 - Beacon of the Savior + 1245369, -- 救世主道标(吸收) - Beacon of the Savior (absorb) + 1241717, -- 炽天使屏障 - Seraphic Barrier + 432496, -- 圣洁壁垒 - Holy Bulwark (Holy Armaments shield variant) + 432502, -- 圣洁武器 - Holy Armaments (Sacred Weapon variant) -- priest - 139, -- 恢复 - Renew + 139, -- 恢复 - Renew (removed in 12.0) 200829, -- 恳求 - Plea (added in 12.0, Disc) 41635, -- 愈合祷言 - Prayer of Mending 17, -- 真言术:盾 - Power Word: Shield @@ -820,6 +1150,8 @@ local spells = { 372847, -- 光明之泉恢复 - Blessed Bolt -- 443526, -- 慰藉预兆 - Premonition of Solace (removed in 12.0) 1253593, -- 虚空之盾 - Void Shield + 1300009, -- 虚空之盾(展开幻象) - Void Shield (Unfolding Vision) + 453846, -- Resonant Energy -- shaman 974, -- 大地之盾 - Earth Shield @@ -827,65 +1159,128 @@ local spells = { 61295, -- 激流 - Riptide 382024, -- 大地生命武器 - Earthliving Weapon 375986, -- 始源之潮 - Primordial Wave + 207400, -- 先祖活力 - Ancestral Vigor 444490, -- 源水气泡 - Hydrobubble -- 73920, -- 治疗之雨 - Healing Rain -- 456366, -- 治疗之雨 - Healing Rain } -function F.FirstRun() - local icons = "\n\n" - for i, id in pairs(spells) do +local function BuildHealersSpellIcons(ids) + local icons, n = "\n\n", 0 + for _, id in ipairs(ids) do local icon = select(2, F.GetSpellInfo(id)) if icon then + n = n + 1 icons = icons .. "|T"..icon..":0|t" - if i % 11 == 0 then + if n % 11 == 0 then icons = icons .. "\n" end end end + return icons +end - local popup = Cell.CreateConfirmPopup(Cell.frames.anchorFrame, 200, L["Would you like Cell to create a \"Healers\" indicator (icons)?"]..icons, function(self) - local currentLayoutTable = Cell.vars.currentLayoutTable +local function NextCustomIndicatorName(layoutTable) + local last = layoutTable["indicators"][#layoutTable["indicators"]] + if last["type"] == "built-in" then + return "indicator1" + end + local n = tonumber(strmatch(last["indicatorName"], "%d+")) or 0 + return "indicator" .. (n + 1) +end - local last = #currentLayoutTable["indicators"] - if currentLayoutTable["indicators"][last]["type"] == "built-in" then - indicatorName = "indicator1" - else - indicatorName = "indicator"..(tonumber(strmatch(currentLayoutTable["indicators"][last]["indicatorName"], "%d+"))+1) +local function MakeHealersIndicatorTable(indicatorName) + return { + ["name"] = "Healers", + ["indicatorName"] = indicatorName, + ["type"] = "icons", + ["enabled"] = true, + ["position"] = {"TOPRIGHT", "button", "TOPRIGHT", 0, 3}, + ["frameLevel"] = 5, + ["size"] = {13, 13}, + ["num"] = 5, + ["numPerLine"] = 5, + ["orientation"] = "right-to-left", + ["spacing"] = {0, 0}, + ["font"] = { + {"Cell ".._G.DEFAULT, 11, "Outline", false, "TOPRIGHT", 2, 1, {1, 1, 1}}, + {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, + }, + ["showStack"] = true, + ["showDuration"] = false, + ["showAnimation"] = true, + ["auraType"] = "buff", + ["castBy"] = "me", + ["auras"] = F.Copy(spells), + } +end + +function F.PromptHealersIndicator(parent, opts) + opts = opts or {} + local layoutTable = Cell.vars.currentLayoutTable + if not (parent and layoutTable) then return end + + local existing + for _, indicator in ipairs(layoutTable["indicators"]) do + if indicator["name"] == "Healers" then + existing = indicator + break end + end - tinsert(currentLayoutTable["indicators"], { - ["name"] = "Healers", - ["indicatorName"] = indicatorName, - ["type"] = "icons", - ["enabled"] = true, - ["position"] = {"TOPRIGHT", "button", "TOPRIGHT", 0, 3}, - ["frameLevel"] = 5, - ["size"] = {13, 13}, - ["num"] = 5, - ["numPerLine"] = 5, - ["orientation"] = "right-to-left", - ["spacing"] = {0, 0}, - ["font"] = { - {"Cell ".._G.DEFAULT, 11, "Outline", false, "TOPRIGHT", 2, 1, {1, 1, 1}}, - {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, - }, - ["showStack"] = true, - ["showDuration"] = false, - ["showAnimation"] = true, - ["glowOptions"] = {"None", {0.95, 0.95, 0.32, 1}}, - ["auraType"] = "buff", - ["castBy"] = "me", - ["auras"] = spells, - }) - Cell.Fire("UpdateIndicators", Cell.vars.currentLayout, indicatorName, "create", currentLayoutTable["indicators"][last+1]) - CellDB["firstRun"] = false - F.ReloadIndicatorList() - end, function() - CellDB["firstRun"] = false - end) - popup:SetPoint("TOPLEFT") - popup:Show() + if existing then + local seen, missing = {}, {} + for _, id in ipairs(existing["auras"] or {}) do + seen[id] = true + end + for _, id in ipairs(spells) do + if not seen[id] then + tinsert(missing, id) + end + end + if #missing == 0 then + if opts.printIfUpToDate then + F.Print(L["Healers indicator is up to date."]) + end + return + end + return Cell.CreateConfirmPopup(parent, opts.width or 200, + string.format(L["Healers indicator already exists. Add new spells?"], #missing) .. BuildHealersSpellIcons(missing), + function() + for _, id in ipairs(missing) do + tinsert(existing["auras"], id) + end + Cell.Fire("UpdateIndicators", Cell.vars.currentLayout, existing["indicatorName"], "auras", "buff", existing["auras"]) + F.ReloadIndicatorList() + if opts.onDone then opts.onDone() end + end, opts.onReject, opts.mask, nil, nil, opts.key) + end + + local createText = opts.createText or L["Would you like Cell to create a \"Healers\" indicator (icons)?"] + return Cell.CreateConfirmPopup(parent, opts.width or 200, + createText .. BuildHealersSpellIcons(spells), + function() + local indicatorName = NextCustomIndicatorName(layoutTable) + local t = MakeHealersIndicatorTable(indicatorName) + tinsert(layoutTable["indicators"], t) + Cell.Fire("UpdateIndicators", Cell.vars.currentLayout, indicatorName, "create", t) + CellDB["firstRun"] = false + F.ReloadIndicatorList() + if opts.onDone then opts.onDone() end + end, opts.onReject, opts.mask, nil, nil, opts.key) +end + +function F.FirstRun() + local popup = F.PromptHealersIndicator(Cell.frames.anchorFrame, { + printIfUpToDate = true, + onReject = function() + CellDB["firstRun"] = false + end, + }) + if popup then + popup:SetPoint("TOPLEFT") + popup:Show() + end end ------------------------------------------------- @@ -911,198 +1306,147 @@ end -- targetedSpells ------------------------------------------------- local targetedSpells = { - -- Cataclysm ------------------- - -- 格瑞姆巴托 - 451971, -- 熔岩之拳 - 451224, -- 暗影烈焰笼罩 - 451364, -- 残忍打击 - 451261, -- 大地之箭 - 449444, -- 熔火乱舞 - 450100, -- 碾碎 - - -- Mists of Pandaria ----------- - -- 青龙寺 - Temple of the Jade Serpent - 106823, -- 翔龙猛袭 - Serpent Strike - 106841, -- 青龙猛袭 - Jade Serpent Strike - - -- Legion ---------------------- - -- 群星庭院 - Court of Stars - 211473, -- 暗影鞭笞 - Shadow Slash - -- 英灵殿 - Halls of Valor - 193092, -- 放血扫击 - Bloodletting Sweep - 193659, -- 邪炽冲刺 - Felblaze Rush - 192018, -- 光明之盾 - Shield of Light - 196838, -- 血之气息 - Scent of Blood - - -- Battle for Azeroth ---------- - -- 围攻伯拉勒斯 - 454438, -- 艾泽里特炸药 - 272571, -- 窒息之水 - 257063, -- 盐渍飞弹 - 256709, -- 钢刃之歌 - -- 暴富矿区!! - 263628, -- 充能护盾 - -- 麦卡贡行动 - 1215411, -- 刺破 - 291928, -- 巨力震击 - 292264, -- 巨力震击 - 285152, -- 索敌击飞 - - -- Shadowlands ----------------- - -- 通灵战潮 - Necrotic Wake - 320788, -- 冻结之缚 - Frozen Binds - 320596, -- 深重呕吐 - Heaving Retch - 338606, -- 病态凝视 - Morbid Fixation - 343556, -- 病态凝视 - Morbid Fixation - 333479, -- 吐疫 - -- 奈萨里奥的巢穴 - Castle Nathria - 344496, -- 震荡爆发 - Reverberating Eruption - -- 赎罪大厅 - Halls of Atonement - 319941, -- 碎石之跃 - Stone Shattering Leap - 325535, -- 射击 - 326829, -- 邪恶箭矢 - 338003, -- 邪恶箭矢 - 1235766, -- 致死打击 - 1237071, -- 石拳 - 322936, -- 粉碎砸击 - -- Mists of Tirna Scithe - 323057, -- 灵魂之箭 - 321828, -- 拍手手 - 322614, -- 心灵连接 - Mind Link - 463248, -- 排斥 - 463217, -- 心能挥砍 - -- 彼界 - De Other Side - 320132, -- 暗影之怒 - Shadowfury - 332234, -- 挥发精油 - Essential Oil - -- Spires of Ascenscion - 334053, -- 净化冲击波 - Purifying Blast - 317963, -- 知识烦扰 - Burden of Knowledge - -- Sanguine Depths - 319713, -- 巨兽奔袭 - Juggernaut Rush - -- 伤逝剧场 - Theater of Pain - 324079, -- 收割之镰 - Reaping Scythe - 333861, -- 回旋利刃 - Ricocheting Blade - 342675, -- 骨矛 - 320644, -- 残酷连击 - 323515, -- 仇恨打击 - 1217138, -- 通灵箭 - -- Plaguefall - -- 328429, -- 窒息勒压 - 356924, -- 屠戮 - Carnage - 356666, -- 刺骨之寒 - Biting Cold - -- 塔扎维什:琳彩天街 - 352796, -- 代理打击 - 357512, -- 狂暴冲锋 - 347903, -- 垃圾邮件 - 354297, -- 凌光箭 - 353836, -- 凌光箭 - 1240912, -- 穿刺 - 350916, -- 安保猛击 - 350101, -- 诅咒锁链 - 355477, -- 强力脚踢 - -- 塔扎维什:索·莉亚的宏图 - 355225, -- 水箭 - 356843, -- 盐渍飞弹 - - -- Dragonflight ---------------- - -- 化身巨龙牢窟 - Vault of the Incarnates - 375870, -- 致死石爪 - Mortal Stoneclaws - 395906, -- 电化之颌 - Electrified Jaws - 372158, -- 破甲一击 - Sundering Strike - 372056, -- 碾压 - Crush - 375580, -- 西风猛击 - Zephyr Slam - 376276, -- 震荡猛击 - Concussive Slam - -- 亚贝鲁斯,焰影熔炉 - Aberrus, the Shadowed Crucible - 401022, -- 灾祸掠击 - Calamitous Strike - 407790, -- 身影碎离 - Sunder Shadow - -- 阿梅达希尔,梦境之愿 - Amirdrassil, the Dream's Hope - 418637, -- 狂怒冲锋 - Furious Charge - -- 红玉新生法池 - Ruby Life Pools - 372858, -- 灼热打击 - Searing Blows - 381512, -- 风暴猛击 - Stormslam - -- 奈萨鲁斯 - Neltharus - 374533, -- 炽热挥舞 - Heated Swings - 377018, -- 熔火真金 - Molten Gold - -- 蕨皮山谷 - Brackenhid Hollow - 381444, -- 野蛮冲撞 - Savage Charge - 373912, -- 腐朽打击 - Decaystrike - -- 碧蓝魔馆 - Azure Vault - 374789, -- 注能打击 - Infused Strike - 372222, -- 奥术顺劈 - Arcane Cleave - 384978, -- 巨龙打击 - Dragon Strike - 391136, -- 肩部猛击 - Shoulder Slam - -- 诺库德阻击战 - The Nokhud Offensive - 376827, -- 传导打击 - Conductive Strike - 376829, -- 雷霆打击 - Thunder Strike - 375937, -- 撕裂猛击 - Rending Strike - 375929, -- 野蛮打击 - Savage Strike - 376644, -- 钢铁之矛 - Iron Spear - 376865, -- 静电之矛 - Static Spear - 382836, -- 残杀 - Brutalize - - -- The War Within -------------- - -- 圣焰隐修院 - 424420, -- 余烬冲击 - 424414, -- 贯穿护甲 - 427583, -- 忏悔 - 447270, -- 掷矛 - 448515, -- 神圣审判 - 424421, -- 火球术 - 444743, -- 连珠火球 - 427357, -- 神圣惩击 - 462859, -- 随意射击 - -- 艾拉-卡拉,回响之城 - 439506, -- 钻地冲击 - 434786, -- 蛛网箭 - 438471, -- 贪食撕咬 - -- 矶石宝库 - 429545, -- 噤声齿轮 - 424888, -- 震地猛击 - 459210, -- 暗影爪击 - 428711, -- 火成岩锤 - -- 破晨号 - 431491, -- 污邪斩击 - 451119, -- 深渊轰击 - 431303, -- 暗夜箭 - 431333, -- 折磨射线 - 451107, -- 迸发虫茧 - -- 尼鲁巴尔王宫 - 459524, -- 致命之箭 - -- 暗焰裂口 - 421277, -- 暗焰之锄 - 427011, -- 暗影冲击 - 422245, -- 穿岩凿 - 422116, -- 鲁莽冲锋 - -- 燧酿酒庄 - 432229, -- 醉酿投 - 439031, -- 干杯勾拳 - 436592, -- 点钞大炮 - 440134, -- 蜂蜜料汁 - -- 驭雷栖巢 - 445457, -- 湮灭波 - 430109, -- 闪电箭 - 430238, -- 虚空箭 - 474031, -- 虚空碾压 - 430805, -- 弧形虚空 - -- 水闸行动 - 1213805, -- 射钉枪 - 465595, -- 闪电箭 - 468631, -- 鱼叉 - 459779, -- 滚桶冲锋 - 459799, -- 重击 - 473690, -- 动能胶质炸药 - 473351, -- 电气重碾 - 469478, -- 淤泥之爪 - 466190, -- 雷霆重拳 - 1214468, -- 特技射击 - -- 奥尔达尼生态圆顶 - 1229474, -- 啃噬 - 1235368, -- 奥术猛袭 - 1229510, -- 弧光震击 - 1222815, -- 奥术箭 - 1221483, -- 电弧能量 - 1219482, -- 裂隙利爪 - 1226111, -- 不稳定的喷发 + -- Altar of Fangs -------------- + 1296220, -- Triple Shot + 1296050, -- Regurgitate + 1296058, -- Regurgitate + 1300503, -- Spiteful Hunt + 1299130, -- Burrowing Charge + 1300044, -- Venom Jet + 1301413, -- Boneslicer + 1294567, -- Paralyzing Shots + 1289416, -- Envenom + 1308865, -- Infest + 1294557, -- Piercing Hiss + 1306381, -- Fetid Spit (Stinkender Auswurf) + 1308862, -- Fetid Spit + 1294432, -- Septic Spatter + 1306232, -- Septic Spatter + 1305637, -- Septic Spatter + 1306235, -- Septic Spatter + 1306129, -- Septic Spatter + 1305641, -- Septic Spatter + 1294934, -- Noxious Spray + 1294859, -- Rattle + 1294197, -- Experimental Toxin + 1293985, -- Experimental Toxin + 1293983, -- Experimental Toxin + + -- Murder Row ------------------ + 1214357, -- Fire Bomb + 1214637, -- Axe Toss + 474478, -- Killing Spree + 474765, -- Same-Day Delivery + 474766, -- Same-Day Delivery + 1222795, -- Envenom + 473898, -- Legion Strike + 474375, -- Chaos Bolt + 1217633, -- Corroding Spittle + 1217973, -- Curse of Doom + 1216954, -- Eye Beam + 1215872, -- Fel Beam + 1216570, -- Fel Missiles + 1201554, -- Seduction + 1217881, -- Shadow Bite + 1293101, -- Shadow Bite + + -- Den of Nalorakk ------------- + 1243569, -- Overwhelming Onslaught + 1242860, -- Echoing Maul + 1242976, -- Echoing Maul + 1241226, -- Bloodrush + 1241214, -- Earth Bolt + 1246687, -- Lightning Bolt + 1241217, -- Shredding Claws + 1246847, -- Shoot + 1238439, -- Razor Dive + 1232012, -- Serrated Fists + + -- The Blinding Vale ----------- + 1237090, -- Bloodthirsty Gaze + 1235640, -- Thornblade + 1238066, -- Thornblade + 1234850, -- Lightsower Dash + 1235564, -- Lightblossom Beam + 1239824, -- Lightfire + 1241058, -- Grievous Thrash + 1242135, -- Grievous Gash + 1247685, -- Thornspike + 1246607, -- Concentrated Lightbeam + 1237855, -- Earthrupture Strike + 1238063, -- Light Bolt + 1238368, -- Lightmaw Beams + 1250829, -- Potad-Toss + 1238232, -- Seed Shot + 1250100, -- Tongue Toss + + -- Voidscar Arena -------------- + 1222098, -- Nether Dash + 1222100, -- Nether Dash + 1227264, -- Cosmic Crash + 1222642, -- Hulking Claw + 1226120, -- Poison Splash + 1249236, -- Fire Spit + 1228176, -- Lava Bolt + 1233472, -- Rip and Slice + 1239855, -- Sky Strike + 1234890, -- Smashing Charge + 1250640, -- Venomous Spit + 1227020, -- Dimensional Shred + + -- Kings' Rest ----------------- + 265773, -- Spit Gold + 268932, -- Quaking Leap + 1303327, -- Quaking Leap + 1303115, -- Aerial Smash + 266951, -- Barrel Through + 266231, -- Severing Axe + 267618, -- Drain Fluids + 267702, -- Entomb + 271555, -- Entomb + 268586, -- Blade Combo + 1303488, -- Savage Maul + 270506, -- Deadeye Shot + 270507, -- Poison Barrage + 270492, -- Hex + 270284, -- Purification Beam + 270482, -- Blooded Leap + 270503, -- Hunting Leap + 269230, -- Hunting Leap + 269231, -- Hunting Leap + 270928, -- Bladestorm + 270891, -- Channel Lightning + 270920, -- Seduction + 270865, -- Hidden Blade + 271640, -- Dark Revelation + 270931, -- Darkshot + + -- Temple of Sethraliss -------- + 264574, -- Power Shot + 268008, -- Snake Charm + 267237, -- Drain + 263309, -- Cyclone Strike + 263958, -- A Knot of Snakes + 1290029, -- A Knot of Snakes + 1289109, -- Thunder Spit + 1289059, -- Gale Force + 1288864, -- Tempest Winds + 1288428, -- Overload + + -- Ruby Life Pools ------------- + 372858, -- Searing Blows + 381512, -- Stormslam + 372107, -- Molten Boulder + 372863, -- Ritual of Blazebinding + 372851, -- Chillstorm + 1307308, -- Chillstorm + 381862, -- Inferno Spit + 381602, -- Flamespit + 372087, -- Blazing Rush + 372047, -- Steel Barrage + 392640, -- Rolling Thunder + 392451, -- Flashfire + 373693, -- Living Bomb } function I.GetDefaultTargetedSpellsList() @@ -1122,11 +1466,11 @@ local actions = { {"A", {0.4, 1, 0}}, }, { - 431416, -- 阿加治疗药水 - Algari Healing Potion + 1234768, -- 银月治疗药水 - Silvermoon Health Potion {"A", {1, 0.1, 0.1}}, }, { - 431932, -- 淬火药水 - Tempered Potion + 1236616, -- 圣光潜能 - Light's Potential {"C3", {1, 1, 0}}, }, } @@ -1317,6 +1661,11 @@ function I.UpdateCrowdControls(t) end function I.IsCrowdControls(name, id) - if issecretvalue and (issecretvalue(name) or issecretvalue(id)) then return end - return builtInCrowdControls[name] or builtInCrowdControls[id] or customCrowdControls[name] + if F.IsValueNonSecret(name) and (builtInCrowdControls[name] or customCrowdControls[name]) then + return true + end + + if F.IsValueNonSecret(id) then + return builtInCrowdControls[id] + end end diff --git a/Defaults/Indicator_Defaults.lua b/Defaults/Indicator_Defaults.lua index 2890810c..538eed5d 100644 --- a/Defaults/Indicator_Defaults.lua +++ b/Defaults/Indicator_Defaults.lua @@ -113,6 +113,7 @@ function I.GetDefaultCustomIndicatorTable(name, indicatorName, type, auraType) }, ["showStack"] = false, ["showDuration"] = false, + ["animationStyle"] = "none", ["auraType"] = auraType, ["auras"] = {}, ["glowOptions"] = {"None", {0.95, 0.95, 0.32, 1}} @@ -208,6 +209,7 @@ function I.GetDefaultCustomIndicatorTable(name, indicatorName, type, auraType) }, ["showStack"] = false, ["showDuration"] = false, + ["animationStyle"] = "clock", ["auraType"] = auraType, ["auras"] = {}, ["glowOptions"] = {"None", {0.95, 0.95, 0.32, 1}} @@ -231,6 +233,7 @@ function I.GetDefaultCustomIndicatorTable(name, indicatorName, type, auraType) }, ["showStack"] = false, ["showDuration"] = false, + ["animationStyle"] = "clock", ["auraType"] = auraType, ["auras"] = {}, ["glowOptions"] = {"None", {0.95, 0.95, 0.32, 1}} @@ -267,7 +270,6 @@ end -- dispels: custom debuff type color ------------------------------------------------- function I.GetDebuffTypeColor(debuffType) - -- Midnight 12.0.0+: debuffType may be secret; cannot use as table key if issecretvalue and issecretvalue(debuffType) then return 0, 0, 0 end if debuffType and CellDB["debuffTypeColor"][debuffType] then return CellDB["debuffTypeColor"][debuffType]["r"], CellDB["debuffTypeColor"][debuffType]["g"], @@ -285,7 +287,6 @@ function I.SetDebuffTypeColor(debuffType, r, g, b) end end --- Midnight 12.0.0 removed the DebuffTypeColor global; provide a local fallback -- with the standard Blizzard debuff type colors local CellDebuffTypeColorFallback = { ["none"] = {r = 0.80, g = 0.00, b = 0.00}, diff --git a/Defaults/Layout_Defaults.lua b/Defaults/Layout_Defaults.lua index 1232e3da..a073480d 100644 --- a/Defaults/Layout_Defaults.lua +++ b/Defaults/Layout_Defaults.lua @@ -1,7 +1,7 @@ local addonName, Cell = ... -- number of built-in indicators -Cell.defaults.builtIns = 30 +Cell.defaults.builtIns = 31 Cell.defaults.indicatorIndices = { ["nameText"] = 1, @@ -23,17 +23,18 @@ Cell.defaults.indicatorIndices = { ["aoeHealing"] = 17, ["externalCooldowns"] = 18, ["defensiveCooldowns"] = 19, - ["allCooldowns"] = 20, - ["tankActiveMitigation"] = 21, - ["dispels"] = 22, - ["debuffs"] = 23, - ["raidDebuffs"] = 24, - ["privateAuras"] = 25, - ["targetedSpells"] = 26, - ["targetCounter"] = 27, - ["crowdControls"] = 28, - ["actions"] = 29, - ["missingBuffs"] = 30, + ["offensiveCooldowns"] = 20, + ["allCooldowns"] = 21, + ["tankActiveMitigation"] = 22, + ["dispels"] = 23, + ["debuffs"] = 24, + ["raidDebuffs"] = 25, + ["privateAuras"] = 26, + ["targetedSpells"] = 27, + ["targetCounter"] = 28, + ["crowdControls"] = 29, + ["actions"] = 30, + ["missingBuffs"] = 31, } Cell.defaults.layout = { @@ -61,6 +62,7 @@ Cell.defaults.layout = { ["raidEnabled"] = false, ["sameSizeAsMain"] = true, ["sameArrangementAsMain"] = true, + ["petSide"] = "right", ["size"] = {66, 46}, ["position"] = {}, ["powerSize"] = 2, @@ -124,7 +126,7 @@ Cell.defaults.layout = { ["name"] = "Name Text", ["indicatorName"] = "nameText", ["type"] = "built-in", - ["enabled"] = true, + ["enabled"] = false, ["position"] = {"CENTER", "healthBar", "CENTER", 0, 0}, ["frameLevel"] = 1, ["font"] = {"Cell ".._G.DEFAULT, 13, "None", true}, @@ -336,7 +338,7 @@ Cell.defaults.layout = { ["name"] = "AoE Healing", ["indicatorName"] = "aoeHealing", ["type"] = "built-in", - ["enabled"] = true, + ["enabled"] = false, ["height"] = 10, ["color"] = {1, 1, 0}, }, -- 17 @@ -350,6 +352,7 @@ Cell.defaults.layout = { ["size"] = {12, 20}, ["showDuration"] = false, ["showAnimation"] = true, + ["animationStyle"] = "clock", ["num"] = 2, ["orientation"] = "right-to-left", ["font"] = { @@ -368,6 +371,7 @@ Cell.defaults.layout = { ["size"] = {12, 20}, ["showDuration"] = false, ["showAnimation"] = true, + ["animationStyle"] = "clock", ["num"] = 2, ["orientation"] = "left-to-right", ["font"] = { @@ -376,6 +380,25 @@ Cell.defaults.layout = { }, ["glowOptions"] = {"None", {0.95, 0.95, 0.32, 1}} }, -- 19 + { + ["name"] = "Offensive Cooldowns", + ["indicatorName"] = "offensiveCooldowns", + ["type"] = "built-in", + ["enabled"] = false, + ["position"] = {"BOTTOM", "button", "BOTTOM", 0, 4}, + ["frameLevel"] = 10, + ["size"] = {20, 20}, + ["showDuration"] = false, + ["showAnimation"] = true, + ["animationStyle"] = "clock", + ["num"] = 2, + ["orientation"] = "left-to-right", + ["font"] = { + {"Cell ".._G.DEFAULT, 11, "Outline", false, "TOPRIGHT", 2, 1, {1, 1, 1}}, + {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, + }, + ["glowOptions"] = {"None", {0.95, 0.95, 0.32, 1}} + }, -- 20 { ["name"] = "Externals + Defensives", ["indicatorName"] = "allCooldowns", @@ -386,6 +409,7 @@ Cell.defaults.layout = { ["size"] = {12, 20}, ["showDuration"] = false, ["showAnimation"] = true, + ["animationStyle"] = "clock", ["num"] = 2, ["orientation"] = "left-to-right", ["font"] = { @@ -393,17 +417,17 @@ Cell.defaults.layout = { {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, }, ["glowOptions"] = {"None", {0.95, 0.95, 0.32, 1}} - }, -- 20 + }, -- 21 { ["name"] = "Tank Active Mitigation", ["indicatorName"] = "tankActiveMitigation", ["type"] = "built-in", - ["enabled"] = true, + ["enabled"] = false, ["position"] = {"TOPLEFT", "button", "TOPLEFT", 10, 0}, ["frameLevel"] = 5, ["size"] = {20, 6}, ["color"] = {"class_color", {0.25, 1, 0}}, - }, -- 21 + }, -- 22 { ["name"] = "Dispels", ["indicatorName"] = "dispels", @@ -420,10 +444,10 @@ Cell.defaults.layout = { ["Poison"] = true, ["Bleed"] = true, }, - ["highlightType"] = "gradient-half", + ["highlightType"] = "edge-bottom", ["iconStyle"] = "blizzard", ["orientation"] = "right-to-left", - }, -- 22 + }, -- 23 { ["name"] = "Debuffs", ["indicatorName"] = "debuffs", @@ -434,6 +458,8 @@ Cell.defaults.layout = { ["size"] = {{13, 13}, {17, 17}}, ["showDuration"] = false, ["showAnimation"] = true, + ["animationStyle"] = "clock", + ["showStack"] = true, ["showTooltip"] = false, ["enableBlacklistShortcut"] = false, ["num"] = 3, @@ -442,8 +468,9 @@ Cell.defaults.layout = { {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, }, ["dispellableByMe"] = false, + ["nonPlayerAuras"] = false, ["orientation"] = "left-to-right", - }, -- 23 + }, -- 24 { ["name"] = "Raid Debuffs", ["indicatorName"] = "raidDebuffs", @@ -462,7 +489,7 @@ Cell.defaults.layout = { ["onlyShowTopGlow"] = false, ["orientation"] = "left-to-right", ["showTooltip"] = false, - }, -- 24 + }, -- 25 { ["name"] = "Private Auras", ["indicatorName"] = "privateAuras", @@ -471,14 +498,18 @@ Cell.defaults.layout = { ["position"] = {"TOP", "button", "TOP", 0, 3}, ["frameLevel"] = 25, ["size"] = {18, 18}, - ["privateAuraOptions"] = {true, false}, - }, -- 25 + ["privateAuraOptions"] = {true, false, 1, false, 1, false, true, 1, 6, 0, 0}, + ["numPerLine"] = 1, + ["spacing"] = {1, 1}, + ["orientation"] = "left-to-right", + }, -- 26 { ["name"] = "Targeted Spells", ["indicatorName"] = "targetedSpells", ["type"] = "built-in", ["enabled"] = true, ["showAllSpells"] = false, + ["displayMode"] = "Both", ["position"] = {"TOPLEFT", "button", "TOPLEFT", -4, 4}, ["frameLevel"] = 50, ["size"] = {20, 20}, @@ -486,7 +517,7 @@ Cell.defaults.layout = { ["num"] = 1, ["font"] = {"Cell ".._G.DEFAULT, 12, "Outline", false, "TOPRIGHT", 2, 1, {1, 1, 1}}, ["orientation"] = "left-to-right", - }, -- 26 + }, -- 27 { ["name"] = "Target Counter", ["indicatorName"] = "targetCounter", @@ -501,7 +532,7 @@ Cell.defaults.layout = { ["pve"] = false, ["pvp"] = true, }, - }, -- 27 + }, -- 28 { ["name"] = "Crowd Controls", ["indicatorName"] = "crowdControls", @@ -519,14 +550,14 @@ Cell.defaults.layout = { }, ["dispellableByMe"] = false, ["orientation"] = "left-to-right", - }, -- 28 + }, -- 29 { ["name"] = "Actions", ["indicatorName"] = "actions", ["type"] = "built-in", ["enabled"] = true, ["speed"] = 1, - }, -- 29 + }, -- 30 { ["name"] = "Missing Buffs", ["indicatorName"] = "missingBuffs", @@ -536,7 +567,7 @@ Cell.defaults.layout = { ["frameLevel"] = 10, ["size"] = {13, 13}, ["orientation"] = "right-to-left", - }, -- 30 + }, -- 31 }, } @@ -549,4 +580,4 @@ Cell.defaults.layoutAutoSwitch = { ["arena"] = "default", ["battleground15"] = "default", ["battleground40"] = "default", -} \ No newline at end of file +} diff --git a/Defaults/Layout_Defaults_Cata_Wrath.lua b/Defaults/Layout_Defaults_Cata_Wrath.lua index 92e24b70..0ddc4ee4 100644 --- a/Defaults/Layout_Defaults_Cata_Wrath.lua +++ b/Defaults/Layout_Defaults_Cata_Wrath.lua @@ -422,6 +422,7 @@ Cell.defaults.layout = { ["size"] = {{13, 13}, {17, 17}}, ["showDuration"] = false, ["showAnimation"] = true, + ["animationStyle"] = "clock", ["showTooltip"] = false, ["enableBlacklistShortcut"] = false, ["num"] = 3, @@ -430,6 +431,7 @@ Cell.defaults.layout = { {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, }, ["dispellableByMe"] = false, + ["nonPlayerAuras"] = false, ["orientation"] = "left-to-right", }, -- 23 { @@ -443,6 +445,7 @@ Cell.defaults.layout = { ["border"] = 2, ["num"] = 1, ["showDuration"] = true, + ["animationStyle"] = "clock", ["font"] = { {"Cell ".._G.DEFAULT, 11, "Outline", false, "TOPRIGHT", 2, 1, {1, 1, 1}}, {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, diff --git a/Defaults/Layout_Defaults_Mists.lua b/Defaults/Layout_Defaults_Mists.lua index 3fd18288..e77008e2 100644 --- a/Defaults/Layout_Defaults_Mists.lua +++ b/Defaults/Layout_Defaults_Mists.lua @@ -448,6 +448,7 @@ Cell.defaults.layout = { {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, }, ["dispellableByMe"] = false, + ["nonPlayerAuras"] = false, ["orientation"] = "left-to-right", }, -- 24 { diff --git a/Defaults/Layout_Defaults_TBC_Vanilla.lua b/Defaults/Layout_Defaults_TBC_Vanilla.lua index 530ca96b..d724785c 100644 --- a/Defaults/Layout_Defaults_TBC_Vanilla.lua +++ b/Defaults/Layout_Defaults_TBC_Vanilla.lua @@ -405,6 +405,7 @@ Cell.defaults.layout = { ["size"] = {{13, 13}, {17, 17}}, ["showDuration"] = false, ["showAnimation"] = true, + ["animationStyle"] = "clock", ["showTooltip"] = false, ["enableBlacklistShortcut"] = false, ["num"] = 3, @@ -413,6 +414,7 @@ Cell.defaults.layout = { {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, }, ["dispellableByMe"] = false, + ["nonPlayerAuras"] = false, ["orientation"] = "left-to-right", }, -- 22 { @@ -426,6 +428,7 @@ Cell.defaults.layout = { ["border"] = 2, ["num"] = 1, ["showDuration"] = true, + ["animationStyle"] = "clock", ["font"] = { {"Cell ".._G.DEFAULT, 11, "Outline", false, "TOPRIGHT", 2, 1, {1, 1, 1}}, {"Cell ".._G.DEFAULT, 11, "Outline", false, "BOTTOMRIGHT", 2, -1, {1, 1, 1}}, diff --git a/HideBlizzard.lua b/HideBlizzard.lua index 76234c35..c600c66f 100644 --- a/HideBlizzard.lua +++ b/HideBlizzard.lua @@ -1,95 +1,397 @@ local _, Cell = ... local F = Cell.funcs --- stolen from elvui -local hiddenParent = CreateFrame("Frame", nil, _G.UIParent) -hiddenParent:SetAllPoints() +Cell.vars.editModeOpen = false + +local hardDisabled = false +local editModeHooked = false +local hiddenParent = CreateFrame("Frame") hiddenParent:Hide() +local containerHooks = {} +local TrySuppressForGroup -local function HideFrame(frame) - if not frame then return end +local function ShouldHideBlizzardParty() + return CellDB and CellDB["general"] and CellDB["general"]["hideBlizzardParty"] +end - frame:UnregisterAllEvents() - frame:Hide() - frame:SetParent(hiddenParent) +local function ShouldHideBlizzardRaid() + return CellDB and CellDB["general"] and CellDB["general"]["hideBlizzardRaid"] +end - local health = frame.healthBar or frame.healthbar - if health then - health:UnregisterAllEvents() - end +local function ShouldHideBlizzardRaidManager() + return CellDB and CellDB["general"] and CellDB["general"]["hideBlizzardRaidManager"] +end + +function F.IsEditModeOpen() + return Cell.vars.editModeOpen == true +end + +local function FrameIsForbidden(frame) + if not frame then return true end + local ok, forbidden = pcall(function() + return frame.IsForbidden and frame:IsForbidden() + end) + return (not ok) or forbidden +end + +local function SetFrameAlpha(frame, alpha) + if not frame or FrameIsForbidden(frame) then return end + pcall(frame.SetAlpha, frame, alpha) +end - local power = frame.manabar - if power then - power:UnregisterAllEvents() +local function HideSelectionHighlight(frame) + if not frame or FrameIsForbidden(frame) then return end + local hl = frame.selectionHighlight + if hl then + pcall(hl.SetShown, hl, false) + pcall(hl.SetAlpha, hl, 0) + pcall(hl.SetIgnoreParentAlpha, hl, false) end + local ind = frame.selectionIndicator + if ind then + pcall(ind.SetShown, ind, false) + pcall(ind.SetAlpha, ind, 0) + end +end - local spell = frame.castBar or frame.spellbar - if spell then - spell:UnregisterAllEvents() +local function ForEachCompactPartyMember(func) + local members = _G.MEMBERS_PER_RAID_GROUP or 5 + for i = 1, members do + func(_G["CompactPartyFrameMember" .. i]) + func(_G["CompactPartyFramePet" .. i]) + end + local cpf = _G.CompactPartyFrame + if cpf then + if cpf.memberUnitFrames then + for _, frame in ipairs(cpf.memberUnitFrames) do + func(frame) + end + end + if cpf.petUnitFrames then + for _, frame in ipairs(cpf.petUnitFrames) do + func(frame) + end + end end +end - local altpowerbar = frame.powerBarAlt - if altpowerbar then - altpowerbar:UnregisterAllEvents() +local function ForEachCompactRaidMember(func) + local members = _G.MEMBERS_PER_RAID_GROUP or 5 + for i = 1, 8 do + for m = 1, members do + func(_G["CompactRaidGroup" .. i .. "Member" .. m]) + end + func(_G["CompactRaidGroup" .. i]) end + for i = 1, 80 do + func(_G["CompactRaidFrame" .. i]) + end +end - local buffFrame = frame.BuffFrame - if buffFrame then - buffFrame:UnregisterAllEvents() +local function HideGoldHighlights() + if ShouldHideBlizzardParty() then + HideSelectionHighlight(_G.CompactPartyFrame) + ForEachCompactPartyMember(HideSelectionHighlight) + end + if ShouldHideBlizzardRaid() then + ForEachCompactRaidMember(HideSelectionHighlight) end +end + +local function QuietStatusBar(bar) + if not bar or FrameIsForbidden(bar) then return end + pcall(bar.UnregisterAllEvents, bar) + pcall(bar.SetScript, bar, "OnUpdate", nil) +end - local petFrame = frame.PetFrame - if petFrame then - petFrame:UnregisterAllEvents() +local function QuietUnitParts(frame) + if not frame then return end + local container = frame.HealthBarContainer or frame.HealthBarsContainer + local health = (container and (container.HealthBar or container.healthBar)) + or frame.healthbar or frame.HealthBar or frame.healthBar + QuietStatusBar(health) + QuietStatusBar(frame.ManaBar or frame.manabar or frame.powerBar or frame.PowerBar) + QuietStatusBar(frame.castBar or frame.spellbar or frame.CastingBarFrame) + QuietStatusBar(frame.powerBarAlt or frame.PowerBarAlt) + if frame.BuffFrame then + pcall(frame.BuffFrame.UnregisterAllEvents, frame.BuffFrame) + end + if frame.DebuffFrame then + pcall(frame.DebuffFrame.UnregisterAllEvents, frame.DebuffFrame) + end + if frame.AurasFrame then + pcall(frame.AurasFrame.UnregisterAllEvents, frame.AurasFrame) end end -function F.HideBlizzardParty() - _G.UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") +local function KillFrame(frame, reparent, hide) + if not frame or FrameIsForbidden(frame) then return end + pcall(function() + frame:UnregisterAllEvents() + if not InCombatLockdown() then + if hide then + frame:Hide() + end + if reparent then + frame:SetParent(hiddenParent) + end + else + frame:SetAlpha(0) + end + end) + QuietUnitParts(frame) + HideSelectionHighlight(frame) +end - -- Midnight 12.0.0+ may have different party frame structure - if _G.CompactPartyFrame then - _G.CompactPartyFrame:UnregisterAllEvents() - _G.CompactPartyFrame:SetParent(hiddenParent) +local function ShrinkContainer(frame) + if not frame or FrameIsForbidden(frame) then return end + pcall(function() + frame:SetAlpha(0) + if not InCombatLockdown() then + frame:SetScale(0.001) + end + end) + HideSelectionHighlight(frame) +end + +local function HookContainerOnShow(frame) + if not frame or containerHooks[frame] then return end + containerHooks[frame] = true + frame:HookScript("OnShow", function() + C_Timer.After(0, TrySuppressForGroup) + end) +end + +local function QuietCompactMember(frame) + if not frame or FrameIsForbidden(frame) then return end + pcall(frame.UnregisterAllEvents, frame) + SetFrameAlpha(frame, 0) + QuietUnitParts(frame) + HideSelectionHighlight(frame) + if frame.PetFrame then + QuietCompactMember(frame.PetFrame) end +end - if _G.PartyFrame then - _G.PartyFrame:UnregisterAllEvents() - _G.PartyFrame:SetScript("OnShow", nil) - if _G.PartyFrame.PartyMemberFramePool then - for frame in _G.PartyFrame.PartyMemberFramePool:EnumerateActive() do - HideFrame(frame) +local function QuietTraditionalMember(frame) + if not frame or FrameIsForbidden(frame) then return end + KillFrame(frame, true, true) + if frame.PetFrame then + KillFrame(frame.PetFrame, true, true) + end +end + +local function QuietTraditionalParty() + local pf = _G.PartyFrame + if pf and not FrameIsForbidden(pf) then + KillFrame(pf, true, true) + ShrinkContainer(pf) + if pf.PartyMemberFramePool then + for memberFrame in pf.PartyMemberFramePool:EnumerateActive() do + KillFrame(memberFrame, false, true) + QuietUnitParts(memberFrame) + if memberFrame.PetFrame then + KillFrame(memberFrame.PetFrame, false, true) + end end end - HideFrame(_G.PartyFrame) - else - -- Legacy party frame fallback - for i = 1, 4 do - HideFrame(_G["PartyMemberFrame"..i]) - HideFrame(_G["CompactPartyMemberFrame"..i]) + SetFrameAlpha(pf.DropdownButton, 0) + SetFrameAlpha(pf.PartyMemberFrameDropDown, 0) + if not InCombatLockdown() then + if pf.DropdownButton then + pcall(pf.DropdownButton.Hide, pf.DropdownButton) + end end - if _G.PartyMemberBackground then - HideFrame(_G.PartyMemberBackground) + end + for i = 1, 4 do + QuietTraditionalMember(_G["PartyMemberFrame" .. i]) + QuietTraditionalMember(_G["PartyMemberFrame" .. i .. "PetFrame"]) + end + if _G.PartyMemberBackground then + KillFrame(_G.PartyMemberBackground, false, true) + end +end + +local function QuietCompactParty() + local cpf = _G.CompactPartyFrame + if cpf and not FrameIsForbidden(cpf) then + pcall(cpf.UnregisterAllEvents, cpf) + ShrinkContainer(cpf) + SetFrameAlpha(cpf.title, 0) + SetFrameAlpha(cpf.borderFrame, 0) + HideSelectionHighlight(cpf) + if not InCombatLockdown() then + if cpf.dropdown then + pcall(cpf.dropdown.Hide, cpf.dropdown) + end + if cpf.menuButton then + pcall(cpf.menuButton.Hide, cpf.menuButton) + end + else + SetFrameAlpha(cpf.dropdown, 0) + SetFrameAlpha(cpf.menuButton, 0) end + HookContainerOnShow(cpf) end + ForEachCompactPartyMember(QuietCompactMember) end -function F.HideBlizzardRaid() - _G.UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") +local function QuietCompactRaid() + local container = _G.CompactRaidFrameContainer + if container and not FrameIsForbidden(container) then + pcall(container.UnregisterAllEvents, container) + ShrinkContainer(container) + HookContainerOnShow(container) + end + for i = 1, 8 do + SetFrameAlpha(_G["CompactRaidGroup" .. i], 0) + end + ForEachCompactRaidMember(QuietCompactMember) +end + +local function QuietRaidManager() + local manager = _G.CompactRaidFrameManager + if not manager or FrameIsForbidden(manager) then return end + pcall(function() + manager:UnregisterAllEvents() + if not InCombatLockdown() then + manager:Hide() + manager:SetParent(hiddenParent) + if CompactRaidFrameManager_SetSetting then + CompactRaidFrameManager_SetSetting("IsShown", "0") + end + else + manager:SetAlpha(0) + end + end) + SetFrameAlpha(manager.container, 0) + SetFrameAlpha(manager.toggleButton, 0) + SetFrameAlpha(manager.displayFrame, 0) + HookContainerOnShow(manager) +end - if _G.CompactRaidFrameContainer then - _G.CompactRaidFrameContainer:UnregisterAllEvents() - _G.CompactRaidFrameContainer:SetParent(hiddenParent) +local function ApplyHiddenBlizzard() + if ShouldHideBlizzardParty() then + QuietTraditionalParty() + QuietCompactParty() + end + if ShouldHideBlizzardRaid() then + QuietCompactRaid() + end + if ShouldHideBlizzardRaidManager() then + QuietRaidManager() end + HideGoldHighlights() end -function F.HideBlizzardRaidManager() - if CompactRaidFrameManager_SetSetting then - CompactRaidFrameManager_SetSetting("IsShown", "0") +local function HardDisableBlizzardFrames() + if hardDisabled then return true end + if InCombatLockdown() then return false end + if not (ShouldHideBlizzardParty() or ShouldHideBlizzardRaid() or ShouldHideBlizzardRaidManager()) then + return false end - if _G.CompactRaidFrameManager then - _G.CompactRaidFrameManager:UnregisterAllEvents() - _G.CompactRaidFrameManager:SetParent(hiddenParent) + pcall(function() + if ShouldHideBlizzardParty() and ShouldHideBlizzardRaid() then + UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") + end + if ShouldHideBlizzardRaid() and CompactRaidFrameContainer then + CompactRaidFrameContainer:UnregisterAllEvents() + end + if ShouldHideBlizzardRaidManager() and CompactRaidFrameManager then + CompactRaidFrameManager:UnregisterAllEvents() + CompactRaidFrameManager:SetParent(hiddenParent) + if CompactRaidFrameManager_SetSetting then + CompactRaidFrameManager_SetSetting("IsShown", "0") + end + end + if ShouldHideBlizzardParty() then + if CompactPartyFrame then + CompactPartyFrame:UnregisterAllEvents() + end + if PartyFrame then + PartyFrame:UnregisterAllEvents() + end + end + end) + + ApplyHiddenBlizzard() + hardDisabled = true + return true +end + +TrySuppressForGroup = function() + HardDisableBlizzardFrames() + ApplyHiddenBlizzard() +end + +local function QueueSuppress() + C_Timer.After(0, TrySuppressForGroup) +end + +local function HookEditMode() + if editModeHooked then return end + local em = _G.EditModeManagerFrame + if not em then return end + editModeHooked = true + + em:HookScript("OnShow", function() + Cell.vars.editModeOpen = true + C_Timer.After(0, TrySuppressForGroup) + end) + + em:HookScript("OnHide", function() + Cell.vars.editModeOpen = false + C_Timer.After(0.25, TrySuppressForGroup) + end) +end + +local boot = CreateFrame("Frame") +boot:RegisterEvent("PLAYER_LOGIN") +boot:RegisterEvent("PLAYER_ENTERING_WORLD") +boot:RegisterEvent("PLAYER_REGEN_ENABLED") +boot:RegisterEvent("PLAYER_TARGET_CHANGED") +boot:RegisterEvent("ADDON_LOADED") +boot:SetScript("OnEvent", function(_, event, addonName) + if event == "ADDON_LOADED" then + if addonName == "Blizzard_EditMode" + or addonName == "Blizzard_UnitFrame" + or addonName == "Blizzard_CompactRaidFrames" + or addonName == "Blizzard_RaidFrame" + or addonName == "Blizzard_Game" then + C_Timer.After(0, function() + HookEditMode() + TrySuppressForGroup() + end) + end + return end -end \ No newline at end of file + + if event == "PLAYER_TARGET_CHANGED" then + C_Timer.After(0, HideGoldHighlights) + return + end + + C_Timer.After(0, function() + HookEditMode() + TrySuppressForGroup() + end) +end) + +function F.HideBlizzardParty() + hardDisabled = false + QueueSuppress() + C_Timer.After(0.5, TrySuppressForGroup) +end + +function F.HideBlizzardRaid() + hardDisabled = false + QueueSuppress() + C_Timer.After(0.5, TrySuppressForGroup) +end + +function F.HideBlizzardRaidManager() + hardDisabled = false + QueueSuppress() + C_Timer.After(0.5, TrySuppressForGroup) +end diff --git a/HideBlizzard_Classic.lua b/HideBlizzard_Classic.lua new file mode 100644 index 00000000..91237cea --- /dev/null +++ b/HideBlizzard_Classic.lua @@ -0,0 +1,111 @@ +local _, Cell = ... +local F = Cell.funcs + +local hiddenParent = CreateFrame("Frame", nil, _G.UIParent) +hiddenParent:SetAllPoints() +hiddenParent:Hide() + +local function KeepHidden(frame) + if not frame or frame.__cellKeepHidden then return end + frame.__cellKeepHidden = true + frame:HookScript("OnShow", frame.Hide) +end + +local function HideFrame(frame) + if not frame then return end + + frame:UnregisterAllEvents() + frame:Hide() + frame:SetParent(hiddenParent) + KeepHidden(frame) + + local health = frame.healthBar or frame.healthbar + if health then + health:UnregisterAllEvents() + end + + local power = frame.manabar + if power then + power:UnregisterAllEvents() + end + + local spell = frame.castBar or frame.spellbar + if spell then + spell:UnregisterAllEvents() + end + + local altpowerbar = frame.powerBarAlt + if altpowerbar then + altpowerbar:UnregisterAllEvents() + end + + local buffFrame = frame.BuffFrame + if buffFrame then + buffFrame:UnregisterAllEvents() + end + + local petFrame = frame.PetFrame + if petFrame then + petFrame:UnregisterAllEvents() + end +end + +function F.HideBlizzardParty() + _G.UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") + + if _G.CompactPartyFrame then + _G.CompactPartyFrame:UnregisterAllEvents() + _G.CompactPartyFrame:SetParent(hiddenParent) + KeepHidden(_G.CompactPartyFrame) + end + + if _G.PartyFrame then + _G.PartyFrame:UnregisterAllEvents() + _G.PartyFrame:SetScript("OnShow", nil) + if _G.PartyFrame.PartyMemberFramePool then + for frame in _G.PartyFrame.PartyMemberFramePool:EnumerateActive() do + HideFrame(frame) + end + end + HideFrame(_G.PartyFrame) + else + for i = 1, 4 do + HideFrame(_G["PartyMemberFrame" .. i]) + HideFrame(_G["CompactPartyMemberFrame" .. i]) + end + if _G.PartyMemberBackground then + HideFrame(_G.PartyMemberBackground) + end + end +end + +function F.HideBlizzardRaid() + _G.UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") + + if _G.CompactRaidFrameContainer then + _G.CompactRaidFrameContainer:UnregisterAllEvents() + _G.CompactRaidFrameContainer:SetParent(hiddenParent) + KeepHidden(_G.CompactRaidFrameContainer) + end + + for i = 1, 8 do + local group = _G["CompactRaidGroup" .. i] + if group then + group:UnregisterAllEvents() + group:SetParent(hiddenParent) + KeepHidden(group) + end + end +end + +function F.HideBlizzardRaidManager() + if CompactRaidFrameManager_SetSetting then + CompactRaidFrameManager_SetSetting("IsShown", "0") + end + + if _G.CompactRaidFrameManager then + _G.CompactRaidFrameManager:UnregisterAllEvents() + _G.CompactRaidFrameManager:SetParent(hiddenParent) + KeepHidden(_G.CompactRaidFrameManager) + end +end diff --git a/HideBlizzard_Mists.lua b/HideBlizzard_Mists.lua new file mode 100644 index 00000000..db358035 --- /dev/null +++ b/HideBlizzard_Mists.lua @@ -0,0 +1,144 @@ +local _, Cell = ... +local F = Cell.funcs + +local hiddenParent = CreateFrame("Frame", nil, _G.UIParent) +hiddenParent:SetAllPoints() +hiddenParent:Hide() + +local function SoftKeepHidden(frame) + if not frame or frame.__cellSoftKeepHidden then return end + frame.__cellSoftKeepHidden = true + if type(frame.Show) ~= "function" then return end + hooksecurefunc(frame, "Show", function(self) + if InCombatLockdown() then return end + if self.IsShown and self:IsShown() then + self:Hide() + end + end) +end + +local function HideFrame(frame) + if not frame then return end + + frame:UnregisterAllEvents() + frame:Hide() + frame:SetParent(hiddenParent) + SoftKeepHidden(frame) + + local health = frame.healthBar or frame.healthbar + if health then + health:UnregisterAllEvents() + end + + local power = frame.manabar + if power then + power:UnregisterAllEvents() + end + + local spell = frame.castBar or frame.spellbar + if spell then + spell:UnregisterAllEvents() + end + + local altpowerbar = frame.powerBarAlt + if altpowerbar then + altpowerbar:UnregisterAllEvents() + end + + local buffFrame = frame.BuffFrame + if buffFrame then + buffFrame:UnregisterAllEvents() + end + + local petFrame = frame.PetFrame + if petFrame then + petFrame:UnregisterAllEvents() + end +end + +function F.HideBlizzardParty() + _G.UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") + + if _G.CompactPartyFrame then + _G.CompactPartyFrame:UnregisterAllEvents() + _G.CompactPartyFrame:SetParent(hiddenParent) + _G.CompactPartyFrame:Hide() + SoftKeepHidden(_G.CompactPartyFrame) + end + + if _G.PartyFrame then + _G.PartyFrame:UnregisterAllEvents() + _G.PartyFrame:SetScript("OnShow", nil) + if _G.PartyFrame.PartyMemberFramePool then + for frame in _G.PartyFrame.PartyMemberFramePool:EnumerateActive() do + HideFrame(frame) + end + end + HideFrame(_G.PartyFrame) + else + for i = 1, 4 do + HideFrame(_G["PartyMemberFrame" .. i]) + local compactMember = _G["CompactPartyMemberFrame" .. i] + if compactMember then + compactMember:UnregisterAllEvents() + compactMember:Hide() + compactMember:SetParent(hiddenParent) + end + end + if _G.PartyMemberBackground then + HideFrame(_G.PartyMemberBackground) + end + end +end + +function F.HideBlizzardRaid() + _G.UIParent:UnregisterEvent("GROUP_ROSTER_UPDATE") + + if _G.CompactRaidFrameContainer then + _G.CompactRaidFrameContainer:UnregisterAllEvents() + _G.CompactRaidFrameContainer:SetParent(hiddenParent) + _G.CompactRaidFrameContainer:Hide() + SoftKeepHidden(_G.CompactRaidFrameContainer) + end + + for i = 1, 8 do + local group = _G["CompactRaidGroup" .. i] + if group then + group:UnregisterAllEvents() + group:SetParent(hiddenParent) + group:Hide() + SoftKeepHidden(group) + end + end +end + +function F.HideBlizzardRaidManager() + if CompactRaidFrameManager_SetSetting then + CompactRaidFrameManager_SetSetting("IsShown", "0") + end + + if _G.CompactRaidFrameManager then + _G.CompactRaidFrameManager:UnregisterAllEvents() + _G.CompactRaidFrameManager:SetParent(hiddenParent) + _G.CompactRaidFrameManager:Hide() + SoftKeepHidden(_G.CompactRaidFrameManager) + end +end + +local reinforce = CreateFrame("Frame") +reinforce:RegisterEvent("PLAYER_ENTERING_WORLD") +reinforce:RegisterEvent("GROUP_ROSTER_UPDATE") +reinforce:RegisterEvent("PLAYER_REGEN_ENABLED") +reinforce:SetScript("OnEvent", function() + if InCombatLockdown() then return end + if type(CellDB) ~= "table" or type(CellDB.general) ~= "table" then return end + if CellDB.general.hideBlizzardParty then + F.HideBlizzardParty() + end + if CellDB.general.hideBlizzardRaid then + F.HideBlizzardRaid() + end + if CellDB.general.hideBlizzardRaidManager then + F.HideBlizzardRaidManager() + end +end) diff --git a/Indicators/Actions.lua b/Indicators/Actions.lua index ea50e3b0..08d51f12 100644 --- a/Indicators/Actions.lua +++ b/Indicators/Actions.lua @@ -10,12 +10,9 @@ local orientation, speed ------------------------------------------------- -- events ------------------------------------------------- --- Audited for Midnight 12.0.0+ compatibility: no changes required. -- CLEU (COMBAT_LOG_EVENT_UNFILTERED) is not used — it is fully commented out below. -- The active handler listens to UNIT_SPELLCAST_SUCCEEDED for group members only. --- spellID from UNIT_SPELLCAST_SUCCEEDED for allied units is non-secret. -- Cell.vars.actions[spellID] table key usage is safe because the spellID comes --- from your own group's spellcasts, which are never restricted by GetRestrictedActionStatus. -- CLEU: subevent, source, target, spellId, spellName -- [15:10] SPELL_HEAL 秋静葉 秋静葉 6262 治疗石 @@ -40,7 +37,6 @@ eventFrame:SetScript("OnEvent", function(self, event, unit, castGUID, spellID) print("|cFFFF3030[Cell]|r |cFFB2B2B2" .. event .. ":|r", unit, "|cFF00FF00" .. (spellID or "nil") .. "|r", name) end - -- Midnight 12.0.0+: spellID from UNIT_SPELLCAST_SUCCEEDED is secret during restricted contexts if Cell.isMidnight and issecretvalue and issecretvalue(spellID) then return end if Cell.vars.actions[spellID] then diff --git a/Indicators/AoEHealing.lua b/Indicators/AoEHealing.lua index fce270d0..e57961f4 100644 --- a/Indicators/AoEHealing.lua +++ b/Indicators/AoEHealing.lua @@ -10,8 +10,6 @@ local I = Cell.iFuncs ------------------------------------------------- -- NOTE: This indicator relied on COMBAT_LOG_EVENT_UNFILTERED (SPELL_HEAL / -- SPELL_PERIODIC_HEAL / SPELL_SUMMON) to detect AoE healing events. --- COMBAT_LOG_EVENT_UNFILTERED is removed in Midnight (WoW 12.0.0). --- When Cell.isMidnight is true the eventFrame is never registered, so no -- flash animation will trigger. The indicator frame still exists and can be -- re-enabled if a suitable non-CLEU API becomes available in a future build. @@ -93,7 +91,6 @@ function I.CreateAoEHealing(parent) end function I.EnableAoEHealing(enabled) - -- On Midnight (12.0.0+) COMBAT_LOG_EVENT_UNFILTERED is unavailable; -- the eventFrame has no OnEvent script in that case, so registration -- is intentionally skipped. if Cell.isMidnight then return end diff --git a/Indicators/Base.lua b/Indicators/Base.lua index b1c89250..422b45f4 100644 --- a/Indicators/Base.lua +++ b/Indicators/Base.lua @@ -7,21 +7,12 @@ local I = Cell.iFuncs ---@type PixelPerfectFuncs local P = Cell.pixelPerfectFuncs --- NOTE (Midnight 12.0.0+): Health text formatting with arithmetic on health/maxHealth --- is guarded for secret values in Indicators/Built-in.lua (HealthText_SetValue). --- All other numeric display in this file uses SetText/SetFormattedText which accept secrets. local LCG = LibStub("LibCustomGlow-1.0") --- Midnight 12.0.0+: aura count (applications) may be secret; sanitize for safe comparisons/table-key use -local function _SanitizeCount(count) - if issecretvalue and issecretvalue(count) then return 0 end - return count or 0 -end - --- Midnight 12.0.0+: helper for stack text display (most common pattern) local function _StackText(count) - count = _SanitizeCount(count) + if not F.IsValueNonSecret(count) then return count end + count = count or 0 return (count == 0 or count == 1) and "" or count end @@ -89,9 +80,49 @@ end ------------------------------------------------- -- Shared ------------------------------------------------- +local function GetCountdownFrame(frame) + return frame._countdownCooldown or frame.cooldown +end + +local function ApplyCountdownFont(frame, font2) + local countdown = GetCountdownFrame(frame) + if not countdown then return end + if not countdown.GetCountdownFontString then return end + local cdText = countdown:GetCountdownFontString() + if not cdText then return end + + local textParent = frame._countdownTextParent or frame.iconFrame + if textParent then + if cdText:GetParent() ~= textParent then + cdText:SetParent(textParent) + end + cdText:ClearAllPoints() + cdText:SetPoint("CENTER", textParent, "CENTER", 0, 0) + end + + if font2 then + local fontFace = F.GetFont(font2[1]) or cdText:GetFont() + local fontSize = font2[2] or 11 + local outline = font2[3] + local flags = outline == "Outline" and "OUTLINE" + or outline == "Monochrome" and "OUTLINE,MONOCHROME" + or "" + cdText:SetFont(fontFace, fontSize, flags) + else + local fontFace = cdText:GetFont() + cdText:SetFont(fontFace, 11, "OUTLINE") + end +end + local function Shared_SetFont(frame, font1, font2) I.SetFont(frame.stack, frame, unpack(font1)) I.SetFont(frame.duration, frame, unpack(font2)) + frame._durationFont = font2 + if Cell.isMidnight then + frame._countdownFontApplied = false + ApplyCountdownFont(frame, font2) + frame._countdownFontApplied = true + end end local function Shared_ShowStack(frame, show) @@ -103,6 +134,19 @@ local function Shared_ShowDuration(frame, show) frame.duration:SetShown(show) end +local function Shared_ShowAnimation(frame, show) + frame.showAnimation = show + if not frame.cooldown then return end + if show then + frame.cooldown:Show() + else + frame.cooldown:Hide() + end +end + +local STATUSBAR_INTERPOLATION_NONE = Enum and Enum.StatusBarInterpolation and Enum.StatusBarInterpolation.None +local STATUSBAR_TIMER_DIRECTION_ELAPSED = Enum and Enum.StatusBarTimerDirection and Enum.StatusBarTimerDirection.ElapsedTime + ------------------------------------------------- -- VerticalCooldown ------------------------------------------------- @@ -117,7 +161,8 @@ end local function VerticalCooldown_OnUpdate(self, elapsed) self.elapsed = (self.elapsed or 0) + elapsed if self.elapsed >= 0.1 then - self:SetValue(self:GetValue() + self.elapsed) + self._currentValue = (self._currentValue or 0) + self.elapsed + self:SetValue(self._currentValue) self.elapsed = 0 end end @@ -140,7 +185,8 @@ local function VerticalCooldown_ShowCooldown(self, start, duration, _, icon, deb self.elapsed = 0.1 -- update immediately self:SetMinMaxValues(0, duration) - self:SetValue(GetTime() - start) + self._currentValue = GetTime() - start + self:SetValue(self._currentValue) self:Show() end @@ -221,8 +267,10 @@ local function Shared_CreateCooldown_Clock(frame) P.Point(cooldown, "BOTTOMRIGHT", frame, -CELL_BORDER_SIZE, CELL_BORDER_SIZE) cooldown:SetReverse(true) cooldown:SetDrawEdge(false) - cooldown:SetSwipeTexture(Cell.vars.whiteTexture) - cooldown:SetSwipeColor(0, 0, 0, 0.77) + if Cell.isRetail then + cooldown:SetSwipeTexture(Cell.vars.whiteTexture) + end + cooldown:SetSwipeColor(0, 0, 0, 0.65) -- cooldown:SetEdgeTexture([[Interface\Cooldown\UI-HUD-ActionBar-SecondaryCooldown]]) -- cooldown text @@ -238,8 +286,10 @@ end -- SetCooldownStyle ------------------------------------------------- local function Shared_SetCooldownStyle(frame, style, noIcon) + style = style == "CLOCK" and "CLOCK" or "VERTICAL" if frame.style == style then return end + local oldCooldown = frame.cooldown if frame.cooldown then frame.cooldown:SetParent(nil) frame.cooldown:Hide() @@ -256,6 +306,13 @@ local function Shared_SetCooldownStyle(frame, style, noIcon) Shared_CreateCooldown_Vertical(frame) end end + + if oldCooldown and frame.stack and frame.stack:GetParent() == oldCooldown then + frame.stack:SetParent(frame.cooldown) + end + if oldCooldown and frame.duration and frame.duration:GetParent() == oldCooldown then + frame.duration:SetParent(frame.cooldown) + end end -------------------------------------------------- @@ -305,43 +362,14 @@ local StopGlow = { } local function Shared_SetupGlow(frame, glowOptions) - frame.glowType = glowOptions[1] - frame.glowOptions = {} - ButtonGlow_Stop(frame) PixelGlow_Stop(frame) AutoCastGlow_Stop(frame) ProcGlow_Stop(frame) - - frame.StartGlow = StartGlow[strlower(frame.glowType)] - frame.StopGlow = StopGlow[strlower(frame.glowType)] - - if frame.glowType == "Normal" then - frame.glowOptions.color = glowOptions[2] - elseif frame.glowType == "Pixel" then - frame.glowOptions.color = glowOptions[2] - frame.glowOptions.N = glowOptions[3] - frame.glowOptions.frequency = glowOptions[4] - frame.glowOptions.length = glowOptions[5] - frame.glowOptions.thickness = glowOptions[6] - elseif frame.glowType == "Shine" then - frame.glowOptions.color = glowOptions[2] - frame.glowOptions.N = glowOptions[3] - frame.glowOptions.frequency = glowOptions[4] - frame.glowOptions.scale = glowOptions[5] - elseif frame.glowType == "Proc" then - frame.glowOptions = {color = glowOptions[2], duration = glowOptions[3], startAnim = false} - end - - if frame.glowType ~= "None" then - frame:StartGlow() - if not frame._sizeChangedHooked then - frame._sizeChangedHooked = true - frame:HookScript("OnSizeChanged", function() - frame:StartGlow() - end) - end - end + frame.glowType = "None" + frame.glowOptions = {} + frame.StartGlow = StartGlow["none"] + frame.StopGlow = StopGlow["none"] end function I.Glow_SetupForChildren(parent, glowOptions) @@ -354,7 +382,11 @@ end -- Icon_OnUpdate ------------------------------------------------- local function Icon_OnUpdate(frame, elapsed) - frame._remain = frame._duration - (GetTime() - frame._start) + if F.IsValueNonSecret(frame._duration) and F.IsValueNonSecret(frame._start) then + frame._remain = F.GetRemain(frame._start, frame._duration) + else + frame._remain = 0 + end if frame._remain < 0 then frame._remain = 0 end if frame._remain > frame._threshold then @@ -396,7 +428,11 @@ local function Icon_OnUpdate(frame, elapsed) end local function Icon_OnUpdate_ElapsedTime(frame, elapsed) - frame._remain = frame._duration - (GetTime() - frame._start) + if F.IsValueNonSecret(frame._duration) and F.IsValueNonSecret(frame._start) then + frame._remain = F.GetRemain(frame._start, frame._duration) + else + frame._remain = 0 + end if frame._remain < 0 then frame._remain = 0 end if frame._remain > frame._threshold then @@ -422,7 +458,11 @@ local function Icon_OnUpdate_ElapsedTime(frame, elapsed) end -- format - frame._elapsedTime = GetTime() - frame._start + if F.IsValueNonSecret(frame._start) then + frame._elapsedTime = GetTime() - frame._start + else + frame._elapsedTime = 0 + end if frame._elapsedTime > frame._duration then frame._elapsedTime = frame._duration end if frame._elapsedTime > 60 then @@ -432,6 +472,299 @@ local function Icon_OnUpdate_ElapsedTime(frame, elapsed) end end +------------------------------------------------- +-- cooldown from aura +------------------------------------------------- +local _GetAuraDuration = C_UnitAuras and C_UnitAuras.GetAuraDuration +local _GetAuraAppDisplayCount = C_UnitAuras and C_UnitAuras.GetAuraApplicationDisplayCount + +local function BorderIcon_GetCountdownCooldown(frame) + return frame._countdownCooldown or frame.cooldown +end + +local function BorderIcon_SetCountdownVisibility(frame, show) + local countdown = BorderIcon_GetCountdownCooldown(frame) + if countdown and countdown.SetHideCountdownNumbers then + countdown:SetHideCountdownNumbers(not show) + end +end + +local function BorderIcon_CreateClockCooldown(frame) + local cooldown = CreateFrame("Cooldown", nil, frame) + frame.cooldown = cooldown + cooldown:SetAllPoints(frame) + if Cell.isRetail then + cooldown:SetSwipeTexture(Cell.vars.whiteTexture) + end + cooldown:SetSwipeColor(0, 0, 0, 0.65) + cooldown:SetHideCountdownNumbers(true) + if Cell.isMidnight and cooldown.SetCountdownAbbrevThreshold then + cooldown:SetCountdownAbbrevThreshold(60) + end + cooldown.noCooldownCount = true + cooldown._SetCooldown = cooldown.SetCooldown + cooldown.SetCooldown = nil + + frame._countdownTextParent = frame.iconFrame +end + +local function BorderIcon_CreateVerticalCountdown(frame) + local countdown = CreateFrame("Cooldown", nil, frame.cooldown) + frame._countdownCooldown = countdown + countdown:SetFrameLevel(frame.cooldown:GetFrameLevel() + 1) + P.Point(countdown, "TOPLEFT") + P.Point(countdown, "BOTTOMRIGHT") + countdown:SetDrawSwipe(false) + countdown:SetDrawEdge(false) + countdown:SetDrawBling(false) + countdown:SetHideCountdownNumbers(true) + if Cell.isMidnight and countdown.SetCountdownAbbrevThreshold then + countdown:SetCountdownAbbrevThreshold(60) + end + countdown.noCooldownCount = true + countdown._SetCooldown = countdown.SetCooldown + countdown.SetCooldown = nil + + frame._countdownTextParent = countdown +end + +local function BorderIcon_Vertical_OnUpdate(self, elapsed) + self.elapsed = (self.elapsed or 0) + elapsed + if self.elapsed >= 0.1 then + self._currentValue = (self._currentValue or 0) + self.elapsed + if self._duration and self._currentValue > self._duration then + self._currentValue = self._duration + end + self:SetValue(self._currentValue) + self.elapsed = 0 + + if self._duration and self._currentValue >= self._duration then + self:SetScript("OnUpdate", nil) + if not self._cooldownDoneFired then + self._cooldownDoneFired = true + local onCooldownDone = self:GetScript("OnCooldownDone") + if onCooldownDone then + onCooldownDone(self) + end + end + end + end +end + +local function BorderIcon_Vertical_ShowCooldown(self, start, duration) + self._cooldownDoneFired = false + self:SetScript("OnUpdate", BorderIcon_Vertical_OnUpdate) + self.elapsed = 0.1 + self._duration = duration + self:SetMinMaxValues(0, duration) + self._currentValue = GetTime() - start + if self._currentValue < 0 then self._currentValue = 0 end + if self._currentValue > duration then self._currentValue = duration end + self:SetValue(self._currentValue) + self:Show() +end + +local function BorderIcon_Vertical_SetDurationObject(self, durObj) + self:SetScript("OnUpdate", nil) + self.elapsed = nil + self._duration = nil + self._currentValue = nil + self:SetMinMaxValues(0, 1) + self:SetTimerDuration(durObj, STATUSBAR_INTERPOLATION_NONE, STATUSBAR_TIMER_DIRECTION_ELAPSED) + if self.SetToTargetValue then + self:SetToTargetValue() + end + self:Show() +end + +local function BorderIcon_CreateVerticalCooldown(frame) + local cooldown = CreateFrame("StatusBar", nil, frame.iconFrame) + frame.cooldown = cooldown + cooldown:Hide() + cooldown:SetFrameLevel(frame.iconFrame:GetFrameLevel() + 1) + P.Point(cooldown, "TOPLEFT") + P.Point(cooldown, "BOTTOMRIGHT") + cooldown:SetOrientation("VERTICAL") + cooldown:SetReverseFill(true) + cooldown:SetStatusBarTexture(Cell.vars.whiteTexture) + cooldown._SetScript = cooldown.SetScript + cooldown._GetScript = cooldown.GetScript + cooldown.ShowCooldown = BorderIcon_Vertical_ShowCooldown + cooldown.SetDurationObject = BorderIcon_Vertical_SetDurationObject + cooldown.SetCooldown = BorderIcon_Vertical_ShowCooldown + cooldown.SetReverse = function(self, reverse) + self:SetReverseFill(reverse) + end + cooldown.SetSwipeColor = function(self, r, g, b, a) + self:GetStatusBarTexture():SetVertexColor(r, g, b, a or 0.77) + end + cooldown.SetScript = function(self, scriptType, handler) + if scriptType == "OnCooldownDone" then + self._onCooldownDone = handler + return + end + return self:_SetScript(scriptType, handler) + end + cooldown.GetScript = function(self, scriptType) + if scriptType == "OnCooldownDone" then + return self._onCooldownDone + end + return self:_GetScript(scriptType) + end + cooldown:GetStatusBarTexture():SetVertexColor(0, 0, 0, 0.77) + + local spark = cooldown:CreateTexture(nil, "OVERLAY") + cooldown.spark = spark + P.Height(spark, 1) + spark:SetColorTexture(0.7, 0.7, 0.7, 0.9) + spark:SetPoint("TOPLEFT", cooldown:GetStatusBarTexture(), "BOTTOMLEFT") + spark:SetPoint("TOPRIGHT", cooldown:GetStatusBarTexture(), "BOTTOMRIGHT") + + if Cell.isMidnight then + BorderIcon_CreateVerticalCountdown(frame) + else + frame._countdownTextParent = cooldown + end +end + +local function BorderIcon_SetCooldownStyle(frame, style) + style = style == "CLOCK" and "CLOCK" or "VERTICAL" + if frame.style == style then return end + + if frame.cooldown then + frame.cooldown:SetParent(nil) + frame.cooldown:Hide() + frame.cooldown = nil + end + if frame._countdownCooldown then + frame._countdownCooldown:SetParent(nil) + frame._countdownCooldown:Hide() + frame._countdownCooldown = nil + end + + frame.style = style + frame._countdownTextParent = nil + frame._countdownFontApplied = false + + if style == "CLOCK" then + BorderIcon_CreateClockCooldown(frame) + frame.stack:SetParent(frame.iconFrame) + frame.duration:SetParent(frame.iconFrame) + else + BorderIcon_CreateVerticalCooldown(frame) + frame.stack:SetParent(frame.cooldown) + frame.duration:SetParent(frame.cooldown) + end + + if Cell.isMidnight then + frame.duration:Hide() + BorderIcon_SetCountdownVisibility(frame, frame.showDuration) + if frame.showDuration then + ApplyCountdownFont(frame, frame._durationFont) + frame._countdownFontApplied = true + end + end +end + +local function BorderIcon_SetCooldownFromAura(frame, unit, auraInstanceID, texture, refreshing) + if texture == nil then + frame:Hide() + return + end + + -- Icon and stack + frame.icon:SetTexture(texture) + if _GetAuraAppDisplayCount then + local displayCount = _GetAuraAppDisplayCount(unit, auraInstanceID) + frame.stack:SetText(displayCount and _StackText(displayCount) or "") + else + frame.stack:SetText("") + end + + local durObj = _GetAuraDuration and _GetAuraDuration(unit, auraInstanceID) + if durObj and frame.style == "VERTICAL" and frame.cooldown and frame.cooldown.SetDurationObject then + frame.cooldown:SetDurationObject(durObj) + if frame._countdownCooldown and frame._countdownCooldown.SetCooldownFromDurationObject then + frame._countdownCooldown:SetReverse(true) + frame._countdownCooldown:SetCooldownFromDurationObject(durObj, true) + BorderIcon_SetCountdownVisibility(frame, frame.showDuration) + if frame.showDuration and not frame._countdownFontApplied then + ApplyCountdownFont(frame, frame._durationFont) + frame._countdownFontApplied = true + end + frame._countdownCooldown:Show() + end + elseif durObj and frame.cooldown and frame.cooldown._SetCooldown + and frame.cooldown.SetCooldownFromDurationObject then + frame.cooldown:SetReverse(true) + frame.cooldown:SetCooldownFromDurationObject(durObj, true) + BorderIcon_SetCountdownVisibility(frame, frame.showDuration) + if frame.showDuration and not frame._countdownFontApplied then + ApplyCountdownFont(frame, frame._durationFont) + frame._countdownFontApplied = true + end + frame.cooldown:Show() + else + if frame._countdownCooldown then + frame._countdownCooldown:Hide() + end + frame.border:Show() + frame.border:SetColorTexture(0, 0, 0) + frame.cooldown:Hide() + end + + if Cell.isMidnight then + frame.duration:Hide() + end + frame:SetScript("OnUpdate", nil) + frame:Show() + + if refreshing then + frame.ag:Play() + end +end + +local function BarIcon_SetCooldownFromAura(frame, unit, auraInstanceID, texture, refreshing) + frame.icon:SetTexture(texture) + if _GetAuraAppDisplayCount then + local displayCount = _GetAuraAppDisplayCount(unit, auraInstanceID) + frame.stack:SetText(displayCount and _StackText(displayCount) or "") + else + frame.stack:SetText("") + end + + local durObj = _GetAuraDuration and _GetAuraDuration(unit, auraInstanceID) + if durObj then + if not frame._midnightCooldown then + local cd = CreateFrame("Cooldown", nil, frame) + cd:SetAllPoints(frame.icon) + cd:SetFrameLevel(frame:GetFrameLevel() + 5) + cd:SetSwipeTexture(Cell.vars.whiteTexture) + cd:SetSwipeColor(0, 0, 0) + cd:SetDrawEdge(false) + cd:SetReverse(true) + cd:SetHideCountdownNumbers(true) + cd.noCooldownCount = true + frame._midnightCooldown = cd + end + if frame._midnightCooldown.SetCooldownFromDurationObject then + frame._midnightCooldown:SetCooldownFromDurationObject(durObj, false) + frame._midnightCooldown:Show() + end + else + if frame._midnightCooldown then frame._midnightCooldown:Hide() end + end + + frame.cooldown:Hide() + frame.duration:Hide() + frame:SetBackdropColor(0, 0, 0) + frame:Show() + + if refreshing then + frame.ag:Play() + end +end + ------------------------------------------------- -- CreateAura_BorderIcon ------------------------------------------------- @@ -447,6 +780,9 @@ local function BorderIcon_SetCooldown(frame, start, duration, debuffType, textur frame.border:Show() frame.border:SetColorTexture(r, g, b) frame.cooldown:Hide() + if frame._countdownCooldown then + frame._countdownCooldown:Hide() + end frame.duration:Hide() frame:SetScript("OnUpdate", nil) frame._start = nil @@ -456,29 +792,84 @@ local function BorderIcon_SetCooldown(frame, start, duration, debuffType, textur frame._threshold = nil frame._elapsedTime = nil else - frame.border:Hide() - frame.cooldown:Show() - frame.cooldown:SetSwipeColor(r, g, b) - frame.cooldown:_SetCooldown(start, duration) + frame:SetScript("OnUpdate", nil) + frame._start = nil + frame._duration = nil + frame._remain = nil + frame._elapsed = nil + frame._threshold = nil + frame._elapsedTime = nil - if not frame.showDuration then - frame.duration:Hide() + if frame.style == "VERTICAL" then + frame.border:Show() + frame.border:SetColorTexture(r, g, b) + frame.cooldown:ShowCooldown(start, duration) + if Cell.isMidnight and frame._countdownCooldown and frame._countdownCooldown._SetCooldown then + frame.duration:Hide() + frame._countdownCooldown:_SetCooldown(start, duration) + BorderIcon_SetCountdownVisibility(frame, frame.showDuration) + if frame.showDuration then + ApplyCountdownFont(frame, frame._durationFont) + frame._countdownFontApplied = true + end + frame._countdownCooldown:Show() + else + if not frame.showDuration then + frame.duration:Hide() + else + if frame.showDuration == true then + frame._threshold = duration + elseif frame.showDuration >= 1 then + frame._threshold = frame.showDuration + else -- < 1 + frame._threshold = frame.showDuration * duration + end + frame.duration:Show() + frame._start = start + frame._duration = duration + frame._elapsed = 0.1 -- update immediately + frame:SetScript("OnUpdate", useElapsedTime and Icon_OnUpdate_ElapsedTime or Icon_OnUpdate) + end + end else - if frame.showDuration == true then - frame._threshold = duration - elseif frame.showDuration >= 1 then - frame._threshold = frame.showDuration - else -- < 1 - frame._threshold = frame.showDuration * duration + frame.border:Hide() + frame.cooldown:Show() + frame.cooldown:SetSwipeColor(r, g, b, 0.65) + frame.cooldown:_SetCooldown(start, duration) + + frame:SetScript("OnUpdate", nil) + frame._start = nil + frame._duration = nil + frame._remain = nil + frame._elapsed = nil + frame._threshold = nil + frame._elapsedTime = nil + + if Cell.isMidnight and frame.cooldown.SetHideCountdownNumbers then + frame.duration:Hide() + BorderIcon_SetCountdownVisibility(frame, frame.showDuration) + if frame.showDuration then + ApplyCountdownFont(frame, frame._durationFont) + frame._countdownFontApplied = true + end + else + if not frame.showDuration then + frame.duration:Hide() + else + if frame.showDuration == true then + frame._threshold = duration + elseif frame.showDuration >= 1 then + frame._threshold = frame.showDuration + else -- < 1 + frame._threshold = frame.showDuration * duration + end + frame.duration:Show() + frame._start = start + frame._duration = duration + frame._elapsed = 0.1 -- update immediately + frame:SetScript("OnUpdate", useElapsedTime and Icon_OnUpdate_ElapsedTime or Icon_OnUpdate) + end end - frame.duration:Show() - end - - if frame.showDuration then - frame._start = start - frame._duration = duration - frame._elapsed = 0.1 -- update immediately - frame:SetScript("OnUpdate", useElapsedTime and Icon_OnUpdate_ElapsedTime or Icon_OnUpdate) end end @@ -499,19 +890,42 @@ end local function BorderIcon_ShowDuration(frame, show) frame.showDuration = show - if show then - frame.duration:Show() - else + if Cell.isMidnight then frame.duration:Hide() + BorderIcon_SetCountdownVisibility(frame, show) + if show then + ApplyCountdownFont(frame, frame._durationFont) + frame._countdownFontApplied = true + end + else + if show then + frame.duration:Show() + else + frame.duration:Hide() + end end end +local function BorderIcon_ShowStack(frame, show) + frame.showStack = show + frame.stack:SetShown(show) +end + local function BorderIcon_UpdatePixelPerfect(frame) P.Resize(frame) P.Repoint(frame) P.Repoint(frame.iconFrame) P.Repoint(frame.stack) P.Repoint(frame.duration) + if frame.cooldown then + P.Repoint(frame.cooldown) + if frame.cooldown.spark then + P.Resize(frame.cooldown.spark) + end + end + if frame._countdownCooldown then + P.Repoint(frame._countdownCooldown) + end end function I.CreateAura_BorderIcon(name, parent, borderSize) @@ -526,23 +940,11 @@ function I.CreateAura_BorderIcon(name, parent, borderSize) border:SetAllPoints(frame) border:Hide() - local cooldown = CreateFrame("Cooldown", name.."Cooldown", frame) - frame.cooldown = cooldown - cooldown:SetAllPoints(frame) - cooldown:SetSwipeTexture(Cell.vars.whiteTexture) - cooldown:SetSwipeColor(1, 1, 1) - cooldown:SetHideCountdownNumbers(true) - -- disable omnicc - cooldown.noCooldownCount = true - -- prevent some addons from adding cooldown text - cooldown._SetCooldown = cooldown.SetCooldown - cooldown.SetCooldown = nil - local iconFrame = CreateFrame("Frame", name.."IconFrame", frame) frame.iconFrame = iconFrame P.Point(iconFrame, "TOPLEFT", frame, "TOPLEFT", borderSize, -borderSize) P.Point(iconFrame, "BOTTOMRIGHT", frame, "BOTTOMRIGHT", -borderSize, borderSize) - iconFrame:SetFrameLevel(cooldown:GetFrameLevel()+1) + iconFrame:SetFrameLevel(frame:GetFrameLevel() + 1) local icon = iconFrame:CreateTexture(name.."Icon", "ARTWORK") frame.icon = icon @@ -568,9 +970,16 @@ function I.CreateAura_BorderIcon(name, parent, borderSize) frame.SetFont = Shared_SetFont frame.SetBorder = BorderIcon_SetBorder frame.SetCooldown = BorderIcon_SetCooldown + frame.SetCooldownFromAura = BorderIcon_SetCooldownFromAura frame.ShowDuration = BorderIcon_ShowDuration + frame.SetCooldownStyle = BorderIcon_SetCooldownStyle + frame.ShowAnimation = function() end + frame.ShowStack = BorderIcon_ShowStack + frame.SetupGlow = function() end frame.UpdatePixelPerfect = BorderIcon_UpdatePixelPerfect + BorderIcon_SetCooldownStyle(frame, CELL_COOLDOWN_STYLE) + return frame end @@ -644,6 +1053,12 @@ local function BarIcon_ShowAnimation(frame, show) end end +local function BarIcon_SetCooldownStyle(frame, style) + Shared_SetCooldownStyle(frame, style) + ReCalcTexCoord(frame, frame:GetSize()) + frame:UpdatePixelPerfect() +end + local function BarIcon_UpdatePixelPerfect(frame) P.Resize(frame) P.Repoint(frame) @@ -688,9 +1103,11 @@ function I.CreateAura_BarIcon(name, parent) frame.SetFont = Shared_SetFont frame.SetCooldown = BarIcon_SetCooldown + frame.SetCooldownFromAura = BarIcon_SetCooldownFromAura frame.ShowDuration = Shared_ShowDuration frame.ShowStack = Shared_ShowStack frame.ShowAnimation = BarIcon_ShowAnimation + frame.SetCooldownStyle = BarIcon_SetCooldownStyle frame.SetupGlow = Shared_SetupGlow frame.UpdatePixelPerfect = BarIcon_UpdatePixelPerfect @@ -902,6 +1319,14 @@ local function Icons_ShowAnimation(icons, show) end end +local function Icons_SetCooldownStyle(icons, style) + for i = 1, icons.maxNum do + if icons[i].SetCooldownStyle then + icons[i]:SetCooldownStyle(style) + end + end +end + local function Icons_UpdatePixelPerfect(icons) P.Repoint(icons) P.Resize(icons) @@ -932,6 +1357,7 @@ function I.CreateAura_Icons(name, parent, num) icons.ShowDuration = Icons_ShowDuration icons.ShowStack = Icons_ShowStack icons.ShowAnimation = Icons_ShowAnimation + icons.SetCooldownStyle = Icons_SetCooldownStyle icons.SetupGlow = I.Glow_SetupForChildren icons.UpdatePixelPerfect = Icons_UpdatePixelPerfect @@ -1011,7 +1437,7 @@ local function Text_OnUpdateColor(frame) end local function Text_OnUpdateDuration(frame, elapsed) - frame._remain = frame._duration - (GetTime() - frame._start) + frame._remain = F.GetRemain(frame._start, frame._duration) if frame._remain < 0 then frame._remain = 0 end frame._elapsed = frame._elapsed + elapsed @@ -1042,7 +1468,7 @@ local function Text_OnUpdate(frame, elapsed) if frame._elapsed >= 0.1 then frame._elapsed = 0 - frame._remain = frame._duration - (GetTime() - frame._start) + frame._remain = F.GetRemain(frame._start, frame._duration) -- update color Text_OnUpdateColor(frame) end @@ -1050,12 +1476,18 @@ end local circled = {"①","②","③","④","⑤","⑥","⑦","⑧","⑨","⑩","⑪","⑫","⑬","⑭","⑮","⑯","⑰","⑱","⑲","⑳","㉑","㉒","㉓","㉔","㉕","㉖","㉗","㉘","㉙","㉚","㉛","㉜","㉝","㉞","㉟","㊱","㊲","㊳","㊴","㊵","㊶","㊷","㊸","㊹","㊺","㊻","㊼","㊽","㊾","㊿"} local function Text_SetCooldown(frame, start, duration, debuffType, texture, count) + local isSecretCount = not F.IsValueNonSecret(count) + if duration == 0 then -- always show stack - count = _SanitizeCount(count) - count = count == 0 and 1 or count - count = frame.circledStackNums and circled[count] or count - frame.text:SetText(count) + if isSecretCount then + frame.text:SetText(count) + else + count = count or 0 + count = count == 0 and 1 or count + count = frame.circledStackNums and circled[count] or count + frame.text:SetText(count) + end frame.text:SetTextColor(frame.colors[1][1], frame.colors[1][2], frame.colors[1][3], frame.colors[1][4]) frame:SetScript("OnUpdate", nil) frame._count = nil @@ -1068,27 +1500,35 @@ local function Text_SetCooldown(frame, start, duration, debuffType, texture, cou frame._duration = duration if frame.durationTbl[1] then - count = _SanitizeCount(count) - if frame.showStack and count ~= 0 then - if frame.circledStackNums then - frame._count = circled[count].." " + if isSecretCount then + frame._count = "" + else + count = count or 0 + if frame.showStack and count ~= 0 then + if frame.circledStackNums then + frame._count = circled[count].." " + else + frame._count = count.." " + end else - frame._count = count.." " + frame._count = "" end - else - frame._count = "" end frame._elapsed = 0.1 -- update immediately frame:SetScript("OnUpdate", Text_OnUpdateDuration) else -- always show stack - count = _SanitizeCount(count) - count = count == 0 and 1 or count - if frame.circledStackNums then - frame.text:SetText(circled[count]) - else + if isSecretCount then frame.text:SetText(count) + else + count = count or 0 + count = count == 0 and 1 or count + if frame.circledStackNums then + frame.text:SetText(circled[count]) + else + frame.text:SetText(count) + end end frame._elapsed = 0.1 -- update immediately @@ -1145,7 +1585,7 @@ local function Rect_OnUpdateColor(frame) end local function Rect_OnUpdate(frame, elapsed) - frame._remain = frame._duration - (GetTime() - frame._start) + frame._remain = F.GetRemain(frame._start, frame._duration) if frame._remain < 0 then frame._remain = 0 end frame._elapsed = frame._elapsed + elapsed @@ -1242,6 +1682,7 @@ function I.CreateAura_Rect(name, parent) frame.SetColors = Rect_SetColors frame.ShowStack = Shared_ShowStack frame.ShowDuration = Shared_ShowDuration + frame.ShowAnimation = Shared_ShowAnimation frame.SetupGlow = Shared_SetupGlow frame.UpdatePixelPerfect = Rect_UpdatePixelPerfect @@ -1257,7 +1698,7 @@ local function Bar_SetFont(bar, font1, font2) end local function Bar_OnUpdate(bar, elapsed) - bar._remain = bar._duration - (GetTime() - bar._start) + bar._remain = F.GetRemain(bar._start, bar._duration) if bar._remain < 0 then bar._remain = 0 end bar:SetValue(bar._remain) @@ -1265,7 +1706,23 @@ local function Bar_OnUpdate(bar, elapsed) if bar._elapsed >= 0.1 then bar._elapsed = 0 -- update color - if bar.colors[3][1] and bar._remain <= bar.colors[3][2] then + local dc = Cell.vars.iconDurationColors + if dc then + if bar._remain < dc[3][4] then + if bar.state ~= 3 then + bar.state = 3 + bar:SetStatusBarColor(dc[3][1], dc[3][2], dc[3][3]) + end + elseif bar._remain < (dc[2][4] * bar._duration) then + if bar.state ~= 2 then + bar.state = 2 + bar:SetStatusBarColor(dc[2][1], dc[2][2], dc[2][3]) + end + elseif bar.state ~= 1 then + bar.state = 1 + bar:SetStatusBarColor(dc[1][1], dc[1][2], dc[1][3]) + end + elseif bar.colors[3][1] and bar._remain <= bar.colors[3][2] then if bar.state ~= 3 then bar.state = 3 bar:SetStatusBarColor(bar.colors[3][3][1], bar.colors[3][3][2], bar.colors[3][3][3], bar.colors[3][3][4]) @@ -1345,10 +1802,20 @@ end local function Bar_SetMaxValue(bar, maxValue) if maxValue[1]then - bar.maxValue = maxValue[2] + if bar.SetMinMaxValues then + local minValue, _ = bar:GetMinMaxValues() + bar:SetMinMaxValues(minValue, maxValue[2]) + else + bar.maxValue = maxValue[2] + end bar.allowSmaller = maxValue[3] else - bar.maxValue = nil + if bar.SetMinMaxValues then + local minValue, _ = bar:GetMinMaxValues() + bar:SetMinMaxValues(minValue, 0) + else + bar.maxValue = nil + end bar.allowSmaller = nil end end @@ -1375,6 +1842,15 @@ function I.CreateAura_Bar(name, parent) bar.SetMaxValue = Bar_SetMaxValue bar.SetupGlow = Shared_SetupGlow bar.SetColors = Bar_SetColors + bar._SetOrientation = bar.SetOrientation + function bar:SetOrientation(orientation) + if orientation == "vertical" or orientation == "top-to-bottom" or orientation == "bottom-to-top" then + self:_SetOrientation("VERTICAL") + else + self:_SetOrientation("HORIZONTAL") + end + self:SetReverseFill(orientation == "right-to-left" or orientation == "bottom-to-top") + end return bar end @@ -1383,7 +1859,7 @@ end -- CreateAura_Bars ------------------------------------------------- local function Bars_OnUpdate(bar, elapsed) - bar._remain = bar._duration - (GetTime() - bar._start) + bar._remain = F.GetRemain(bar._start, bar._duration) if bar._remain < 0 then bar._remain = 0 end bar:SetValue(bar._remain) @@ -1498,7 +1974,7 @@ local function Color_OnUpdate(color, elapsed) if color._elapsed >= 0.1 then color._elapsed = 0 - color._remain = color._duration - (GetTime() - color._start) + color._remain = F.GetRemain(color._start, color._duration) -- update color if color._remain <= color.colors[6][1] then if color.state ~= 3 then @@ -1640,7 +2116,7 @@ local function Texture_OnUpdate(texture, elapsed) if texture._elapsed >= 0.1 then texture._elapsed = 0 - texture._remain = texture._duration - (GetTime() - texture._start) + texture._remain = F.GetRemain(texture._start, texture._duration) if texture._remain < 0 then texture._remain = 0 end texture.tex:SetAlpha(texture._remain / texture._duration * 0.9 + 0.1) end @@ -1702,7 +2178,7 @@ local function Glow_OnUpdate(glow, elapsed) if glow._elapsed >= 0.1 then glow._elapsed = 0 - glow._remain = glow._duration - (GetTime() - glow._start) + glow._remain = F.GetRemain(glow._start, glow._duration) if glow._remain < 0 then glow._remain = 0 end glow:SetAlpha(glow._remain / glow._duration * 0.9 + 0.1) end @@ -1754,7 +2230,7 @@ end -- CreateAura_QuickAssistBars ------------------------------------------------- local function QuickAssistBars_OnUpdate(bar, elapsed) - bar._remain = bar._duration - (GetTime() - bar._start) + bar._remain = F.GetRemain(bar._start, bar._duration) if bar._remain < 0 then bar._remain = 0 end bar:SetValue(bar._remain) end @@ -1881,7 +2357,7 @@ end -- CreateAura_Overlay ------------------------------------------------- local function Overlay_OnUpdate(overlay, elapsed) - overlay._remain = overlay._duration - (GetTime() - overlay._start) + overlay._remain = F.GetRemain(overlay._start, overlay._duration) if overlay._remain < 0 then overlay._remain = 0 end overlay:_SetValue(overlay._remain) @@ -1972,7 +2448,7 @@ end -- CreateAura_Block ------------------------------------------------- local function Block_OnUpdate_Duration(frame, elapsed) - frame._remain = frame._duration - (GetTime() - frame._start) + frame._remain = F.GetRemain(frame._start, frame._duration) if frame._remain < 0 then frame._remain = 0 end frame._elapsed = frame._elapsed + elapsed @@ -2035,7 +2511,11 @@ local function Block_SetCooldown_Duration(frame, start, duration, debuffType, te frame._threshold = nil else -- frame.cooldown:SetSwipeColor(r, g, b) - frame.cooldown:ShowCooldown(start, duration) + if frame.showAnimation == false then + frame.cooldown:Hide() + else + frame.cooldown:ShowCooldown(start, duration) + end if not frame.showDuration then frame._threshold = -1 @@ -2066,7 +2546,7 @@ local function Block_SetCooldown_Duration(frame, start, duration, debuffType, te end local function Block_OnUpdate_Stack(frame, elapsed) - frame._remain = frame._duration - (GetTime() - frame._start) + frame._remain = F.GetRemain(frame._start, frame._duration) if frame._remain < 0 then frame._remain = 0 end if frame._remain > frame._threshold then @@ -2101,7 +2581,11 @@ local function Block_SetCooldown_Stack(frame, start, duration, debuffType, textu frame._threshold = nil else -- frame.cooldown:SetSwipeColor(r, g, b) - frame.cooldown:ShowCooldown(start, duration) + if frame.showAnimation == false then + frame.cooldown:Hide() + else + frame.cooldown:ShowCooldown(start, duration) + end if not frame.showDuration then frame._threshold = -1 @@ -2162,6 +2646,11 @@ local function Block_UpdatePixelPerfect(frame) end end +local function Block_SetCooldownStyle(frame, style) + Shared_SetCooldownStyle(frame, style, true) + frame:UpdatePixelPerfect() +end + function I.CreateAura_Block(name, parent) local frame = CreateFrame("Frame", name, parent, "BackdropTemplate") frame:Hide() @@ -2178,7 +2667,9 @@ function I.CreateAura_Block(name, parent) frame.SetColors = Block_SetColors frame.ShowStack = Shared_ShowStack frame.ShowDuration = Shared_ShowDuration + frame.ShowAnimation = Shared_ShowAnimation frame.SetCooldown = Block_SetCooldown_Duration + frame.SetCooldownStyle = Block_SetCooldownStyle frame.SetupGlow = Shared_SetupGlow frame.UpdatePixelPerfect = Block_UpdatePixelPerfect @@ -2202,7 +2693,7 @@ end -- CreateAura_Blocks ------------------------------------------------- local function Blocks_OnUpdate(frame, elapsed) - frame._remain = frame._duration - (GetTime() - frame._start) + frame._remain = F.GetRemain(frame._start, frame._duration) if frame._remain < 0 then frame._remain = 0 end if frame._remain > frame._threshold then @@ -2236,7 +2727,11 @@ local function Blocks_SetCooldown(frame, start, duration, debuffType, texture, c frame._remain = nil frame._threshold = nil else - frame.cooldown:ShowCooldown(start, duration) + if frame.showAnimation == false then + frame.cooldown:Hide() + else + frame.cooldown:ShowCooldown(start, duration) + end if not frame.showDuration then frame._threshold = -1 @@ -2266,6 +2761,14 @@ local function Blocks_SetCooldown(frame, start, duration, debuffType, texture, c end end +local function Blocks_SetCooldownStyle(blocks, style) + for i = 1, blocks.maxNum do + if blocks[i].SetCooldownStyle then + blocks[i]:SetCooldownStyle(style) + end + end +end + function I.CreateAura_Blocks(name, parent, num) local blocks = CreateFrame("Frame", name, parent) blocks:Hide() @@ -2285,6 +2788,8 @@ function I.CreateAura_Blocks(name, parent, num) blocks.SetNumPerLine = Icons_SetNumPerLine blocks.ShowDuration = Icons_ShowDuration blocks.ShowStack = Icons_ShowStack + blocks.ShowAnimation = Icons_ShowAnimation + blocks.SetCooldownStyle = Blocks_SetCooldownStyle blocks.SetupGlow = I.Glow_SetupForChildren blocks.UpdatePixelPerfect = Icons_UpdatePixelPerfect @@ -2307,7 +2812,7 @@ local function Border_OnUpdate(border, elapsed) if border._elapsed >= 0.1 then border._elapsed = 0 - border._remain = border._duration - (GetTime() - border._start) + border._remain = F.GetRemain(border._start, border._duration) if border._remain < 0 then border._remain = 0 end border:SetAlpha(border._remain / border._duration * 0.9 + 0.1) end @@ -2383,4 +2888,4 @@ function I.CreateAura_Border(name, parent) border.UpdatePixelPerfect = Border_UpdatePixelPerfect return border -end \ No newline at end of file +end diff --git a/Indicators/Built-in.lua b/Indicators/Built-in.lua index 35f36133..43c4a6bf 100644 --- a/Indicators/Built-in.lua +++ b/Indicators/Built-in.lua @@ -9,8 +9,11 @@ local A = Cell.animations ---@type PixelPerfectFuncs local P = Cell.pixelPerfectFuncs -local LCG = LibStub("LibCustomGlow-1.0") -local LibTranslit = LibStub("LibTranslit-1.0") +local LCG = LibStub and LibStub("LibCustomGlow-1.0", true) +local LibTranslit = LibStub and LibStub("LibTranslit-1.0", true) +local AbbreviateNumbers = AbbreviateNumbers +local UnitHealthPercent = UnitHealthPercent +local CurveConstants = CurveConstants local function noop() end @@ -58,6 +61,40 @@ end function I.Cooldowns_UpdateSize_WithSpacing(self, iconsShown) if not (self.width and self.height and self.orientation) then return end -- not init + if self.numPerLine then + local count = iconsShown + if not count then + count = 0 + for i = 1, #self do + if self[i]:IsShown() then + count = i + end + end + end + + if iconsShown then + for i = iconsShown + 1, #self do + self[i]:Hide() + end + end + + if count == 0 then + self:_SetSize(0.001, 0.001) + return + end + + local perLine = math.min(self.numPerLine, count) + local lines = ceil(count / perLine) + local spacingX, spacingY = self.spacingX or 1, self.spacingY or 1 + + if self.orientation == "horizontal" then + P.SetGridSize(self, self.width, self.height, spacingX, spacingY, perLine, lines) + else + P.SetGridSize(self, self.width, self.height, spacingX, spacingY, lines, perLine) + end + return + end + if iconsShown then -- call from I.UnitButton_UpdateBuffs or preview for i = iconsShown + 1, #self do self[i]:Hide() @@ -106,6 +143,20 @@ function I.Cooldowns_ShowAnimation(self, show) end end +function I.Cooldowns_ShowStack(self, show) + for i = 1, #self do + self[i]:ShowStack(show) + end +end + +function I.Cooldowns_SetCooldownStyle(self, style) + for i = 1, #self do + if self[i].SetCooldownStyle then + self[i]:SetCooldownStyle(style) + end + end +end + function I.Cooldowns_UpdatePixelPerfect(self) P.Repoint(self) for i = 1, #self do @@ -156,6 +207,7 @@ end function I.Cooldowns_SetOrientation_WithSpacing(self, orientation) local point1, point2, x, y + local newLinePoint2, newLineX, newLineY if orientation == "left-to-right" then point1 = "TOPLEFT" @@ -163,36 +215,96 @@ function I.Cooldowns_SetOrientation_WithSpacing(self, orientation) self.orientation = "horizontal" x = 1 y = 0 + newLinePoint2 = "BOTTOMLEFT" + newLineX = 0 + newLineY = -(self.spacingY or 1) elseif orientation == "right-to-left" then point1 = "TOPRIGHT" point2 = "TOPLEFT" self.orientation = "horizontal" x = -1 y = 0 + newLinePoint2 = "BOTTOMRIGHT" + newLineX = 0 + newLineY = -(self.spacingY or 1) elseif orientation == "top-to-bottom" then point1 = "TOPLEFT" point2 = "BOTTOMLEFT" self.orientation = "vertical" x = 0 y = -1 + newLinePoint2 = "TOPRIGHT" + newLineX = self.spacingX or 1 + newLineY = 0 elseif orientation == "bottom-to-top" then point1 = "BOTTOMLEFT" point2 = "TOPLEFT" self.orientation = "vertical" x = 0 y = 1 + newLinePoint2 = "BOTTOMRIGHT" + newLineX = self.spacingX or 1 + newLineY = 0 + elseif orientation == "krysio-style" then + point1 = "TOPLEFT" + point2 = "TOPRIGHT" + self.orientation = "horizontal" + x = 1 + y = 0 + newLinePoint2 = "BOTTOMLEFT" + newLineX = 0 + newLineY = -(self.spacingY or 1) end - for i = 1, #self do - P.ClearPoints(self[i]) - if i == 1 then - P.Point(self[i], point1) - else - P.Point(self[i], point1, self[i-1], point2, x, y) + self.growth = orientation + x = x * (self.spacingX or 1) + y = y * (self.spacingY or 1) + + if self.growth == "krysio-style" then + -- Krysio Style: first aura centered, second to the left, third+ to the right + for i = 1, #self do + P.ClearPoints(self[i]) + if i == 1 then + P.Point(self[i], "CENTER") + elseif i == 2 then + P.Point(self[i], "TOPRIGHT", self[1], "TOPLEFT", -(self.spacingX or 1), 0) + elseif i == 3 then + -- Third aura anchors to center (self[1]), not to self[2] which is on the left + P.Point(self[i], "TOPLEFT", self[1], "TOPRIGHT", self.spacingX or 1, 0) + else + P.Point(self[i], "TOPLEFT", self[i-1], "TOPRIGHT", self.spacingX or 1, 0) + end + end + else + for i = 1, #self do + P.ClearPoints(self[i]) + if i == 1 then + P.Point(self[i], point1) + elseif self.numPerLine and i % self.numPerLine == 1 then + P.Point(self[i], point1, self[i-self.numPerLine], newLinePoint2, newLineX, newLineY) + else + P.Point(self[i], point1, self[i-1], point2, x, y) + end end end self:UpdateSize() + + if self.UpdatePrivateAuraAnchor then + self:UpdatePrivateAuraAnchor(self.unit) + end +end + +function I.Cooldowns_SetNumPerLine_WithSpacing(self, numPerLine) + self.numPerLine = math.min(tonumber(numPerLine) or #self, #self) + self.numPerLine = math.max(self.numPerLine, 1) + self:SetOrientation(self.growth or "left-to-right") +end + +function I.Cooldowns_SetSpacing_WithSpacing(self, spacing) + self.spacingX = spacing and spacing[1] or 1 + self.spacingY = spacing and spacing[2] or 1 + self:SetOrientation(self.growth or "left-to-right") end ------------------------------------------------- @@ -211,16 +323,47 @@ function I.CreateDefensiveCooldowns(parent) defensiveCooldowns.SetOrientation = I.Cooldowns_SetOrientation defensiveCooldowns.ShowDuration = I.Cooldowns_ShowDuration defensiveCooldowns.ShowAnimation = I.Cooldowns_ShowAnimation + defensiveCooldowns.SetCooldownStyle = I.Cooldowns_SetCooldownStyle defensiveCooldowns.SetupGlow = I.Glow_SetupForChildren defensiveCooldowns.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect for i = 1, 5 do local name = parent:GetName().."DefensiveCooldown"..i - local frame = I.CreateAura_BarIcon(name, defensiveCooldowns) + local frame = Cell.isMidnight + and I.CreateAura_BorderIcon(name, defensiveCooldowns, 1.5) + or I.CreateAura_BarIcon(name, defensiveCooldowns) tinsert(defensiveCooldowns, frame) end end +------------------------------------------------- +-- CreateOffensiveCooldowns +------------------------------------------------- +function I.CreateOffensiveCooldowns(parent) + local offensiveCooldowns = CreateFrame("Frame", parent:GetName().."OffensiveCooldownParent", parent.widgets.indicatorFrame) + parent.indicators.offensiveCooldowns = offensiveCooldowns + offensiveCooldowns:Hide() + + offensiveCooldowns._SetSize = offensiveCooldowns.SetSize + offensiveCooldowns.SetSize = I.Cooldowns_SetSize + offensiveCooldowns.UpdateSize = I.Cooldowns_UpdateSize + offensiveCooldowns.SetFont = I.Cooldowns_SetFont + offensiveCooldowns.SetOrientation = I.Cooldowns_SetOrientation + offensiveCooldowns.ShowDuration = I.Cooldowns_ShowDuration + offensiveCooldowns.ShowAnimation = I.Cooldowns_ShowAnimation + offensiveCooldowns.SetCooldownStyle = I.Cooldowns_SetCooldownStyle + offensiveCooldowns.SetupGlow = I.Glow_SetupForChildren + offensiveCooldowns.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect + + for i = 1, 5 do + local name = parent:GetName().."OffensiveCooldown"..i + local frame = Cell.isMidnight + and I.CreateAura_BorderIcon(name, offensiveCooldowns, 1.5) + or I.CreateAura_BarIcon(name, offensiveCooldowns) + tinsert(offensiveCooldowns, frame) + end +end + ------------------------------------------------- -- CreateExternalCooldowns ------------------------------------------------- @@ -236,12 +379,15 @@ function I.CreateExternalCooldowns(parent) externalCooldowns.SetOrientation = I.Cooldowns_SetOrientation externalCooldowns.ShowDuration = I.Cooldowns_ShowDuration externalCooldowns.ShowAnimation = I.Cooldowns_ShowAnimation + externalCooldowns.SetCooldownStyle = I.Cooldowns_SetCooldownStyle externalCooldowns.SetupGlow = I.Glow_SetupForChildren externalCooldowns.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect for i = 1, 5 do local name = parent:GetName().."ExternalCooldown"..i - local frame = I.CreateAura_BarIcon(name, externalCooldowns) + local frame = Cell.isMidnight + and I.CreateAura_BorderIcon(name, externalCooldowns, 1.5) + or I.CreateAura_BarIcon(name, externalCooldowns) tinsert(externalCooldowns, frame) end end @@ -261,12 +407,15 @@ function I.CreateAllCooldowns(parent) allCooldowns.SetOrientation = I.Cooldowns_SetOrientation allCooldowns.ShowDuration = I.Cooldowns_ShowDuration allCooldowns.ShowAnimation = I.Cooldowns_ShowAnimation + allCooldowns.SetCooldownStyle = I.Cooldowns_SetCooldownStyle allCooldowns.SetupGlow = I.Glow_SetupForChildren allCooldowns.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect for i = 1, 5 do - local name = parent:GetName().."ExternalCooldown"..i - local frame = I.CreateAura_BarIcon(name, allCooldowns) + local name = parent:GetName().."AllCooldown"..i + local frame = Cell.isMidnight + and I.CreateAura_BorderIcon(name, allCooldowns, 1.5) + or I.CreateAura_BarIcon(name, allCooldowns) tinsert(allCooldowns, frame) end end @@ -460,20 +609,49 @@ local function Debuffs_EnableBlacklistShortcut(debuffs, enabled) for i = 1, 10 do if enabled then + debuffs[i]:EnableMouse(true) + if debuffs[i].SetMouseClickEnabled then + debuffs[i]:SetMouseClickEnabled(true) + end + if not debuffs.showTooltip and debuffs[i].SetMouseMotionEnabled then + debuffs[i]:SetMouseMotionEnabled(false) + end debuffs[i]:SetScript("OnMouseUp", function(self, button, isInside) - if button == "RightButton" and isInside and IsLeftAltKeyDown() and IsLeftControlKeyDown() - and self.spellId and not F.TContains(CellDB["debuffBlacklist"], self.spellId) then - -- print msg - local name, icon = F.GetSpellInfo(self.spellId) - if name and icon then - F.Print(L["Added |T%d:0|t|cFFFF3030%s(%d)|r into debuff blacklist."]:format(icon, name, self.spellId)) + if button ~= "RightButton" or not isInside or not IsLeftAltKeyDown() or not IsLeftControlKeyDown() then + return + end + + if not (self.spellId and F.IsValueNonSecret(self.spellId)) then + F.Print("Cannot blacklist this debuff: Midnight is hiding its spell ID.") + return + end + + if Cell.isRetail then + -- New system: auraBlacklist.debuffs + local sid = self.spellId + if not F.GetAuraBlacklistEntry or not F.GetAuraBlacklistEntry(sid, "HARMFUL") then + local name, icon = F.GetSpellInfo(sid) + if name and icon then + F.Print(L["Added |T%d:0|t|cFFFF3030%s(%d)|r into debuff blacklist."]:format(icon, name, sid)) + end + if F.ToggleAuraBlacklist then + F.ToggleAuraBlacklist(sid, "HARMFUL", true, true) + end + Cell.Fire("UpdateIndicators", Cell.vars.currentLayout, "", "bigDebuffs") + F.ReloadIndicatorOptions(Cell.defaults.indicatorIndices.debuffs) + end + else + -- Legacy system: debuffBlacklist (non-Retail) + if not F.TContains(CellDB["debuffBlacklist"], self.spellId) then + local name, icon = F.GetSpellInfo(self.spellId) + if name and icon then + F.Print(L["Added |T%d:0|t|cFFFF3030%s(%d)|r into debuff blacklist."]:format(icon, name, self.spellId)) + end + tinsert(CellDB["debuffBlacklist"], self.spellId) + Cell.vars.debuffBlacklist = F.ConvertTable(CellDB["debuffBlacklist"]) + Cell.Fire("UpdateIndicators", Cell.vars.currentLayout, "", "debuffBlacklist") + F.ReloadIndicatorOptions(Cell.defaults.indicatorIndices.debuffs) end - -- update db - tinsert(CellDB["debuffBlacklist"], self.spellId) - Cell.vars.debuffBlacklist = F.ConvertTable(CellDB["debuffBlacklist"]) - Cell.Fire("UpdateIndicators", Cell.vars.currentLayout, "", "debuffBlacklist") - -- refresh - F.ReloadIndicatorOptions(Cell.defaults.indicatorIndices.debuffs) end end) else @@ -506,6 +684,8 @@ function I.CreateDebuffs(parent) debuffs.ShowDuration = I.Cooldowns_ShowDuration debuffs.ShowAnimation = I.Cooldowns_ShowAnimation + debuffs.ShowStack = I.Cooldowns_ShowStack + debuffs.SetCooldownStyle = I.Cooldowns_SetCooldownStyle debuffs.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect debuffs.ShowTooltip = Debuffs_ShowTooltip @@ -513,7 +693,12 @@ function I.CreateDebuffs(parent) for i = 1, 10 do local name = parent:GetName().."Debuff"..i - local frame = I.CreateAura_BarIcon(name, debuffs) + local frame + if Cell.isMidnight then + frame = I.CreateAura_BorderIcon(name, debuffs, 1.5) + else + frame = I.CreateAura_BarIcon(name, debuffs) + end tinsert(debuffs, frame) frame._SetCooldown = frame.SetCooldown @@ -593,15 +778,18 @@ local function Dispels_SetDispels(self, dispelTypes) if not found and self.highlightType ~= "none" and dispelType and showHighlight then found = true local r, g, b = I.GetDebuffTypeColor(dispelType) - if self.highlightType == "entire" then - self.highlight:SetTexture(Cell.vars.whiteTexture) - self.highlight:SetVertexColor(r, g, b, 0.5) - elseif self.highlightType == "current" or self.highlightType == "current+" then - self.highlight:SetTexture(Cell.vars.texture) + if self.highlightType == "edge-top" or self.highlightType == "edge-bottom" or self.highlightType == "gradient-sharp" then + if self.highlightType == "edge-bottom" or self.highlightType == "gradient-sharp" then + self.highlight:SetTexture("Interface\\AddOns\\Cell\\Media\\Edge-Fade-Bottom") + else + self.highlight:SetTexture("Interface\\AddOns\\Cell\\Media\\Edge-Fade-Top") + end + self.highlight:SetTexCoord(0, 1, 0, 1) self.highlight:SetVertexColor(r, g, b, 1) - elseif self.highlightType == "gradient" or self.highlightType == "gradient-half" then + else -- entire (full health bar) self.highlight:SetTexture(Cell.vars.whiteTexture) - self.highlight:SetGradient("VERTICAL", CreateColor(r, g, b, 1), CreateColor(r, g, b, 0)) + self.highlight:SetTexCoord(0, 1, 0, 1) + self.highlight:SetVertexColor(r, g, b, 0.5) end self.highlight:Show() end @@ -613,10 +801,10 @@ local function Dispels_SetDispels(self, dispelTypes) end end - self:UpdateSize(i) + self:UpdateSize(self.showIcons and i or 0) -- hide unused - for j = i+1, 5 do + for j = (self.showIcons and i or 0)+1, 5 do self[j]:Hide() end end @@ -683,43 +871,25 @@ local function Dispels_SetOrientation(self, orientation) end local function Dispels_UpdateHighlight(self, highlightType) + if highlightType == "gradient-sharp" then + highlightType = "edge-bottom" + elseif highlightType ~= "edge-top" and highlightType ~= "edge-bottom" then + highlightType = "entire" + end self.highlightType = highlightType self.highlight:SetBlendMode("BLEND") - - if highlightType == "none" then - self.highlight:Hide() - elseif highlightType == "gradient" then - -- self.highlight:SetParent(self.parent.widgets.indicatorFrame) - self.highlight:ClearAllPoints() - self.highlight:SetAllPoints(self.parent.widgets.healthBar) - self.highlight:SetTexture(Cell.vars.whiteTexture) - self.highlight:SetDrawLayer("ARTWORK", 0) - elseif highlightType == "gradient-half" then - -- self.highlight:SetParent(self.parent.widgets.indicatorFrame) - self.highlight:ClearAllPoints() - self.highlight:SetPoint("BOTTOMLEFT", self.parent.widgets.healthBar) - self.highlight:SetPoint("TOPRIGHT", self.parent.widgets.healthBar, "RIGHT") - self.highlight:SetTexture(Cell.vars.whiteTexture) - self.highlight:SetDrawLayer("ARTWORK", 0) - elseif highlightType == "entire" then - -- self.highlight:SetParent(self.parent.widgets.indicatorFrame) - self.highlight:ClearAllPoints() - self.highlight:SetAllPoints(self.parent.widgets.healthBar) + self.highlight:ClearAllPoints() + self.highlight:SetAllPoints(self.parent.widgets.healthBar) + self.highlight:SetDrawLayer("ARTWORK", 0) + if highlightType == "edge-top" then + self.highlight:SetTexture("Interface\\AddOns\\Cell\\Media\\Edge-Fade-Top") + self.highlight:SetTexCoord(0, 1, 0, 1) + elseif highlightType == "edge-bottom" then + self.highlight:SetTexture("Interface\\AddOns\\Cell\\Media\\Edge-Fade-Bottom") + self.highlight:SetTexCoord(0, 1, 0, 1) + else self.highlight:SetTexture(Cell.vars.whiteTexture) - self.highlight:SetDrawLayer("ARTWORK", 0) - elseif highlightType == "current" then - -- self.highlight:SetParent(self.parent.widgets.healthBar) - self.highlight:ClearAllPoints() - self.highlight:SetAllPoints(self.parent.widgets.healthBar:GetStatusBarTexture()) - self.highlight:SetTexture(Cell.vars.texture) - self.highlight:SetDrawLayer("ARTWORK", -7) - elseif highlightType == "current+" then - -- self.highlight:SetParent(self.parent.widgets.healthBar) - self.highlight:ClearAllPoints() - self.highlight:SetAllPoints(self.parent.widgets.healthBar:GetStatusBarTexture()) - self.highlight:SetTexture(Cell.vars.texture) - self.highlight:SetDrawLayer("ARTWORK", -7) - self.highlight:SetBlendMode("ADD") + self.highlight:SetTexCoord(0, 1, 0, 1) end end @@ -767,7 +937,11 @@ local function UpdateDebuffsForCurrentZone(instanceName) if iName == "" then return end if iName == instanceName or instanceName == nil then - currentAreaDebuffs = F.GetDebuffList(iName) + if F.GetDebuffList then + currentAreaDebuffs = F.GetDebuffList(iName) or {} + else + currentAreaDebuffs = {} + end F.Debug("|cffff77AARaidDebuffsChanged:|r", iName) end end @@ -777,9 +951,7 @@ eventFrame:SetScript("OnEvent", function() end) local function CheckCondition(operator, checkedValue, currentValue) - -- Midnight 12.0.0+: applications (count) may be secret even when spellId is not; - -- comparisons on secret values throw errors - if issecretvalue and (issecretvalue(currentValue) or issecretvalue(checkedValue)) then return end + if not F.IsValueNonSecret(currentValue) or not F.IsValueNonSecret(checkedValue) then return end if operator == "=" then if currentValue == checkedValue then return true end elseif operator == ">" then @@ -796,8 +968,7 @@ local function CheckCondition(operator, checkedValue, currentValue) end function I.GetDebuffOrder(spellName, spellId, count) - -- Midnight 12.0.0+: spellId/spellName may be secret; cannot use as table key - if issecretvalue and (issecretvalue(spellId) or issecretvalue(spellName)) then return end + if not F.IsValueNonSecret(spellId) or not F.IsValueNonSecret(spellName) then return end local t = currentAreaDebuffs[spellId] or currentAreaDebuffs[spellName] if not t then return end @@ -813,30 +984,15 @@ function I.GetDebuffOrder(spellName, spellId, count) end function I.GetDebuffGlow(spellName, spellId, count) - -- Midnight 12.0.0+: spellId/spellName may be secret; cannot use as table key - if issecretvalue and (issecretvalue(spellId) or issecretvalue(spellName)) then return end - local t = currentAreaDebuffs[spellId] or currentAreaDebuffs[spellName] - if not t then return end - - local showGlow - if t["glowCondition"] then - if t["glowCondition"][1] == "Stack" then - showGlow = CheckCondition(t["glowCondition"][2], t["glowCondition"][3], count) - end - else - showGlow = true - end + return "None", nil +end - if showGlow then - return t["glowType"], t["glowOptions"] - else - return "None", nil - end +function I.GetCurrentAreaDebuffs() + return currentAreaDebuffs end function I.IsDebuffUseElapsedTime(spellName, spellId) - -- Midnight 12.0.0+: spellId/spellName may be secret; cannot use as table key - if issecretvalue and (issecretvalue(spellId) or issecretvalue(spellName)) then return end + if not F.IsValueNonSecret(spellId) or not F.IsValueNonSecret(spellName) then return end local t = currentAreaDebuffs[spellId] or currentAreaDebuffs[spellName] if not t then return end @@ -844,59 +1000,28 @@ function I.IsDebuffUseElapsedTime(spellName, spellId) end local function RaidDebuffs_ShowGlow(self, glowType, glowOptions, noHiding) - if glowType == "Normal" then - if not noHiding then - LCG.PixelGlow_Stop(self.parent) - LCG.AutoCastGlow_Stop(self.parent) - LCG.ProcGlow_Stop(self.parent) - end - LCG.ButtonGlow_Start(self.parent, glowOptions[1]) - elseif glowType == "Pixel" then - if not noHiding then - LCG.ButtonGlow_Stop(self.parent) - LCG.AutoCastGlow_Stop(self.parent) - LCG.ProcGlow_Stop(self.parent) - end - -- color, N, frequency, length, thickness - LCG.PixelGlow_Start(self.parent, glowOptions[1], glowOptions[2], glowOptions[3], glowOptions[4], glowOptions[5]) - elseif glowType == "Shine" then - if not noHiding then - LCG.ButtonGlow_Stop(self.parent) - LCG.PixelGlow_Stop(self.parent) - LCG.ProcGlow_Stop(self.parent) - end - -- color, N, frequency, scale - LCG.AutoCastGlow_Start(self.parent, glowOptions[1], glowOptions[2], glowOptions[3], glowOptions[4]) - elseif glowType == "Proc" then - if not noHiding then - LCG.ButtonGlow_Stop(self.parent) - LCG.PixelGlow_Stop(self.parent) - LCG.AutoCastGlow_Stop(self.parent) - end - -- color, duration - LCG.ProcGlow_Start(self.parent, {color=glowOptions[1], duration=glowOptions[2], startAnim=false}) - else - LCG.ButtonGlow_Stop(self.parent) - LCG.PixelGlow_Stop(self.parent) - LCG.AutoCastGlow_Stop(self.parent) - LCG.ProcGlow_Stop(self.parent) - end + if not LCG then return end + LCG.ButtonGlow_Stop(self.parent) + LCG.PixelGlow_Stop(self.parent) + LCG.AutoCastGlow_Stop(self.parent) + LCG.ProcGlow_Stop(self.parent) end -local hiders = { +local hiders = LCG and { ["Normal"] = LCG.ButtonGlow_Stop, ["Pixel"] = LCG.PixelGlow_Stop, ["Shine"] = LCG.AutoCastGlow_Stop, ["Proc"] = LCG.ProcGlow_Stop, -} +} or {} local function RaidDebuffs_HideGlow(self, glowType) - if not glowType then - for _, stop in pairs(hiders) do - stop(self.parent) - end - else - hiders[glowType](self.parent) + local stop = type(glowType) == "string" and hiders[glowType] + if stop then + stop(self.parent) + return + end + for _, fn in pairs(hiders) do + fn(self.parent) end end @@ -939,6 +1064,7 @@ function I.CreateRaidDebuffs(parent) raidDebuffs.SetFont = I.Cooldowns_SetFont raidDebuffs.ShowGlow = RaidDebuffs_ShowGlow raidDebuffs.HideGlow = RaidDebuffs_HideGlow + raidDebuffs.SetCooldownStyle = I.Cooldowns_SetCooldownStyle raidDebuffs.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect raidDebuffs.ShowTooltip = RaidDebuffs_ShowTooltip @@ -954,46 +1080,309 @@ end ------------------------------------------------- -- private auras ------------------------------------------------- +local PRIVATE_AURAS_MAX = 5 +local privateAurasPending = {} +local privateAurasCombatFrame = CreateFrame("Frame") + +local function PrivateAuras_Queue(self, func) + privateAurasPending[self] = func + privateAurasCombatFrame:RegisterEvent("PLAYER_REGEN_ENABLED") +end + +privateAurasCombatFrame:SetScript("OnEvent", function() + privateAurasCombatFrame:UnregisterEvent("PLAYER_REGEN_ENABLED") + + for self, func in pairs(privateAurasPending) do + privateAurasPending[self] = nil + pcall(func) + end +end) + +local function PrivateAuras_GetMaxAuras(self, options) + local maxAuras = options and options[3] or self.maxAuras or 1 + maxAuras = tonumber(maxAuras) or 1 + maxAuras = math.floor(maxAuras + 0.5) + + if maxAuras < 1 then + maxAuras = 1 + elseif maxAuras > PRIVATE_AURAS_MAX then + maxAuras = PRIVATE_AURAS_MAX + end + + return maxAuras +end + +local function PrivateAuras_RemoveAllAnchors(self) + if not (C_UnitAuras and C_UnitAuras.RemovePrivateAuraAnchor) then return end + + if InCombatLockdown() then + PrivateAuras_Queue(self, function() + PrivateAuras_RemoveAllAnchors(self) + end) + return + end + + for i = 1, #self do + local holder = self[i] + if holder.auraAnchorID then + local success = pcall(C_UnitAuras.RemovePrivateAuraAnchor, holder.auraAnchorID) + if success then + holder.auraAnchorID = nil + end + end + end + + if self.dispelContainerAnchorID then + local success = pcall(C_UnitAuras.RemovePrivateAuraAnchor, self.dispelContainerAnchorID) + if success then + self.dispelContainerAnchorID = nil + end + end +end + +local function PrivateAuras_UpdateHolderVisibility(self, maxAuras) + for i = 1, #self do + if i <= maxAuras then + self[i]:Show() + else + self[i]:Hide() + end + end + + self:UpdateSize(maxAuras) +end + +local function PrivateAuras_UpdateAnchorFrame(self, holder) + local anchorFrame = holder.privateAuraAnchor or holder + local idx = holder._cellPrivateAuraIndex or 1 + + local parentStrata = self:GetFrameStrata() + if parentStrata == "MEDIUM" or parentStrata == "LOW" or parentStrata == "BACKGROUND" then + holder:SetFrameStrata("HIGH") + anchorFrame:SetFrameStrata("HIGH") + else + holder:SetFrameStrata(parentStrata) + anchorFrame:SetFrameStrata(parentStrata) + end + local baseLevel = (self:GetFrameLevel() or 1) + 10 + idx + holder:SetFrameLevel(baseLevel) + anchorFrame:SetFrameLevel(baseLevel + 1) + + anchorFrame:ClearAllPoints() + anchorFrame:SetPoint("CENTER", holder) + + if self.hideTooltip then + anchorFrame:SetSize(0.001, 0.001) + else + anchorFrame:SetSize(holder:GetWidth(), holder:GetHeight()) + end + + if not InCombatLockdown() then + pcall(function() + anchorFrame:EnableMouse(not self.hideTooltip) + if anchorFrame.SetPropagateMouseMotion then + anchorFrame:SetPropagateMouseMotion(not self.hideTooltip) + end + if anchorFrame.SetPropagateMouseClicks then + anchorFrame:SetPropagateMouseClicks(not self.hideTooltip) + end + end) + end + + return anchorFrame +end + +local function PrivateAuras_UpdateSize(self, iconsShown) + if iconsShown then + for i = iconsShown + 1, #self do + self[i]:Hide() + end + end + + if self.width and self.height then + self:_SetSize(self.width, self.height) + end +end + +local function PrivateAuras_HookBlizzardEventFrames(frame, depth) + if depth > 5 then return end + for i = 1, frame:GetNumChildren() do + local child = select(i, frame:GetChildren()) + if child and not child.__cell_hooked then + local origOnEvent = child:GetScript("OnEvent") + local origOnUpdate = child:GetScript("OnUpdate") + if origOnEvent then + child.__cell_hooked = true + child:SetScript("OnEvent", function(...) + local ok, err = pcall(origOnEvent, ...) + if not ok and not tostring(err):find("CheckExistingDispelHasCorrectType") then + geterrorhandler()(err) + end + end) + end + if origOnUpdate then + if not child.__cell_hooked then + child.__cell_hooked = true + end + child:SetScript("OnUpdate", function(...) + local ok, err = pcall(origOnUpdate, ...) + if not ok and not tostring(err):find("CheckExistingDispelHasCorrectType") then + geterrorhandler()(err) + end + end) + end + if not child.__cell_hooked then + PrivateAuras_HookBlizzardEventFrames(child, depth + 1) + end + end + end +end + +local function PrivateAuras_UpdateDispelOverlayVisibility(self) + if not self.dispelOverlayFrame then return end + + local targetAlpha = 0 + if self.showDispelOverlay then + local hasNormalDispel = false + if self.button then + if self.button._debuffs_dispel then + for dispelType, value in pairs(self.button._debuffs_dispel) do + if value then + hasNormalDispel = true + break + end + end + end + + if not hasNormalDispel and self.button._dispelAuraID then + hasNormalDispel = true + end + end + + if not hasNormalDispel then + targetAlpha = tonumber(self.dispelOverlayAlpha) or 1 + end + end + + if self.dispelOverlayFrame:GetAlpha() ~= targetAlpha then + self.dispelOverlayFrame:SetAlpha(targetAlpha) + end +end + local function PrivateAuras_UpdatePrivateAuraAnchor(self, unit) - -- remove old - if self.auraAnchorID then - C_UnitAuras.RemovePrivateAuraAnchor(self.auraAnchorID) - self.unit = nil - self.auraAnchorID = nil + -- krysio: MoP/Classic no tiene AddPrivateAuraAnchor. Si el API existe como + -- stub con firma distinta, el pcall abajo igual lanza error de argumentos. + if not Cell.isMidnight then return end + + -- Defer until combat ends if needed. + if InCombatLockdown() then + self._pendingUnit = unit + PrivateAuras_Queue(self, function() + if self:IsShown() then + PrivateAuras_UpdatePrivateAuraAnchor(self, self._pendingUnit) + else + PrivateAuras_RemoveAllAnchors(self) + end + self._pendingUnit = nil + end) + return end + local maxAuras = PrivateAuras_GetMaxAuras(self) + PrivateAuras_RemoveAllAnchors(self) + self.unit = unit + + PrivateAuras_UpdateHolderVisibility(self, maxAuras) + -- add new - if unit then + if unit and C_UnitAuras and C_UnitAuras.AddPrivateAuraAnchor then local _showCountdownFrame, _showCountdownNumbers = true, false if type(self.showCountdownFrame) == "boolean" then _showCountdownFrame = self.showCountdownFrame end if type(self.showCountdownNumbers) == "boolean" then _showCountdownNumbers = self.showCountdownNumbers end - self.unit = unit - self.auraAnchorID = C_UnitAuras.AddPrivateAuraAnchor({ - unitToken = unit, - auraIndex = 1, - parent = self, - showCountdownFrame = _showCountdownFrame, - showCountdownNumbers = _showCountdownNumbers, - iconInfo = { - iconWidth = self:GetWidth(), - iconHeight = self:GetHeight(), - iconAnchor = { - point = "CENTER", - relativeTo = self, - relativePoint = "CENTER", - offsetX = 0, - offsetY = 0, + for i = 1, maxAuras do + local holder = self[i] + local anchorFrame = PrivateAuras_UpdateAnchorFrame(self, holder) + local success, auraAnchorID = pcall(C_UnitAuras.AddPrivateAuraAnchor, { + unitToken = unit, + auraIndex = i, + parent = anchorFrame, + isContainer = false, + showCountdownFrame = _showCountdownFrame, + showCooldownFrame = _showCountdownFrame, + showCountdownNumbers = _showCountdownNumbers, + iconInfo = { + iconWidth = F.GetWidth(holder), + iconHeight = F.GetHeight(holder), + borderScale = (F.GetWidth(holder) / 16) * (self.borderScale or 1), + iconAnchor = { + point = "CENTER", + relativeTo = anchorFrame, + relativePoint = "CENTER", + offsetX = 0, + offsetY = 0, + }, }, - }, - -- durationAnchor = { - -- point = "BOTTOMRIGHT", - -- relativeTo = self, - -- relativePoint = "BOTTOMRIGHT", - -- offsetX = 0, - -- offsetY = 0, - -- }, - }) + -- durationAnchor = { + -- point = "BOTTOMRIGHT", + -- relativeTo = holder, + -- relativePoint = "BOTTOMRIGHT", + -- offsetX = 0, + -- offsetY = 0, + -- }, + }) + if success then + holder.auraAnchorID = auraAnchorID + end + end + + if self.showDispelOverlay and self.dispelOverlayFrame then + local overlay = self.dispelOverlayFrame + local groupType = strfind(unit, "^party") and 4 or 5 + overlay:SetAttribute("max-buffs", 0) + overlay:SetAttribute("max-debuffs", 0) + overlay:SetAttribute("max-dispel-debuffs", 1) + overlay:SetAttribute("ignore-buffs", true) + overlay:SetAttribute("ignore-debuffs", true) + overlay:SetAttribute("ignore-dispel-debuffs", true) + overlay:SetAttribute("show-dispel-indicator-overlay", true) + overlay:SetAttribute("suppress-dispel-border-icons", true) + overlay:SetAttribute("power-bar-used-height", 0) + overlay:SetAttribute("icon-size", 10) + overlay:SetAttribute("set-aura-size-to-icon-size", false) + overlay:SetAttribute("group-type", groupType) + overlay:SetAttribute("dispel-indicator-option", self.dispelByMeOnly and 1 or 2) + overlay:SetAttribute("aura-organization-type", tonumber(self.dispelGradientDirection) or 0) + overlay:SetAttribute("update-settings", true) + + overlay:ClearAllPoints() + local inset = tonumber(self.dispelOverlayInset) or 0 + local overlayAnchor = self:GetParent() or self + overlay:SetPoint("TOPLEFT", overlayAnchor, "TOPLEFT", -inset, inset) + overlay:SetPoint("BOTTOMRIGHT", overlayAnchor, "BOTTOMRIGHT", inset, -inset) + overlay:SetFrameStrata("HIGH") + overlay:SetFrameLevel((self:GetFrameLevel() or 1) + (tonumber(self.dispelOverlayFrameLevel) or 6)) + overlay:SetAlpha(0) + overlay:Show() + + local success, anchorID = pcall(C_UnitAuras.AddPrivateAuraAnchor, { + unitToken = unit, + auraIndex = 1, + parent = overlay, + isContainer = true, + showCountdownFrame = false, + showCountdownNumbers = false, + }) + if success then + self.dispelContainerAnchorID = anchorID + C_Timer.After(0, function() + PrivateAuras_HookBlizzardEventFrames(overlay, 0) + end) + end + PrivateAuras_UpdateDispelOverlayVisibility(self) + elseif self.dispelOverlayFrame then + self.dispelOverlayFrame:Hide() + end end end @@ -1002,18 +1391,89 @@ function I.CreatePrivateAuras(parent) parent.indicators.privateAuras = privateAuras privateAuras:Hide() + privateAuras.button = parent + privateAuras.UpdateDispelOverlayVisibility = PrivateAuras_UpdateDispelOverlayVisibility privateAuras.UpdatePrivateAuraAnchor = PrivateAuras_UpdatePrivateAuraAnchor privateAuras._SetSize = privateAuras.SetSize + privateAuras.UpdateSize = PrivateAuras_UpdateSize + privateAuras.SetOrientation = I.Cooldowns_SetOrientation_WithSpacing + privateAuras.SetNumPerLine = I.Cooldowns_SetNumPerLine_WithSpacing + privateAuras.SetSpacing = I.Cooldowns_SetSpacing_WithSpacing + privateAuras.maxAuras = 1 + privateAuras.numPerLine = 1 + privateAuras.spacingX = 1 + privateAuras.spacingY = 1 + privateAuras.borderScale = 1 + privateAuras.hideTooltip = false + privateAuras.showDispelOverlay = false + privateAuras.dispelByMeOnly = true + privateAuras.dispelOverlayAlpha = 1 + privateAuras.dispelOverlayFrameLevel = 6 + privateAuras.dispelOverlayInset = 0 + privateAuras.dispelGradientDirection = 0 + + privateAuras.dispelOverlayFrame = CreateFrame("Frame", nil, privateAuras) + privateAuras.dispelOverlayFrame:Hide() + + for i = 1, PRIVATE_AURAS_MAX do + local holder = CreateFrame("Frame", nil, privateAuras) + holder._cellPrivateAuraIndex = i + holder.privateAuraAnchor = CreateFrame("Frame", nil, holder) + holder.privateAuraAnchor:SetPoint("CENTER", holder) + + if not InCombatLockdown() then + pcall(function() + holder.privateAuraAnchor:EnableMouse(true) + if holder.privateAuraAnchor.SetPropagateMouseMotion then + holder.privateAuraAnchor:SetPropagateMouseMotion(true) + end + if holder.privateAuraAnchor.SetPropagateMouseClicks then + holder.privateAuraAnchor:SetPropagateMouseClicks(true) + end + end) + end + + tinsert(privateAuras, holder) + end + + privateAuras:SetOrientation("left-to-right") function privateAuras:SetSize(width, height) - privateAuras:_SetSize(width, height) + privateAuras.width = width + privateAuras.height = height + for i = 1, #privateAuras do + privateAuras[i]:SetSize(width, height) + PrivateAuras_UpdateAnchorFrame(privateAuras, privateAuras[i]) + end + PrivateAuras_UpdateHolderVisibility(privateAuras, PrivateAuras_GetMaxAuras(privateAuras)) privateAuras:UpdatePrivateAuraAnchor(privateAuras.unit) end function privateAuras:UpdateOptions(t) self.showCountdownFrame = t[1] self.showCountdownNumbers = t[2] + self.maxAuras = PrivateAuras_GetMaxAuras(self, t) + self.hideTooltip = t[4] or false + self.borderScale = tonumber(t[5]) or 1 + self.showDispelOverlay = t[6] or false + self.dispelByMeOnly = t[7] ~= false + self.dispelOverlayAlpha = tonumber(t[8]) or 1 + self.dispelOverlayFrameLevel = tonumber(t[9]) or 6 + self.dispelOverlayInset = tonumber(t[10]) or 0 + self.dispelGradientDirection = tonumber(t[11]) or 0 + + for i = 1, #self do + local holder = self[i] + PrivateAuras_UpdateAnchorFrame(self, holder) + if holder.cooldown then + holder.cooldown:SetDrawSwipe(self.showCountdownFrame) + holder.cooldown:SetHideCountdownNumbers(not (self.showCountdownFrame and self.showCountdownNumbers)) + end + end + + PrivateAuras_UpdateHolderVisibility(self, self.maxAuras) privateAuras:UpdatePrivateAuraAnchor(privateAuras.unit) + PrivateAuras_UpdateDispelOverlayVisibility(privateAuras) end end @@ -1127,19 +1587,37 @@ function I.CreateNameText(parent) -- update vehicle local vp, _, vrp, _, vy = nameText.vehicle:GetPoint(1) if vp and vrp and vy then - if string.find(vp, "TOP") then - vp, vrp = "TOP", "BOTTOM" - else -- BOTTOM - vp, vrp = "BOTTOM", "TOP" - end + if F.IsValueNonSecret(vp) and F.IsValueNonSecret(vrp) and F.IsValueNonSecret(point) then + if string.find(vp, "TOP") then + vp, vrp = "TOP", "BOTTOM" + else -- BOTTOM + vp, vrp = "BOTTOM", "TOP" + end - nameText.vehicle:ClearAllPoints() - if string.find(point, "LEFT") then - nameText.vehicle:SetPoint(vp.."LEFT", nameText.name, vrp.."LEFT", 0, vy) - elseif string.find(point, "RIGHT") then - nameText.vehicle:SetPoint(vp.."RIGHT", nameText.name, vrp.."RIGHT", 0, vy) - else -- "CENTER" - nameText.vehicle:SetPoint(vp, nameText.name, vrp, 0, vy) + nameText.vehicle:ClearAllPoints() + if string.find(point, "LEFT") then + nameText.vehicle:SetPoint(vp.."LEFT", nameText.name, vrp.."LEFT", 0, vy) + elseif string.find(point, "RIGHT") then + nameText.vehicle:SetPoint(vp.."RIGHT", nameText.name, vrp.."RIGHT", 0, vy) + else -- "CENTER" + nameText.vehicle:SetPoint(vp, nameText.name, vrp, 0, vy) + end + else + -- and safe default positioning for the vehicle text. + local isLeft = (point == "TOPLEFT" or point == "BOTTOMLEFT" or point == "LEFT") + local isRight = (point == "TOPRIGHT" or point == "BOTTOMRIGHT" or point == "RIGHT") + + local safeVp, safeVrp = "TOP", "BOTTOM" + local safeVy = (vy and F.IsValueNonSecret(vy)) and vy or -1 + + nameText.vehicle:ClearAllPoints() + if isLeft then + nameText.vehicle:SetPoint("TOPLEFT", nameText.name, "BOTTOMLEFT", 0, safeVy) + elseif isRight then + nameText.vehicle:SetPoint("TOPRIGHT", nameText.name, "BOTTOMRIGHT", 0, safeVy) + else + nameText.vehicle:SetPoint("TOP", nameText.name, "BOTTOM", 0, safeVy) + end end end end @@ -1158,15 +1636,23 @@ function I.CreateNameText(parent) -- only check nickname for players if parent.states.isPlayer then - if CELL_NICKTAG_ENABLED and Cell.NickTag then + if F.IsValueNonSecret(parent.states.name) and CELL_NICKTAG_ENABLED and Cell.NickTag then name = Cell.NickTag:GetNickname(parent.states.name, nil, true) end - name = name or F.GetNickname(parent.states.name, parent.states.fullName) + if F.IsValueNonSecret(parent.states.name) and F.IsValueNonSecret(parent.states.fullName) then + if F.GetNickname then + name = name or F.GetNickname(parent.states.name, parent.states.fullName) + else + name = name or parent.states.name + end + else + name = parent.states.name + end else name = parent.states.name end - if Cell.loaded and CellDB["general"]["translit"] then + if Cell.loaded and CellDB["general"]["translit"] and F.IsValueNonSecret(name) and LibTranslit then name = LibTranslit:Transliterate(name) end @@ -1182,10 +1668,11 @@ function I.CreateNameText(parent) end end - if nameText.name:GetText() then + local currentNameText = nameText.name:GetText() + if currentNameText and F.IsValueNonSecret(currentNameText) then if nameText.isPreview then if nameText.showGroupNumber then - nameText.name:SetText("|cffbbbbbb7-|r"..nameText.name:GetText()) + nameText.name:SetText("|cffbbbbbb7-|r"..currentNameText) end else if IsInRaid() and nameText.showGroupNumber then @@ -1193,13 +1680,21 @@ function I.CreateNameText(parent) if raidIndex then local subgroup = select(3, GetRaidRosterInfo(raidIndex)) -- nameText.name:SetText("|TInterface\\AddOns\\Cell\\Media\\Icons\\group"..subgroup..":0:0:0:-1:64:64:6:58:6:58|t"..nameText.name:GetText()) - nameText.name:SetText("|cffbbbbbb"..subgroup.."-|r"..nameText.name:GetText()) + nameText.name:SetText("|cffbbbbbb"..subgroup.."-|r"..currentNameText) end end end end - nameText:SetSize(nameText.name:GetWidth(), nameText.name:GetHeight()) + local width, height = F.GetWidth(nameText.name), F.GetHeight(nameText.name) + if width <= 0 then + width = F.GetWidth(parent.widgets.healthBar) + if width <= 0 then width = 50 end + end + if height <= 0 then + height = 14 + end + nameText:SetSize(width, height) end function nameText:UpdateVehicleName() @@ -1376,9 +1871,8 @@ local function StatusText_ShowTimer(self) self.timer:Show() - -- Midnight 12.0.0+: guid may be secret for NPC/boss units local showGuid = self.parent.states.guid - if not (issecretvalue and issecretvalue(showGuid)) then + if F.IsValueNonSecret(showGuid) then if showGuid and not startTimeCache[showGuid] then startTimeCache[showGuid] = GetTime() end end @@ -1387,7 +1881,7 @@ local function StatusText_ShowTimer(self) self.parent.states.guid = UnitGUID(self.parent.states.unit) end local tickGuid = self.parent.states.guid - if tickGuid and not (issecretvalue and issecretvalue(tickGuid)) and startTimeCache[tickGuid] then + if tickGuid and F.IsValueNonSecret(tickGuid) and startTimeCache[tickGuid] then self.timer:SetFormattedText(F.FormatTime(GetTime() - startTimeCache[tickGuid])) else self.timer:SetText("") @@ -1400,9 +1894,8 @@ local function StatusText_HideTimer(self, reset) self.timer:SetText("") if reset then if self.ticker then self.ticker:Cancel() end - -- Midnight 12.0.0+: guid may be secret for NPC/boss units local guid = self.parent.states.guid - if guid and not (issecretvalue and issecretvalue(guid)) then + if guid and F.IsValueNonSecret(guid) then startTimeCache[guid] = nil end end @@ -1453,42 +1946,33 @@ local formatter = { end, -- health - ["health"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) then return "" end + ["health"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(health) end, - ["health_short"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) then return "" end + ["health_short"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.FormatNumber(health)) end, - ["health_percent"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) then return "" end + ["health_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.Round(health / maxHealth * 100)) end, - ["deficit"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) then return "" end + ["deficit"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(health - maxHealth) end, - ["deficit_short"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) then return "" end + ["deficit_short"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.FormatNumber(health - maxHealth)) end, - ["deficit_percent"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) then return "" end + ["deficit_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.Round((health - maxHealth) / maxHealth * 100)) end, -- effective health - ["effective"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) and absorbs == 0 and healAbsorbs == 0 then return "" end + ["effective"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(health + absorbs - healAbsorbs) end, - ["effective_short"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) and absorbs == 0 and healAbsorbs == 0 then return "" end + ["effective_short"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.FormatNumber(health + absorbs - healAbsorbs)) end, - ["effective_percent"] = function(pattern, hideIfEmptyOrFull, health, maxHealth, absorbs, healAbsorbs) - if hideIfEmptyOrFull and (health == 0 or health == maxHealth) and absorbs == 0 and healAbsorbs == 0 then return "" end + ["effective_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.Round((health + absorbs - healAbsorbs) / maxHealth * 100)) end, @@ -1542,6 +2026,64 @@ local function BuildPattern(config) end end +-- segment (with %d placeholder) and a key identifying which argument to use: +-- "health" = raw health, "pct" = health percent (0-100), "shields" = absorbs, +-- "healAbsorbs" = heal absorbs, nil = skip (format is "none") +local function BuildSecretSegment(config) + if config.format == "none" then + return nil, nil + end + + local prefix + if config.delimiter == nil then + prefix = "" + else + prefix = "|cffababab" .. config.delimiter .. "|r" + end + + local isPercent = config.format:find("percent$") and true or false + + local colorStart, colorEnd + if config.color[1] == "class_color" then + colorStart, colorEnd = "", "" + else + colorStart = "|cff" .. F.ConvertRGBToHEX(F.ConvertRGB_256(unpack(config.color[2]))) + colorEnd = "|r" + end + + -- Determine which argument to pass for this segment + local fmt = config.format:gsub("_no_sign$", "") + local argKey + local isAbsorb = false + if fmt == "shields" or fmt == "shields_short" or fmt == "shields_percent" then + argKey = "shields" + isAbsorb = true + elseif fmt == "healabsorbs" or fmt == "healabsorbs_short" or fmt == "healabsorbs_percent" then + argKey = "healAbsorbs" + isAbsorb = true + elseif isPercent then + argKey = "pct" + else + argKey = "health" + end + + -- so fall back to abbreviated raw number (no %% suffix). + local isAbsorbPercent = isAbsorb and isPercent + local isNoSign = config.format:find("_no_sign$") and true or false + local suffix = (isPercent and not isAbsorbPercent and not isNoSign) and "%%" or "" + + -- Use abbreviated display for _short formats and absorb percent fallback + local useAbbrev = (config.format:find("short") or isAbsorbPercent) and not (isPercent and not isAbsorbPercent) + local specifier = (useAbbrev and AbbreviateNumbers) and "%s" or "%d" + local segment = prefix .. colorStart .. specifier .. suffix .. colorEnd + + if useAbbrev and AbbreviateNumbers then + argKey = argKey .. "_abbr" + end + + return segment, argKey +end + local function HealthText_SetFormat(self, format) self.GetHealth1 = formatter[format.health1.format:gsub("_no_sign$", "")] self.GetHealth2 = formatter[format.health2.format:gsub("_no_sign$", "")] @@ -1549,30 +2091,97 @@ local function HealthText_SetFormat(self, format) self.GetHealAbsorbs = formatter[format.healAbsorbs.format:gsub("_no_sign$", "")] self.health1 = BuildPattern(format.health1) - self.health1_hideIfEmptyOrFull = format.health1.hideIfEmptyOrFull self.health2 = BuildPattern(format.health2) - self.health2_hideIfEmptyOrFull = format.health2.hideIfEmptyOrFull self.shields = BuildPattern(format.shields) self.healAbsorbs = BuildPattern(format.healAbsorbs) -end -local function HealthText_SetValue(self, health, maxHealth, shields, healAbsorbs) - -- On Midnight 12.0.0+, health/maxHealth may be secret values in restricted contexts. - -- Arithmetic (/, *, -, comparison) on secret values causes errors. - -- AbbreviateNumbers() and FontString:SetText() accept secret values safely. - -- DO NOT divide, multiply, or compare secret values. - if Cell.isMidnight and F.IsAuraRestricted and F.IsAuraRestricted() then - local healthStr = AbbreviateNumbers and AbbreviateNumbers(health) or tostring(health) - self.text:SetText(healthStr) - self:SetWidth(self.text:GetStringWidth()) + -- Each active component gets a segment with a %d/%s placeholder. + -- Stored individually so the render path can skip zero-value absorb components. + local segments = {} + local argKeys = {} + for _, cfg in ipairs({format.health1, format.health2, format.shields, format.healAbsorbs}) do + local seg, key = BuildSecretSegment(cfg) + if seg then + segments[#segments + 1] = seg + argKeys[#argKeys + 1] = key + end + end + if #segments > 0 then + self._secretSegments = segments + self._secretArgKeys = argKeys + else + self._secretSegments = nil + self._secretArgKeys = nil + end + +end + +local function HealthText_SetValue(self, health, maxHealth, shields, healAbsorbs, unit) + -- safety net for any remaining Lua arithmetic in the formatters below. + if not F.IsValueNonSecret(health) or not F.IsValueNonSecret(maxHealth) + or not F.IsValueNonSecret(shields) or not F.IsValueNonSecret(healAbsorbs) then + -- Absorb components are skipped when their value is known to be 0. + local segments = self._secretSegments + local argKeys = self._secretArgKeys + if segments and argKeys then + local fmtParts = {} + local argValues = {} + local healthPct + for i, key in ipairs(argKeys) do + local raw + local isAbsorbKey = (key == "shields" or key == "shields_abbr" + or key == "healAbsorbs" or key == "healAbsorbs_abbr") + if key == "pct" then + if not healthPct and unit and UnitHealthPercent then + if CurveConstants and CurveConstants.ScaleTo100 then + healthPct = UnitHealthPercent(unit, true, CurveConstants.ScaleTo100) + else + healthPct = UnitHealthPercent(unit) + end + end + raw = healthPct or health + elseif key == "health" or key == "health_abbr" then + raw = health + elseif key == "shields" or key == "shields_abbr" then + raw = (shields == nil) and 0 or shields + elseif key == "healAbsorbs" or key == "healAbsorbs_abbr" then + raw = (healAbsorbs == nil) and 0 or healAbsorbs + else + raw = 0 + end + if isAbsorbKey and F.IsValueNonSecret(raw) and raw == 0 then + -- skip this component + else + fmtParts[#fmtParts + 1] = segments[i] + argValues[#argValues + 1] = key:sub(-5) == "_abbr" and AbbreviateNumbers(raw) or raw + end + end + local fmt = table.concat(fmtParts) + local n = #argValues + if n == 0 then + self.text:SetText("") + elseif n == 1 then + self.text:SetFormattedText(fmt, argValues[1]) + elseif n == 2 then + self.text:SetFormattedText(fmt, argValues[1], argValues[2]) + elseif n == 3 then + self.text:SetFormattedText(fmt, argValues[1], argValues[2], argValues[3]) + elseif n == 4 then + self.text:SetFormattedText(fmt, argValues[1], argValues[2], argValues[3], argValues[4]) + end + else + self.text:SetFormattedText("%d", health) + end + -- so the frame isn't zero-width (which would clip the text entirely). + local sw = self.text:GetStringWidth() + self:SetWidth(F.IsValueNonSecret(sw) and sw or 50) return end - maxHealth = maxHealth == 0 and 1 or maxHealth self.text:SetFormattedText("%s%s%s%s", - self.GetHealth1(self.health1, self.health1_hideIfEmptyOrFull, health, maxHealth, shields, healAbsorbs), - self.GetHealth2(self.health2, self.health2_hideIfEmptyOrFull, health, maxHealth, shields, healAbsorbs), + self.GetHealth1(self.health1, false, health, maxHealth, shields, healAbsorbs), + self.GetHealth2(self.health2, false, health, maxHealth, shields, healAbsorbs), self.GetShields(self.shields, health, maxHealth, shields, healAbsorbs), self.GetHealAbsorbs(self.healAbsorbs, health, maxHealth, shields, healAbsorbs)) self:SetWidth(self.text:GetStringWidth()) @@ -1600,7 +2209,8 @@ local function HealthText_SetFont(self, font, size, outline, shadow) self.text:SetShadowColor(0, 0, 0, 0) end - self:SetSize(self.text:GetStringWidth(), size) + local w = self.text:GetStringWidth() + self:SetSize((F.IsValueNonSecret(w) and w > 0) and w or 50, size) end local function HealthText_SetPoint(self, point, relativeTo, relativePoint, x, y) @@ -1653,34 +2263,41 @@ end ------------------------------------------------- -- power text ------------------------------------------------- +local function FitPowerTextWidth(self) + local w = self.text:GetStringWidth() + self:SetWidth((F.IsValueNonSecret(w) and w > 0) and w or 50) +end + local function SetPower_Percentage(self, current, max) - if self.hideIfEmptyOrFull and (current == 0 or current == max) then - self:Hide() + if F.IsValueNonSecret(current) and F.IsValueNonSecret(max) and max ~= 0 then + self.text:SetFormattedText("%d%%", current / max * 100) else - self.text:SetFormattedText("%d%%", current/max*100) - self:SetWidth(self.text:GetStringWidth()) - self:Show() + self.text:SetFormattedText("%d%%", current) end + FitPowerTextWidth(self) + self:Show() end local function SetPower_Number(self, current, max) - if self.hideIfEmptyOrFull and (current == 0 or current == max) then - self:Hide() - else + if F.IsValueNonSecret(current) then self.text:SetText(current) - self:SetWidth(self.text:GetStringWidth()) - self:Show() + else + self.text:SetFormattedText("%d", current) end + FitPowerTextWidth(self) + self:Show() end local function SetPower_Number_Short(self, current, max) - if self.hideIfEmptyOrFull and (current == 0 or current == max) then - self:Hide() - else + if F.IsValueNonSecret(current) then self.text:SetText(F.FormatNumber(current)) - self:SetWidth(self.text:GetStringWidth()) - self:Show() + elseif AbbreviateNumbers then + self.text:SetFormattedText("%s", AbbreviateNumbers(current)) + else + self.text:SetFormattedText("%d", current) end + FitPowerTextWidth(self) + self:Show() end local function PowerText_SetFont(self, font, size, outline, shadow) @@ -1705,7 +2322,8 @@ local function PowerText_SetFont(self, font, size, outline, shadow) self.text:SetShadowColor(0, 0, 0, 0) end - self:SetSize(self.text:GetStringWidth(), size) + FitPowerTextWidth(self) + self:SetHeight(size) end local function PowerText_SetPoint(self, point, relativeTo, relativePoint, x, y) @@ -1721,12 +2339,14 @@ local function PowerText_SetPoint(self, point, relativeTo, relativePoint, x, y) end local function PowerText_SetFormat(self, format) + self._format = format if format == "percentage" then self.SetValue = SetPower_Percentage - elseif format == "number" then - self.SetValue = SetPower_Number elseif format == "number-short" then self.SetValue = SetPower_Number_Short + else + self._format = "number" + self.SetValue = SetPower_Number end end @@ -1734,9 +2354,6 @@ local function PowerText_SetColor(self, r, g, b) self.text:SetTextColor(r, g, b) end -local function PowerText_SetHideIfEmptyOrFull(self, hideIfEmptyOrFull) - self.hideIfEmptyOrFull = hideIfEmptyOrFull -end local function PowerText_UpdatePreviewColor(self, color) local r, g, b @@ -1763,9 +2380,10 @@ function I.CreatePowerText(parent) powerText.SetPoint = PowerText_SetPoint powerText.SetFormat = PowerText_SetFormat powerText.SetColor = PowerText_SetColor - powerText.SetHideIfEmptyOrFull = PowerText_SetHideIfEmptyOrFull + powerText.SetHideIfEmptyOrFull = function() end powerText.UpdatePreviewColor = PowerText_UpdatePreviewColor - powerText.SetValue = noop + powerText.SetValue = SetPower_Number + powerText._format = "number" end ------------------------------------------------- @@ -1794,6 +2412,10 @@ local function GetTexCoordsForRoleSmall(role) end local function RoleIcon_SetRole(self, role) + if role ~= nil and not F.IsValueNonSecret(role) then + return + end + self.tex:SetTexCoord(0, 1, 0, 1) self.tex:SetVertexColor(1, 1, 1) @@ -2067,7 +2689,7 @@ end -- shield bar ------------------------------------------------- local function ShieldBar_SetHorizontalValue(bar, percent) - local maxWidth = bar.parentHealthBar:GetWidth() + local maxWidth = F.GetWidth(bar.parentHealthBar) local barWidth if percent >= 1 then barWidth = maxWidth @@ -2075,10 +2697,15 @@ local function ShieldBar_SetHorizontalValue(bar, percent) barWidth = maxWidth * percent end bar:SetWidth(max(barWidth, 3)) + bar:SetBackdropBorderColor(0, 0, 0, 1) + -- StatusBar must be "full" so the texture fills the frame; + -- the frame width controls the visible portion. + bar:SetMinMaxValues(0, 1) + bar:SetValue(1) end local function ShieldBar_SetVerticalValue(bar, percent) - local maxHeight = bar.parentHealthBar:GetHeight() + local maxHeight = F.GetHeight(bar.parentHealthBar) local barHeight if percent >= 1 then barHeight = maxHeight @@ -2086,46 +2713,53 @@ local function ShieldBar_SetVerticalValue(bar, percent) barHeight = maxHeight * percent end bar:SetHeight(max(barHeight, 3)) + bar:SetBackdropBorderColor(0, 0, 0, 1) + bar:SetMinMaxValues(0, 1) + bar:SetValue(1) +end + +-- fill to show shield amount. Keeps original height/position (thin bar at +-- bottom like power bar). Border hidden so only the filled portion is visible. +local function ShieldBar_SetAbsorbs(bar, absorbs, healthMax) + bar:SetBackdropBorderColor(0, 0, 0, 0) + local parent = bar.parentHealthBar + if parent then + bar:SetWidth(parent:GetWidth()) + end + bar:SetMinMaxValues(0, healthMax) + bar:SetValue(absorbs) end local function ShieldBar_SetPoint(bar, point, anchorTo, anchorPoint, x, y) - -- if point == "HEALTH_BAR_HORIZONTAL" then - -- bar:_SetPoint("TOPLEFT", b.widgets.healthBar) - -- bar:_SetPoint("BOTTOMLEFT", b.widgets.healthBar) - -- bar.SetValue = ShieldBar_SetHorizontalValue - -- elseif point == "HEALTH_BAR_VERTICAL" then - -- bar:_SetPoint("TOPLEFT", b.widgets.healthBar) - -- bar:_SetPoint("BOTTOMLEFT", b.widgets.healthBar) - -- bar.SetValue = ShieldBar_SetVerticalValue if point == "HEALTH_BAR" then bar:_SetPoint("TOPLEFT", bar.parentHealthBar, P.Scale(-1), P.Scale(1)) bar:_SetPoint("BOTTOMLEFT", bar.parentHealthBar, P.Scale(-1), P.Scale(-1)) - bar.SetValue = ShieldBar_SetHorizontalValue + bar.SetPercent = ShieldBar_SetHorizontalValue else bar:_SetPoint(point, anchorTo, anchorPoint, x, y) - bar.SetValue = ShieldBar_SetHorizontalValue + bar.SetPercent = ShieldBar_SetHorizontalValue end end function I.CreateShieldBar(parent) - local shieldBar = CreateFrame("Frame", parent:GetName().."ShieldBar", parent.widgets.indicatorFrame, "BackdropTemplate") + local shieldBar = CreateFrame("StatusBar", parent:GetName().."ShieldBar", parent.widgets.indicatorFrame, "BackdropTemplate") parent.indicators.shieldBar = shieldBar - -- shieldBar:SetSize(4, 4) shieldBar:Hide() shieldBar:SetBackdrop({edgeFile=Cell.vars.whiteTexture, edgeSize=P.Scale(1)}) shieldBar:SetBackdropBorderColor(0, 0, 0, 1) - - local tex = shieldBar:CreateTexture(nil, "BORDER", nil, -7) - tex:SetAllPoints() + shieldBar:SetStatusBarTexture(Cell.vars.whiteTexture) + shieldBar:SetMinMaxValues(0, 1) + shieldBar:SetValue(0) shieldBar._SetPoint = shieldBar.SetPoint shieldBar.SetPoint = ShieldBar_SetPoint - shieldBar.SetValue = ShieldBar_SetHorizontalValue + shieldBar.SetPercent = ShieldBar_SetHorizontalValue + shieldBar.SetAbsorbs = ShieldBar_SetAbsorbs shieldBar.parentHealthBar = parent.widgets.healthBar function shieldBar:SetColor(r, g, b, a) - tex:SetColorTexture(r, g, b, a) + shieldBar:SetStatusBarColor(r, g, b, a) end function shieldBar:UpdatePixelPerfect() @@ -2144,10 +2778,17 @@ function I.CreateHealthThresholds(parent) healthThresholds:SetAllPoints(parent.widgets.healthBar) healthThresholds.tex = healthThresholds:CreateTexture(nil, "ARTWORK") + healthThresholds._midnightCurves = nil + healthThresholds._midnightCurveKey = nil function healthThresholds:SetThickness(thickness) healthThresholds.thickness = thickness P.Size(healthThresholds.tex, thickness, thickness) + for i = 1, #healthThresholds do + if healthThresholds[i] then + P.Size(healthThresholds[i], thickness, thickness) + end + end end function healthThresholds:SetOrientation(orientation) @@ -2170,6 +2811,11 @@ function I.CreateHealthThresholds(parent) break end end + for i = 1, #healthThresholds do + if healthThresholds[i] then + healthThresholds[i]:Hide() + end + end if found then if healthThresholds.orientation == "horizontal" then healthThresholds.tex:SetPoint("LEFT", Cell.vars.healthThresholds[found][1] * parent.widgets.healthBar:GetWidth(), 0) @@ -2177,12 +2823,125 @@ function I.CreateHealthThresholds(parent) healthThresholds.tex:SetPoint("BOTTOM", 0, Cell.vars.healthThresholds[found][1] * parent.widgets.healthBar:GetHeight()) end healthThresholds.tex:SetColorTexture(unpack(Cell.vars.healthThresholds[found][2])) + healthThresholds.tex:Show() healthThresholds:Show() else + healthThresholds.tex:Hide() healthThresholds:Hide() end end + local function EnsureMidnightTexturesAndCurves() + local thresholds = Cell.vars.healthThresholds + if type(thresholds) ~= "table" or #thresholds == 0 then + healthThresholds._midnightCurves = nil + healthThresholds._midnightCurveKey = nil + return false + end + + local keyParts = {} + for i, t in ipairs(thresholds) do + keyParts[i] = tostring(t[1] or 0) + end + local key = table.concat(keyParts, ",") + if healthThresholds._midnightCurves and healthThresholds._midnightCurveKey == key then + return true + end + if not C_CurveUtil or not C_CurveUtil.CreateCurve then + healthThresholds._midnightCurves = nil + healthThresholds._midnightCurveKey = nil + return false + end + + local curves = {} + for i, t in ipairs(thresholds) do + local curr = t[1] or 0 + local prev = (i > 1 and thresholds[i - 1][1]) or 0 + local curve = C_CurveUtil.CreateCurve() + if i == 1 then + curve:AddPoint(0.0, 1.0) + curve:AddPoint(math.max(curr - 0.0001, 0), 1.0) + curve:AddPoint(curr, 0.0) + curve:AddPoint(1.0, 0.0) + else + curve:AddPoint(0.0, 0.0) + curve:AddPoint(math.max(prev - 0.0001, 0), 0.0) + curve:AddPoint(prev, 1.0) + curve:AddPoint(math.max(curr - 0.0001, prev), 1.0) + curve:AddPoint(curr, 0.0) + curve:AddPoint(1.0, 0.0) + end + curves[i] = curve + + local tex = healthThresholds[i] or healthThresholds:CreateTexture(nil, "ARTWORK") + healthThresholds[i] = tex + P.Size(tex, healthThresholds.thickness or 1, healthThresholds.thickness or 1) + tex:SetColorTexture(unpack(t[2])) + tex:ClearAllPoints() + if healthThresholds.orientation == "horizontal" then + tex:SetPoint("TOP") + tex:SetPoint("BOTTOM") + tex:SetPoint("LEFT", curr * parent.widgets.healthBar:GetWidth(), 0) + else + tex:SetPoint("LEFT") + tex:SetPoint("RIGHT") + tex:SetPoint("BOTTOM", 0, curr * parent.widgets.healthBar:GetHeight()) + end + tex:Hide() + end + + for i = #thresholds + 1, #healthThresholds do + if healthThresholds[i] then + healthThresholds[i]:Hide() + end + end + + healthThresholds._midnightCurves = curves + healthThresholds._midnightCurveKey = key + return true + end + + function healthThresholds:CheckThresholdMidnight(calc) + if not calc then + self:Hide() + return + end + + local unit = parent.states and parent.states.displayedUnit + if unit then + local health = UnitHealth(unit) + local healthMax = UnitHealthMax(unit) + if F.IsValueNonSecret(health) and F.IsValueNonSecret(healthMax) and type(healthMax) == "number" and healthMax > 0 then + self:CheckThreshold(health / healthMax) + return + end + end + + if not (calc.EvaluateCurrentHealthPercent and EnsureMidnightTexturesAndCurves()) then + self:Hide() + return + end + + healthThresholds.tex:Hide() + local curves = healthThresholds._midnightCurves + local thresholds = Cell.vars.healthThresholds + for i = 1, #thresholds do + local tex = healthThresholds[i] + local curve = curves[i] + if tex and curve then + local alpha = calc:EvaluateCurrentHealthPercent(curve) + tex:Show() + tex:SetAlpha(alpha) + end + end + for i = #thresholds + 1, #healthThresholds do + if healthThresholds[i] then + healthThresholds[i]:Hide() + end + end + self:Show() + end + if parent == CellIndicatorsPreviewButton then healthThresholds.tex:Hide() @@ -2191,7 +2950,6 @@ function I.CreateHealthThresholds(parent) healthThresholds[i] = healthThresholds[i] or healthThresholds:CreateTexture(nil, "ARTWORK") P.Size(healthThresholds[i], healthThresholds.thickness, healthThresholds.thickness) healthThresholds[i]:SetColorTexture(unpack(t[2])) - -- healthThresholds[i]:SetBlendMode("ADD") healthThresholds[i]:ClearAllPoints() if healthThresholds.orientation == "horizontal" then @@ -2203,9 +2961,9 @@ function I.CreateHealthThresholds(parent) healthThresholds[i]:SetPoint("RIGHT") healthThresholds[i]:SetPoint("BOTTOM", 0, t[1] * parent.widgets.healthBar:GetHeight()) end + healthThresholds[i]:SetAlpha(1) healthThresholds[i]:Show() end - -- hide unused for i = #Cell.vars.healthThresholds+1, #healthThresholds do if healthThresholds[i] then healthThresholds[i]:Hide() @@ -2378,6 +3136,7 @@ function I.CreateCrowdControls(parent) crowdControls.ShowDuration = I.Cooldowns_ShowDuration crowdControls.SetOrientation = I.Cooldowns_SetOrientation_WithSpacing crowdControls.SetFont = I.Cooldowns_SetFont + crowdControls.SetCooldownStyle = I.Cooldowns_SetCooldownStyle crowdControls.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect for i = 1, 3 do @@ -2434,6 +3193,7 @@ function I.CreateMissingBuffs(parent) missingBuffs.SetSize = I.Cooldowns_SetSize missingBuffs.UpdateSize = I.Cooldowns_UpdateSize missingBuffs.SetOrientation = I.Cooldowns_SetOrientation + missingBuffs.SetCooldownStyle = I.Cooldowns_SetCooldownStyle missingBuffs.UpdatePixelPerfect = I.Cooldowns_UpdatePixelPerfect for i = 1, 3 do @@ -2441,7 +3201,7 @@ function I.CreateMissingBuffs(parent) local frame = I.CreateAura_BarIcon(name, missingBuffs) tinsert(missingBuffs, frame) frame:HookScript("OnSizeChanged", function() - LCG.ButtonGlow_Start(frame) + if LCG then LCG.ButtonGlow_Start(frame) end end) end end @@ -2481,7 +3241,7 @@ local function ShowMissingBuff(b, index, icon) local f = b.indicators.missingBuffs[index] f:SetCooldown(0, 0, nil, icon, 0) - LCG.ButtonGlow_Start(f) + if LCG then LCG.ButtonGlow_Start(f) end end function I.ShowMissingBuff(unit, icon) @@ -2491,4 +3251,4 @@ function I.ShowMissingBuff(unit, icon) if missingBuffsCounter[unit] > 3 then return end F.HandleUnitButton("unit", unit, ShowMissingBuff, missingBuffsCounter[unit], icon) -end \ No newline at end of file +end diff --git a/Indicators/Custom.lua b/Indicators/Custom.lua index c7cce5eb..5e45d55d 100644 --- a/Indicators/Custom.lua +++ b/Indicators/Custom.lua @@ -5,14 +5,6 @@ local F = Cell.funcs ---@class CellIndicatorFuncs local I = Cell.iFuncs --- NOTE for Custom Indicator authors (Midnight 12.0.0+): --- In restricted contexts (encounters, M+, PvP, combat), aura data fields --- (spellId, expirationTime, applications, icon, etc.) are Secret Values. --- - DO NOT compare secret values with == or use arithmetic on them --- - DO NOT use secret values as table keys --- - FontString:SetText() and SetTexture() ACCEPT secrets safely --- - Use issecretvalue(val) to check if a value is secret --- - Use GetRestrictedActionStatus(0) to check if aura access is restricted ------------------------------------------------- -- custom indicators @@ -32,7 +24,7 @@ function I.UpdateIndicatorTable(indicatorTable) local auraType = indicatorTable["auraType"] -- keep custom indicators in table - if indicatorTable["enabled"] then enabledIndicators[indicatorName] = true end + if indicatorTable["enabled"] and not (Cell.isRetail and indicatorTable["type"] == "glow") then enabledIndicators[indicatorName] = true end -- NOTE: icons is different from other custom indicators, more like the Debuffs indicator if indicatorTable["type"] == "icons" then @@ -70,6 +62,18 @@ function I.UpdateIndicatorTable(indicatorTable) if auraType == "buff" then customIndicators[auraType][indicatorName]["_auras"] = F.Copy(indicatorTable["auras"]) --* save ids customIndicators[auraType][indicatorName]["trackByName"] = indicatorTable["trackByName"] + customIndicators[auraType][indicatorName]["keepInHealers"] = indicatorTable["keepInHealers"] + + if Cell.isMidnight and not indicatorTable["trackByName"] then + local nameLookup = {} + for _, spellId in ipairs(indicatorTable["auras"]) do + if type(spellId) == "number" then + local name = F.GetSpellInfo(spellId) + if name then nameLookup[name] = true end + end + end + customIndicators[auraType][indicatorName]["_nameLookup"] = nameLookup + end end end @@ -93,7 +97,17 @@ function I.CreateIndicator(parent, indicatorTable) elseif indicatorTable["type"] == "texture" then indicator = I.CreateAura_Texture(nil, parent.widgets.indicatorFrame) elseif indicatorTable["type"] == "glow" then - indicator = I.CreateAura_Glow(nil, parent.widgets.highLevelFrame) + if Cell.isRetail then + indicator = CreateFrame("Frame", nil, parent) + indicator:Hide() + indicator.indicatorType = "glow" + indicator.SetCooldown = function() end + indicator.SetupGlow = function() end + indicator.SetFadeOut = function() end + indicator.Show = function() end + else + indicator = I.CreateAura_Glow(nil, parent.widgets.highLevelFrame) + end elseif indicatorTable["type"] == "overlay" then indicator = I.CreateAura_Overlay(nil, parent) elseif indicatorTable["type"] == "block" then @@ -166,6 +180,17 @@ local function UpdateCustomIndicators(layout, indicatorName, setting, value, val else customIndicators[value][indicatorName]["auras"] = F.ConvertSpellTable(value2, customIndicators[value][indicatorName]["trackByName"]) end + if Cell.isMidnight and value == "buff" and customIndicators["buff"][indicatorName] + and not customIndicators["buff"][indicatorName]["trackByName"] then + local nameLookup = {} + for _, spellId in ipairs(value2) do + if type(spellId) == "number" then + local name = F.GetSpellInfo(spellId) + if name then nameLookup[name] = true end + end + end + customIndicators["buff"][indicatorName]["_nameLookup"] = nameLookup + end elseif setting == "checkbutton" then if customIndicators["buff"][indicatorName] then customIndicators["buff"][indicatorName][value] = value2 @@ -175,6 +200,22 @@ local function UpdateCustomIndicators(layout, indicatorName, setting, value, val else customIndicators["buff"][indicatorName]["auras"] = F.ConvertSpellTable(customIndicators["buff"][indicatorName]["_auras"], value2) end + if Cell.isMidnight then + if not value2 then + -- Switched FROM trackByName=true TO false: build name lookup + local nameLookup = {} + for _, spellId in ipairs(customIndicators["buff"][indicatorName]["_auras"]) do + if type(spellId) == "number" then + local name = F.GetSpellInfo(spellId) + if name then nameLookup[name] = true end + end + end + customIndicators["buff"][indicatorName]["_nameLookup"] = nameLookup + else + -- Switched FROM trackByName=false TO true: name lookup no longer needed + customIndicators["buff"][indicatorName]["_nameLookup"] = nil + end + end end elseif customIndicators["debuff"][indicatorName] then customIndicators["debuff"][indicatorName][value] = value2 @@ -196,7 +237,9 @@ function I.ResetCustomIndicators(unitButton, auraType) local unit = unitButton.states.displayedUnit for indicatorName, indicatorTable in pairs(customIndicators[auraType]) do - if enabledIndicators[indicatorName] and unitButton.indicators[indicatorName] then + if enabledIndicators[indicatorName] and unitButton.indicators[indicatorName] + and not (I.ShouldSkipLegacyHealers and I.ShouldSkipLegacyHealers(indicatorTable)) + and not (I.ShouldSkipLegacyCustom and I.ShouldSkipLegacyCustom(indicatorTable)) then unitButton.indicators[indicatorName]:Hide(true) if indicatorTable["num"] then if not indicatorTable["found"][unit] then @@ -219,54 +262,67 @@ end ------------------------------------------------- -- update ------------------------------------------------- +local function ResolveAuraEntry(indicatorTable, spell) + local auras = indicatorTable["auras"] + local entry = auras and auras[spell] + if entry == nil and auras then + entry = auras[0] + end + return entry +end + local function Update(indicator, indicatorTable, unit, spell, start, duration, debuffType, icon, count, refreshing) + local entry = ResolveAuraEntry(indicatorTable, spell) + if entry == nil then return end + + local order, color + if type(entry) == "table" then + order, color = entry[1] or 999, entry[2] + else + order = entry + end + if indicatorTable["num"] then if indicatorTable["hasColor"] then - tinsert(indicatorTable["found"][unit], {indicatorTable["auras"][spell][1], start, duration, debuffType, icon, count, refreshing, indicatorTable["auras"][spell][2]}) + tinsert(indicatorTable["found"][unit], {order, start, duration, debuffType, icon, count, refreshing, color}) else - tinsert(indicatorTable["found"][unit], {indicatorTable["auras"][spell], start, duration, debuffType, icon, count, refreshing}) + tinsert(indicatorTable["found"][unit], {order, start, duration, debuffType, icon, count, refreshing}) end else - if indicatorTable["hasColor"] then - if indicatorTable["auras"][spell][1] < indicatorTable["topOrder"][unit] then - indicatorTable["topOrder"][unit] = indicatorTable["auras"][spell][1] - indicatorTable["top"][unit]["start"] = start - indicatorTable["top"][unit]["duration"] = duration - indicatorTable["top"][unit]["debuffType"] = debuffType - indicatorTable["top"][unit]["texture"] = icon - indicatorTable["top"][unit]["count"] = count - indicatorTable["top"][unit]["refreshing"] = refreshing - indicatorTable["top"][unit]["color"] = indicatorTable["auras"][spell][2] - end - else - if indicatorTable["auras"][spell] < indicatorTable["topOrder"][unit] then - indicatorTable["topOrder"][unit] = indicatorTable["auras"][spell] - indicatorTable["top"][unit]["start"] = start - indicatorTable["top"][unit]["duration"] = duration - indicatorTable["top"][unit]["debuffType"] = debuffType - indicatorTable["top"][unit]["texture"] = icon - indicatorTable["top"][unit]["count"] = count - indicatorTable["top"][unit]["refreshing"] = refreshing + if order < (indicatorTable["topOrder"][unit] or 999) then + indicatorTable["topOrder"][unit] = order + indicatorTable["top"][unit]["start"] = start + indicatorTable["top"][unit]["duration"] = duration + indicatorTable["top"][unit]["debuffType"] = debuffType + indicatorTable["top"][unit]["texture"] = icon + indicatorTable["top"][unit]["count"] = count + indicatorTable["top"][unit]["refreshing"] = refreshing + if color then + indicatorTable["top"][unit]["color"] = color end end end end -function I.UpdateCustomIndicators(unitButton, auraInfo) +function I.UpdateCustomIndicators(unitButton, auraInfo, auraTypeOverride) local unit = unitButton.states.displayedUnit - -- Midnight 12.0.0+: bail only if aura type (isHelpful) is secret — we need it to classify buff/debuff. - -- spellId may still be secret for some auras even with hotfix; don't bail on spellId. - if issecretvalue and issecretvalue(auraInfo.isHelpful) then return end + local auraType = auraTypeOverride + if not auraType then + if not F.IsValueNonSecret(auraInfo.isHelpful) then return end + auraType = auraInfo.isHelpful and "buff" or "debuff" + end + + -- Early exit: no custom indicators of this type are configured. + -- Avoids pairs() call + loop overhead called per-aura per event. + local indicators = customIndicators[auraType] + if not indicators or not next(indicators) then return end - local auraType = auraInfo.isHelpful and "buff" or "debuff" local icon = auraInfo.icon - -- Midnight 12.0.0+: dispelName may be secret; sanitize to avoid table-key/comparison crashes downstream local rawDispelName = auraInfo.dispelName - local debuffType = auraInfo.isHarmful and ((rawDispelName and (not issecretvalue or not issecretvalue(rawDispelName))) and rawDispelName or "") or nil + local debuffType = auraInfo.isHarmful and ((rawDispelName and F.IsValueNonSecret(rawDispelName)) and rawDispelName or "") or nil local count = auraInfo.applications local duration = auraInfo.duration - -- Use per-aura check for duration: non-secret auras get real timers, secret ones get zeroed. local start if F.IsAuraNonSecret(auraInfo) then start = (auraInfo.expirationTime or 0) - auraInfo.duration @@ -274,15 +330,53 @@ function I.UpdateCustomIndicators(unitButton, auraInfo) start = 0 duration = 0 end - local castByMe = auraInfo.sourceUnit == "player" or auraInfo.sourceUnit == "pet" + local sourceUnit = auraInfo.sourceUnit - -- check Bleed if auraInfo.isHarmful then debuffType = I.CheckDebuffType(debuffType, auraInfo.spellId) end + -- Dedup: first pass collects which spells are tracked by non-Healers custom + -- indicators, so the Healers indicator can skip them in pass 2. + local consumedSpell + + -- Pass 1: mark spell as consumed if any non-Healers indicator would match for indicatorName, indicatorTable in pairs(customIndicators[auraType]) do - if indicatorName and enabledIndicators[indicatorName] and unitButton.indicators[indicatorName] then + if indicatorName and enabledIndicators[indicatorName] and unitButton.indicators[indicatorName] + and indicatorTable["name"] ~= "Healers" + and not indicatorTable["keepInHealers"] then + local spell = indicatorTable["trackByName"] and auraInfo.name or auraInfo.spellId + local matchedAura = spell and F.IsValueNonSecret(spell) and indicatorTable["auras"][spell] + if not matchedAura and not indicatorTable["trackByName"] and Cell.isMidnight + and not F.IsValueNonSecret(auraInfo.spellId) + and auraInfo.name and F.IsValueNonSecret(auraInfo.name) then + matchedAura = indicatorTable["_nameLookup"] and indicatorTable["_nameLookup"][auraInfo.name] + if matchedAura then spell = auraInfo.name end + end + if matchedAura then + -- check caster + local castBy = indicatorTable["castBy"] + local castByMatches = castBy == "anyone" + if not castByMatches and F.IsValueNonSecret(sourceUnit) then + local castByMe = sourceUnit == "player" or sourceUnit == "pet" + castByMatches = (castBy == "me" and castByMe) or (castBy == "others" and not castByMe) + elseif not castByMatches and F.IsValueNonSecret(auraInfo.isFromPlayerOrPlayerPet) then + local fromPlayer = auraInfo.isFromPlayerOrPlayerPet + castByMatches = (castBy == "me" and fromPlayer) or (castBy == "others" and not fromPlayer) + end + if castByMatches then + consumedSpell = spell + break + end + end + end + end + + -- Pass 2: process all indicators (Healers skips consumed spells) + for indicatorName, indicatorTable in pairs(customIndicators[auraType]) do + if indicatorName and enabledIndicators[indicatorName] and unitButton.indicators[indicatorName] + and not (I.ShouldSkipLegacyHealers and I.ShouldSkipLegacyHealers(indicatorTable)) + and not (I.ShouldSkipLegacyCustom and I.ShouldSkipLegacyCustom(indicatorTable)) then local spell --* trackByName if indicatorTable["trackByName"] then spell = auraInfo.name @@ -290,14 +384,33 @@ function I.UpdateCustomIndicators(unitButton, auraInfo) spell = auraInfo.spellId end - -- Midnight 12.0.0+: spell (name or spellId) may be secret; cannot use as table key - if spell and (not issecretvalue or not issecretvalue(spell)) and indicatorTable["auras"][spell] or (indicatorTable["auras"][0] and duration ~= 0) then -- is in indicator spell list - -- check caster - if (indicatorTable["castBy"] == "me" and castByMe) or (indicatorTable["castBy"] == "others" and not castByMe) or (indicatorTable["castBy"] == "anyone") then - if auraType == "buff" then - Update(unitButton.indicators[indicatorName], indicatorTable, unit, spell, start, duration, debuffType, icon, count, auraInfo.refreshing) - else -- debuff - Update(unitButton.indicators[indicatorName], indicatorTable, unit, spell, start, duration, debuffType, icon, count, auraInfo.refreshing) + if indicatorTable["name"] == "Healers" and consumedSpell and F.IsValueNonSecret(spell) + and consumedSpell == spell then + else + local matchedAura = spell and F.IsValueNonSecret(spell) and indicatorTable["auras"][spell] + if not matchedAura and not indicatorTable["trackByName"] and Cell.isMidnight + and not F.IsValueNonSecret(auraInfo.spellId) + and auraInfo.name and F.IsValueNonSecret(auraInfo.name) then + matchedAura = indicatorTable["_nameLookup"] and indicatorTable["_nameLookup"][auraInfo.name] + if matchedAura then spell = auraInfo.name end + end + if matchedAura or (indicatorTable["auras"][0] and (duration ~= 0 or auraInfo._hasSecrets)) then -- is in indicator spell list + -- check caster + local castBy = indicatorTable["castBy"] + local castByMatches = castBy == "anyone" + if not castByMatches and F.IsValueNonSecret(sourceUnit) then + local castByMe = sourceUnit == "player" or sourceUnit == "pet" + castByMatches = (castBy == "me" and castByMe) or (castBy == "others" and not castByMe) + elseif not castByMatches and F.IsValueNonSecret(auraInfo.isFromPlayerOrPlayerPet) then + local fromPlayer = auraInfo.isFromPlayerOrPlayerPet + castByMatches = (castBy == "me" and fromPlayer) or (castBy == "others" and not fromPlayer) + end + if castByMatches then + if auraType == "buff" then + Update(unitButton.indicators[indicatorName], indicatorTable, unit, spell, start, duration, debuffType, icon, count, auraInfo.refreshing) + else -- debuff + Update(unitButton.indicators[indicatorName], indicatorTable, unit, spell, start, duration, debuffType, icon, count, auraInfo.refreshing) + end end end end @@ -310,11 +423,12 @@ end ------------------------------------------------- local sort = table.sort local function comparator(a, b) + if not a then return false end + if not b then return true end if a[1] and b[1] then return a[1] < b[1] - else - return a[2] <= b[2] end + return (a[2] or 0) <= (b[2] or 0) end function I.ShowCustomIndicators(unitButton, auraType) @@ -323,10 +437,12 @@ function I.ShowCustomIndicators(unitButton, auraType) local unit = unitButton.states.displayedUnit for indicatorName, indicatorTable in pairs(customIndicators[auraType]) do local indicator = unitButton.indicators[indicatorName] - if indicator and enabledIndicators[indicatorName] then + if indicator and enabledIndicators[indicatorName] + and not (I.ShouldSkipLegacyHealers and I.ShouldSkipLegacyHealers(indicatorTable)) + and not (I.ShouldSkipLegacyCustom and I.ShouldSkipLegacyCustom(indicatorTable)) then if indicatorTable["num"] then local t = indicatorTable["found"][unit] - if t[1] then + if t and t[1] then sort(t, comparator) for i = 1, indicatorTable["num"] do if not t[i] then break end @@ -351,4 +467,4 @@ function I.ShowCustomIndicators(unitButton, auraType) end end end -end \ No newline at end of file +end diff --git a/Indicators/StatusIcon.lua b/Indicators/StatusIcon.lua index f63d09e3..392217ce 100644 --- a/Indicators/StatusIcon.lua +++ b/Indicators/StatusIcon.lua @@ -30,9 +30,7 @@ local RESURRECTING = F.GetSpellInfo(160029) -- 1. SPELL_AURA_REMOVED (soulstone / Resurrecting debuff removal) -- 2. UNIT_DIED sub-event (to detect soulstone deaths) -- 3. SPELL_RESURRECT (to detect incoming resurrections) --- COMBAT_LOG_EVENT_UNFILTERED is removed in Midnight (WoW 12.0.0). -- --- Midnight replacements: -- - Incoming resurrection: already handled via INCOMING_RESURRECT_CHANGED -- in eventFrame + UnitHasIncomingResurrection() in I.UpdateStatusIcon. -- - Resurrecting debuff removal: handled by UNIT_AURA → UpdateStatusIcon_Resurrection @@ -68,7 +66,6 @@ if not Cell.isMidnight then end end) else - -- Midnight path: detect soulstone deaths via UNIT_AURA + UNIT_HEALTH. -- UNIT_AURA fires when any aura is added/removed on a tracked unit. -- We watch for soulstone buff removal and immediately note the guid; -- then UNIT_HEALTH (UnitIsDeadOrGhost) confirms the death. @@ -76,25 +73,26 @@ else if event == "UNIT_AURA" then local guid = UnitGUID(unit) if not guid then return end - -- Midnight 12.0.0+: UnitGUID may return secret strings for non-group units if issecretvalue and issecretvalue(guid) then return end - -- Check if soulstone buff is now absent but was present - -- (simple: after UNIT_AURA fires, see if unit still has it) - local hasSoulstone = F.FindAuraByName and F.FindAuraByName(unit, "BUFF", SOULSTONE) - if not hasSoulstone and soulstones[guid] then - -- aura gone; keep window open for death - C_Timer.After(0.1, function() - soulstones[guid] = nil - end) - elseif hasSoulstone then - soulstones[guid] = GetTime() + local function refreshSoulstone() + local hasSoulstone = F.FindAuraByName and F.FindAuraByName(unit, "BUFF", SOULSTONE) + if not hasSoulstone and soulstones[guid] then + C_Timer.After(0.1, function() + soulstones[guid] = nil + end) + elseif hasSoulstone then + soulstones[guid] = GetTime() + end + F.HandleUnitButton("unit", unit, I.UpdateStatusIcon_Resurrection) + end + if F.IsLiveAuraScanBlocked and F.IsLiveAuraScanBlocked() then + C_Timer.After(0, refreshSoulstone) + else + refreshSoulstone() end - -- Also refresh rez icon in case Resurrecting debuff changed - F.HandleUnitButton("unit", unit, I.UpdateStatusIcon_Resurrection) elseif event == "UNIT_HEALTH" then local guid = UnitGUID(unit) if not guid then return end - -- Midnight 12.0.0+: UnitGUID may return secret strings for non-group units if issecretvalue and issecretvalue(guid) then return end if UnitIsDeadOrGhost(unit) then if soulstones[guid] then @@ -211,8 +209,6 @@ function I.UpdateStatusIcon_Resurrection(button, start, duration) end if not start then - -- Midnight 12.0.0+: AuraUtil.FindAura/UnpackAuraData fails on secret aura data - -- F.IsAuraRestricted() is unreliable; skip entirely on Midnight if Cell.isMidnight then resurrectionIcon:Hide() return @@ -384,14 +380,16 @@ function I.EnableStatusIcon(enabled) end -- resurrection / soulstone tracking if not Cell.isMidnight then - -- Pre-Midnight: use CLEU for soulstone removal, UNIT_DIED, SPELL_RESURRECT cleuFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") else - -- Midnight (12.0.0+): COMBAT_LOG_EVENT_UNFILTERED unavailable. -- Use UNIT_AURA for soulstone buff tracking and UNIT_HEALTH for -- death detection. INCOMING_RESURRECT_CHANGED (above) handles -- incoming rez detection via UnitHasIncomingResurrection(). - cleuFrame:RegisterEvent("UNIT_AURA") + if not (F.IsLiveAuraScanBlocked and F.IsLiveAuraScanBlocked()) then + cleuFrame:RegisterEvent("UNIT_AURA") + else + cleuFrame:UnregisterEvent("UNIT_AURA") + end cleuFrame:RegisterEvent("UNIT_HEALTH") end else diff --git a/Indicators/TargetCounter.lua b/Indicators/TargetCounter.lua index 3bd87630..89829769 100644 --- a/Indicators/TargetCounter.lua +++ b/Indicators/TargetCounter.lua @@ -63,7 +63,6 @@ local function StartTicker() for unit in pairs(nameplates) do local target = UnitGUID(unit.."target") - -- Midnight 12.0.0+: UnitGUID for nameplate targets may return secret strings if Cell.isMidnight and issecretvalue and issecretvalue(target) then nameplateTargets[unit] = nil elseif not target then -- no target diff --git a/Indicators/TargetedSpells.lua b/Indicators/TargetedSpells.lua index e7cc75ae..c33fb5e1 100644 --- a/Indicators/TargetedSpells.lua +++ b/Indicators/TargetedSpells.lua @@ -1,94 +1,289 @@ local _, Cell = ... -local L = Cell.L ---@type CellFuncs local F = Cell.funcs ---@class CellIndicatorFuncs local I = Cell.iFuncs local LCG = LibStub("LibCustomGlow-1.0") -local UnitIsVisible = UnitIsVisible +--[[ + Targeted Spells +]] + local UnitExists = UnitExists -local UnitGUID = UnitGUID -local UnitIsUnit = UnitIsUnit -local UnitIsEnemy = UnitIsEnemy +local UnitCanAttack = UnitCanAttack local UnitCastingInfo = UnitCastingInfo local UnitChannelInfo = UnitChannelInfo +local UnitClass = UnitClass +local UnitRace = UnitRace +local UnitSex = UnitSex +local UnitGroupRolesAssigned = UnitGroupRolesAssigned +local IsInRaid = IsInRaid +local IsInGroup = IsInGroup +local GetTime = GetTime +local C_Spell = C_Spell +local issecret = issecretvalue or function() return false end + +local PARTY_UNITS = { "player", "party1", "party2", "party3", "party4" } +local PICKUP_DELAY = 0.1 +local VERIFY_DELAY = 0.15 +local RETARGET_DELAY = 0.05 + +local maxIcons = 1 +local showAllSpells = false +local displayMode = "Icons" -- "None", "Icons" +local enabled = false +local SWIPE_COLOR = { 0.95, 0.95, 0.32, 1 } +local targetedSpellsByName = {} + -local casts = {} -local castsOnUnit, sortedCastsOnUnit = {}, {} -local recheck = {} -local maxIcons, showAllSpells local eventFrame = CreateFrame("Frame") +eventFrame:Hide() + +-- cast tracking +local casts = {} -- sourceUnit -> castInfo +local castsOnUnit, sortedCastsOnUnit = {}, {} +local gen = {} -- sourceUnit -> generation (stale timer guard) +local plateTokens = {} -local function Reset() - wipe(recheck) +local rosterByClass = {} +local rosterRole, rosterRace, rosterSex = {}, {}, {} +local lastRosterSync = 0 +local matchBuf = {} + +local function WipeCastTables() wipe(casts) wipe(castsOnUnit) wipe(sortedCastsOnUnit) + wipe(gen) +end + +local function IsPartyContext() + if IsInRaid() then return false end + return true -- solo + party +end + +------------------------------------------------- +-- roster / classify +------------------------------------------------- +local function RebuildRoster() + wipe(rosterByClass) + wipe(rosterRole) + wipe(rosterRace) + wipe(rosterSex) + for i = 1, #PARTY_UNITS do + local u = PARTY_UNITS[i] + local ex = UnitExists(u) + if not issecret(ex) and ex == true then + local _, token = UnitClass(u) + if not issecret(token) and type(token) == "string" then + local list = rosterByClass[token] + if not list then + list = {} + rosterByClass[token] = list + end + list[#list + 1] = u + end + local role = UnitGroupRolesAssigned(u) + if not issecret(role) and type(role) == "string" and role ~= "NONE" then + rosterRole[u] = role + end + local _, raceToken = UnitRace(u) + if not issecret(raceToken) and type(raceToken) == "string" then + rosterRace[u] = raceToken + end + local sex = UnitSex(u) + if not issecret(sex) and type(sex) == "number" then + rosterSex[u] = sex + end + end + end + lastRosterSync = GetTime() +end + +local function Narrow(targetVal, rosterMap) + if targetVal == nil or #matchBuf <= 1 then return end + local exact = 0 + for i = 1, #matchBuf do + if rosterMap[matchBuf[i]] == targetVal then + exact = exact + 1 + end + end + if exact == 0 then return end + for i = #matchBuf, 1, -1 do + if rosterMap[matchBuf[i]] ~= targetVal then + tremove(matchBuf, i) + end + end +end + +local function Classify(caster) + local tgt = caster .. "target" + local _, cls = UnitClass(tgt) + if issecret(cls) or type(cls) ~= "string" then return nil end + + wipe(matchBuf) + local cands = rosterByClass[cls] + if cands then + for i = 1, #cands do + matchBuf[i] = cands[i] + end + end + if #matchBuf == 0 then + if GetTime() - lastRosterSync > 1 then + RebuildRoster() + cands = rosterByClass[cls] + if cands then + for i = 1, #cands do + matchBuf[i] = cands[i] + end + end + end + if #matchBuf == 0 then return nil end + end + + local role = UnitGroupRolesAssigned(tgt) + if issecret(role) or role == "NONE" then role = nil end + Narrow(role, rosterRole) + + local okR, _, raceToken = pcall(UnitRace, tgt) + if not okR or issecret(raceToken) or type(raceToken) ~= "string" then + raceToken = nil + end + Narrow(raceToken, rosterRace) + + local okS, sex = pcall(UnitSex, tgt) + if not okS or issecret(sex) or type(sex) ~= "number" then + sex = nil + end + Narrow(sex, rosterSex) + + if #matchBuf ~= 1 then return nil end + return matchBuf[1] end ------------------------------------------------- --- show / hide +-- show / hide (Cell visuals) ------------------------------------------------- +local function GetGlowOptions() + local glow = Cell.vars and Cell.vars.targetedSpellsGlow + if type(glow) ~= "table" or not glow[1] or glow[1] == "None" then + return I.GetDefaultTargetedSpellsGlow() + end + return glow +end + +local function GetSwipeColor() + local glow = GetGlowOptions() + if type(glow[2]) == "table" then + return glow[2] + end + return SWIPE_COLOR +end + +local function EnsureTsGlowFrame(frame) + local glowFrame = frame.tsGlowFrame + if not glowFrame then + local button = frame:GetParent() + while button and not button.widgets do + button = button:GetParent() + end + if button and button.widgets then + if not button.widgets.tsGlowFrame then + glowFrame = CreateFrame("Frame", button:GetName().."TSGlowFrame", button) + button.widgets.tsGlowFrame = glowFrame + glowFrame:SetFrameLevel(button:GetFrameLevel() + 200) + glowFrame:SetAllPoints(button) + end + frame.tsGlowFrame = button.widgets.tsGlowFrame + glowFrame = frame.tsGlowFrame + end + end + if glowFrame then + glowFrame:Show() + glowFrame:SetAlpha(1) + local parent = glowFrame:GetParent() + if parent then + local w, h = parent:GetWidth(), parent:GetHeight() + if w > 0 and h > 0 and (glowFrame:GetWidth() == 0 or glowFrame:GetHeight() == 0) then + glowFrame:SetSize(w, h) + glowFrame:ClearAllPoints() + glowFrame:SetAllPoints(parent) + end + end + end + return glowFrame +end + local function HideCasts(b) - b.indicators.targetedSpells:UpdateSize(0) - b.indicators.targetedSpells:HideGlow() + local ts = b.indicators and b.indicators.targetedSpells + if not ts then return end + ts:UpdateSize(0) + ts:HideGlow() + EnsureTsGlowFrame(ts) end local function ShowCasts(b, showGlow, sortedCasts, num) + local ts = b.indicators and b.indicators.targetedSpells + if not ts then return end + + ts:Show() + + if displayMode == "None" then + ts:UpdateSize(0) + ts:HideGlow() + return + end + num = min(maxIcons, num) for i = 1, num do local cast = sortedCasts[i] - b.indicators.targetedSpells[i].cooldown:SetReverse(not cast.isChanneling) - b.indicators.targetedSpells[i]:SetCooldown(cast.startTime, cast.endTime-cast.startTime, cast.icon, cast.count) - end - b.indicators.targetedSpells:UpdateSize(num) - - if showGlow then - b.indicators.targetedSpells:ShowGlow(unpack(Cell.vars.targetedSpellsGlow)) - else - b.indicators.targetedSpells:HideGlow() + ts[i].cooldown:SetReverse(not cast.isChanneling) + ts[i]:SetCooldown(cast.startTime, cast.endTime - cast.startTime, cast.icon, cast.count) end + ts:UpdateSize(num) + ts:HideGlow() end -------------------------------------------------- --- update casts for guid -------------------------------------------------- -local function GetCastsOnUnit(guid) - if castsOnUnit[guid] then - wipe(castsOnUnit[guid]) - wipe(sortedCastsOnUnit[guid]) +local function GetCastsOnUnit(targetUnit) + if castsOnUnit[targetUnit] then + wipe(castsOnUnit[targetUnit]) + wipe(sortedCastsOnUnit[targetUnit]) else - castsOnUnit[guid] = {} - sortedCastsOnUnit[guid] = {} + castsOnUnit[targetUnit] = {} + sortedCastsOnUnit[targetUnit] = {} end + local castIndex = 0 local inListFound - for sourceGUID, castInfo in pairs(casts) do - if guid == castInfo["targetGUID"] then - if castInfo["endTime"] > GetTime() then -- not expired - local spellId = castInfo["spellId"] - if not castsOnUnit[guid][spellId] then - castsOnUnit[guid][spellId] = {["count"] = 0} + local now = GetTime() + for sourceKey, castInfo in pairs(casts) do + if targetUnit == castInfo.targetUnit then + if castInfo.endTime > now then + castIndex = castIndex + 1 + local key = castInfo.nonSecretSpellId or castIndex + local bucket = castsOnUnit[targetUnit][key] + if not bucket then + bucket = { count = 0 } + castsOnUnit[targetUnit][key] = bucket end - if not castsOnUnit[guid][spellId]["endTime"] or castsOnUnit[guid][spellId]["endTime"] > castInfo["endTime"] then --! shorter duration - castsOnUnit[guid][spellId]["startTime"] = castInfo["startTime"] - castsOnUnit[guid][spellId]["endTime"] = castInfo["endTime"] - castsOnUnit[guid][spellId]["icon"] = castInfo["icon"] + if not bucket.endTime or bucket.endTime > castInfo.endTime then + bucket.startTime = castInfo.startTime + bucket.endTime = castInfo.endTime + bucket.icon = castInfo.icon + bucket.isChanneling = castInfo.isChanneling + bucket.inList = castInfo.inList end - castsOnUnit[guid][spellId]["count"] = castsOnUnit[guid][spellId]["count"] + 1 - - if Cell.vars.targetedSpellsList[spellId] then - castsOnUnit[guid][spellId]["inList"] = true + bucket.count = bucket.count + 1 + if castInfo.inList then + bucket.inList = true inListFound = true end else - casts[sourceGUID] = nil + casts[sourceKey] = nil end end end - - return castsOnUnit[guid], inListFound + return castsOnUnit[targetUnit], inListFound end local function Comparator(a, b) @@ -98,205 +293,306 @@ local function Comparator(a, b) return a.startTime < b.startTime end -local function UpdateCastsOnUnit(guid) - if not guid then return end - - -- local startTime, endTime, spellId, icon, isChanneling - local t, showGlow = GetCastsOnUnit(guid) - - for spellId, castInfo in pairs(t) do - tinsert(sortedCastsOnUnit[guid], castInfo) - - -- if not endTime then --! init - -- startTime, endTime, spellId, icon, isChanneling = castInfo["startTime"], castInfo["endTime"], castInfo["spellId"], castInfo["icon"], castInfo["isChanneling"] - -- else - -- spellId = castInfo["spellId"] - -- if Cell.vars.targetedSpellsList[spellId] then --! [IN LIST] - -- if not inListFound or endTime > castInfo["endTime"] then --! NOT FOUND BEFORE or SHORTER DURATION - -- startTime, endTime, icon, isChanneling = castInfo["startTime"], castInfo["endTime"], castInfo["icon"], castInfo["isChanneling"] - -- end - -- elseif not inListFound and endTime > castInfo["endTime"] then --! [NOT IN LIST] NOT FOUND BEFORE and SHORTER DURATION - -- startTime, endTime, icon, isChanneling = castInfo["startTime"], castInfo["endTime"], castInfo["icon"], castInfo["isChanneling"] - -- end - -- end - - -- if Cell.vars.targetedSpellsList[spellId] then - -- inListFound = true - -- end +local function UpdateCastsOnUnit(targetUnit) + if not targetUnit then return end + local t, showGlow = GetCastsOnUnit(targetUnit) + for _, castInfo in pairs(t) do + tinsert(sortedCastsOnUnit[targetUnit], castInfo) end - - local n = #sortedCastsOnUnit[guid] - + local n = #sortedCastsOnUnit[targetUnit] if n == 0 then - F.HandleUnitButton("guid", guid, HideCasts) + F.HandleUnitButton("unit", targetUnit, HideCasts) else - table.sort(sortedCastsOnUnit[guid], Comparator) - F.HandleUnitButton("guid", guid, ShowCasts, showGlow, sortedCastsOnUnit[guid], n) + table.sort(sortedCastsOnUnit[targetUnit], Comparator) + F.HandleUnitButton("unit", targetUnit, ShowCasts, showGlow, sortedCastsOnUnit[targetUnit], n) + end +end + +local function HideAll() + F.IterateAllUnitButtons(HideCasts, true) +end + +local function RefreshAllShown() + local seen = {} + for _, castInfo in pairs(casts) do + local u = castInfo.targetUnit + if u and not seen[u] then + seen[u] = true + UpdateCastsOnUnit(u) + end end end ------------------------------------------------- --- check if sourceUnit is casting +-- cast resolve ------------------------------------------------- -local function CheckUnitCast(sourceUnit, isRecheck) - if not UnitIsEnemy("player", sourceUnit) then return end - - -- On Midnight 12.0.0+, enemy spellcast info is secret in instances - -- Player's own casts (and pets) are always non-secret - if Cell.isMidnight then - local isPlayerCast = (sourceUnit == "player" or sourceUnit == "pet" or sourceUnit == "vehicle") - if not isPlayerCast and F.IsAuraRestricted and F.IsAuraRestricted() then - return -- skip enemy spell tracking during restricted periods +local function RebuildNameIndex() + wipe(targetedSpellsByName) + local list = Cell.vars and Cell.vars.targetedSpellsList + if type(list) ~= "table" then return end + for spellId in pairs(list) do + if type(spellId) == "number" then + local name = F.GetSpellInfo and F.GetSpellInfo(spellId) + if type(name) == "string" and name ~= "" then + targetedSpellsByName[name] = spellId + end end end +end - local sourceGUID = UnitGUID(sourceUnit) - -- Midnight 12.0.0+: UnitGUID for nameplates may return secret strings - if Cell.isMidnight and issecretvalue and issecretvalue(sourceGUID) then return end - local targetGUID - local previousTarget, isChanneling +local function ShouldTrackSpell(spellId, castName) + local nonSecretId = (spellId ~= nil and F.IsValueNonSecret(spellId)) and spellId or nil + local nonSecretName = (type(castName) == "string" and F.IsValueNonSecret(castName)) and castName or nil + + local list = Cell.vars and Cell.vars.targetedSpellsList + local inList = false + local resolvedId = nonSecretId + + if nonSecretId and list and list[nonSecretId] then + inList = true + elseif nonSecretName and targetedSpellsByName[nonSecretName] then + inList = true + resolvedId = resolvedId or targetedSpellsByName[nonSecretName] + elseif not nonSecretId and not nonSecretName then + if displayMode == "None" then + return false, false, nil + end + return true, true, nil + end - if casts[sourceGUID] then - previousTarget = casts[sourceGUID]["targetGUID"] - if casts[sourceGUID]["endTime"] <= GetTime() then - --! expired - casts[sourceGUID] = nil - UpdateCastsOnUnit(previousTarget) - previousTarget = nil + if inList then + return true, true, resolvedId + end + if showAllSpells then + return true, false, resolvedId + end + if displayMode == "Icons" then + if C_Spell and C_Spell.IsSpellImportant and spellId ~= nil then + local important = C_Spell.IsSpellImportant(spellId) + if not F.IsValueNonSecret(important) or important then + return true, true, resolvedId + end end + return false, false, resolvedId + end + if displayMode == "Border" or displayMode == "Both" then + return true, false, resolvedId end + return false, false, resolvedId +end + +local function Resolve(sourceUnit, myGen) + if not enabled or not IsPartyContext() then return end + if gen[sourceUnit] ~= myGen then return end - -- name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellId - local name, _, texture, startTimeMS, endTimeMS, _, _, notInterruptible, spellId = UnitCastingInfo(sourceUnit) - if not name then - -- name, text, texture, startTimeMS, endTimeMS, isTradeSkill, notInterruptible, spellId - name, _, texture, startTimeMS, endTimeMS, _, notInterruptible, spellId = UnitChannelInfo(sourceUnit) + local name, _, texture, startMS, endMS, _, _, _, spellId = UnitCastingInfo(sourceUnit) + local isChanneling = false + if type(name) == "nil" then + name, _, texture, startMS, endMS, _, _, spellId = UnitChannelInfo(sourceUnit) isChanneling = true end + if type(name) == "nil" then return end - -- print(sourceUnit, name, spellId) - - if spellId and (Cell.vars.targetedSpellsList[spellId] or showAllSpells) then - if casts[sourceGUID] then - casts[sourceGUID]["startTime"] = startTimeMS/1000 - casts[sourceGUID]["endTime"] = endTimeMS/1000 - casts[sourceGUID]["spellId"] = spellId - casts[sourceGUID]["icon"] = texture - else - casts[sourceGUID] = { - ["startTime"] = startTimeMS/1000, - ["endTime"] = endTimeMS/1000, - ["spellId"] = spellId, - ["icon"] = texture, - ["isChanneling"] = isChanneling, - -- ["targetGUID"] = targetGUID, - -- ["sourceUnit"] = sourceUnit, - -- ["targetUnit"] = targetUnit, - ["recheck"] = 0, - } - end + local shouldTrack, inList, nonSecretSpellId = ShouldTrackSpell(spellId, name) + if not shouldTrack then return end - local targetUnit = sourceUnit.."target" - targetUnit = F.GetTargetUnitID(targetUnit) -- units in group (players/pets), no npcs - if targetUnit then targetGUID = UnitGUID(targetUnit) end + if Cell.isMidnight and C_Spell and C_Spell.GetSpellTexture and nonSecretSpellId then + local tex = C_Spell.GetSpellTexture(nonSecretSpellId) + if tex then texture = tex end + elseif Cell.isMidnight and C_Spell and C_Spell.GetSpellTexture and spellId and F.IsValueNonSecret(spellId) then + local tex = C_Spell.GetSpellTexture(spellId) + if tex then texture = tex end + end - -- update spell target - casts[sourceGUID]["targetUnit"] = targetUnit - casts[sourceGUID]["targetGUID"] = targetGUID - casts[sourceGUID]["nonNameplate"] = not strfind(sourceUnit, "^nameplate") + local startTime, endTime + if F.IsValueNonSecret(startMS) and F.IsValueNonSecret(endMS) then + startTime = startMS / 1000 + endTime = endMS / 1000 + else + startTime = GetTime() + endTime = GetTime() + 3 + end - UpdateCastsOnUnit(targetGUID) + local previousTarget = casts[sourceUnit] and casts[sourceUnit].targetUnit + if GetTime() - lastRosterSync > 2 then + RebuildRoster() + end + local targetUnit = Classify(sourceUnit) - if not isRecheck then - if not recheck[sourceGUID] or not (strfind(sourceUnit, "target$") or strfind(sourceUnit, "^nameplate")) then - recheck[sourceGUID] = sourceUnit + if not targetUnit then + if casts[sourceUnit] then + casts[sourceUnit] = nil + if previousTarget then + UpdateCastsOnUnit(previousTarget) end - eventFrame:Show() end + return end - if previousTarget and previousTarget ~= targetGUID then + casts[sourceUnit] = { + startTime = startTime, + endTime = endTime, + spellId = spellId, + nonSecretSpellId = nonSecretSpellId, + icon = texture or "Interface\\Icons\\INV_Misc_QuestionMark", + isChanneling = isChanneling, + inList = inList, + sourceUnit = sourceUnit, + targetUnit = targetUnit, + nonNameplate = not strfind(sourceUnit, "^nameplate"), + } + + if previousTarget and previousTarget ~= targetUnit then UpdateCastsOnUnit(previousTarget) end + UpdateCastsOnUnit(targetUnit) end -------------------------------------------------- --- recheck -------------------------------------------------- -eventFrame:Hide() -eventFrame:SetScript("OnUpdate", function(self, elapsed) - self.elapsed = (self.elapsed or 0) + elapsed - if self.elapsed >= 0.1 then - self.elapsed = 0 - - local empty = true - - for guid, unit in pairs(recheck) do - if casts[guid] then - casts[guid]["recheck"] = casts[guid]["recheck"] + 1 - if casts[guid]["recheck"] >= 6 then - recheck[guid] = nil - else - empty = false - local recheckRequired = (not casts[guid]["targetUnit"] and UnitExists(unit.."target")) or (casts[guid]["targetUnit"] and not UnitIsUnit(unit.."target", casts[guid]["targetUnit"])) - if recheckRequired then - -- print(unit, casts[guid]["recheck"], recheckRequired) - CheckUnitCast(unit, true) - end - end - else - recheck[guid] = nil - end - end +local function OnCastStart(sourceUnit) + if not enabled or not IsPartyContext() then return end + if type(sourceUnit) ~= "string" then return end + + local hostile = UnitCanAttack("player", sourceUnit) + if not issecret(hostile) and hostile ~= true then return end - if empty then - eventFrame:Hide() + gen[sourceUnit] = (gen[sourceUnit] or 0) + 1 + local myGen = gen[sourceUnit] + local prev = casts[sourceUnit] + if prev then + local previousTarget = prev.targetUnit + casts[sourceUnit] = nil + if previousTarget then + UpdateCastsOnUnit(previousTarget) end end -end) + + C_Timer.After(PICKUP_DELAY, function() + Resolve(sourceUnit, myGen) + end) + C_Timer.After(PICKUP_DELAY + VERIFY_DELAY, function() + Resolve(sourceUnit, myGen) + end) +end + +local function OnCastStop(sourceUnit) + if not sourceUnit then return end + gen[sourceUnit] = (gen[sourceUnit] or 0) + 1 + local prev = casts[sourceUnit] + if not prev then return end + local previousTarget = prev.targetUnit + casts[sourceUnit] = nil + if previousTarget then + UpdateCastsOnUnit(previousTarget) + end +end + +local function OnRetarget(sourceUnit) + if not enabled or not IsPartyContext() then return end + if not casts[sourceUnit] and not plateTokens[sourceUnit] then return end + gen[sourceUnit] = (gen[sourceUnit] or 0) + 1 + local myGen = gen[sourceUnit] + C_Timer.After(RETARGET_DELAY, function() + Resolve(sourceUnit, myGen) + end) + C_Timer.After(RETARGET_DELAY + VERIFY_DELAY, function() + Resolve(sourceUnit, myGen) + end) +end + +local function ClearEverything() + WipeCastTables() + HideAll() +end + +local function AdoptPlateCast(unit) + local castName = UnitCastingInfo(unit) + if type(castName) == "nil" then + castName = UnitChannelInfo(unit) + end + if type(castName) ~= "nil" then + OnCastStart(unit) + end +end ------------------------------------------------- -- events ------------------------------------------------- -eventFrame:SetScript("OnEvent", function(_, event, sourceUnit) - if event == "ENCOUNTER_END" then - Reset() - F.IterateAllUnitButtons(HideCasts, true) +eventFrame:SetScript("OnEvent", function(_, event, unit, ...) + if event == "PLAYER_REGEN_ENABLED" or event == "ENCOUNTER_END" then + ClearEverything() return end - if sourceUnit and strfind(sourceUnit, "^soft") then return end + if event == "GROUP_ROSTER_UPDATE" or event == "PLAYER_ENTERING_WORLD" then + if not IsPartyContext() then + ClearEverything() + return + end + RebuildRoster() + return + end - if event == "PLAYER_TARGET_CHANGED" then - CheckUnitCast("target") + if not enabled or not IsPartyContext() then return end - elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" or event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" or event == "NAME_PLATE_UNIT_ADDED" then - CheckUnitCast(sourceUnit) + if event == "NAME_PLATE_UNIT_ADDED" then + if type(unit) == "string" then + plateTokens[unit] = true + AdoptPlateCast(unit) + end + return + end - elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_INTERRUPTED" or event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then - local sourceGUID = UnitGUID(sourceUnit) - -- Midnight 12.0.0+: UnitGUID may return secret strings — can't use as table key - if issecretvalue and issecretvalue(sourceGUID) then return end - if casts[sourceGUID] then - previousTarget = casts[sourceGUID]["targetGUID"] - casts[sourceGUID] = nil - UpdateCastsOnUnit(previousTarget) + if event == "NAME_PLATE_UNIT_REMOVED" then + if type(unit) == "string" then + plateTokens[unit] = nil + local cast = casts[unit] + if cast and not cast.nonNameplate then + OnCastStop(unit) + end end + return + end - elseif event == "NAME_PLATE_UNIT_REMOVED" then - local sourceGUID = UnitGUID(sourceUnit) - -- Midnight 12.0.0+: UnitGUID may return secret strings — can't use as table key - if issecretvalue and issecretvalue(sourceGUID) then return end - if casts[sourceGUID] and not casts[sourceGUID]["nonNameplate"] then - previousTarget = casts[sourceGUID]["targetGUID"] - casts[sourceGUID] = nil - UpdateCastsOnUnit(previousTarget) + if unit and strfind(unit, "^soft") then return end + + local isPlate = unit and plateTokens[unit] + local isTarget = unit == "target" + + if event == "UNIT_SPELLCAST_START" + or event == "UNIT_SPELLCAST_CHANNEL_START" + or event == "UNIT_SPELLCAST_EMPOWER_START" + then + if isPlate or isTarget then + OnCastStart(unit) + end + elseif event == "UNIT_SPELLCAST_STOP" + or event == "UNIT_SPELLCAST_INTERRUPTED" + or event == "UNIT_SPELLCAST_FAILED" + or event == "UNIT_SPELLCAST_FAILED_QUIET" + or event == "UNIT_SPELLCAST_CHANNEL_STOP" + or event == "UNIT_SPELLCAST_EMPOWER_STOP" + then + if isPlate or isTarget or casts[unit] then + OnCastStop(unit) end + elseif event == "UNIT_SPELLCAST_DELAYED" + or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" + or event == "UNIT_SPELLCAST_EMPOWER_UPDATE" + then + if casts[unit] then + OnRetarget(unit) + end + elseif event == "UNIT_TARGET" then + if casts[unit] or isPlate then + OnRetarget(unit) + end + elseif event == "PLAYER_TARGET_CHANGED" then + AdoptPlateCast("target") end end) ------------------------------------------------- --- create +-- create (Cell look) ------------------------------------------------- local function SetCooldown(frame, start, duration, icon, count) frame.duration:Hide() @@ -310,7 +606,7 @@ local function SetCooldown(frame, start, duration, icon, count) frame.border:Show() frame.cooldown:Show() - frame.cooldown:SetSwipeColor(unpack(Cell.vars.targetedSpellsGlow[2])) + frame.cooldown:SetSwipeColor(unpack(GetSwipeColor())) frame.cooldown:SetCooldown(start, duration) frame.icon:SetTexture(icon) frame:Show() @@ -322,47 +618,55 @@ local function SetFont(frame, ...) end end -local function ShowGlowPreview(frame) - frame:ShowGlow(unpack(Cell.vars.targetedSpellsGlow)) +local function IgnoreGlowParentAlpha(glowFrame) + if not glowFrame then return end + for _, suffix in ipairs({ "_PixelGlow", "_ButtonGlow", "_AutoCastGlow", "_ProcGlow" }) do + local child = glowFrame[suffix] + if child then + child:SetIgnoreParentAlpha(true) + end + end +end + +local function HideGlow(frame) + local glowFrame = frame.tsGlowFrame + if not glowFrame then return end + LCG.ButtonGlow_Stop(glowFrame) + LCG.PixelGlow_Stop(glowFrame) + LCG.AutoCastGlow_Stop(glowFrame) + LCG.ProcGlow_Stop(glowFrame) end local function ShowGlow(frame, glowType, color, arg1, arg2, arg3, arg4) - if glowType == "Normal" then - LCG.PixelGlow_Stop(frame.tsGlowFrame) - LCG.AutoCastGlow_Stop(frame.tsGlowFrame) - LCG.ProcGlow_Stop(frame.tsGlowFrame) - LCG.ButtonGlow_Start(frame.tsGlowFrame, color) - elseif glowType == "Pixel" then - LCG.ButtonGlow_Stop(frame.tsGlowFrame) - LCG.AutoCastGlow_Stop(frame.tsGlowFrame) - LCG.ProcGlow_Stop(frame.tsGlowFrame) - -- color, N, frequency, length, thickness - LCG.PixelGlow_Start(frame.tsGlowFrame, color, arg1, arg2, arg3, arg4) - elseif glowType == "Shine" then - LCG.ButtonGlow_Stop(frame.tsGlowFrame) - LCG.PixelGlow_Stop(frame.tsGlowFrame) - LCG.ProcGlow_Stop(frame.tsGlowFrame) - -- color, N, frequency, scale - LCG.AutoCastGlow_Start(frame.tsGlowFrame, color, arg1, arg2, arg3) - elseif glowType == "Proc" then - LCG.ButtonGlow_Stop(frame.tsGlowFrame) - LCG.PixelGlow_Stop(frame.tsGlowFrame) - LCG.AutoCastGlow_Stop(frame.tsGlowFrame) - -- color, duration - LCG.ProcGlow_Start(frame.tsGlowFrame, {color=color, duration=arg1, startAnim=false}) - else - LCG.ButtonGlow_Stop(frame.tsGlowFrame) - LCG.PixelGlow_Stop(frame.tsGlowFrame) - LCG.AutoCastGlow_Stop(frame.tsGlowFrame) - LCG.ProcGlow_Stop(frame.tsGlowFrame) + HideGlow(frame) +end + +local function ShowPreview(frame) + frame:Show() + + if displayMode == "None" then + for i = 1, #frame do + frame[i]:Hide() + end + frame:UpdateSize(0) + frame:HideGlow() + return + end + + local num = min(maxIcons or 1, #frame) + for i = 1, num do + frame[i]:Show() end + frame:UpdateSize(num) + frame:HideGlow() end -local function HideGlow(frame) - LCG.ButtonGlow_Stop(frame.tsGlowFrame) - LCG.PixelGlow_Stop(frame.tsGlowFrame) - LCG.AutoCastGlow_Stop(frame.tsGlowFrame) - LCG.ProcGlow_Stop(frame.tsGlowFrame) +local function HidePreview(frame) + for i = 1, #frame do + frame[i]:Hide() + end + frame:UpdateSize(0) + frame:HideGlow() end function I.CreateTargetedSpells(parent) @@ -376,18 +680,17 @@ function I.CreateTargetedSpells(parent) targetedSpells.SetBorder = I.Cooldowns_SetBorder targetedSpells.UpdateSize = I.Cooldowns_UpdateSize_WithSpacing targetedSpells.SetOrientation = I.Cooldowns_SetOrientation_WithSpacing + targetedSpells.SetFont = SetFont targetedSpells.ShowGlow = ShowGlow targetedSpells.HideGlow = HideGlow - targetedSpells.SetFont = SetFont - targetedSpells.ShowGlowPreview = ShowGlowPreview - targetedSpells.HideGlowPreview = HideGlow + targetedSpells.SetCooldownStyle = I.Cooldowns_SetCooldownStyle + targetedSpells.ShowGlowPreview = ShowPreview + targetedSpells.HideGlowPreview = HidePreview for i = 1, 3 do local frame = I.CreateAura_BorderIcon(parent:GetName().."TargetedSpells"..i, targetedSpells, 2) tinsert(targetedSpells, frame) frame.SetCooldown = SetCooldown - -- frame:SetScript("OnShow", targetedSpells.UpdateSize) - -- frame:SetScript("OnHide", targetedSpells.UpdateSize) frame.cooldown:SetScript("OnCooldownDone", function() frame:Hide() end) @@ -395,53 +698,78 @@ function I.CreateTargetedSpells(parent) end ------------------------------------------------- --- functions +-- enable / config ------------------------------------------------- --- NOTE: in case there's a casting spell, hide! -local function EnterLeaveInstance() - Reset() - F.IterateAllUnitButtons(HideCasts, true) +local function RegisterEvents() + eventFrame:UnregisterAllEvents() + eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED") + eventFrame:RegisterEvent("ENCOUNTER_END") + eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE") + eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + eventFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED") + eventFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED") + eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED") + eventFrame:RegisterEvent("UNIT_SPELLCAST_START") + eventFrame:RegisterEvent("UNIT_SPELLCAST_STOP") + eventFrame:RegisterEvent("UNIT_SPELLCAST_FAILED") + eventFrame:RegisterEvent("UNIT_SPELLCAST_FAILED_QUIET") + eventFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") + eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") + eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") + eventFrame:RegisterEvent("UNIT_SPELLCAST_DELAYED") + eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE") + eventFrame:RegisterEvent("UNIT_TARGET") + if Cell.isRetail then + eventFrame:RegisterEvent("UNIT_SPELLCAST_EMPOWER_START") + eventFrame:RegisterEvent("UNIT_SPELLCAST_EMPOWER_STOP") + eventFrame:RegisterEvent("UNIT_SPELLCAST_EMPOWER_UPDATE") + end + eventFrame:Show() end -function I.EnableTargetedSpells(enabled) - if enabled then - F.IterateAllUnitButtons(function(b) - b.indicators.targetedSpells:Show() - end, true) - - -- UNIT_SPELLCAST_DELAYED UNIT_SPELLCAST_FAILED UNIT_SPELLCAST_INTERRUPTED UNIT_SPELLCAST_START UNIT_SPELLCAST_STOP - -- UNIT_SPELLCAST_CHANNEL_START UNIT_SPELLCAST_CHANNEL_STOP - -- PLAYER_TARGET_CHANGED ENCOUNTER_END - - eventFrame:RegisterEvent("UNIT_SPELLCAST_START") - eventFrame:RegisterEvent("UNIT_SPELLCAST_STOP") - eventFrame:RegisterEvent("UNIT_SPELLCAST_DELAYED") - eventFrame:RegisterEvent("UNIT_SPELLCAST_FAILED") - eventFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") - eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START") - eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP") - eventFrame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE") - - eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED") - eventFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED") - eventFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED") +function I.EnableTargetedSpells(enable) + enabled = not not enable + ClearEverything() + wipe(plateTokens) - eventFrame:RegisterEvent("ENCOUNTER_END") - - Cell.RegisterCallback("EnterInstance", "TargetedSpells_EnterInstance", EnterLeaveInstance) - Cell.RegisterCallback("LeaveInstance", "TargetedSpells_LeaveInstance", EnterLeaveInstance) - else - Reset() + if not enabled then eventFrame:Hide() eventFrame:UnregisterAllEvents() - - Cell.UnregisterCallback("EnterInstance", "TargetedSpells_EnterInstance") - Cell.UnregisterCallback("LeaveInstance", "TargetedSpells_LeaveInstance") - F.IterateAllUnitButtons(function(b) - HideCasts(b) - b.indicators.targetedSpells:Hide() + local ts = b.indicators and b.indicators.targetedSpells + if ts then + ts:HideGlow() + ts:Hide() + end end, true) + return + end + + RebuildNameIndex() + RegisterEvents() + F.IterateAllUnitButtons(function(b) + local ts = b.indicators and b.indicators.targetedSpells + if ts then + ts:Show() + end + end, true) + + if IsPartyContext() then + RebuildRoster() + if C_NamePlate and C_NamePlate.GetNamePlates then + local plates = C_NamePlate.GetNamePlates() + if type(plates) == "table" then + for i = 1, #plates do + local plate = plates[i] + local u = plate and plate.namePlateUnitToken + if type(u) == "string" then + plateTokens[u] = true + AdoptPlateCast(u) + end + end + end + end + AdoptPlateCast("target") end end @@ -449,6 +777,30 @@ function I.ShowAllTargetedSpells(showAll) showAllSpells = showAll end +function I.RefreshTargetedSpellsList() + RebuildNameIndex() +end + function I.UpdateTargetedSpellsNum(num) - maxIcons = num -end \ No newline at end of file + maxIcons = num or 1 + RefreshAllShown() +end + +function I.UpdateTargetedSpellsDisplayMode(mode) + if mode == "Border" or mode == "Both" then + mode = "Icons" + end + if mode == "None" or mode == "Icons" then + displayMode = mode + else + displayMode = "Icons" + end + local seen = {} + for _, castInfo in pairs(casts) do + local u = castInfo.targetUnit + if u and not seen[u] then + seen[u] = true + UpdateCastsOnUnit(u) + end + end +end diff --git a/Libs/LibGroupInfo.lua b/Libs/LibGroupInfo.lua index a9644ee4..ec66f9c7 100644 --- a/Libs/LibGroupInfo.lua +++ b/Libs/LibGroupInfo.lua @@ -51,11 +51,22 @@ local cache = { } lib.cache = cache +local function CanIndexCache(key) + if key == nil then return false end + if issecretvalue then + local ok, secret = pcall(issecretvalue, key) + if ok and secret then return false end + end + return true +end + function lib:GetCachedInfo(guid) - return guid and cache[guid] + if not CanIndexCache(guid) then return end + return cache[guid] end function lib:GuidToUnit(guid) + if not CanIndexCache(guid) then return end if cache[guid] then return cache[guid].unit end @@ -446,7 +457,17 @@ end) local function AddToQueue(unit, guid) if IS_WRATH or IS_MISTS then - if not UnitIsConnected(unit) or not CheckInteractDistance(unit, 1) or not CanInspect(unit) then + if not UnitIsConnected(unit) or not CanInspect(unit) then + UpdateBaseInfo(unit, guid) + return + end + -- MoP 5.5.x: CheckInteractDistance is protected in combat (ADDON_ACTION_BLOCKED). + -- Skip the distance check while locked down; Wrath/Classic OOC path unchanged. + if IS_MISTS and InCombatLockdown() then + UpdateBaseInfo(unit, guid) + return + end + if not CheckInteractDistance(unit, 1) then UpdateBaseInfo(unit, guid) return end @@ -598,7 +619,6 @@ end function frame:UNIT_LEVEL(unit) local guid = UnitGUID(unit) - -- Midnight 12.0.0+: nameplate GUIDs may be secret; cannot use as table key if not guid or (issecretvalue and issecretvalue(guid)) then return end if cache[guid] then cache[guid].level = UnitLevel(unit) diff --git a/Libs/PixelPerfect.lua b/Libs/PixelPerfect.lua index 271922a1..3ff88208 100644 --- a/Libs/PixelPerfect.lua +++ b/Libs/PixelPerfect.lua @@ -160,16 +160,19 @@ function P.Size(frame, width, height) frame.width = width frame.height = height frame:SetSize(P.Scale(width), P.Scale(height)) + P.DisablePixelSnap(frame) end function P.Width(frame, width) frame.width = width frame:SetWidth(P.Scale(width)) + P.DisablePixelSnap(frame) end function P.Height(frame, height) frame.height = height frame:SetHeight(P.Scale(height)) + P.DisablePixelSnap(frame) end function P.SetGridSize(region, gridWidth, gridHeight, gridSpacingH, gridSpacingV, columns, rows) @@ -192,6 +195,7 @@ function P.SetGridSize(region, gridWidth, gridHeight, gridSpacingH, gridSpacingV else region:SetHeight(P.Scale(gridHeight) * rows + P.Scale(gridSpacingV) * (rows - 1)) end + P.DisablePixelSnap(region) end function P.Point(frame, ...) @@ -212,6 +216,7 @@ function P.Point(frame, ...) tinsert(frame.points, {point, anchorTo or frame:GetParent(), anchorPoint or point, x or 0, y or 0}) local n = #frame.points frame:SetPoint(frame.points[n][1], frame.points[n][2], frame.points[n][3], P.Scale(frame.points[n][4]), P.Scale(frame.points[n][5])) + P.DisablePixelSnap(frame) end function P.ClearPoints(frame) @@ -233,6 +238,7 @@ function P.Resize(frame) frame:SetHeight(P.Scale(frame.height)) end end + P.DisablePixelSnap(frame) end function P.Reborder(frame, ignoreSnippetVar) @@ -336,46 +342,83 @@ end --------------------------------------------------------------------- -- pixel perfect (ElvUI) --------------------------------------------------------------------- -local function CheckPixelSnap(frame, snap) - if (frame and not frame:IsForbidden()) and frame.PixelSnapDisabled and snap then - frame.PixelSnapDisabled = nil +local pixelSnapDisabled = setmetatable({}, { __mode = "k" }) +local pixelSnapCreateHooked = setmetatable({}, { __mode = "k" }) + +function P.DisablePixelSnap(frame) + if not frame then return end + if issecretvalue and issecretvalue(frame) then return end + if issecrettable and issecrettable(frame) then return end + if frame.IsForbidden and frame:IsForbidden() then return end + if pixelSnapDisabled[frame] then return end + + local target = frame + if not frame.SetSnapToPixelGrid and frame.GetStatusBarTexture then + target = frame:GetStatusBarTexture() + if type(target) ~= "table" or not target.SetSnapToPixelGrid then + pixelSnapDisabled[frame] = true + return + end end -end -local function DisablePixelSnap(frame) - if (frame and not frame:IsForbidden()) and not frame.PixelSnapDisabled then - if frame.SetSnapToPixelGrid then - frame:SetSnapToPixelGrid(false) - frame:SetTexelSnappingBias(0) - frame.PixelSnapDisabled = true - elseif frame.GetStatusBarTexture then - local texture = frame:GetStatusBarTexture() - if type(texture) == "table" and texture.SetSnapToPixelGrid then - texture:SetSnapToPixelGrid(false) - texture:SetTexelSnappingBias(0) - frame.PixelSnapDisabled = true + if target and target.SetSnapToPixelGrid then + target:SetSnapToPixelGrid(false) + target:SetTexelSnappingBias(0) + end + pixelSnapDisabled[frame] = true + + if frame.CreateTexture and not pixelSnapCreateHooked[frame] then + pixelSnapCreateHooked[frame] = true + local origCreateTexture = frame.CreateTexture + frame.CreateTexture = function(self, ...) + local tex = origCreateTexture(self, ...) + if tex then + P.DisablePixelSnap(tex) + end + return tex + end + if frame.CreateMaskTexture then + local origCreateMask = frame.CreateMaskTexture + frame.CreateMaskTexture = function(self, ...) + local tex = origCreateMask(self, ...) + if tex then + P.DisablePixelSnap(tex) + end + return tex end end end end +local function CheckPixelSnap(frame, snap) + if frame and pixelSnapDisabled[frame] and snap then + if not (frame.IsForbidden and frame:IsForbidden()) then + pixelSnapDisabled[frame] = nil + end + end +end + local function UpdateMetatable(obj) local t = getmetatable(obj).__index if not obj.DisabledPixelSnap and (t.SetSnapToPixelGrid or t.SetStatusBarTexture or t.SetColorTexture or t.SetVertexColor or t.CreateTexture or t.SetTexCoord or t.SetTexture) then if t.SetSnapToPixelGrid then hooksecurefunc(t, "SetSnapToPixelGrid", CheckPixelSnap) end - if t.SetStatusBarTexture then hooksecurefunc(t, "SetStatusBarTexture", DisablePixelSnap) end - if t.SetColorTexture then hooksecurefunc(t, "SetColorTexture", DisablePixelSnap) end - if t.SetVertexColor then hooksecurefunc(t, "SetVertexColor", DisablePixelSnap) end - if t.CreateTexture then hooksecurefunc(t, "CreateTexture", DisablePixelSnap) end - if t.SetTexCoord then hooksecurefunc(t, "SetTexCoord", DisablePixelSnap) end - if t.SetTexture then hooksecurefunc(t, "SetTexture", DisablePixelSnap) end + if t.SetStatusBarTexture then hooksecurefunc(t, "SetStatusBarTexture", P.DisablePixelSnap) end + if t.SetColorTexture then hooksecurefunc(t, "SetColorTexture", P.DisablePixelSnap) end + if t.SetVertexColor then hooksecurefunc(t, "SetVertexColor", P.DisablePixelSnap) end + if t.CreateTexture then hooksecurefunc(t, "CreateTexture", P.DisablePixelSnap) end + if t.SetTexCoord then hooksecurefunc(t, "SetTexCoord", P.DisablePixelSnap) end + if t.SetTexture then hooksecurefunc(t, "SetTexture", P.DisablePixelSnap) end t.DisabledPixelSnap = true end end -local obj = CreateFrame("Frame") -UpdateMetatable(CreateFrame("StatusBar")) -UpdateMetatable(obj:CreateTexture()) -UpdateMetatable(obj:CreateMaskTexture()) \ No newline at end of file +local build = select(4, GetBuildInfo()) +local skipSharedMetatableHooks = (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE) and build and build >= 120000 +if not skipSharedMetatableHooks then + local obj = CreateFrame("Frame") + UpdateMetatable(CreateFrame("StatusBar")) + UpdateMetatable(obj:CreateTexture()) + UpdateMetatable(obj:CreateMaskTexture()) +end \ No newline at end of file diff --git a/Libs/utf8.lua b/Libs/utf8.lua index 2398e146..c62fd70e 100644 --- a/Libs/utf8.lua +++ b/Libs/utf8.lua @@ -157,84 +157,85 @@ end -- returns the number of characters in a UTF-8 string local function utf8len (s) - -- argument checking - if type(s) ~= "string" then - error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")") - end + if type(s) ~= "string" then + error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")") + end - local pos = 1 - local bytes = s:len() - local len = 0 + if issecretvalue and issecretvalue(s) then + return 0 + end - while pos <= bytes do - len = len + 1 - pos = pos + utf8charbytes(s, pos) - end + local pos = 1 + local bytes = s:len() + local len = 0 - return len + while pos <= bytes do + len = len + 1 + pos = pos + utf8charbytes(s, pos) + end + + return len end -- install in the string library if not string.utf8len then - string.utf8len = utf8len + string.utf8len = utf8len end - --- functions identically to string.sub except that i and j are UTF-8 characters --- instead of bytes local function utf8sub (s, i, j) - -- argument defaults - j = j or -1 + if type(s) ~= "string" then + error("bad argument #1 to 'utf8sub' (string expected, got ".. type(s).. ")") + end - -- argument checking - if type(s) ~= "string" then - error("bad argument #1 to 'utf8sub' (string expected, got ".. type(s).. ")") - end - if type(i) ~= "number" then - error("bad argument #2 to 'utf8sub' (number expected, got ".. type(i).. ")") - end - if type(j) ~= "number" then - error("bad argument #3 to 'utf8sub' (number expected, got ".. type(j).. ")") - end + if issecretvalue and issecretvalue(s) then + return s + end - local pos = 1 - local bytes = s:len() - local len = 0 + -- argument defaults + j = j or -1 - -- only set l if i or j is negative - local l = (i >= 0 and j >= 0) or s:utf8len() - local startChar = (i >= 0) and i or l + i + 1 - local endChar = (j >= 0) and j or l + j + 1 + if type(i) ~= "number" then + error("bad argument #2 to 'utf8sub' (number expected, got ".. type(i).. ")") + end + if type(j) ~= "number" then + error("bad argument #3 to 'utf8sub' (number expected, got ".. type(j).. ")") + end - -- can't have start before end! - if startChar > endChar then - return "" - end + local pos = 1 + local bytes = s:len() + local len = 0 - -- byte offsets to pass to string.sub - local startByte, endByte = 1, bytes + local l = (i >= 0 and j >= 0) or s:utf8len() + local startChar = (i >= 0) and i or l + i + 1 + local endChar = (j >= 0) and j or l + j + 1 - while pos <= bytes do - len = len + 1 + if startChar > endChar then + return "" + end - if len == startChar then - startByte = pos - end + local startByte, endByte = 1, bytes - pos = pos + utf8charbytes(s, pos) + while pos <= bytes do + len = len + 1 - if len == endChar then - endByte = pos - 1 - break - end - end + if len == startChar then + startByte = pos + end + + pos = pos + utf8charbytes(s, pos) - return s:sub(startByte, endByte) + if len == endChar then + endByte = pos - 1 + break + end + end + + return s:sub(startByte, endByte) end -- install in the string library if not string.utf8sub then - string.utf8sub = utf8sub + string.utf8sub = utf8sub end @@ -248,6 +249,10 @@ local function utf8replace (s, mapping) error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")") end + if issecretvalue and issecretvalue(s) then + return s + end + local pos = 1 local bytes = s:len() local charbytes @@ -295,6 +300,10 @@ local function utf8reverse (s) error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")") end + if issecretvalue and issecretvalue(s) then + return s + end + local bytes = s:len() local pos = bytes local charbytes diff --git a/Locales/LoadLocales.xml b/Locales/LoadLocales.xml index 3260f43a..5154cb3d 100644 --- a/Locales/LoadLocales.xml +++ b/Locales/LoadLocales.xml @@ -1,5 +1,6 @@ +