-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStatsPro.lua
More file actions
5988 lines (5517 loc) · 311 KB
/
StatsPro.lua
File metadata and controls
5988 lines (5517 loc) · 311 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- StatsPro.lua
-- Inspired by SwiftStats by TaylorSay (MIT). Boilerplate, color defaults, and the
-- basic stat list are adapted from upstream; the rest is original work. See LICENSE
-- for full attribution.
local _, addon = ...
--[[ ============================================================
1. CONSTANTS
============================================================ ]]
local CURRENT_DB_VERSION = 9
local DURABILITY_SLOT_MIN = 1
local DURABILITY_SLOT_MAX = 19
-- WHY: slot 4 = shirt, slot 18 = deprecated ranged. Slot 19 (tabard) self-filters via max>0.
local DURABILITY_SKIP_SLOTS = { [4] = true, [18] = true }
local DURABILITY_GREEN_THRESHOLD = 60
local DURABILITY_YELLOW_THRESHOLD = 30
local ITEM_LEVEL_WARN_DELTA = 5
local ITEM_LEVEL_DANGER_DELTA = 20
local ITEM_LEVEL_WARN_COLOR = "ffcc33"
local ITEM_LEVEL_DANGER_COLOR = "ff3333"
local GLYPH_LATIN, GLYPH_CYR, GLYPH_HANGUL, GLYPH_HANS, GLYPH_HANT =
"Latin", "Cyrillic", "Hangul", "Hans", "Hant"
-- WHY hybrid native+English labels for non-Latin: dropdown buttons render with the
-- system font of the dropdown frame; on non-CJK clients the frame font lacks CJK
-- glyphs and "한국어" / "中文" render as `?` boxes. Adding English in parens keeps a
-- readable fallback on every client. Latin labels render universally — kept clean.
-- WARNING: LANGUAGE_OPTIONS[1] MUST be the auto entry; CurrentLabel() falls back to
-- it on unknown forceLocale values.
--
-- WHY four locale-keyed tables, two axes — read this BEFORE adding new ones:
-- * REQUIRED-BY-OUTPUT-LOCALE (what label-set we want to render):
-- LANGUAGE_OPTIONS (UI dropdown), LOCALE_GLYPH_REQ (glyph need),
-- LABELS_BY_LOCALE (label data). Indexed by `forceLocale` / output choice.
-- * PROVIDED-BY-CLIENT-LOCALE (what physical fonts THIS install ships):
-- LOCALE_NATIVE_GLYPHS (see do-block after FONT_GLYPH_SUPPORT).
-- Indexed by `GetLocale()` / client install.
-- WARNING: indexing a table by the wrong axis silently breaks coverage detection
-- on locales that need glyphs the client-shipped font can't render — labels go to
-- `?` boxes. Per-table comments below name which axis applies.
local LANGUAGE_OPTIONS = {
{ value = "auto", label = nil }, -- composed dynamically (Auto + native of GetLocale())
{ value = "enUS", label = "English" },
{ value = "deDE", label = "Deutsch" },
{ value = "esES", label = "Español (España)" },
{ value = "esMX", label = "Español (México)" },
{ value = "frFR", label = "Français" },
{ value = "itIT", label = "Italiano" },
{ value = "ptBR", label = "Português (Brasil)" },
{ value = "koKR", label = "한국어 (Korean)" },
{ value = "ruRU", label = "Русский (Russian)" },
{ value = "zhCN", label = "中文 简体 (Simplified)" },
{ value = "zhTW", label = "中文 繁體 (Traditional)" },
}
local LOCALE_GLYPH_REQ = {
enUS = GLYPH_LATIN, deDE = GLYPH_LATIN, esES = GLYPH_LATIN, esMX = GLYPH_LATIN,
frFR = GLYPH_LATIN, itIT = GLYPH_LATIN, ptBR = GLYPH_LATIN,
ruRU = GLYPH_CYR,
koKR = GLYPH_HANGUL, zhCN = GLYPH_HANS, zhTW = GLYPH_HANT,
}
local function FontPathKey(fontPath)
if type(fontPath) ~= "string" then return nil end
return (fontPath:gsub("/", "\\"):lower())
end
local function SameFontPath(a, b)
local ak, bk = FontPathKey(a), FontPathKey(b)
return ak ~= nil and bk ~= nil and ak == bk
end
local function IsBlizzardFontPath(fontPath)
local key = FontPathKey(fontPath)
return key ~= nil and string.sub(key, 1, 6) == "fonts\\"
end
-- WHY two-tier coverage detection: WoW shipped TTF filenames are stable per locale
-- install (FONT_GLYPH_SUPPORT normalized exact-match, O(1) hash); LSM-registered fonts have no
-- glyph-coverage API but popular CJK families ship under predictable filenames, so
-- FONT_GLYPH_PATTERNS (below) catches NotoCJK / SourceHan / WenQuanYi / PingFang /
-- YaHei / JhengHei / SimSun / SimHei / MingLiU / Malgun / Nanum / AppleSDGothicNeo
-- by filename pattern. Unknown paths conservatively Latin-only.
--
-- WHY ARIALN universal vs FRIZQT locale-conditional: Blizzard ships ARIALN with
-- Cyrillic on EVERY non-CJK client (it's the chat/nameplate font where cross-realm
-- Russian names appear — Latin+Cyrillic is mandatory). FRIZQT, by contrast, is the
-- brand-style font with locale-specific design — ruRU FRIZQT ships proper Cyrillic
-- glyphs, but enUS / deDE / frFR / etc. FRIZQT is Latin-design (Cyrillic renders
-- via OS system font fallback — visible but ugly, mismatched kerning/stroke weights).
-- See locale-conditional do-block below for FRIZQT populating.
-- WHY GLYPH_LATIN means StatsPro's western-locale labels (not ASCII-only):
-- frFR/esES/esMX/ptBR strings include accents. LibSharedMedia documents that
-- FRIZQT___CYR misses accented European chars, so it is Cyrillic-capable but
-- intentionally NOT GLYPH_LATIN-compatible here.
local FONT_GLYPH_SUPPORT = {}
local function AddFontGlyphSupport(path, glyphs)
FONT_GLYPH_SUPPORT[FontPathKey(path)] = glyphs
end
AddFontGlyphSupport("Fonts\\2002.ttf", { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANGUL })
AddFontGlyphSupport("Fonts\\2002B.ttf", { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANGUL })
AddFontGlyphSupport("Fonts\\ARHei.TTF", { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANS, GLYPH_HANT })
AddFontGlyphSupport("Fonts\\ARIALN.TTF", { GLYPH_LATIN, GLYPH_CYR })
AddFontGlyphSupport("Fonts\\ARKai_C.ttf", { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANS, GLYPH_HANT })
AddFontGlyphSupport("Fonts\\ARKai_T.ttf", { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANS, GLYPH_HANT })
AddFontGlyphSupport("Fonts\\bHEI00M.ttf", { GLYPH_HANT })
AddFontGlyphSupport("Fonts\\bHEI01B.ttf", { GLYPH_HANT })
AddFontGlyphSupport("Fonts\\bKAI00M.ttf", { GLYPH_HANT })
AddFontGlyphSupport("Fonts\\bLEI00D.ttf", { GLYPH_HANT })
-- FRIZQT__.TTF populated by the locale-conditional do-block below this table.
AddFontGlyphSupport("Fonts\\FRIZQT___CYR.TTF", { GLYPH_CYR })
AddFontGlyphSupport("Fonts\\K_Damage.TTF", { GLYPH_CYR, GLYPH_HANGUL })
AddFontGlyphSupport("Fonts\\K_Pagetext.TTF", { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANGUL })
AddFontGlyphSupport("Fonts\\MORPHEUS.TTF", { GLYPH_LATIN })
AddFontGlyphSupport("Fonts\\MORPHEUS_CYR.TTF", { GLYPH_LATIN, GLYPH_CYR })
AddFontGlyphSupport("Fonts\\NIM_____.ttf", { GLYPH_LATIN, GLYPH_CYR })
AddFontGlyphSupport("Fonts\\SKURRI.TTF", { GLYPH_LATIN })
AddFontGlyphSupport("Fonts\\SKURRI_CYR.TTF", { GLYPH_LATIN, GLYPH_CYR })
-- WHY locale-conditional FRIZQT: same path resolves to a DIFFERENT physical file
-- per client install — properly-designed Cyrillic on ruRU; on other clients Cyrillic
-- only renders via OS system fallback (mixed glyph design). MaybeAutoSwitchFont's
-- ARIALN-fallback handles the cross-locale CYR case (see ARIALN comment above).
-- CJK clients get plain Latin too; their actual CJK coverage lives in the
-- 2002/ARKai/bHEI entries above. See axis-naming comment over LANGUAGE_OPTIONS
-- for why path-keyed (provided-by-client) is the right axis here.
do
local LOCALE_NATIVE_GLYPHS = {
ruRU = { GLYPH_LATIN, GLYPH_CYR },
}
AddFontGlyphSupport("Fonts\\FRIZQT__.TTF", LOCALE_NATIVE_GLYPHS[GetLocale()] or { GLYPH_LATIN })
end
-- Per-client-shipped Blizzard font paths (drives BuildFontsList no-LSM fallback +
-- CurrentFontName reverse-lookup). FONT_GLYPH_SUPPORT above answers "what glyphs
-- at this path?"; this table answers the orthogonal "does THIS client install
-- physically ship a working file at this path?". locale=nil → universal entry
-- (every client). locale=<L> → only on the matching client install (gated by
-- GetLocale(), NOT by db.forceLocale — file-existence axis is install-bound,
-- never user-output-bound). Descriptive names mirror LSM-list convention and
-- fit the 25-char FONT_PICKER_BTN_W ceiling.
local BLIZZARD_SHIPPED_FONTS = {
{ path = "Fonts\\FRIZQT__.TTF", name = "Friz Quadrata TT" },
{ path = "Fonts\\ARIALN.TTF", name = "Arial Narrow" },
{ path = "Fonts\\SKURRI.TTF", name = "Skurri" },
{ path = "Fonts\\MORPHEUS.TTF", name = "Morpheus" },
{ path = "Fonts\\ARKai_T.ttf", name = "Chinese (Simplified)", locale = "zhCN" },
{ path = "Fonts\\bHEI00M.ttf", name = "Chinese (Traditional)", locale = "zhTW" },
{ path = "Fonts\\2002.ttf", name = "Korean", locale = "koKR" },
}
-- WHY ordered list (not hash like FONT_GLYPH_SUPPORT): patterns are first-match-wins,
-- broader/universal-coverage families first. Path basename is lowercased before match
-- (Lua 5.1 string.lower is byte-based; safe for ASCII font filenames). Each pattern
-- requires a script qualifier (cjk, sourcehan, hei, sun, yahei, msyh, msjh, mingliu,
-- pingfang, gothic, nanum, wqy/wenquanyi, applesdgothicneo) — guards against false-
-- positives on plain "Noto Mono" / "Source Sans" Latin-only fonts and addon-folder
-- substrings. WARNING: patterns are Lua patterns — escape % + - ? . ( ) [ ] $ ^ if
-- adding a literal special char in a future pattern.
local FONT_GLYPH_PATTERNS = {
-- Adobe / Google universal CJK (documented Latin + Cyrillic + full CJK coverage)
{ pattern = "noto.*cjk", glyphs = { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANS, GLYPH_HANT, GLYPH_HANGUL } },
{ pattern = "sourcehan", glyphs = { GLYPH_LATIN, GLYPH_CYR, GLYPH_HANS, GLYPH_HANT, GLYPH_HANGUL } },
-- Open-source CN+TW
{ pattern = "wenquanyi", glyphs = { GLYPH_LATIN, GLYPH_HANS, GLYPH_HANT } },
{ pattern = "wqy", glyphs = { GLYPH_LATIN, GLYPH_HANS, GLYPH_HANT } },
-- Apple macOS Chinese (PingFang covers SC+TC; HANGUL via separate font)
{ pattern = "pingfang", glyphs = { GLYPH_LATIN, GLYPH_HANS, GLYPH_HANT } },
-- Microsoft Windows Simplified Chinese
{ pattern = "yahei", glyphs = { GLYPH_LATIN, GLYPH_HANS } },
{ pattern = "msyh", glyphs = { GLYPH_LATIN, GLYPH_HANS } },
-- Microsoft Windows Traditional Chinese
{ pattern = "msjh", glyphs = { GLYPH_LATIN, GLYPH_HANT } },
-- Legacy / classical Windows Chinese
{ pattern = "simsun", glyphs = { GLYPH_LATIN, GLYPH_HANS } },
{ pattern = "simhei", glyphs = { GLYPH_LATIN, GLYPH_HANS } },
{ pattern = "mingliu", glyphs = { GLYPH_LATIN, GLYPH_HANT } },
-- Korean (Apple, Microsoft, Naver)
{ pattern = "applesdgothicneo", glyphs = { GLYPH_LATIN, GLYPH_HANGUL } },
{ pattern = "malgun.*gothic", glyphs = { GLYPH_LATIN, GLYPH_HANGUL } },
{ pattern = "nanum", glyphs = { GLYPH_LATIN, GLYPH_HANGUL } },
}
-- WHY load-time sanity: catches FONT_GLYPH_PATTERNS typos (e.g. nono.*cjk) at addon
-- load, not at user-report time. Silent on success; chat warning on regression.
do
local SAMPLES = {
["noto.*cjk"] = "notosanscjk-regular.otf",
["sourcehan"] = "sourcehansans-regular.otf",
["wenquanyi"] = "wenquanyimicrohei.ttf",
["wqy"] = "wqy-zenhei.ttc",
["pingfang"] = "pingfangsc.ttf",
["yahei"] = "msyahei.ttf",
["msyh"] = "msyh.ttf",
["msjh"] = "msjh.ttf",
["simsun"] = "simsun.ttc",
["simhei"] = "simhei.ttf",
["mingliu"] = "mingliu.ttc",
["applesdgothicneo"] = "applesdgothicneo.ttc",
["malgun.*gothic"] = "malgungothic.ttf",
["nanum"] = "nanumgothic.ttf",
}
for _, p in ipairs(FONT_GLYPH_PATTERNS) do
local sample = SAMPLES[p.pattern]
if not (sample and string.find(sample, p.pattern)) then
print("|cffff4444[StatsPro] FONT_GLYPH_PATTERNS regression: '"
.. tostring(p.pattern) .. "' fails canonical sample|r")
end
end
end
--[[ ============================================================
2. LIBRARIES + API SHIMS
============================================================ ]]
-- LibSharedMedia-3.0 (soft dependency - gracefully falls back if not loaded)
local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
-- WHY: issecretvalue is 12.0+ retail; shim falsy on older clients so addon doesn't hard-error.
local issecretvalue = _G.issecretvalue or function() return false end
-- WHY hijack-guard: STANDARD_TEXT_FONT is a Blizzard global ANY addon can mutate
-- (ChonkyCharacterSheet / Tukui / ElvUI font modules / other "system font replacement"
-- addons all do). Reading it raw lets a third-party pin StatsPro's defaults, migration
-- target, fallback chain, and config UI rendering to an addon-shipped path forever.
-- Guard: trust STANDARD_TEXT_FONT only when it points to a Blizzard-shipped path
-- (`Fonts\…`). Non-Blizzard paths (`Interface\AddOns\…`) fall back to FRIZQT — the
-- localized-labels concern was always about CJK CLIENT-shipped fonts (under Fonts\),
-- not about user-installed font replacements which the user can still pick manually
-- via the Font dropdown if they want them in StatsPro specifically.
local function LocaleAwareDefaultFont()
if IsBlizzardFontPath(STANDARD_TEXT_FONT) then
return STANDARD_TEXT_FONT
end
return "Fonts\\FRIZQT__.TTF"
end
-- WHY single source of truth: Version comes from the TOC `## Version:` line, which
-- BigWigs Packager substitutes from the git tag at release build time (`@project-version@`
-- → e.g. `1.0.1`). Reading via GetAddOnMetadata means every release auto-syncs the
-- in-game settings title without a code edit. Local dev (running from source) sees the
-- literal `@project-version@` token from the unsubstituted TOC — fall back to a
-- hand-maintained constant so the title still reads e.g. `v1.0.3-dev` instead of `vdev`.
-- WARNING: bump CURRENT_RELEASE on every `git tag v*` so dev builds reflect the working base.
local CURRENT_RELEASE = "1.9.7"
local ADDON_VERSION = (C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata)("StatsPro", "Version") or "?"
if ADDON_VERSION:find("project%-version") then ADDON_VERSION = CURRENT_RELEASE .. "-dev" end
--[[ ============================================================
3. DEFAULTS
============================================================ ]]
local defaults = {
-- Position / appearance
point = "CENTER",
relativePoint = "CENTER",
xOfs = 0,
yOfs = 0,
scale = 1.0,
fontSize = 14,
-- Text opacity: stored as INT 25-100 (percentage) in DB, divided by 100 on apply.
-- WHY int-percent (not float 0..1): format-string compat with CreateConfigSlider's "%d%%".
textAlpha = 100,
-- Panel background alpha: stored as INT 0-80 (percentage) in DB, divided by 100 on apply.
-- Default 0 preserves the original fully transparent HUD.
panelBackgroundAlpha = 0,
-- Text outline style: "none" | "outline" | "thick". Default preserves current OUTLINE text.
textOutlineStyle = "outline",
-- WHY LocaleAwareDefaultFont: Blizzard's locale-aware default-font global resolves
-- to the right TTF for the current WoW client locale (CJK-supporting on zhCN/zhTW/
-- koKR; Latin/Cyrillic-supporting elsewhere). Hardcoding FRIZQT would render
-- localized labels (Crit / 暴击 / 치명 / etc.) as `?` boxes on CJK clients. The
-- helper guards against font-replacement addons (Chonky, Tukui, ElvUI) that
-- override STANDARD_TEXT_FONT to their own path — those would hijack our defaults
-- otherwise. Falls back to FRIZQT for any non-Blizzard path.
font = LocaleAwareDefaultFont(),
textAlign = "RIGHT", -- DEPRECATED: kept for DB compat (v1.0+ saves still contain key); no runtime reader
updateInterval = 0.5,
isVisible = true,
isLocked = false,
-- Display mode: "flat" | "sectioned" | "split"
displayMode = "flat",
labelStyle = "full",
-- Split routing: when displayMode="split", checked blocks move to the side panel.
-- Defaults preserve the original split behavior (main = character/offense/tertiary,
-- side = defensive/gear).
splitCharacter = false,
splitItemLevel = true,
splitOffensive = false,
splitTertiary = false,
splitDefensive = true,
splitDurability = true,
splitRepairCost = true,
-- WHY forceLocale (string) replaces the prior boolean useLocalizedLabels:
-- "auto" follows GetLocale(); explicit value ("enUS", "ruRU", ..., "zhTW") forces
-- panels to that locale regardless of WoW client. Auto-switches font if needed
-- (see MaybeAutoSwitchFont). Migration v4→v5 maps legacy useLocalizedLabels=false
-- to forceLocale="enUS"; anything else to "auto". The dropdown is shown on every
-- client locale (replacing the prior HAS_LOCALIZATION-gated checkbox) — useful
-- even on enUS for picking 中文 / 한국어 etc. for screenshots.
forceLocale = "auto",
-- Defensive panel position
defensive_point = "CENTER",
defensive_relativePoint = "CENTER",
defensive_xOfs = 0,
defensive_yOfs = -100,
-- Display formatting
showRating = false,
showPercentage = true,
matchValueColorToStat = false,
targetSnapshot = "mythicPlus",
-- Tertiary stats
showTertiary = false,
hideZeroTertiary = true,
showLeech = true,
showAvoidance = true,
showSpeed = true,
-- Primary stat: Show Main Stat (auto-resolves from spec) + Show Stamina (independent —
-- no spec uses Stamina as primary). Item Level remains a separate gear-summary row,
-- not a rated stat.
showMainStat = false,
showStamina = false,
showItemLevel = false,
-- Defensive stats
showDefensive = false,
hideZeroDefensive = true,
showDodge = true,
showParry = true,
showBlock = true,
showArmor = true,
showStagger = false,
-- Offensive stats
showOffensive = true,
hideZeroOffensive = false, -- combat ratings rarely 0; opt-in only (Vers may legit hit 0)
showCrit = true,
showHaste = true,
showMastery = true,
showVersatility = true,
-- Durability / repair
showDurability = false,
showRepairCost = false,
useAutoColorDurability = true,
useWorstDurability = false, -- default: average (matches vendor display); ON = show worst slot
colors = {
crit = { r = 1, g = 0, b = 0 },
haste = { r = 0, g = 0.5, b = 1 },
mastery = { r = 0, g = 1, b = 0 },
versatility = { r = 1, g = 1, b = 0 },
rating = { r = 0.7, g = 0.7, b = 0.7 },
percentage = { r = 1, g = 1, b = 1 },
leech = { r = 0.8, g = 0.2, b = 0.8 },
avoidance = { r = 0.2, g = 0.8, b = 0.8 },
speed = { r = 1, g = 0.65, b = 0 },
mainStat = { r = 1, g = 0.84, b = 0 },
stamina = { r = 0.5, g = 1, b = 0.5 },
itemLevel = { r = 0.55, g = 0.85, b = 1 },
-- Defensive colors
dodge = { r = 0.4, g = 0.7, b = 1 },
parry = { r = 1, g = 0.4, b = 0.2 },
block = { r = 0.7, g = 0.5, b = 0.3 },
armor = { r = 0.6, g = 0.6, b = 0.7 },
stagger = { r = 0.3, g = 0.8, b = 0.5 },
durability = { r = 1, g = 1, b = 1 },
},
}
--[[ ============================================================
4. STAT DEFINITION TABLES (data-driven; UpdateStats iterates these)
============================================================ ]]
local OFFENSIVE_STATS = {
{ statKey = "crit", label = "Crit", api = GetCritChance, ratingCR = CR_CRIT_MELEE, colorKey = "crit", showKey = "showCrit" },
{ statKey = "haste", label = "Haste", api = GetHaste, ratingCR = CR_HASTE_MELEE, colorKey = "haste", showKey = "showHaste" },
{ statKey = "mastery", label = "Mastery", api = GetMasteryEffect, ratingCR = CR_MASTERY, colorKey = "mastery", showKey = "showMastery" },
-- versatility handled specially (dual-source: rating + flat); gated by showVersatility
}
-- Primary stat label + unitStatId mapping. Used by BuildCharacterLines via the
-- PRIMARY_STATS_BY_ID O(1) lookup. label routes through L() for locale render.
local PRIMARY_STATS = {
{ label = "Strength", unitStatId = 1 },
{ label = "Agility", unitStatId = 2 },
{ label = "Intellect", unitStatId = 4 },
}
-- O(1) lookup by unitStatId (1=Str, 2=Agi, 4=Int) for BuildCharacterLines.
local PRIMARY_STATS_BY_ID = {}
for _, def in ipairs(PRIMARY_STATS) do
PRIMARY_STATS_BY_ID[def.unitStatId] = def
end
-- Stamina is unitStatId 3 — excluded from PRIMARY_STATS / PRIMARY_STATS_BY_ID because
-- GetCurrentMainStatId never returns 3 (no spec uses Stamina as primary).
local STAMINA_UNIT_STAT_ID = 3
-- WHY shim: C_SpecializationInfo.* is the modern API in 12.x retail; legacy
-- GetSpecialization* deprecated since 11.2 and may be removed in 13.x. Defensive
-- chain mirrors the C_AddOns.GetAddOnMetadata-or-GetAddOnMetadata pattern used for ADDON_VERSION.
local function SafeGetSpecIndex()
if C_SpecializationInfo and C_SpecializationInfo.GetSpecialization then
return C_SpecializationInfo.GetSpecialization()
end
return GetSpecialization and GetSpecialization() or nil
end
local function SafeGetSpecInfo(idx)
if C_SpecializationInfo and C_SpecializationInfo.GetSpecializationInfo then
return C_SpecializationInfo.GetSpecializationInfo(idx)
end
return GetSpecializationInfo and GetSpecializationInfo(idx) or nil
end
-- Returns 1 (Str) / 2 (Agi) / 4 (Int) or nil (no spec selected — sub-10 alts /
-- pre-PEW edge / API stub in older clients). Per-render lookup (no caching) — matches
-- the no-spec-event-handler architecture (UpdateStats re-reads every tick anyway).
local function GetCurrentMainStatId()
local idx = SafeGetSpecIndex()
if not idx then return nil end
local _, _, _, _, _, primaryStat = SafeGetSpecInfo(idx)
return primaryStat
end
addon.archonTargets = addon.archonTargets or {}
addon.archonTargets.defaultSnapshotKey = "mythicPlus"
addon.archonTargets.snapshotOptions = {
{ value = "mythicPlus", label = "Mythic+" },
{ value = "raid", label = "Raid" },
}
local cached
addon.archonTargets.specKeyByID = {
[250] = "blood", [251] = "frost", [252] = "unholy",
[577] = "havoc", [581] = "vengeance", [1480] = "devourer",
[102] = "balance", [103] = "feral", [104] = "guardian", [105] = "restoration",
[1467] = "devastation", [1468] = "preservation", [1473] = "augmentation",
[253] = "beast-mastery", [254] = "marksmanship", [255] = "survival",
[62] = "arcane", [63] = "fire", [64] = "frost",
[268] = "brewmaster", [269] = "windwalker", [270] = "mistweaver",
[65] = "holy", [66] = "protection", [70] = "retribution",
[256] = "discipline", [257] = "holy", [258] = "shadow",
[259] = "assassination", [260] = "outlaw", [261] = "subtlety",
[262] = "elemental", [263] = "enhancement", [264] = "restoration",
[265] = "affliction", [266] = "demonology", [267] = "destruction",
[71] = "arms", [72] = "fury", [73] = "protection",
}
function addon.archonTargets.GetCurrentClassToken()
local _, classToken = UnitClass("player")
return classToken
end
function addon.archonTargets.GetCurrentSpecKey()
local idx = SafeGetSpecIndex()
if not idx then return nil end
local specID = SafeGetSpecInfo(idx)
return specID and addon.archonTargets.specKeyByID[specID] or nil
end
function addon.archonTargets.NormalizeSnapshotKey(value)
if value == "raid" then return "raid" end
return addon.archonTargets.defaultSnapshotKey
end
function addon.archonTargets.GetRootSnapshot(snapshotKey)
local root = _G.StatsProArchonTargets
if type(root) ~= "table" then return nil end
local normalizedKey = addon.archonTargets.NormalizeSnapshotKey(snapshotKey)
if root.schemaVersion == 2 then
local snapshots = root.snapshots
local snapshotRoot = type(snapshots) == "table" and snapshots[normalizedKey] or nil
if type(snapshotRoot) ~= "table" and normalizedKey ~= addon.archonTargets.defaultSnapshotKey then
normalizedKey = addon.archonTargets.defaultSnapshotKey
snapshotRoot = type(snapshots) == "table" and snapshots[normalizedKey] or nil
end
if type(snapshotRoot) ~= "table" then return nil end
return snapshotRoot, root, normalizedKey
end
if root.schemaVersion == 1 then
return root, root, addon.archonTargets.defaultSnapshotKey
end
return nil
end
function addon.archonTargets.GetSnapshotLabel(snapshotRoot, snapshotKey)
if type(snapshotRoot) == "table" and type(snapshotRoot.label) == "string" and snapshotRoot.label ~= "" then
return snapshotRoot.label
end
if snapshotKey == "raid" then return "Raid Mythic All Bosses" end
return "M+ High Keys"
end
function addon.archonTargets.GetSnapshotTitle(snapshotRoot, snapshotKey)
if type(snapshotRoot) == "table" and type(snapshotRoot.title) == "string" and snapshotRoot.title ~= "" then
return snapshotRoot.title
end
if snapshotKey == "raid" then return "Raid Target" end
return "M+ Target"
end
function addon.archonTargets.GetSnapshot(classToken, specKey, snapshotKey)
local snapshotRoot, root, normalizedKey = addon.archonTargets.GetRootSnapshot(snapshotKey)
if not snapshotRoot then return nil end
local specs = snapshotRoot.specs
if type(specs) ~= "table" then return nil end
local classData = specs[classToken]
if type(classData) ~= "table" then return nil end
local specData = classData[specKey]
if type(specData) ~= "table" then return nil end
return specData, snapshotRoot, root, normalizedKey
end
function addon.archonTargets.GetCurrentSnapshot()
return addon.archonTargets.GetSnapshot(addon.archonTargets.GetCurrentClassToken(), addon.archonTargets.GetCurrentSpecKey(), cached.targetSnapshot)
end
function addon.archonTargets.GetStatTarget(statKey)
local snapshot, snapshotRoot, root, snapshotKey = addon.archonTargets.GetCurrentSnapshot()
local targets = snapshot and snapshot.targets
local target = type(targets) == "table" and targets[statKey] or nil
if type(target) ~= "number" or issecretvalue(target)
or target ~= target or target <= 0 or target >= math.huge then return nil end
return target, snapshot, snapshotRoot, root, snapshotKey
end
function addon.archonTargets.BuildMeta(statKey, currentRating, ratingCR, currentPct, colorKey)
if type(currentRating) ~= "number" or issecretvalue(currentRating)
or currentRating ~= currentRating or currentRating < 0 or currentRating >= math.huge then return nil end
local target, snapshot, snapshotRoot, _, snapshotKey = addon.archonTargets.GetStatTarget(statKey)
if not target then return nil end
local displayPct = (type(currentPct) == "number" and not issecretvalue(currentPct)
and currentPct == currentPct and currentPct > -math.huge and currentPct < math.huge) and currentPct or nil
return {
statKey = statKey,
colorKey = colorKey or statKey,
ratingCR = ratingCR,
target = target,
current = currentRating,
currentPct = displayPct,
delta = currentRating - target,
sourceUrl = snapshot.sourceUrl,
capturedAt = snapshotRoot.capturedAt,
snapshotKey = snapshotKey,
snapshotLabel = addon.archonTargets.GetSnapshotLabel(snapshotRoot, snapshotKey),
snapshotTitle = addon.archonTargets.GetSnapshotTitle(snapshotRoot, snapshotKey),
activity = snapshotRoot.activity,
bracket = snapshotRoot.bracket,
dungeon = snapshotRoot.dungeon,
difficulty = snapshotRoot.difficulty,
boss = snapshotRoot.boss,
window = snapshotRoot.window,
}
end
local function PlayerCanBlock()
local _, classToken = UnitClass("player")
return classToken == "PALADIN" or classToken == "SHAMAN" or classToken == "WARRIOR"
end
local function IsBrewmasterSpec()
local _, classToken = UnitClass("player")
if classToken ~= "MONK" then return false end
local idx = SafeGetSpecIndex()
if not idx then return false end
local specID = SafeGetSpecInfo(idx)
return specID == 268
end
local function GetStaggerChance()
if not IsBrewmasterSpec() then return nil end
if not C_PaperDollInfo or not C_PaperDollInfo.GetStaggerPercentage then return nil end
local ok, stagger = pcall(C_PaperDollInfo.GetStaggerPercentage, "player")
if not ok then return nil end
if issecretvalue(stagger) then return stagger end
if type(stagger) ~= "number" or stagger ~= stagger or stagger < 0 or stagger == math.huge then return nil end
return stagger
end
local DEFENSIVE_STATS = {
{ label = "Dodge", api = GetDodgeChance, colorKey = "dodge", showKey = "showDodge" },
{ label = "Parry", api = GetParryChance, colorKey = "parry", showKey = "showParry" },
{ label = "Block", api = GetBlockChance, colorKey = "block", showKey = "showBlock", appliesFn = PlayerCanBlock },
{ label = "Stagger", api = GetStaggerChance, colorKey = "stagger", showKey = "showStagger", appliesFn = IsBrewmasterSpec },
-- Armor & DR handled specially: armor = absolute number, DR = cached arithmetic
}
local TERTIARY_STATS = {
{ label = "Leech", api = GetLifesteal, ratingCR = CR_LIFESTEAL, colorKey = "leech", showKey = "showLeech" },
{ label = "Avoidance", api = GetAvoidance, ratingCR = CR_AVOIDANCE, colorKey = "avoidance", showKey = "showAvoidance" },
-- speed handled specially (yps→% via GetUnitSpeed)
}
--[[ ============================================================
5. CACHE KEY TABLES (single source of truth for CacheSettings loops)
============================================================ ]]
local CACHED_BOOL_KEYS = {
"isLocked", "isVisible",
"showRating", "showPercentage", "matchValueColorToStat",
"showOffensive", "hideZeroOffensive",
"showCrit", "showHaste", "showMastery", "showVersatility",
"showTertiary", "hideZeroTertiary", "showLeech", "showAvoidance", "showSpeed",
"showMainStat", "showStamina", "showItemLevel",
-- Defensive & durability:
"showDefensive", "hideZeroDefensive",
"showDodge", "showParry", "showBlock", "showArmor", "showStagger",
"showDurability", "showRepairCost", "useAutoColorDurability", "useWorstDurability",
-- Split routing:
"splitCharacter", "splitItemLevel", "splitOffensive", "splitTertiary",
"splitDefensive", "splitDurability", "splitRepairCost",
}
--[[ ============================================================
6. SAVED VARIABLES + RUNTIME STATE
============================================================ ]]
if type(StatsProDB) ~= "table" then StatsProDB = {} end
local function EnsureStatsProDBTable()
if type(StatsProDB) ~= "table" then StatsProDB = {} end
return StatsProDB
end
-- Legacy-DB carry-forward runs in OnPlayerEnteringWorld (section 13) — NOT here at
-- file scope. Two sources are checked:
-- * `_G.SwiftStatsDB` — the original public SwiftStats by TaylorSay (the upstream
-- this addon was inspired by); covers the common case of a user moving from the
-- CurseForge upstream to StatsPro.
-- * `_G.SwiftStatsLocalDB` — fallback for an earlier internal name of this addon
-- (renamed to StatsPro before publication); a tiny audience.
-- WoW loads addon SavedVariables alongside the addon's code in alphabetical folder-
-- name order. StatsPro loads BEFORE either source addon, so at file scope the source
-- globals are still nil. By PEW every enabled addon's SavedVariables are loaded; the
-- check fires reliably regardless of load order.
cached = {
colorStrings = {},
-- WHY {}: cached table inits at file scope BEFORE LABELS_BY_LOCALE declaration
-- (sect 6 vs sect 7). Empty table fallback gives identity-map L() behavior
-- (table[key]=nil; "nil or englishKey" returns the English key) — safe for any
-- L()-using code that runs pre-CacheSettings at config build time before PEW.
-- CacheSettings overwrites with real LABELS_BY_LOCALE entry at PEW.
-- WARNING: never mutate; treat read-only.
activeLabels = {},
activeLabelsLocale = "enUS",
-- WARNING: versatility / armor reads taint in combat — cache clean values,
-- reuse cached value during combat. Vers starts unknown so cold-start
-- secret/nil reads do not render as a real 0.0%.
versTotal = nil,
versTotalRating = 0,
armorDR = 0,
itemLevelOverall = nil,
itemLevelEquipped = nil,
durabilityValue = 100, -- holds avg or min depending on cached.useWorstDurability
repairCost = 0, -- live repair cost in copper (sum from per-slot tooltip scan)
-- WARNING: GetUnitSpeed returns secret values in combat → math.max taints. Cache OOC.
speedPct = 0,
displayMode = "flat",
labelStyle = "full",
targetSnapshot = "mythicPlus",
updateInterval = 0.5,
}
-- Dirty flag for event-driven cache refresh (durability scan is per-19-slot, not free)
local durabilityDirty = true
-- Dirty flag for item-level refresh (overall iLvl can change from gear or bags)
local itemLevelDirty = true
-- Init guard: UpdateStats must not run before CacheSettings populates cached.colorStrings
local isLoaded = false
--[[ ============================================================
7. HELPERS
============================================================ ]]
-- Compact short-form stat labels, hand-curated per locale to match StatsPro's
-- 4-7-char aesthetic across every client language. Translation philosophy:
-- preserve the same visual weight as the English "Crit" / "Vers" — abbreviated
-- where the natural translation is long, full where it's already short. Aim for
-- ≥4 chars when the language supports it (3-char abbreviations like "Par" or
-- "Cel" read as truncations rather than words and look unfinished).
--
-- Ships with current WoW addon locale tables:
-- enUS (canonical, identity map)
-- ruRU (Russian native-speaker reviewed by maintainer)
-- zhCN / zhTW (use official WoW Chinese client stat terminology — high confidence)
-- deDE / frFR / esES / esMX / itIT / ptBR / koKR (deeper review pass against
-- each language's WoW client term + community shorthand conventions; native-
-- speaker spot-checks still welcome via GitHub Issues for per-row tweaks).
-- Locales not in this table (any future Blizzard locale, e.g. plPL) fall back
-- to enUS via the `or LABELS_BY_LOCALE.enUS` selector in CacheSettings — panels
-- silently render English labels for the unsupported locale.
--
-- LOAD-BEARING INVARIANT: LABELS_BY_LOCALE.enUS MUST exist as the universal
-- fallback. Removing it breaks every L() call when forceLocale resolves to a
-- locale missing from this table.
--
-- WARNING: keys MUST match exactly the English literals used at the call sites:
-- - def.label values from OFFENSIVE_STATS / DEFENSIVE_STATS / PRIMARY_STATS /
-- TERTIARY_STATS (section 4)
-- - hardcoded literals in special-case branches: "Vers" / "Speed" / "Armor"
-- (additive rows for dual-source stats not in the loop tables)
-- - "Durability" in BuildDurabilityLines / "Repair" in BuildRepairCostPayload
-- - section keys used by SectionHeader(): Character / Offensive / Tertiary /
-- Defensive / Gear
-- Adding a new key here without updating callers is a no-op; adding a new caller
-- without a key here falls back gracefully to the English literal (`L(k) → k`).
--
-- WARNING: Armor and Defensive must be visually DISTINCT in the same locale.
-- Armor is a stat row label; Defensive is the sectioned-mode divider. Same word
-- for both makes the divider blend into the row beneath it.
local LABELS_BY_LOCALE = {
enUS = {
Crit = "Crit", Haste = "Haste", Mastery = "Mastery", Vers = "Vers",
Dodge = "Dodge", Parry = "Parry", Block = "Block", Armor = "Armor", Stagger = "Stagger",
Strength = "Strength", Agility = "Agility", Intellect = "Intellect", Stamina = "Stamina",
ItemLevel = "iLvl",
Leech = "Leech", Avoidance = "Avoidance", Speed = "Speed",
Durability = "Durability", Repair = "Repair",
Defensive = "Defensive",
-- Settings UI words (config menu only, never appear on the panel itself):
Color = "Color",
-- ===== Settings UI strings =====
-- Tabs (Defensive reuses the existing key above):
["Stats"] = "Stats", ["Layout"] = "Layout", ["Appearance"] = "Appearance",
-- Section headers / split block labels (Durability reuses the existing key above):
["Character"] = "Character", ["Item Level"] = "Item Level",
["Offensive"] = "Offensive", ["Tertiary"] = "Tertiary",
["Gear"] = "Gear", ["Repair Cost"] = "Repair Cost",
["Side Panel"] = "Side Panel", ["Side Panel Contains"] = "Side Panel Contains",
["Value Display"] = "Value Display",
["Frame & Position"] = "Frame & Position",
["Typography"] = "Typography",
["Readability"] = "Readability",
["Localization"] = "Localization",
["Offensive Stats"] = "Offensive Stats",
["Tertiary Stats"] = "Tertiary Stats",
["Defensive Stats"] = "Defensive Stats",
-- Checkboxes:
["Show Stats Panel"] = "Show Stats Panel", ["Lock Frames"] = "Lock Frames",
["Show Main Stat"] = "Show Main Stat",
["Show Stamina"] = "Show Stamina",
["Show Item Level"] = "Show Item Level",
["Show Rating"] = "Show Rating", ["Show Percentage"] = "Show Percentage",
["Match Value Color to Stat"] = "Match Value Color to Stat",
["Show Offensive Stats"] = "Show Offensive Stats", ["Hide Zero Values"] = "Hide Zero Values",
["Show Crit"] = "Show Crit", ["Show Haste"] = "Show Haste",
["Show Mastery"] = "Show Mastery", ["Show Versatility"] = "Show Versatility",
["Show Tertiary Stats"] = "Show Tertiary Stats",
["Show Leech"] = "Show Leech", ["Show Avoidance"] = "Show Avoidance", ["Show Speed"] = "Show Speed",
["Show Defensive Stats"] = "Show Defensive Stats",
["Show Dodge"] = "Show Dodge", ["Show Parry"] = "Show Parry",
["Show Block"] = "Show Block", ["Show Armor"] = "Show Armor", ["Show Stagger"] = "Show Stagger",
["Show Durability"] = "Show Durability", ["Show Repair Cost"] = "Show Repair Cost",
["Auto Color by Threshold"] = "Auto Color by Threshold",
["Use Worst Slot (instead of average)"] = "Use Worst Slot (instead of average)",
-- Sliders:
["Scale:"] = "Scale:", ["Refresh Rate (sec):"] = "Refresh Rate (sec):", ["Font Size:"] = "Font Size:", ["Text Opacity:"] = "Text Opacity:", ["Panel Background:"] = "Panel Background:",
-- Dropdown captions:
["Display Mode:"] = "Display Mode:", ["Tooltip Targets:"] = "Tooltip Targets:", ["Label Style:"] = "Label Style:", ["Text Outline:"] = "Text Outline:", ["Font:"] = "Font:", ["Language:"] = "Language:",
-- Dropdown options (Display Mode):
["Flat"] = "Flat", ["Sectioned"] = "Sectioned", ["Split"] = "Split",
["Mythic+"] = "Mythic+", ["Raid"] = "Raid",
["Full"] = "Full", ["Short"] = "Short", ["Hidden"] = "Hidden",
["None"] = "None", ["Outline"] = "Outline", ["Thick Outline"] = "Thick Outline",
["M+ Target"] = "M+ Target", ["Raid Target"] = "Raid Target",
["M+ High Keys"] = "M+ High Keys", ["Raid Mythic All Bosses"] = "Raid Mythic All Bosses",
["Target:"] = "Target:", ["Current:"] = "Current:", ["Missing:"] = "Missing:",
["Over:"] = "Over:", ["Matched:"] = "Matched:", ["Snapshot:"] = "Snapshot:",
["Stats panel shown"] = "Stats panel shown", ["Stats panel hidden"] = "Stats panel hidden",
["Settings reset to defaults"] = "Settings reset to defaults",
["Commands: /ss or /statspro (config), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help"] = "Commands: /ss or /statspro (config), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help",
-- Buttons + title:
["Reset to Defaults"] = "Reset to Defaults", ["Close"] = "Close",
["Open Settings"] = "Open Settings", ["Settings"] = "Settings",
-- Templates:
["Auto (current: %s)"] = "Auto (current: %s)",
["|cffffaa44⚠|r Font may not render %s glyphs. Pick a SharedMedia font with proper coverage."] = "|cffffaa44⚠|r Font may not render %s glyphs. Pick a SharedMedia font with proper coverage.",
-- Launcher description:
["Displays your secondary, defensive stats and durability on screen. Click below to open the full settings window."] = "Displays your secondary, defensive stats and durability on screen. Click below to open the full settings window.",
},
-- ruRU: Russian. Haste/Speed disambig is structural — WoW client uses "Скорость"
-- for BOTH stats, so we transliterate Haste as "Хаст" (community shorthand) to free
-- "Скор" for Speed. Leech uses "Вамп" (вампиризм) over the literal "Кров" because
-- "Кров…" risks being mis-read as "Кровотечение" (Bleed). All stat rows use 4-char
-- forms where the language allows; "Сила" / "Блок" / "Крит" are already 4 chars.
ruRU = {
Crit = "Крит", Haste = "Хаст", Mastery = "Маст", Vers = "Унив",
Dodge = "Укл", Parry = "Пари", Block = "Блок", Armor = "Брон", Stagger = "Пошат",
Strength = "Сила", Agility = "Ловк", Intellect = "Инт", Stamina = "Выно",
ItemLevel = "УрП",
Leech = "Вамп", Avoidance = "Избег", Speed = "Скор",
Durability = "Проч", Repair = "Рем",
Defensive = "Защита",
Color = "Цвет",
-- ===== Settings UI =====
-- Tabs (Defensive uses "Защита" via the existing key above):
["Stats"] = "Статы", ["Layout"] = "Макет", ["Appearance"] = "Внешний вид",
-- Section headers / split block labels (Durability reuses "Проч" — short form):
["Character"] = "Персонаж", ["Item Level"] = "Уровень предметов",
["Offensive"] = "Атака", ["Tertiary"] = "Третичные",
["Gear"] = "Экипировка", ["Repair Cost"] = "Стоимость ремонта",
["Side Panel"] = "Боковая панель", ["Side Panel Contains"] = "В боковой панели",
["Value Display"] = "Отображение значений",
["Frame & Position"] = "Окно и позиция",
["Typography"] = "Типографика",
["Readability"] = "Читаемость",
["Localization"] = "Локализация",
["Offensive Stats"] = "Атакующие характеристики",
["Tertiary Stats"] = "Третичные характеристики",
["Defensive Stats"] = "Защитные характеристики",
-- Checkboxes:
["Show Stats Panel"] = "Показать панель статов", ["Lock Frames"] = "Закрепить окна",
["Show Main Stat"] = "Показывать мейн-стат",
["Show Stamina"] = "Показывать Выносливость",
["Show Item Level"] = "Показывать уровень предметов",
["Show Rating"] = "Показывать рейтинг", ["Show Percentage"] = "Показывать процент",
["Match Value Color to Stat"] = "Цвет значения по характеристике",
["Show Offensive Stats"] = "Показывать атакующие", ["Hide Zero Values"] = "Скрывать нулевые значения",
["Show Crit"] = "Показывать Крит", ["Show Haste"] = "Показывать Хаст",
["Show Mastery"] = "Показывать Мастерство", ["Show Versatility"] = "Показывать Универсальность",
["Show Tertiary Stats"] = "Показывать третичные",
["Show Leech"] = "Показывать Вампиризм", ["Show Avoidance"] = "Показывать Избегание", ["Show Speed"] = "Показывать Скорость",
["Show Defensive Stats"] = "Показывать защитные",
["Show Dodge"] = "Показывать Уклонение", ["Show Parry"] = "Показывать Парирование",
["Show Block"] = "Показывать Блок", ["Show Armor"] = "Показывать Броню", ["Show Stagger"] = "Показывать Пошатывание",
["Show Durability"] = "Показывать прочность", ["Show Repair Cost"] = "Показывать стоимость ремонта",
["Auto Color by Threshold"] = "Авто-цвет по порогу",
["Use Worst Slot (instead of average)"] = "По худшему слоту (вместо среднего)",
-- Sliders:
["Scale:"] = "Масштаб:", ["Refresh Rate (sec):"] = "Частота обновления (сек):", ["Font Size:"] = "Размер шрифта:", ["Text Opacity:"] = "Прозрачность текста:", ["Panel Background:"] = "Фон панели:",
-- Dropdown captions:
["Display Mode:"] = "Режим отображения:", ["Tooltip Targets:"] = "Цели в подсказке:", ["Label Style:"] = "Стиль меток:", ["Text Outline:"] = "Контур текста:", ["Font:"] = "Шрифт:", ["Language:"] = "Язык:",
-- Dropdown options (Display Mode):
["Flat"] = "Плоский", ["Sectioned"] = "По секциям", ["Split"] = "Разделённый",
["Mythic+"] = "Мифик+", ["Raid"] = "Рейд",
["Full"] = "Полный", ["Short"] = "Короткий", ["Hidden"] = "Скрытый",
["None"] = "Нет", ["Outline"] = "Контур", ["Thick Outline"] = "Толстый контур",
["M+ Target"] = "Цель M+", ["Raid Target"] = "Цель рейда",
["M+ High Keys"] = "M+ высокие ключи", ["Raid Mythic All Bosses"] = "Эпох. рейд, все боссы",
["Target:"] = "Цель:", ["Current:"] = "Сейчас:", ["Missing:"] = "Не хватает:",
["Over:"] = "Сверх:", ["Matched:"] = "Совпало:", ["Snapshot:"] = "Снимок:",
["Stats panel shown"] = "Панель статов показана", ["Stats panel hidden"] = "Панель статов скрыта",
["Settings reset to defaults"] = "Настройки сброшены по умолчанию",
["Commands: /ss or /statspro (config), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help"] = "Команды: /ss или /statspro (настройки), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help",
-- Buttons + title:
["Reset to Defaults"] = "Сбросить настройки", ["Close"] = "Закрыть",
["Open Settings"] = "Открыть настройки", ["Settings"] = "Настройки",
-- Templates:
["Auto (current: %s)"] = "Авто (сейчас: %s)",
["|cffffaa44⚠|r Font may not render %s glyphs. Pick a SharedMedia font with proper coverage."] = "|cffffaa44⚠|r Шрифт может не отображать символы %s. Выберите шрифт SharedMedia с нужным покрытием.",
-- Launcher description:
["Displays your secondary, defensive stats and durability on screen. Click below to open the full settings window."] = "Отображает вторичные, защитные характеристики и прочность экипировки на экране. Нажмите ниже, чтобы открыть окно настроек.",
},
-- deDE: German. Haste="Tempo" matches the WoW German client term; Speed="Lauf"
-- (run-speed) keeps the Haste/Speed split clear. Vers="Viels" evokes Vielseitigkeit
-- without colliding with the everyday word "viel" (many/much). Durability="Haltb"
-- avoids collision with the everyday word "Halt" (stop). Strength="Stär" preserves
-- the umlaut character of Stärke at 4 chars (single char "Stä" reads truncated).
deDE = {
Crit = "Krit", Haste = "Tempo", Mastery = "Meist", Vers = "Viels",
Dodge = "Ausw", Parry = "Par", Block = "Block", Armor = "Rüst", Stagger = "Staff",
Strength = "Stär", Agility = "Bew", Intellect = "Int", Stamina = "Aus",
ItemLevel = "GS",
Leech = "Saug", Avoidance = "Verm", Speed = "Lauf",
Durability = "Haltb", Repair = "Repar",
Defensive = "Defensiv",
Color = "Farbe",
-- ===== Settings UI (best-effort draft, native-speaker review welcome via Issues) =====
-- Speed checkbox uses "Lauftempo" (long form) to disambiguate from Haste="Tempo".
["Stats"] = "Werte", ["Layout"] = "Layout", ["Appearance"] = "Darstellung",
["Character"] = "Charakter", ["Item Level"] = "Gegenstandsstufe",
["Offensive"] = "Offensiv", ["Tertiary"] = "Tertiär",
["Gear"] = "Ausrüstung", ["Repair Cost"] = "Reparaturkosten",
["Side Panel"] = "Seitenpanel", ["Side Panel Contains"] = "Seitenpanel enthält",
["Value Display"] = "Werteanzeige",
["Frame & Position"] = "Fenster & Position",
["Typography"] = "Typografie",
["Readability"] = "Lesbarkeit",
["Localization"] = "Lokalisierung",
["Offensive Stats"] = "Offensivwerte",
["Tertiary Stats"] = "Tertiärwerte",
["Defensive Stats"] = "Defensivwerte",
["Show Stats Panel"] = "Wertepanel anzeigen", ["Lock Frames"] = "Fenster sperren",
["Show Main Stat"] = "Hauptattribut anzeigen",
["Show Stamina"] = "Ausdauer anzeigen",
["Show Item Level"] = "Gegenstandsstufe anzeigen",
["Show Rating"] = "Wertung anzeigen", ["Show Percentage"] = "Prozent anzeigen",
["Match Value Color to Stat"] = "Wertfarbe wie Statfarbe",
["Show Offensive Stats"] = "Offensivwerte anzeigen", ["Hide Zero Values"] = "Nullwerte ausblenden",
["Show Crit"] = "Krit. anzeigen", ["Show Haste"] = "Tempo anzeigen",
["Show Mastery"] = "Meisterschaft anzeigen", ["Show Versatility"] = "Vielseitigkeit anzeigen",
["Show Tertiary Stats"] = "Tertiärwerte anzeigen",
["Show Leech"] = "Aussaugen anzeigen", ["Show Avoidance"] = "Vermeidung anzeigen", ["Show Speed"] = "Lauftempo anzeigen",
["Show Defensive Stats"] = "Defensivwerte anzeigen",
["Show Dodge"] = "Ausweichen anzeigen", ["Show Parry"] = "Parieren anzeigen",
["Show Block"] = "Blocken anzeigen", ["Show Armor"] = "Rüstung anzeigen", ["Show Stagger"] = "Staffeln anzeigen",
["Show Durability"] = "Haltbarkeit anzeigen", ["Show Repair Cost"] = "Reparaturkosten anzeigen",
["Auto Color by Threshold"] = "Auto-Farbe nach Schwellwert",
["Use Worst Slot (instead of average)"] = "Schlechtester Slot (statt Durchschnitt)",
["Scale:"] = "Skalierung:", ["Refresh Rate (sec):"] = "Aktualisierungsrate (Sek.):", ["Font Size:"] = "Schriftgröße:", ["Text Opacity:"] = "Textdeckkraft:", ["Panel Background:"] = "Panelhintergrund:",
["Display Mode:"] = "Anzeigemodus:", ["Tooltip Targets:"] = "Tooltip-Ziele:", ["Label Style:"] = "Labelstil:", ["Text Outline:"] = "Textkontur:", ["Font:"] = "Schrift:", ["Language:"] = "Sprache:",
["Flat"] = "Flach", ["Sectioned"] = "Gruppiert", ["Split"] = "Geteilt",
["Mythic+"] = "Mythic+", ["Raid"] = "Raid",
["Full"] = "Voll", ["Short"] = "Kurz", ["Hidden"] = "Versteckt",
["None"] = "Keine", ["Outline"] = "Kontur", ["Thick Outline"] = "Dicke Kontur",
["M+ Target"] = "M+ Ziel", ["Raid Target"] = "Raid-Ziel",
["M+ High Keys"] = "M+ hohe Schlüssel", ["Raid Mythic All Bosses"] = "Raid Mythisch alle Bosse",
["Target:"] = "Ziel:", ["Current:"] = "Aktuell:", ["Missing:"] = "Fehlt:",
["Over:"] = "Drüber:", ["Matched:"] = "Erreicht:", ["Snapshot:"] = "Datenstand:",
["Stats panel shown"] = "Statpanel angezeigt", ["Stats panel hidden"] = "Statpanel ausgeblendet",
["Settings reset to defaults"] = "Einstellungen auf Standard zurückgesetzt",
["Commands: /ss or /statspro (config), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help"] = "Befehle: /ss oder /statspro (Einstellungen), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help",
["Reset to Defaults"] = "Auf Standard", ["Close"] = "Schließen",
["Open Settings"] = "Einstellungen öffnen", ["Settings"] = "Einstellungen",
["Auto (current: %s)"] = "Auto (aktuell: %s)",
["|cffffaa44⚠|r Font may not render %s glyphs. Pick a SharedMedia font with proper coverage."] = "|cffffaa44⚠|r Schrift unterstützt %s eventuell nicht. Wähle eine SharedMedia-Schrift mit Glyphenabdeckung.",
["Displays your secondary, defensive stats and durability on screen. Click below to open the full settings window."] = "Zeigt sekundäre, defensive Werte und Haltbarkeit auf dem Bildschirm an. Klicke unten, um das Einstellungsfenster zu öffnen.",
},
-- frFR: French. Hâte (4 chars, accented form) is WoW's official Haste term; Vit
-- (Vitesse) distinct. Strength="Forc" and Durability="Dura" use 4-char forms so
-- they don't collide with the everyday words "Fort" / "Dur". Esqu (Esquive) at 4
-- chars reads more clearly than the truncated 3-char "Esq".
frFR = {
Crit = "Crit", Haste = "Hâte", Mastery = "Maît", Vers = "Polyv",
Dodge = "Esqu", Parry = "Par", Block = "Bloc", Armor = "Arm", Stagger = "Report",
Strength = "Forc", Agility = "Agil", Intellect = "Int", Stamina = "End",
ItemLevel = "NivObj",
Leech = "Vamp", Avoidance = "Évit", Speed = "Vit",
Durability = "Dura", Repair = "Rép",
Defensive = "Défense",
Color = "Couleur",
-- ===== Settings UI (best-effort draft, native-speaker review welcome via Issues) =====
["Stats"] = "Stats", ["Layout"] = "Disposition", ["Appearance"] = "Apparence",
["Character"] = "Personnage", ["Item Level"] = "Niveau d'objet",
["Offensive"] = "Offensif", ["Tertiary"] = "Tertiaire",
["Gear"] = "Équipement", ["Repair Cost"] = "Coût de réparation",
["Side Panel"] = "Panneau latéral", ["Side Panel Contains"] = "Panneau latéral contient",
["Value Display"] = "Affichage des valeurs",
["Frame & Position"] = "Cadre & Position",
["Typography"] = "Typographie",
["Readability"] = "Lisibilité",
["Localization"] = "Localisation",
["Offensive Stats"] = "Stats Offensives",
["Tertiary Stats"] = "Stats Tertiaires",
["Defensive Stats"] = "Stats Défensives",
["Show Stats Panel"] = "Afficher le panneau", ["Lock Frames"] = "Verrouiller les cadres",
["Show Main Stat"] = "Afficher stat principale",
["Show Stamina"] = "Afficher Endurance",
["Show Item Level"] = "Afficher niveau d'objet",
["Show Rating"] = "Afficher cote", ["Show Percentage"] = "Afficher %",
["Match Value Color to Stat"] = "Couleur valeur = stat",
["Show Offensive Stats"] = "Afficher offensives", ["Hide Zero Values"] = "Masquer valeurs nulles",
["Show Crit"] = "Afficher Crit", ["Show Haste"] = "Afficher Hâte",
["Show Mastery"] = "Afficher Maîtrise", ["Show Versatility"] = "Afficher Polyvalence",
["Show Tertiary Stats"] = "Afficher tertiaires",
["Show Leech"] = "Afficher Vampirisme", ["Show Avoidance"] = "Afficher Évitement", ["Show Speed"] = "Afficher Vitesse",
["Show Defensive Stats"] = "Afficher défensives",
["Show Dodge"] = "Afficher Esquive", ["Show Parry"] = "Afficher Parade",
["Show Block"] = "Afficher Blocage", ["Show Armor"] = "Afficher Armure", ["Show Stagger"] = "Afficher Report",
["Show Durability"] = "Afficher durabilité", ["Show Repair Cost"] = "Afficher coût de réparation",
["Auto Color by Threshold"] = "Couleur auto par seuil",
["Use Worst Slot (instead of average)"] = "Pire emplacement (vs moyenne)",
["Scale:"] = "Échelle :", ["Refresh Rate (sec):"] = "Fréquence (sec) :", ["Font Size:"] = "Taille de police :", ["Text Opacity:"] = "Opacité du texte :", ["Panel Background:"] = "Arrière-plan du panneau :",
["Display Mode:"] = "Mode d'affichage :", ["Tooltip Targets:"] = "Cibles infobulle :", ["Label Style:"] = "Style d'étiquette :", ["Text Outline:"] = "Contour du texte :", ["Font:"] = "Police :", ["Language:"] = "Langue :",
["Flat"] = "Plat", ["Sectioned"] = "Par sections", ["Split"] = "Séparé",
["Mythic+"] = "Mythique+", ["Raid"] = "Raid",
["Full"] = "Complet", ["Short"] = "Court", ["Hidden"] = "Masqué",
["None"] = "Aucun", ["Outline"] = "Contour", ["Thick Outline"] = "Contour épais",
["M+ Target"] = "Cible M+", ["Raid Target"] = "Cible raid",
["M+ High Keys"] = "M+ hautes clés", ["Raid Mythic All Bosses"] = "Raid mythique tous les boss",
["Target:"] = "Cible :", ["Current:"] = "Actuel :", ["Missing:"] = "Manquant :",
["Over:"] = "Excès :", ["Matched:"] = "Atteint :", ["Snapshot:"] = "Instantané :",
["Stats panel shown"] = "Panneau de stats affiché", ["Stats panel hidden"] = "Panneau de stats masqué",
["Settings reset to defaults"] = "Paramètres réinitialisés",
["Commands: /ss or /statspro (config), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help"] = "Commandes : /ss ou /statspro (config), /ss show, /ss hide, /ss toggle, /ss reset, /ss debug, /ss help",
["Reset to Defaults"] = "Par défaut", ["Close"] = "Fermer",
["Open Settings"] = "Ouvrir les paramètres", ["Settings"] = "Paramètres",
["Auto (current: %s)"] = "Auto (actuel : %s)",
["|cffffaa44⚠|r Font may not render %s glyphs. Pick a SharedMedia font with proper coverage."] = "|cffffaa44⚠|r La police peut ne pas afficher les glyphes %s. Choisissez une police SharedMedia avec couverture appropriée.",
["Displays your secondary, defensive stats and durability on screen. Click below to open the full settings window."] = "Affiche vos statistiques secondaires, défensives et la durabilité à l'écran. Cliquez ci-dessous pour ouvrir la fenêtre de paramètres complète.",
},
-- esES: Spanish (Spain). WoW Spanish client uses Celeridad / Velocidad for the
-- Haste/Speed split → Cele / Vel. Leech="Robo" matches "Robo de vida" (life steal),
-- the WoW Spanish term — closer to client wording than the literal "Suc(ción)".
-- Most rows use 4-char forms (Esqu / Fuer / Agil) — 3-char abbreviations look
-- unfinished beside Spanish's typically-longer words.