DopingControl v0.6.4
This commit is contained in:
+109
@@ -4,6 +4,115 @@ All notable changes to DopingControl. Versions are the ones actually
|
||||
released here — a new feature raises the middle number and resets the last
|
||||
one, the last number is for fixes without new features.
|
||||
|
||||
## Unreleased
|
||||
|
||||
## v0.6.4
|
||||
|
||||
### Equipment expectation top-up now self-heals every refresh, not just once at ADDON_LOADED
|
||||
|
||||
Field report after v0.6.3 shipped (WAIST seeded `true` for every role):
|
||||
after deploying and reloading, the Equipment matrix still showed 16
|
||||
columns (no WAI) and the owner's own row read "(read failed)" / sum "--".
|
||||
|
||||
Offline reproduction against the real captured dump
|
||||
(`dc_raid_1788070493.txt`, the scan taken right after the reload) and the
|
||||
real live `db.expectations` shape
|
||||
(`WTF\...\SavedVariables\DopingControl.lua`, all 20 role/class lines —
|
||||
`NECK`/`R1`/`R2 = true`, `WAIST` absent everywhere) through the actual
|
||||
`DC_Gear.Assemble` -> `DC_Model.build` -> `DC_Matrix` pipeline found no
|
||||
Lua error anywhere — no nil dereference, nothing caught by a `pcall`.
|
||||
Both symptoms are real, and neither is what it first looked like:
|
||||
|
||||
* The "(read failed)"/"--" row is a correct, unrelated read: the dump's
|
||||
own `P`/`H`/`X` lines already carry `aurasRead=0`/
|
||||
`unreadableReason=read failed` before any render happens —
|
||||
`scan/aura.lua`'s "zero USABLE auras this scan => aurasRead=false" rule
|
||||
firing on a genuine zero-buff moment. Nothing to do with WAIST or
|
||||
equipment.
|
||||
* The missing WAIST column traces to `core/model.lua`'s `anyClassExpects`:
|
||||
it only falls back to the `DC.DEFAULT_EXPECT` seed for a (role, class)
|
||||
combo with NO saved line at all — an *existing* class line (any line
|
||||
materialized before the v0.6.3 seed change) is trusted as complete, key
|
||||
by key. `DC.EnsureExpectations`/`topUpLine` (`core/config.lua`) are
|
||||
themselves correct — proven by running them against the exact real
|
||||
saved shape above — but they only ran once, at `ADDON_LOADED`. A
|
||||
`db.expectations` line materialized before v0.6.3 shipped therefore
|
||||
stayed short the new key until a SECOND full reload happened to run
|
||||
`EnsureExpectations` again against the current seed.
|
||||
|
||||
Fix: `ui/matrix.lua`'s `MX.ExpectTable` — the one resolver both the header
|
||||
and `MX.Refresh` read the expectation table from — now defensively re-runs
|
||||
`DC.EnsureExpectations(d)` every time it resolves, i.e. on every
|
||||
scan/window redraw, not only once at boot. `topUpLine` only ever fills
|
||||
`nil` keys, so this is idempotent and side-effect-free on an
|
||||
already-current table; it self-heals a stale class line the moment the
|
||||
window redraws, without requiring a second reload.
|
||||
|
||||
New regression test `tools/luatests/test_expectations_topup.lua`: embeds
|
||||
the real captured `db.expectations` shape (all 20 role/class lines,
|
||||
verbatim) and a real gear/hit dump line, reproduces the exact
|
||||
16-column/no-WAI/`(read failed)` symptom through the unmodified
|
||||
`DC_Model.build`, then proves `MX.ExpectTable` self-heals it to 17 columns
|
||||
with WAIST at column 6.
|
||||
|
||||
## v0.6.3
|
||||
|
||||
### Waist is expected by default for every role
|
||||
|
||||
Owner decision, reversing the "confirmed, not a bug" call below: every role
|
||||
wears an enchantable belt, so WAIST now belongs with the other worn
|
||||
equipment slots (HEAD, CHST, WRST, ...) instead of the opt-in neck/ring
|
||||
treatment. `data/expectations.lua`'s seed now carries `WAIST = true` for
|
||||
all five roles. Existing SavedVariables pick up the flag automatically —
|
||||
`core/config.lua`'s `topUpLine` already adds any seed slot the saved line
|
||||
has never seen (`nil`) as `true` on load, the same mechanism that
|
||||
delivered the neck/ring columns to existing saves; no separate migration
|
||||
was needed. An explicit `WAIST = false` set for a role/class still hides
|
||||
the column, same opt-out behavior as any other slot.
|
||||
|
||||
### Confirmed: the waist column's off-by-default is intended, not a bug
|
||||
|
||||
A field report said the belt was worn but the Equipment tab still showed
|
||||
16 columns, no WAI, after the v0.6.2 waist-slot fix was deployed and the
|
||||
UI reloaded. Traced end to end (`core/model.lua` `tabSlots`/
|
||||
`anyClassExpects`, `ui/matrix.lua`'s `vm.slots` column build, the live
|
||||
SavedVariables `expectations` table): the gating is working exactly as
|
||||
v0.6.2 shipped and tested it — the waist enchant column is opt-in per
|
||||
role/class, the same treatment as neck/rings, and simply had not been
|
||||
enabled for any role/class yet (unlike neck/rings, which already carry
|
||||
`= true` in every class line of the reporting player's save from before
|
||||
this default existed). No code was hiding anything; the column appears
|
||||
the moment the checkbox is enabled in Options > Equipment.
|
||||
|
||||
Added a regression test (`tools/luatests/test_model.lua`, section 8d)
|
||||
that exercises the real production path — `core/model.lua`'s `M.build`
|
||||
through `tabSlots`/`anyClassExpects`, the same function `ui/matrix.lua`
|
||||
renders from — instead of the static `DC.SLOTS_EQUIPMENT` array the
|
||||
layout test uses. It pins both halves: WAIST absent from `vm.slots`
|
||||
under the default seed, and present + classifying correctly (HAS/
|
||||
MISSING/UNKNOWN) once a role/class expectation is switched on. This
|
||||
closes the gap that let the "is it really gated correctly" question go
|
||||
unanswered by the existing suite.
|
||||
|
||||
## v0.6.2
|
||||
|
||||
A fix release.
|
||||
|
||||
### The waist slot was never scanned
|
||||
|
||||
No equipment read — the raid dump, the Equipment tab, the Hit tab's
|
||||
belt contribution — ever covered the waist (inventory slot 6). The slot
|
||||
list that drives every `GetInventoryItemLink` read was borrowed from the
|
||||
Equipment tab's enchant-tracking column list, which had no waist column;
|
||||
that list never included the waist at all, so it was silently skipped in
|
||||
every read that reused it, not only display.
|
||||
|
||||
The waist turns out to be enchantable on this server (a custom "Belt
|
||||
Buckle" item line, the same kind of addition that already made the neck
|
||||
and both rings enchantable beyond stock behaviour), so it now gets its
|
||||
own Equipment-tab column — off by default, like the neck and ring
|
||||
columns, until enabled per role/class in the options grid.
|
||||
|
||||
## v0.6.1
|
||||
|
||||
A fix release. Everything here is something that was supposed to work
|
||||
|
||||
+7
-1
@@ -1,7 +1,8 @@
|
||||
## Interface: 11200
|
||||
## Title: DopingControl
|
||||
## Notes: Raid consumables, buffs, debuffs, resistances, hit and enchant checker
|
||||
## Version: 0.6.1
|
||||
## Author: ShempError
|
||||
## Version: 0.6.4
|
||||
## SavedVariables: DopingControlDB
|
||||
|
||||
core\const.lua
|
||||
@@ -16,6 +17,7 @@ data\resist.lua
|
||||
core\model.lua
|
||||
core\roles.lua
|
||||
core\sim.lua
|
||||
core\demo.lua
|
||||
scan\talents.lua
|
||||
scan\reach.lua
|
||||
scan\aura.lua
|
||||
@@ -24,9 +26,13 @@ scan\resist.lua
|
||||
scan\hit.lua
|
||||
scan\engine.lua
|
||||
core\probe.lua
|
||||
ui\cellsheet.lua
|
||||
ui\widgets.lua
|
||||
ui\matrix.lua
|
||||
ui\options.lua
|
||||
ui\report.lua
|
||||
features\minimap.lua
|
||||
core\init.lua
|
||||
|
||||
# DEV ONLY -- remove this line and dev\ when cutting a release orphan commit.
|
||||
dev\raiddump.lua
|
||||
|
||||
@@ -183,6 +183,13 @@ same gaps, every time. The full UI (all six tabs, roles, pills, report preview)
|
||||
inspected and configured without being in a group, and all chat output is disabled while test
|
||||
mode is on, so nothing simulated can ever reach another player.
|
||||
|
||||
## Demo mode
|
||||
|
||||
`/dc demo` (or the button in the options panel) shows the same simulated raid, but with **all
|
||||
six school protection columns at once** — fire, frost, nature, shadow, holy and arcane — even
|
||||
though five of them are switched off by default. It is a viewing mode, nothing else: your saved
|
||||
expectations are never modified, and the mode itself is not saved either, so a reload ends it.
|
||||
|
||||
## Install
|
||||
|
||||
**Option A — download (simplest):**
|
||||
@@ -213,6 +220,7 @@ git clone https://github.com/ShempError/DopingControl.git DopingControl
|
||||
| `/dc options` | opens the options panel (also: right-click the minimap button) |
|
||||
| `/dc report` | opens the raid report preview |
|
||||
| `/dc test` | toggles test mode (simulated 40-player raid, chat output disabled) |
|
||||
| `/dc demo` | toggles demo mode (the simulated raid with all six protection columns shown; your expectations stay untouched, the mode is not saved) |
|
||||
| `/dc probe [range <name>]` | dumps raw scan-API returns to a file for debugging data-source gaps (SuperWoW required) |
|
||||
| `/dc talents [on\|off\|clear]` | shows how many players' talents are in, or stops asking for them |
|
||||
| `/dc unknown` | lists buff names the scanner saw but could not classify — useful for reporting gaps in the data tables |
|
||||
@@ -239,6 +247,10 @@ either way.
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md). The short form:
|
||||
|
||||
- **v0.6.4** — the equipment expectation top-up self-heals on every refresh, so a profile
|
||||
saved before the waist column existed picks it up without a relogin.
|
||||
- **v0.6.3** — the waist slot is expected equipment by default for every role.
|
||||
- **v0.6.2** — fix: the waist slot was never scanned, so a missing belt could not be reported.
|
||||
- **v0.6.1** — fixes: other players' resistances are reconstructed from their gear (the tab
|
||||
showed `?` for everyone but you), the weapon column can report a missing imbue again, six
|
||||
consumables whose aura name differs from the item name are matched, "gaps only" no longer
|
||||
|
||||
@@ -47,6 +47,44 @@ DC.DEFAULTS = {
|
||||
skillBooks = {},
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.SimOwnsStore(db) -> bool
|
||||
--
|
||||
-- The ONE owner predicate for DC.store, and the question every scan gate
|
||||
-- asks -- never db.testMode alone. Both simulated modes replace the store
|
||||
-- with the simulated raid, and while either is on "real data never beats
|
||||
-- the simulation":
|
||||
-- * db.testMode -- /dc test, persisted (DEFAULTS above)
|
||||
-- * DC.demoMode -- /dc demo, transient (core/demo.lua)
|
||||
--
|
||||
-- Why it exists: the gates named db.testMode only, so a finished scan
|
||||
-- overwrote the sim store while demo mode was still armed. The demo
|
||||
-- expectation layer then graded the REAL raid against all six school
|
||||
-- protection columns (five of which ship with no seed entry at all): every
|
||||
-- raider grew fabricated MISSING cells, the banner vanished with the "sim"
|
||||
-- source that had been the only marker that this is a mode and not a
|
||||
-- measurement, and each row's whisper button went live against a real
|
||||
-- player.
|
||||
--
|
||||
-- Why HERE and not in core/demo.lua, where the mode lives: config.lua loads
|
||||
-- second, ahead of every gate that calls this, so the gates need no
|
||||
-- load-order guard -- and a demo module that fails to load can never take
|
||||
-- test mode's protection down with it (DC.demoMode is then simply nil).
|
||||
--
|
||||
-- db is optional; the fallback is for callers that do not hold one.
|
||||
-- Always returns a real boolean -- the gates invert it.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.SimOwnsStore(db)
|
||||
db = db or DC.db or DopingControlDB
|
||||
if db and db.testMode then
|
||||
return true
|
||||
end
|
||||
if DC.demoMode then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Generic deep copy (tables only; keys are copied by reference, values
|
||||
-- recursively). No cycle handling -- config/expectation shapes are trees.
|
||||
function DC.DeepCopy(src)
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
-- DopingControl core/demo.lua
|
||||
-- Demo mode: the showcase view. It reuses test mode's simulated raid
|
||||
-- (DC_Sim.buildStore -- no second roster exists) and lays its OWN
|
||||
-- expectation table on top so ALL SIX school protection columns
|
||||
-- (FR/FrR/NR/SR/HR/AR) are visible at once.
|
||||
--
|
||||
-- Two hard rules, both of them the reason this file exists at all:
|
||||
--
|
||||
-- 1. The demo layer NEVER writes into db.expectations. Live, five of the
|
||||
-- six school columns ship with no seed entry (data/expectations.lua)
|
||||
-- and are therefore hidden -- that is a deliberate default, and a
|
||||
-- screenshot mode must not rewrite the user's saved matrix to get its
|
||||
-- picture. BuildDemoExpectations deep-copies every line it starts
|
||||
-- from, so no table is ever shared with the DB either.
|
||||
-- 2. Demo state is TRANSIENT -- DC.demoMode / DC.demoExpectations live in
|
||||
-- memory only and are gone after a reload. Deliberately unlike
|
||||
-- db.testMode (which is persisted): the same treatment DC.simRoles
|
||||
-- gets in core/roles.lua, and the BulwarkFrame /bf demo pattern.
|
||||
--
|
||||
-- Why the columns must be forced per (role, class) and not per role: the
|
||||
-- model resolves a row through expect[role][class] and falls back to the
|
||||
-- DC.DEFAULT_EXPECT ROLE line whenever that class line is missing
|
||||
-- (core/model.lua, expectLine) -- a partially filled demo table would fall
|
||||
-- back into the seed and silently lose the school columns again. The same
|
||||
-- fallback drives the dynamic-column check (anyClassExpects), so an
|
||||
-- incomplete layer would also drop the columns from the header.
|
||||
--
|
||||
-- FR is forced too, although the seed already ships it true for all five
|
||||
-- roles: the user may have unchecked it, and "all six schools" is the
|
||||
-- point of the mode.
|
||||
--
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Transient state (NEVER SavedVariables -- see rule 2 above).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.demoMode = false
|
||||
DC.demoExpectations = nil
|
||||
|
||||
-- DC.demoMode is the demo half of DC.SimOwnsStore (core/config.lua) -- the
|
||||
-- predicate every scan gate asks. It deliberately lives in config.lua, not
|
||||
-- here: config.lua loads second, ahead of every gate, so a broken demo
|
||||
-- module can never take TEST mode's protection down with it.
|
||||
|
||||
-- The six school protection columns of the consumables slot table
|
||||
-- (data/consumables.lua). FR is the only one with a seed entry.
|
||||
DC.DEMO_SCHOOL_SLOTS = { "FR", "FrR", "NR", "SR", "HR", "AR" }
|
||||
|
||||
local function forceSchools(line)
|
||||
local n = table.getn(DC.DEMO_SCHOOL_SLOTS)
|
||||
for i = 1, n do
|
||||
line[DC.DEMO_SCHOOL_SLOTS[i]] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.BuildDemoExpectations(db) -> demo table (also cached in
|
||||
-- DC.demoExpectations)
|
||||
--
|
||||
-- Shape is exactly the live one -- demo[role][class][slotId] -- built per
|
||||
-- (role, performing class) over DC_Roles.ROLE_CLASSES, so it can be handed
|
||||
-- to DC_Model.build in place of db.expectations without any special case
|
||||
-- in the model.
|
||||
--
|
||||
-- Source per class line, in order: the user's saved line, else the
|
||||
-- DC.DEFAULT_EXPECT seed line -- deep-copied either way, then the six
|
||||
-- school slots forced on. A legacy old-shape role line (booleans directly
|
||||
-- under [role], migrated away by DC.EnsureExpectations at ADDON_LOADED)
|
||||
-- is not a table per class and therefore falls back to the seed; demo mode
|
||||
-- showing seed defaults for one session is acceptable, writing into the DB
|
||||
-- to avoid it is not.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.BuildDemoExpectations(db)
|
||||
local src = db and db.expectations
|
||||
local out = {}
|
||||
local nRoles = table.getn(DC.ROLES)
|
||||
for r = 1, nRoles do
|
||||
local role = DC.ROLES[r]
|
||||
local seedLine = (DC.DEFAULT_EXPECT and DC.DEFAULT_EXPECT[role]) or {}
|
||||
local savedByClass = src and src[role]
|
||||
local classes = (DC_Roles and DC_Roles.ROLE_CLASSES
|
||||
and DC_Roles.ROLE_CLASSES[role]) or {}
|
||||
local nClasses = table.getn(classes)
|
||||
if nClasses > 0 then
|
||||
local byClass = {}
|
||||
for c = 1, nClasses do
|
||||
local cls = classes[c]
|
||||
local saved = savedByClass and savedByClass[cls]
|
||||
local line
|
||||
if type(saved) == "table" then
|
||||
line = DC.DeepCopy(saved)
|
||||
else
|
||||
line = DC.DeepCopy(seedLine)
|
||||
end
|
||||
forceSchools(line)
|
||||
byClass[cls] = line
|
||||
end
|
||||
out[role] = byClass
|
||||
else
|
||||
-- defensive: without DC_Roles there is no class domain to fill,
|
||||
-- so emit the legacy role x slot line (the model falls back to
|
||||
-- the seed for the cells, but the header tooltip and any
|
||||
-- legacy reader still see the schools)
|
||||
local line = DC.DeepCopy(seedLine)
|
||||
forceSchools(line)
|
||||
out[role] = line
|
||||
end
|
||||
end
|
||||
DC.demoExpectations = out
|
||||
return out
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.SetDemoMode(v) -- the switch behind /dc demo and the options button.
|
||||
-- Mirrors DC.SetTestMode (core/init.lua) but touches no SavedVariables.
|
||||
--
|
||||
-- Store handling: entering demo mode swaps in the simulated raid. Leaving
|
||||
-- it hands the store back to the REAL scan -- unless db.testMode is on,
|
||||
-- which owns the simulated store on its own account and must not be kicked
|
||||
-- out of it. DC.SetTestMode mirrors that in the other direction, so neither
|
||||
-- mode can empty the other's showcase; both ask DC.SimOwnsStore
|
||||
-- (core/config.lua), which is also what every scan gate now asks instead
|
||||
-- of db.testMode.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.SetDemoMode(v)
|
||||
local db = DC.db or DopingControlDB
|
||||
if v then
|
||||
DC.demoMode = true
|
||||
DC.BuildDemoExpectations(db)
|
||||
if DC_Sim and DC_Sim.buildStore then
|
||||
-- buildStore() also refills the transient DC.simRoles -- do NOT
|
||||
-- wipe them afterwards (see DC.SetTestMode)
|
||||
DC.store = DC_Sim.buildStore()
|
||||
end
|
||||
else
|
||||
DC.demoMode = false
|
||||
DC.demoExpectations = nil
|
||||
if not (db and db.testMode) then
|
||||
DC.store = { players = {}, source = "scan" }
|
||||
if DC_Scan and DC_Scan.Start then
|
||||
DC_Scan.Start(true) -- leaving demo mode: immediate rescan
|
||||
end
|
||||
end
|
||||
end
|
||||
if DC_Matrix and DC_Matrix.Refresh then
|
||||
DC_Matrix.Refresh()
|
||||
end
|
||||
if DC_Options and DC_Options.Refresh then
|
||||
DC_Options.Refresh() -- relabels the options button when it is open
|
||||
end
|
||||
end
|
||||
+36
-3
@@ -59,6 +59,14 @@ function DC.SetTestMode(v)
|
||||
-- into a suggestion badge (the sim roster deliberately contains
|
||||
-- exactly two suggestion-badge cases).
|
||||
DC.store = DC_Sim.buildStore()
|
||||
elseif DC.demoMode then
|
||||
-- Demo mode owns the simulated store on its own account
|
||||
-- (DC.SimOwnsStore) -- the mirror image of core/demo.lua's
|
||||
-- "leaving demo mode must not kick test mode out of the sim
|
||||
-- store". Without this the showcase would be emptied here and the
|
||||
-- rescan below would be dropped by the scan gate anyway, leaving a
|
||||
-- blank window. /dc demo is what ends demo mode.
|
||||
DC.store = DC_Sim.buildStore()
|
||||
else
|
||||
DC.store = { players = {}, source = "scan" }
|
||||
DC_Scan.Start(true) -- leaving test mode: immediate real rescan
|
||||
@@ -108,7 +116,7 @@ end
|
||||
-- ------------------------------------------------------------------
|
||||
-- Slash commands: /dc, /doping
|
||||
-- ------------------------------------------------------------------
|
||||
local HELP_TEXT = "commands: /dc (window) | scan | options | report | test | probe [range <name>] | unknown | talents [on|off|clear]"
|
||||
local HELP_TEXT = "commands: /dc (window) | scan | options | report | test | demo | probe [range <name>] | unknown | talents [on|off|clear]"
|
||||
|
||||
function DC.HandleSlash(msg)
|
||||
msg = msg or ""
|
||||
@@ -119,9 +127,15 @@ function DC.HandleSlash(msg)
|
||||
if cmd == "" then
|
||||
DC_Matrix.Toggle()
|
||||
elseif cmd == "scan" then
|
||||
-- Real data never beats the simulation while EITHER simulated mode
|
||||
-- owns the store (DC.SimOwnsStore, core/config.lua). Test mode is
|
||||
-- named first because it is the persisted one -- but the refusal
|
||||
-- must name the mode that is actually on, or the user toggles the
|
||||
-- wrong switch and the scan keeps refusing.
|
||||
if db and db.testMode then
|
||||
-- real data never beats the simulation while test mode is on
|
||||
DC.Print("test mode is on - /dc test to switch back before scanning")
|
||||
elseif DC.SimOwnsStore(db) then
|
||||
DC.Print("demo mode is on - /dc demo to switch back before scanning")
|
||||
else
|
||||
DC_Scan.Start()
|
||||
end
|
||||
@@ -133,9 +147,28 @@ function DC.HandleSlash(msg)
|
||||
DC.SetTestMode(not (db and db.testMode))
|
||||
if db and db.testMode then
|
||||
DC.Print("test mode ON - simulated raid, chat output disabled")
|
||||
elseif DC.demoMode then
|
||||
-- SetTestMode kept the sim store for demo mode and started no
|
||||
-- rescan; a "rescanning" message here would be false.
|
||||
DC.Print("test mode OFF - demo mode still on, still showing the simulated raid (/dc demo to leave)")
|
||||
else
|
||||
DC.Print("test mode OFF - rescanning the real group")
|
||||
end
|
||||
elseif cmd == "demo" then
|
||||
-- Showcase view: the simulated raid plus a TRANSIENT expectation
|
||||
-- layer that shows all six school protection columns at once
|
||||
-- (core/demo.lua). Nothing about it is saved -- unlike /dc test,
|
||||
-- which persists db.testMode.
|
||||
if not DC.SetDemoMode then
|
||||
DC.Print("demo module not loaded")
|
||||
else
|
||||
DC.SetDemoMode(not DC.demoMode)
|
||||
if DC.demoMode then
|
||||
DC.Print("demo mode ON - simulated raid, all six protection columns shown")
|
||||
else
|
||||
DC.Print("demo mode OFF - back to the normal view")
|
||||
end
|
||||
end
|
||||
elseif cmd == "probe" then
|
||||
-- core/probe.lua owns the probe subcommand grammar:
|
||||
-- DC_Probe.HandleSlash(rest) handles "" (battery),
|
||||
@@ -222,7 +255,7 @@ if CreateFrame then
|
||||
|
||||
elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then
|
||||
local db = DC.db
|
||||
if db and not db.testMode and MatrixIsShown() then
|
||||
if db and not DC.SimOwnsStore(db) and MatrixIsShown() then
|
||||
DC_Scan.Start()
|
||||
end
|
||||
|
||||
|
||||
@@ -850,6 +850,22 @@ function M.classify(player, slotdef, expected, books)
|
||||
for name in pairs(names) do
|
||||
if nameMapsToSlot(name, slotdef.id) then
|
||||
if not (haveIds and nameHasIds[name]) then
|
||||
-- Consumable/class-buff name collision (decision
|
||||
-- 2026-08-15, data/classbuffs.lua
|
||||
-- DC.CONSUMABLE_CLASSBUFF_NAME_COLLISION): a
|
||||
-- name-only match (this branch only runs when no
|
||||
-- spell ID resolved the buff) can never satisfy a
|
||||
-- CONSUMABLE slot when the name is also a class buff
|
||||
-- name -- UNKNOWN, not HAS. One-sided: the class-buff
|
||||
-- slot side of the same name is unaffected (see the
|
||||
-- constant's comment for why).
|
||||
local cdef = DC.CONSUMABLES and DC.CONSUMABLES[name]
|
||||
local collide = DC.CONSUMABLE_CLASSBUFF_NAME_COLLISION
|
||||
if cdef and cdef.slot == slotdef.id
|
||||
and collide and collide[name] then
|
||||
return S.UNKNOWN,
|
||||
"name-only, ambiguous with class buff: " .. name
|
||||
end
|
||||
local icon = nil
|
||||
if textures then
|
||||
icon = textures[name]
|
||||
|
||||
+194
-56
@@ -35,6 +35,27 @@
|
||||
-- (built programmatically below, so id-matched and name-matched
|
||||
-- players are both covered), and every gear entry TABLE carries
|
||||
-- texture + name + quality -- test mode showcases the icon feature.
|
||||
-- * SCHOOL PROTECTION carriers, one per school, ALL by spell ID:
|
||||
-- Simtank SR (17548), Simsteel FrR (17544), Simsham NR (17546),
|
||||
-- Simheala HR (7245), Simmage AR (17549) -- plus the seeded FR
|
||||
-- carriers. Their columns stay hidden under the shipped defaults; demo
|
||||
-- mode (core/demo.lua) switches all six on at once and these five are
|
||||
-- what makes that view show HAS cells rather than an empty block. Held
|
||||
-- by ID on purpose: "Shadow Protection" is ALSO the priest class buff
|
||||
-- (data/consumables.lua:312), so a name-based carrier would fake an SR
|
||||
-- fulfillment the moment the column is visible.
|
||||
-- * v6 SLOT MODEL (17 active default columns of the 22-slot table --
|
||||
-- the five school protection slots FrR/NR/SR/HR/AR default off and
|
||||
-- stay hidden) + identification-ladder showcase: the
|
||||
-- four TANKs run the full KG tank set (green pill across all 17
|
||||
-- columns); every new column (BL/ZANZA/GAE/SCHOOL/DREAMT/SHARD/ALC)
|
||||
-- has HAS, MISSING and NOTEXP cells somewhere in the roster. Generic
|
||||
-- "Well Fed" carriers split three ways for the matrix ladder:
|
||||
-- effect line resolving to an ICON-carrying item (Simwall Dumplings,
|
||||
-- Simrogue Squid, Simmage Danonzo's Delight), effect line resolving
|
||||
-- to an item WITHOUT a sourced icon (Simheala Herbal Salad ->
|
||||
-- slot-generic icon, tooltip names the item), and no effects table
|
||||
-- at all (Simsteel and others -> "item unknown" fallback).
|
||||
-- * DEBUFFS tab: exactly 3 afflicted players
|
||||
-- -- Simrogue + Simtree share "Resurrection Sickness" (afflicted=2 =>
|
||||
-- first column, count sorting testable), Simfrost carries "Curse of
|
||||
@@ -191,13 +212,29 @@ end
|
||||
-- one shared list is safe to reuse across every role and class.
|
||||
-- cleanAuras() returns a FRESH table on every call (set() already builds
|
||||
-- a new table each time); the source list itself is read-only.
|
||||
--
|
||||
-- SPROT is filled by "Prayer of Shadow Protection" and NEVER by the bare
|
||||
-- "Shadow Protection": that bare name belongs to BOTH the priest buff
|
||||
-- (data/classbuffs.lua SPROT) and the lesser SR potion aura
|
||||
-- (data/consumables.lua:312, spell 7242). These players carry no aura IDs
|
||||
-- at all (ids = {}), so the model's ID-before-name rule cannot fire
|
||||
-- (core/model.lua: haveIds == false) and the bare name would satisfy the
|
||||
-- SR POTION column as well. Live that stays invisible because SR ships
|
||||
-- default-off -- but demo mode (core/demo.lua) switches SR on for
|
||||
-- everyone, which is exactly where the false HAS would show up. The
|
||||
-- Prayer variant maps to SPROT only (classbuffs.lua:51, ids {27683}) and
|
||||
-- is the raid-wide form anyway, so the class-buff showcase is unchanged
|
||||
-- while the SR column stays honest.
|
||||
-- ------------------------------------------------------------------
|
||||
local CLEAN_AURA_NAMES = {
|
||||
"Flask of the Titans", "Well Fed", "Juju Might", "Juju Power",
|
||||
"Elixir of the Mongoose", "Greater Arcane Elixir", "Mageblood Potion",
|
||||
"Elixir of Superior Defense", "Elixir of Fortitude",
|
||||
"Elixir of the Mongoose", "R.O.I.D.S.", "Spirit of Zanza",
|
||||
"Greater Arcane Elixir", "Elixir of Shadow Power", "Dreamtonic",
|
||||
"Dreamshard Elixir", "Mageblood Potion",
|
||||
"Elixir of Superior Defense", "Elixir of Fortitude", "Medivh's Merlot",
|
||||
"Greater Fire Protection", "Mark of the Wild", "Power Word: Fortitude",
|
||||
"Shadow Protection", "Arcane Intellect", "Soulstone Resurrection",
|
||||
"Prayer of Shadow Protection", "Arcane Intellect",
|
||||
"Soulstone Resurrection",
|
||||
}
|
||||
|
||||
local function cleanAuras()
|
||||
@@ -225,31 +262,59 @@ local ICONS = {
|
||||
["Juju Power"] = "Interface\\Icons\\INV_Misc_MonsterScales_11",
|
||||
["Elixir of Giants"] = "Interface\\Icons\\INV_Potion_61",
|
||||
["Elixir of the Giants"] = "Interface\\Icons\\INV_Potion_61",
|
||||
["Strike of the Scorpok"] = "Interface\\Icons\\INV_Misc_MonsterScales_09",
|
||||
["Rage of Ages"] = "Interface\\Icons\\INV_Scroll_03",
|
||||
-- AGI
|
||||
["Elixir of the Mongoose"] = "Interface\\Icons\\INV_Potion_32",
|
||||
["Elixir of Greater Agility"] = "Interface\\Icons\\INV_Potion_94",
|
||||
["Greater Agility"] = "Interface\\Icons\\INV_Potion_94",
|
||||
-- SP
|
||||
-- BL (aura names + item-alias keys, like the data table)
|
||||
["Rage of Ages"] = "Interface\\Icons\\INV_Stone_15",
|
||||
["R.O.I.D.S."] = "Interface\\Icons\\INV_Stone_15",
|
||||
["Strike of the Scorpok"] = "Interface\\Icons\\INV_Misc_Dust_02",
|
||||
["Ground Scorpok Assay"] = "Interface\\Icons\\INV_Misc_Dust_02",
|
||||
["Spirit of Boar"] = "Interface\\Icons\\INV_Drink_12",
|
||||
["Lung Juice Cocktail"] = "Interface\\Icons\\INV_Drink_12",
|
||||
["Infallible Mind"] = "Interface\\Icons\\INV_Potion_32",
|
||||
["Spiritual Domination"] = "Interface\\Icons\\INV_Misc_Food_30",
|
||||
["Gizzard Gum"] = "Interface\\Icons\\INV_Misc_Food_30",
|
||||
-- ZANZA
|
||||
["Spirit of Zanza"] = "Interface\\Icons\\INV_Potion_30",
|
||||
-- GAE / SCHOOL / DREAMT / SHARD (split out of the old SP column)
|
||||
["Greater Arcane Elixir"] = "Interface\\Icons\\INV_Potion_25",
|
||||
["Arcane Elixir"] = "Interface\\Icons\\INV_Potion_30",
|
||||
["Elixir of Shadow Power"] = "Interface\\Icons\\INV_Potion_46",
|
||||
["Elixir of Frost Power"] = "Interface\\Icons\\INV_Potion_03",
|
||||
["Greater Frost Power"] = "Interface\\Icons\\INV_Potion_13",
|
||||
["Dreamtonic"] = "Interface\\Icons\\INV_Potion_10",
|
||||
["Dreamshard Elixir"] = "Interface\\Icons\\INV_Potion_12",
|
||||
-- MP5 (both TWoW name variants share the item icon)
|
||||
["Mageblood Potion"] = "Interface\\Icons\\INV_Potion_45",
|
||||
["Mageblood"] = "Interface\\Icons\\INV_Potion_45",
|
||||
-- ARM
|
||||
["Elixir of Superior Defense"] = "Interface\\Icons\\INV_Potion_66",
|
||||
["Elixir of Greater Defense"] = "Interface\\Icons\\INV_Potion_43",
|
||||
-- HP
|
||||
-- HPELX
|
||||
["Elixir of Fortitude"] = "Interface\\Icons\\INV_Potion_44",
|
||||
["Health II"] = "Interface\\Icons\\INV_Potion_44",
|
||||
-- ALC (alcohol column, split out of the old HP column)
|
||||
["Rumsey Rum Black Label"] = "Interface\\Icons\\INV_Drink_04",
|
||||
["Rumsey Rum"] = "Interface\\Icons\\INV_Drink_08",
|
||||
["Medivh's Merlot"] = "Interface\\Icons\\INV_Drink_Waterskin_05",
|
||||
["Medivh's Merlot Blue"] = "Interface\\Icons\\INV_Drink_Waterskin_01",
|
||||
["Medivh's Merlot Blue Label"] = "Interface\\Icons\\INV_Drink_Waterskin_01",
|
||||
["Kreeg's Stout Beatdown"] = "Interface\\Icons\\INV_Drink_05",
|
||||
-- FR
|
||||
["Greater Fire Protection"] = "Interface\\Icons\\INV_Potion_24",
|
||||
["Fire Protection"] = "Interface\\Icons\\INV_Potion_16",
|
||||
-- FrR/NR/SR/HR/AR (school protection showcase -- these columns are
|
||||
-- hidden until their expectation is enabled per role/class, and demo
|
||||
-- mode switches all six on at once; the carriers hold their potion by
|
||||
-- SPELL ID, never by name -- "Shadow Protection" is also the priest
|
||||
-- class buff, so a name-based carrier would fake an SR fulfillment)
|
||||
["Greater Shadow Protection"] = "Interface\\Icons\\INV_Potion_23",
|
||||
["Greater Frost Protection"] = "Interface\\Icons\\INV_Potion_20",
|
||||
["Greater Nature Protection"] = "Interface\\Icons\\INV_Potion_22",
|
||||
["Holy Protection"] = "Interface\\Icons\\INV_Potion_09",
|
||||
["Greater Arcane Protection"] = "Interface\\Icons\\INV_Potion_83",
|
||||
-- class buffs
|
||||
["Arcane Intellect"] = "Interface\\Icons\\Spell_Holy_MagicalSentry",
|
||||
["Arcane Brilliance"] = "Interface\\Icons\\Spell_Holy_ArcaneIntellect",
|
||||
@@ -257,7 +322,17 @@ local ICONS = {
|
||||
["Gift of the Wild"] = "Interface\\Icons\\Spell_Nature_GiftoftheWild",
|
||||
["Power Word: Fortitude"] = "Interface\\Icons\\Spell_Holy_WordFortitude",
|
||||
["Prayer of Fortitude"] = "Interface\\Icons\\Spell_Holy_PrayerOfFortitude",
|
||||
-- the bare name stays: ID-bearing SPROT carriers resolve through
|
||||
-- idToName (976/10957/10958 -> "Shadow Protection") and texturesFor
|
||||
-- looks the icon up under THAT name. Name-only players use the Prayer
|
||||
-- variant instead (see CLEAN_AURA_NAMES).
|
||||
-- both spellings share the vanilla Shadow Protection art (the same
|
||||
-- alias convention the AP/STR/MP5 entries above use). Deliberately NOT
|
||||
-- a "Spell_Holy_PrayerofShadowProtection" path: the Prayer rank is not
|
||||
-- a stock 1.12 spell, so that texture cannot be assumed to exist in
|
||||
-- the client, and a missing path renders an empty icon.
|
||||
["Shadow Protection"] = "Interface\\Icons\\Spell_Shadow_AntiShadow",
|
||||
["Prayer of Shadow Protection"] = "Interface\\Icons\\Spell_Shadow_AntiShadow",
|
||||
["Soulstone Resurrection"] = "Interface\\Icons\\Spell_Shadow_SoulGem",
|
||||
}
|
||||
|
||||
@@ -335,9 +410,10 @@ local HIT_MAIN_SUB = {
|
||||
DRUID = "Staves",
|
||||
}
|
||||
|
||||
-- All 16 equipment slots the scan reads (11 enchant + neck + 2 rings +
|
||||
-- 2 trinket invSlots).
|
||||
local INV_SLOTS = { 1, 2, 3, 15, 5, 9, 10, 7, 8, 16, 17, 18, 11, 12, 13, 14 }
|
||||
-- All 17 equipment slots the scan reads (12 armor enchant, WAIST
|
||||
-- included -- 2026-08-30 owner decision, WAIST is worn equipment like
|
||||
-- any other slot now -- + neck + 2 rings + 2 trinket invSlots).
|
||||
local INV_SLOTS = { 1, 2, 3, 15, 5, 6, 9, 10, 7, 8, 16, 17, 18, 11, 12, 13, 14 }
|
||||
|
||||
-- Per-slot base item cosmetics: EVERY gear entry table carries texture,
|
||||
-- name and quality (the UI renders item icons).
|
||||
@@ -347,6 +423,7 @@ local SLOT_BASE = {
|
||||
[3] = { name = "Simulated Spaulders", quality = 3, texture = "Interface\\Icons\\INV_Shoulder_01" },
|
||||
[15] = { name = "Simulated Drape", quality = 3, texture = "Interface\\Icons\\INV_Misc_Cape_16" },
|
||||
[5] = { name = "Simulated Breastplate", quality = 4, texture = "Interface\\Icons\\INV_Chest_Plate04" },
|
||||
[6] = { name = "Simulated Girdle", quality = 3, texture = "Interface\\Icons\\INV_Belt_03" },
|
||||
[9] = { name = "Simulated Bracers", quality = 3, texture = "Interface\\Icons\\INV_Bracer_13" },
|
||||
[10] = { name = "Simulated Gauntlets", quality = 3, texture = "Interface\\Icons\\INV_Gauntlets_28" },
|
||||
[7] = { name = "Simulated Legplates", quality = 4, texture = "Interface\\Icons\\INV_Pants_04" },
|
||||
@@ -365,6 +442,9 @@ local SLOT_BASE = {
|
||||
-- SLOT_BASE. Table overrides are MERGED with the slot cosmetics (missing
|
||||
-- name/quality/texture filled in from SLOT_BASE, everything else -- id,
|
||||
-- enchant, subType, own cosmetics -- kept); "EMPTY" stays "EMPTY".
|
||||
-- WAIST (6) is treated like the plain armor slots here -- always
|
||||
-- enchanted in the base gear, expected by default for every role -- NOT
|
||||
-- grouped with the enchant-free neck/rings/trinkets below.
|
||||
-- Neck/rings (2/11/12) default to enchant=0 like trinkets -- but unlike
|
||||
-- trinkets they ARE enchantable on this server (data/enchants.lua, kind
|
||||
-- "ench"), so Simtank's override below demonstrates a real neck enchant
|
||||
@@ -438,10 +518,15 @@ function DC_Sim.buildStore()
|
||||
add{ name = "Simtank", class = "WARRIOR", online = true,
|
||||
guid = "0xF530000000000001", subgroup = 1,
|
||||
aurasRead = true,
|
||||
auras = { names = {}, ids = set({ 17626, 15852, 16323, 17538,
|
||||
11348, 3593, 17543, 9885, 10938, 10958 }) },
|
||||
-- FLASK Titans, FOOD Chili, STR JujuPower, AGI Mongoose, ARM SupDef,
|
||||
-- HP Fortitude, FR GFPP, MOTW r7, PWF r6, SPROT r3
|
||||
auras = { names = {}, ids = set({ 17626, 15852, 17038, 16323,
|
||||
17538, 10667, 24382, 11348, 3593, 25804, 17543, 17548,
|
||||
9885, 10938, 10958 }) },
|
||||
-- FLASK Titans, FOOD Chili, AP Firewater, STR JujuPower,
|
||||
-- AGI Mongoose, BL RageOfAges(ROIDS), ZANZA, ARM SupDef,
|
||||
-- HPELX Fortitude, ALC RumseyBlack, FR GFPP, SR GSPP (shadow-boss
|
||||
-- prep -- NOTEXP/hidden until the SR expectation is enabled, then
|
||||
-- the column lights up green for him), MOTW r7, PWF r6,
|
||||
-- SPROT r3 -- the full KG tank set, 0 gaps (green pill)
|
||||
weaponMain = true, weaponMainName = "Dense Sharpening Stone",
|
||||
gearRead = true,
|
||||
resistsRead = true, resists = { [2] = 315 },
|
||||
@@ -476,10 +561,16 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000002", subgroup = 1,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Flask of the Titans", "Well Fed",
|
||||
"Juju Power", "Elixir of the Mongoose",
|
||||
"Winterfall Firewater", "Juju Power", "Elixir of the Mongoose",
|
||||
"R.O.I.D.S.", "Spirit of Zanza",
|
||||
"Elixir of Superior Defense", "Elixir of Fortitude",
|
||||
"Greater Fire Protection", "Mark of the Wild",
|
||||
"Power Word: Fortitude", "Shadow Protection" }), ids = {} },
|
||||
"Medivh's Merlot", "Greater Fire Protection",
|
||||
"Mark of the Wild", "Power Word: Fortitude",
|
||||
-- Prayer variant, not the bare name: see CLEAN_AURA_NAMES
|
||||
"Prayer of Shadow Protection" }), ids = {},
|
||||
-- effect line for the generic "Well Fed": the identification
|
||||
-- ladder resolves it to Smoked Desert Dumplings in the matrix
|
||||
effects = { ["Well Fed"] = "Increases Strength by 20." } },
|
||||
weaponMain = true, gearRead = true,
|
||||
resistsRead = true, resists = { [2] = 240 },
|
||||
-- hit: the BELOW-cap showcase -- a human swordsman, so the racial
|
||||
@@ -505,10 +596,15 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000003", subgroup = 1,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed" }),
|
||||
ids = set({ 17629, 11405, 11334, 11349, 25804, 7233,
|
||||
21849, 21562, 10957 }) },
|
||||
-- FLASK ChromRes, STR Giants, AGI GreaterAgi, ARM GreaterDef,
|
||||
-- HP RumseyBlack, FR FireProt, MOTW Gift, PWF Prayer, SPROT r2
|
||||
ids = set({ 17629, 16329, 11405, 11334, 10669, 24382,
|
||||
11349, 3593, 25804, 7233, 17544, 21849, 21562, 10957 }) },
|
||||
-- FLASK ChromRes, AP JujuMight, STR Giants, AGI GreaterAgi,
|
||||
-- BL Scorpok, ZANZA, ARM GreaterDef, HPELX Fortitude(HealthII),
|
||||
-- ALC RumseyBlack, FR FireProt, FrR GFrPP (17544 -- the frost
|
||||
-- carrier of the six-school demo view, hidden by default),
|
||||
-- MOTW Gift, PWF Prayer, SPROT r2.
|
||||
-- NO effects table: his "Well Fed" stays unresolvable -- the
|
||||
-- matrix ladder's slot-generic-icon floor ("item unknown")
|
||||
weaponMain = true, gearRead = true,
|
||||
resistsRead = true, resists = { [2] = 180 },
|
||||
-- hit: the BOOK showcase -- an orc axeman (racial +5 => cap 7.0);
|
||||
@@ -553,9 +649,13 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000005", subgroup = 2,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed" }),
|
||||
ids = set({ 16329, 17538, 16323, 3593, 9885, 10938 }) },
|
||||
-- AP JujuMight, AGI Mongoose, STR JujuPower, HP Fortitude,
|
||||
-- MOTW r7, PWF r6
|
||||
ids = set({ 17626, 16329, 17538, 16323, 10692, 24382,
|
||||
3593, 9885, 10938 }),
|
||||
-- +10 Agility resolves to Grilled Squid through the ladder
|
||||
effects = { ["Well Fed"] = "Increases Agility by 10." } },
|
||||
-- FLASK Titans, AP JujuMight, AGI Mongoose, STR JujuPower,
|
||||
-- BL Cortex(InfallibleMind), ZANZA, HPELX Fortitude (NOTEXP for
|
||||
-- MELEE, harmless), MOTW r7, PWF r6
|
||||
debuffs = debuffsOf({ "Resurrection Sickness" }, { 15007 }),
|
||||
weaponMain = true, gearRead = true,
|
||||
-- hit: the DUAL-WIELD showcase -- two weapon subtypes, so the cell
|
||||
@@ -612,10 +712,12 @@ function DC_Sim.buildStore()
|
||||
add{ name = "Simstab", class = "ROGUE", online = true,
|
||||
guid = "0xF530000000000007", subgroup = 2,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed", "Juju Might", "Juju Power",
|
||||
"Elixir of Greater Agility", "Elixir of Fortitude",
|
||||
"Fire Protection", "Mark of the Wild",
|
||||
"Power Word: Fortitude", "Shadow Protection" }), ids = {} },
|
||||
auras = { names = set({ "Flask of the Titans", "Well Fed",
|
||||
"Juju Might", "Juju Power", "Elixir of Greater Agility",
|
||||
"Lung Juice Cocktail", "Spirit of Zanza",
|
||||
"Elixir of Fortitude", "Fire Protection", "Mark of the Wild",
|
||||
"Power Word: Fortitude",
|
||||
"Prayer of Shadow Protection" }), ids = {} },
|
||||
weaponMain = false, gearRead = true,
|
||||
gear = gearWith({
|
||||
[9] = { id = 5169, enchant = 0 }, -- wrist enchant missing
|
||||
@@ -650,10 +752,15 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000009", subgroup = 2,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed" }),
|
||||
ids = set({ 17038, 11405, 17538, 25804, 17543,
|
||||
9885, 10938, 10958 }) },
|
||||
-- AP Firewater, STR Giants, AGI Mongoose, HP RumseyBlack, FR GFPP,
|
||||
-- MOTW r7, PWF r6, SPROT r3
|
||||
ids = set({ 17626, 17038, 11405, 17538, 10693, 24382,
|
||||
25804, 17543, 17546, 9885, 10938, 10958 }) },
|
||||
-- FLASK Titans, AP Firewater, STR Giants, AGI Mongoose,
|
||||
-- BL Gizzard(SpiritualDomination), ZANZA, ALC RumseyBlack
|
||||
-- (NOTEXP for MELEE, harmless), FR GFPP, NR GNPP (17546 -- the
|
||||
-- nature carrier of the six-school demo view), MOTW r7, PWF r6,
|
||||
-- SPROT r3 (the priest buff by ID -- he holds no SR potion, which
|
||||
-- is exactly the name-collision case the demo view must not
|
||||
-- mistake for a fulfilled SR column)
|
||||
weaponMain = true, gearRead = true,
|
||||
gear = gearWith({
|
||||
[17] = { id = 5197, enchant = 917, subType = "Shields", quality = 3 },
|
||||
@@ -694,9 +801,12 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF53000000000000B", subgroup = 3, distance = 31,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed", "Juju Might",
|
||||
"Elixir of Greater Agility", "Rumsey Rum",
|
||||
"Elixir of Greater Agility", "Ground Scorpok Assay",
|
||||
"Spirit of Zanza", "Rumsey Rum",
|
||||
"Greater Fire Protection", "Mark of the Wild",
|
||||
"Power Word: Fortitude", "Shadow Protection" }), ids = {} },
|
||||
"Power Word: Fortitude",
|
||||
"Prayer of Shadow Protection" }), ids = {} },
|
||||
-- ALC (Rumsey Rum) is NOTEXP for RANGED -- harmless extra
|
||||
weaponMain = true, gearRead = true,
|
||||
-- hit: the RANGED showcase -- a real bow, so the ranged column is
|
||||
-- a question at all; 8 meets its 8.0 cap (a dwarf's racial is on
|
||||
@@ -727,8 +837,15 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF53000000000000D", subgroup = 4,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed" }),
|
||||
ids = set({ 17628, 23028, 17539, 24363, 9885, 10938, 10958 }) },
|
||||
-- FLASK Supreme, AI Brilliance, SP GAE, MP5 Mageblood, MOTW, PWF, SPROT
|
||||
ids = set({ 17628, 23028, 17539, 21920, 45489, 45427, 24382,
|
||||
24363, 17549, 9885, 10938, 10958 }),
|
||||
-- +22 spell damage -> Danonzo's Tel'Abim Delight (ladder)
|
||||
effects = { ["Well Fed"] =
|
||||
"Increases spell damage by up to 22." } },
|
||||
-- FLASK Supreme, AI Brilliance, GAE, SCHOOL FrostPower,
|
||||
-- DREAMT, SHARD, ZANZA, MP5 Mageblood, AR GAPP (17549 -- the
|
||||
-- arcane carrier of the six-school demo view), MOTW, PWF, SPROT --
|
||||
-- the full KG caster set except FR (her designed gap)
|
||||
weaponMain = true, weaponMainName = "Brilliant Wizard Oil",
|
||||
gearRead = true,
|
||||
resistsRead = true, resists = { [4] = 85, [6] = 45 },
|
||||
@@ -795,7 +912,9 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000010", subgroup = 5,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed", "Flask of Supreme Power",
|
||||
"Greater Arcane Elixir", "Mageblood", "Fire Protection",
|
||||
"Greater Arcane Elixir", "Elixir of Shadow Power",
|
||||
"Dreamtonic", "Dreamshard Elixir", "Spirit of Zanza",
|
||||
"Mageblood", "Fire Protection",
|
||||
"Arcane Intellect", "Mark of the Wild",
|
||||
"Power Word: Fortitude" }),
|
||||
ids = {} },
|
||||
@@ -838,10 +957,12 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000012", subgroup = 5,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Shadowform", "Flask of Supreme Power",
|
||||
"Well Fed", "Elixir of Shadow Power", "Mageblood Potion",
|
||||
"Well Fed", "Greater Arcane Elixir", "Elixir of Shadow Power",
|
||||
"Dreamtonic", "Dreamshard Elixir", "Spirit of Zanza",
|
||||
"Mageblood Potion",
|
||||
"Greater Fire Protection", "Arcane Intellect",
|
||||
"Mark of the Wild", "Power Word: Fortitude",
|
||||
"Shadow Protection" }), ids = {} },
|
||||
"Prayer of Shadow Protection" }), ids = {} },
|
||||
weaponMain = true, gearRead = true,
|
||||
gear = gearWith({
|
||||
[18] = { id = 5238, enchant = 0, subType = "Wands" },
|
||||
@@ -868,10 +989,21 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000014", subgroup = 6,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed" }),
|
||||
ids = set({ 17627, 20765, 24363, 3593, 7233,
|
||||
10157, 9885, 10938, 10958 }) },
|
||||
-- FLASK Wisdom, SOUL r5, MP5 Mageblood, HP Fortitude, FR FireProt,
|
||||
-- AI r5, MOTW r7, PWF r6, SPROT r3
|
||||
ids = set({ 17627, 20765, 24363, 45427, 24382, 22790,
|
||||
3593, 7233, 7245, 10157, 9885, 10938, 10958 }),
|
||||
-- mid-ladder showcase: Le Fishe Au Chocolat resolves by its
|
||||
-- dodge effect line but has NO sourced icon in the data (icon
|
||||
-- is a questionmark placeholder even upstream) -> the matrix
|
||||
-- shows the slot-generic FOOD icon while the tooltip still
|
||||
-- names the item. (Herbal Salad carried this showcase until
|
||||
-- its icon was sourced on 2026-08-11.)
|
||||
effects = { ["Well Fed"] =
|
||||
"Increases your chance to dodge by 1% and Defense by 4." } },
|
||||
-- FLASK Wisdom, SOUL r5, MP5 Mageblood, SHARD, ZANZA,
|
||||
-- ALC Kreeg's, HPELX Fortitude (NOTEXP for HEALER, harmless),
|
||||
-- FR FireProt, HR HPP (7245 -- the holy carrier of the six-school
|
||||
-- demo view; no Greater tier exists for holy), AI r5, MOTW r7,
|
||||
-- PWF r6, SPROT r3
|
||||
weaponMain = true, gearRead = true,
|
||||
gear = gearWith({
|
||||
[17] = { id = 5257, enchant = 0, subType = "Miscellaneous",
|
||||
@@ -895,7 +1027,8 @@ function DC_Sim.buildStore()
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Flask of Distilled Wisdom", "Well Fed",
|
||||
"Mageblood", "Arcane Intellect", "Mark of the Wild",
|
||||
"Power Word: Fortitude", "Shadow Protection" }), ids = {} },
|
||||
"Power Word: Fortitude",
|
||||
"Prayer of Shadow Protection" }), ids = {} },
|
||||
debuffs = debuffsOf({ "Resurrection Sickness" }, { 15007 }),
|
||||
weaponMain = false, gearRead = true,
|
||||
gear = gearWith({
|
||||
@@ -908,9 +1041,10 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000017", subgroup = 7,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed" }),
|
||||
ids = set({ 17627, 24363, 25804, 7233, 10156, 9885, 10938 }) },
|
||||
-- FLASK Wisdom, MP5 Mageblood, HP RumseyBlack, FR FireProt,
|
||||
-- AI r4, MOTW r7, PWF r6
|
||||
ids = set({ 17627, 24363, 45427, 24382, 25804, 7233,
|
||||
10156, 9885, 10938 }) },
|
||||
-- FLASK Wisdom, MP5 Mageblood, SHARD, ZANZA, ALC RumseyBlack,
|
||||
-- FR FireProt, AI r4, MOTW r7, PWF r6
|
||||
weaponMain = true, gearRead = true,
|
||||
gear = gearWith({
|
||||
[17] = { id = 5277, enchant = 917, subType = "Shields", quality = 3 },
|
||||
@@ -923,10 +1057,12 @@ function DC_Sim.buildStore()
|
||||
guid = "0xF530000000000018", subgroup = 7,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Flask of Distilled Wisdom", "Well Fed",
|
||||
"Mageblood Potion", "Elixir of Fortitude",
|
||||
"Mageblood Potion", "Dreamshard Elixir", "Spirit of Zanza",
|
||||
"Kreeg's Stout Beatdown", "Elixir of Fortitude",
|
||||
"Greater Fire Protection", "Arcane Intellect",
|
||||
"Mark of the Wild", "Power Word: Fortitude",
|
||||
"Shadow Protection", "Soulstone Resurrection" }), ids = {} },
|
||||
"Prayer of Shadow Protection",
|
||||
"Soulstone Resurrection" }), ids = {} },
|
||||
weaponMain = true, gearRead = true,
|
||||
gear = gearWith({
|
||||
[5] = { id = 5285, enchant = 0 },
|
||||
@@ -935,16 +1071,18 @@ function DC_Sim.buildStore()
|
||||
[18] = "EMPTY",
|
||||
}) }
|
||||
|
||||
-- Simglow: HP via the id-less "Rumsey Rum" name entry (spellID 0) WHILE
|
||||
-- ids are present -- exercises exactly the model rule that id-less
|
||||
-- names still count for players with ids. SPROT missing, EMPTY ranged.
|
||||
-- Simglow: ALC via the id-less "Medivh's Merlot Blue" name entry
|
||||
-- (spellID 0) WHILE ids are present -- exercises exactly the model
|
||||
-- rule that id-less names still count for players with ids.
|
||||
-- SPROT missing, EMPTY ranged.
|
||||
add{ name = "Simglow", class = "PALADIN", online = true,
|
||||
guid = "0xF530000000000019", subgroup = 7,
|
||||
aurasRead = true,
|
||||
auras = { names = set({ "Well Fed", "Rumsey Rum" }),
|
||||
ids = set({ 17627, 21850, 21564, 24363, 7233, 1461, 20762 }) },
|
||||
-- FLASK Wisdom, MOTW Gift, PWF Prayer, MP5 Mageblood, FR FireProt,
|
||||
-- AI r3, SOUL r2
|
||||
auras = { names = set({ "Well Fed", "Medivh's Merlot Blue" }),
|
||||
ids = set({ 17627, 21850, 21564, 24363, 45427, 24382,
|
||||
7233, 1461, 20762 }) },
|
||||
-- FLASK Wisdom, MOTW Gift, PWF Prayer, MP5 Mageblood, SHARD,
|
||||
-- ZANZA, FR FireProt, AI r3, SOUL r2
|
||||
weaponMain = true, gearRead = true,
|
||||
gear = gearWith({
|
||||
[17] = { id = 5297, enchant = 917, subType = "Shields", quality = 3 },
|
||||
|
||||
@@ -66,3 +66,37 @@ for name, def in pairs(DC.CLASSBUFFS) do
|
||||
DC.SPELL_TO_SLOT[def.ids[i]] = def.slot
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Name-only aura collisions with CONSUMABLE slots (decision 2026-08-15):
|
||||
-- a name-only aura match (no spell ID at all for that scan) must NEVER
|
||||
-- satisfy a CONSUMABLE slot when the matched display name is also a class
|
||||
-- buff name -- the scan cannot tell the two apart, and defaulting to HAS
|
||||
-- would hide a missing consumable behind someone else's class buff. Such
|
||||
-- a slot classifies UNKNOWN instead (core/model.lua's aura name-fallback
|
||||
-- branch consults this table; core/const.lua's Iron Rule: UNKNOWN is
|
||||
-- never collapsed into HAS or MISSING).
|
||||
--
|
||||
-- Scope is deliberately ONE-SIDED: only the CONSUMABLE slot side of the
|
||||
-- collision is affected. The class-buff slot (e.g. SPROT) keeps the
|
||||
-- existing name-fallback behavior unchanged -- the ambiguity here means
|
||||
-- "we can't prove the CONSUMABLE was used", not "we can't prove the class
|
||||
-- buff is missing". Do not widen this into "name-only never counts"; that
|
||||
-- would make every ID-less scan MISSING/UNKNOWN across the whole matrix.
|
||||
--
|
||||
-- Keyed by buff display name (a key shared by DC.CONSUMABLES and
|
||||
-- DC.CLASSBUFFS). test_data.lua asserts every entry here is a key in BOTH
|
||||
-- tables, so a stale or speculative entry cannot ship silently.
|
||||
-- "Shadow Protection" -- DC.CONSUMABLES["Shadow Protection"] (SR slot,
|
||||
-- Shadow Protection Potion, spell 7242, data/consumables.lua) vs.
|
||||
-- DC.CLASSBUFFS["Shadow Protection"] above (SPROT slot, priest buff,
|
||||
-- ids 976/10957/10958) -- identical display name, already flagged as a
|
||||
-- collision at both definitions and wowhead-confirmed 2026-08-11 (see
|
||||
-- the SR entry's comment in data/consumables.lua). This is the ONLY
|
||||
-- name shared between DC.CONSUMABLES and DC.CLASSBUFFS (checked against
|
||||
-- every key in both tables) -- no other entry is added without the same
|
||||
-- level of evidence.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.CONSUMABLE_CLASSBUFF_NAME_COLLISION = {
|
||||
["Shadow Protection"] = true,
|
||||
}
|
||||
|
||||
+317
-80
@@ -1,6 +1,65 @@
|
||||
-- DopingControl data/consumables.lua
|
||||
-- Consumables tab: slot definitions + buff->slot table + spellID->slot map.
|
||||
-- Consumables tab: slot definitions + buff->entry table + spellID->slot map
|
||||
-- + the pure item-identification ladder (DC.ResolveItem).
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
--
|
||||
-- SLOT MODEL (v6, 22 slots -- the 17-slot base design plus the five school
|
||||
-- protection-potion columns FrR/NR/SR/HR/AR added 2026-08-11):
|
||||
-- One column = one thing that can be active independently of every other
|
||||
-- column; items competing INSIDE a column are the "pick one" alternatives.
|
||||
-- Source: community-sourced TurtleWoW 1.17.2 consumable-stacking rules,
|
||||
-- trust external -- vanilla 1.12 slot rules, no TBC
|
||||
-- Battle/Guardian categories. The old 11-slot model conflated four
|
||||
-- independent slots into SP (GAE / school elixir / Dreamtonic / Dreamshard
|
||||
-- are simultaneously active in the caster set) and two into HP (Fortitude
|
||||
-- + stamina alcohol stack), and lacked BL/ZANZA entirely.
|
||||
--
|
||||
-- Deliberate compromises (documented, not hidden):
|
||||
-- * ALC is ONE column although different alcohol types stack (Merlot +
|
||||
-- Rumsey Black Label at once; same types do not): modelled as one
|
||||
-- column showing the strongest active, detail in tooltip. The
|
||||
-- slot-collision tests exempt exactly this column.
|
||||
-- * Gift of Arthas and Juju Flurry get NO column (defense elixirs all
|
||||
-- stack; Flurry lasts 20 s).
|
||||
-- * UNVERIFIED gaps (community source does not cover them; marked
|
||||
-- `unverified = true` on the slot/entries, upgrade via dev/raiddump
|
||||
-- evidence next raid): (1) WPN main-hand/off-hand coexistence (oil on
|
||||
-- MH + stone on OH), (2) protection-potion slot rules (all six school
|
||||
-- columns FR/FrR/NR/SR/HR/AR -- cross-school stacking is NOT
|
||||
-- evidenced, so each school gets its own column but every one stays
|
||||
-- flagged unverified), (3) world-buff interactions (no world-buff
|
||||
-- columns at all).
|
||||
--
|
||||
-- Entry fields (DC.CONSUMABLES[buffName]):
|
||||
-- slot : slot id (column) the buff fills
|
||||
-- spellID : aura spell id; 0 = "match by name only" (no known ID)
|
||||
-- item : the consumable's display name (whisper/tooltip/cell)
|
||||
-- icon : BARE icon texture name (no "Interface\Icons\" prefix, no
|
||||
-- extension -- prefix is added at render time); nil when the
|
||||
-- icon could not be sourced (the matrix fallback ladder
|
||||
-- handles nil: slot-generic icon, then the green check)
|
||||
-- discrim : optional Lua 5.0 string.find pattern matched against the
|
||||
-- buff tooltip's EFFECT line (scan/aura.lua auras.effects) --
|
||||
-- only where the buff NAME is generic
|
||||
-- variants : optional ARRAY of { item, icon, discrim } for generic buff
|
||||
-- names ("Well Fed") that many different items produce; the
|
||||
-- identification ladder walks them in order, FIRST match wins
|
||||
-- unverified : optional true -- see the honesty gaps above
|
||||
--
|
||||
-- Icon provenance (2026-08-11, never guessed): wowhead classic tooltip
|
||||
-- API for the vanilla items, octowow.st/db + wowauctions for TurtleWoW
|
||||
-- customs; icon = nil where no source confirmed one. Two lookalike
|
||||
-- collisions are GENUINE, not typos: Dragonbreath Chili and Danonzo's
|
||||
-- Delight share inv_drink_17 with Nightfin Soup; Gordok Green Grog
|
||||
-- shares inv_drink_03 with Rumsey Rum; Cerebral Cortex Compound shares
|
||||
-- inv_potion_32 with the Mongoose elixir.
|
||||
--
|
||||
-- Separator decision (once for all data files): items strings are
|
||||
-- tooltip/FontString-only, so they may use the middle-dot list separator
|
||||
-- "\194\183" (UTF-8 middle dot renders fine in own FontStrings; the group
|
||||
-- headers already use the same glyph). Em dashes stay ASCII "-" (that
|
||||
-- glyph is unproven in the 1.12 font). SENT chat lines remain pure ASCII
|
||||
-- -- they never use these strings.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
@@ -8,116 +67,294 @@ local DC = DopingControl
|
||||
-- ------------------------------------------------------------------
|
||||
-- Slot definitions (array, order = column order).
|
||||
-- Fields: id (stable key, used by DEFAULT_EXPECT / model / UI),
|
||||
-- label (column tile abbrev), sub (tile sub-label), full (long name,
|
||||
-- used in whisper/report), kind ("aura" | "weapon"),
|
||||
-- items (tooltip example text, optional).
|
||||
-- Separator decision (once for all data files): items strings are
|
||||
-- tooltip/FontString-only, so they may use the middle-dot list separator
|
||||
-- "\194\183" (UTF-8 middle dot renders fine in own
|
||||
-- FontStrings; the group headers already use the same glyph). Em dashes
|
||||
-- stay ASCII "-" (that glyph is unproven in the 1.12 font). SENT
|
||||
-- chat lines remain pure ASCII -- they never use these
|
||||
-- strings.
|
||||
-- label (column tile abbrev), sub (tile sub-label), full (long
|
||||
-- name, used in whisper/report), kind ("aura" | "weapon"),
|
||||
-- items (tooltip example text, optional), unverified (optional,
|
||||
-- see header),
|
||||
-- icon (BARE slot-generic icon texture name -- same bare-name
|
||||
-- rule as the entry icons above; the matrix HAS-cell fallback
|
||||
-- ladder renders it when the ITEM behind a buff cannot be
|
||||
-- resolved: resolved item icon -> this slot icon -> green check.
|
||||
-- A representative member item's icon by design -- the cell must
|
||||
-- read as "this column", not as a question mark).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.SLOTS_CONSUMABLES = {
|
||||
{ id = "FLASK", label = "FLK", sub = "Flask", full = "Flask", kind = "aura",
|
||||
{ id = "FLASK", label = "FLK", sub = "Flask", full = "Flask", kind = "aura",
|
||||
icon = "inv_potion_62",
|
||||
items = "Flask of the Titans \194\183 Distilled Wisdom \194\183 Supreme Power" },
|
||||
{ id = "FOOD", label = "FOD", sub = "Food", full = "Food buff", kind = "aura",
|
||||
items = "Well Fed - e.g. Grilled Squid \194\183 Blessed Sunfruit" },
|
||||
{ id = "AP", label = "AP", sub = "Attack", full = "Attack power", kind = "aura",
|
||||
{ id = "FOOD", label = "FOD", sub = "Food", full = "Food buff", kind = "aura",
|
||||
icon = "inv_misc_food_15",
|
||||
items = "one food buff at a time - e.g. Smoked Desert Dumplings \194\183 Danonzo's Tel'Abim foods" },
|
||||
{ id = "AP", label = "AP", sub = "Attack", full = "Attack power", kind = "aura",
|
||||
icon = "inv_potion_92",
|
||||
items = "Winterfall Firewater \194\183 Juju Might" },
|
||||
{ id = "STR", label = "STR", sub = "Str.", full = "Strength", kind = "aura",
|
||||
{ id = "STR", label = "STR", sub = "Str.", full = "Strength", kind = "aura",
|
||||
icon = "inv_potion_61",
|
||||
items = "Juju Power \194\183 Elixir of Giants" },
|
||||
{ id = "AGI", label = "AGI", sub = "Agi.", full = "Agility", kind = "aura",
|
||||
items = "Elixir of the Mongoose" },
|
||||
{ id = "SP", label = "SP", sub = "Spell", full = "Spell power", kind = "aura",
|
||||
items = "Greater Arcane Elixir \194\183 Shadow Power \194\183 Firepower" },
|
||||
{ id = "MP5", label = "MP5", sub = "Mana", full = "Mana regen", kind = "aura",
|
||||
items = "Mageblood Potion \194\183 Nightfin Soup" },
|
||||
{ id = "ARM", label = "ARM", sub = "Armor", full = "Armor", kind = "aura",
|
||||
{ id = "AGI", label = "AGI", sub = "Agi.", full = "Agility", kind = "aura",
|
||||
icon = "inv_potion_32",
|
||||
items = "Elixir of the Mongoose - free-stacker, own column" },
|
||||
{ id = "BL", label = "BL", sub = "Blasted", full = "Blasted Lands buff", kind = "aura",
|
||||
icon = "inv_stone_15",
|
||||
items = "R.O.I.D.S. \194\183 Ground Scorpok Assay \194\183 Lung Juice Cocktail \194\183 Cortex Compound \194\183 Gizzard Gum" },
|
||||
{ id = "ZANZA", label = "ZAN", sub = "Zanza", full = "Spirit of Zanza", kind = "aura",
|
||||
icon = "inv_potion_30",
|
||||
items = "Spirit of Zanza - free-stacker, one Zanza effect at a time" },
|
||||
{ id = "GAE", label = "GAE", sub = "Arcane", full = "Greater Arcane Elixir", kind = "aura",
|
||||
icon = "inv_potion_25",
|
||||
items = "Greater Arcane Elixir \194\183 Arcane Elixir" },
|
||||
{ id = "SCHOOL", label = "SCH", sub = "School", full = "School elixir", kind = "aura",
|
||||
icon = "inv_potion_46",
|
||||
items = "Shadow Power \194\183 Frost Power \194\183 Greater Firepower - one per school slot" },
|
||||
{ id = "DREAMT", label = "DRT", sub = "Dreamt.", full = "Dreamtonic", kind = "aura",
|
||||
icon = "inv_potion_10",
|
||||
items = "Dreamtonic (TurtleWoW custom)" },
|
||||
{ id = "SHARD", label = "SHD", sub = "Shard", full = "Dreamshard Elixir", kind = "aura",
|
||||
icon = "inv_potion_12",
|
||||
items = "Dreamshard Elixir (TurtleWoW custom)" },
|
||||
{ id = "MP5", label = "MP5", sub = "Mana", full = "Mana regen", kind = "aura",
|
||||
icon = "inv_potion_45",
|
||||
items = "Mageblood Potion - Nightfin Soup is a FOOD buff, not this column" },
|
||||
{ id = "ARM", label = "ARM", sub = "Armor", full = "Armor", kind = "aura",
|
||||
icon = "inv_potion_66",
|
||||
items = "Elixir of Superior Defense" },
|
||||
{ id = "HP", label = "HP", sub = "Stam.", full = "Stamina/HP", kind = "aura",
|
||||
items = "Elixir of Fortitude \194\183 Rumsey Rum Black Label" },
|
||||
{ id = "FR", label = "FR", sub = "fire", full = "Fire protection", kind = "aura",
|
||||
{ id = "HPELX", label = "FRT", sub = "Fort.", full = "Elixir of Fortitude", kind = "aura",
|
||||
icon = "inv_potion_43",
|
||||
items = "Elixir of Fortitude - stacks with the alcohol column" },
|
||||
{ id = "ALC", label = "ALC", sub = "Alcohol", full = "Alcohol buff", kind = "aura",
|
||||
icon = "inv_drink_04",
|
||||
items = "Medivh's Merlot \194\183 Rumsey Rum Black Label \194\183 Kreeg's Stout Beatdown - different types stack, same type does not; one column, strongest shown" },
|
||||
{ id = "FR", label = "FR", sub = "fire", full = "Fire protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_24",
|
||||
items = "Greater Fire Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "WPN", label = "WPN", sub = "temp.", full = "Weapon (temporary)", kind = "weapon", -- NOT an aura slot
|
||||
items = "Dense Sharpening Stone \194\183 Brilliant Wizard/Mana Oil - permanent enchant: Equipment tab" },
|
||||
-- The remaining school protection columns follow the FR pattern (one
|
||||
-- column per school, cross-school stacking unverified -- see header).
|
||||
-- Unlike FR they carry NO seed entry in data/expectations.lua: the
|
||||
-- dynamic-column rule keeps them hidden until enabled per role/class.
|
||||
{ id = "FrR", label = "FrR", sub = "frost", full = "Frost protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_20",
|
||||
items = "Greater Frost Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "NR", label = "NR", sub = "nature", full = "Nature protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_22",
|
||||
items = "Greater Nature Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "SR", label = "SR", sub = "shadow", full = "Shadow protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_23",
|
||||
items = "Greater Shadow Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "HR", label = "HR", sub = "holy", full = "Holy protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_09",
|
||||
items = "Holy Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "AR", label = "AR", sub = "arcane", full = "Arcane protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_83",
|
||||
items = "Greater Arcane Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "WPN", label = "WPN", sub = "temp.", full = "Weapon (temporary)", kind = "weapon", unverified = true, -- NOT an aura slot; MH/OH split unverified
|
||||
icon = "inv_stone_sharpeningstone_01",
|
||||
items = "Elemental/Dense Sharpening Stone \194\183 Brilliant Wizard/Mana Oil - permanent enchant: Equipment tab" },
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Buff name -> slot table.
|
||||
-- Buff name -> entry table.
|
||||
-- spellID = 0 means "match by name only" (no known aura spell ID).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.CONSUMABLES = {
|
||||
-- FLASK
|
||||
["Flask of the Titans"] = { slot = "FLASK", spellID = 17626, item = "Flask of the Titans" },
|
||||
["Flask of Supreme Power"] = { slot = "FLASK", spellID = 17628, item = "Flask of Supreme Power" },
|
||||
["Flask of Distilled Wisdom"] = { slot = "FLASK", spellID = 17627, item = "Flask of Distilled Wisdom" },
|
||||
["Flask of Chromatic Resistance"] = { slot = "FLASK", spellID = 17629, item = "Flask of Chromatic Resistance" },
|
||||
["Flask of Petrification"] = { slot = "FLASK", spellID = 17624, item = "Flask of Petrification" },
|
||||
["Flask of the Titans"] = { slot = "FLASK", spellID = 17626, item = "Flask of the Titans", icon = "inv_potion_62" },
|
||||
["Flask of Supreme Power"] = { slot = "FLASK", spellID = 17628, item = "Flask of Supreme Power", icon = "inv_potion_41" },
|
||||
["Flask of Distilled Wisdom"] = { slot = "FLASK", spellID = 17627, item = "Flask of Distilled Wisdom", icon = "inv_potion_97" },
|
||||
["Flask of Chromatic Resistance"] = { slot = "FLASK", spellID = 17629, item = "Flask of Chromatic Resistance", icon = "inv_potion_48" },
|
||||
["Flask of Petrification"] = { slot = "FLASK", spellID = 17624, item = "Flask of Petrification", icon = "inv_potion_26" }, -- NOT inv_potion_98 (a often-repeated wrong guess) -- wowhead-confirmed
|
||||
|
||||
-- FOOD (buff names, not item names; TWoW custom foods likely all show "Well Fed" - unverified)
|
||||
["Well Fed"] = { slot = "FOOD", spellID = 0, item = "Grilled Squid etc." },
|
||||
-- ["Food"] (the eating tick) deliberately REMOVED: it is
|
||||
-- the sit-and-eat channel aura, not a food buff -- it made the FOOD
|
||||
-- cell green for anyone currently chewing (field-tested). Only
|
||||
-- lasting food buffs count.
|
||||
["Increased Stamina"] = { slot = "FOOD", spellID = 0, item = "stamina food" },
|
||||
["Dragonbreath Chili"] = { slot = "FOOD", spellID = 15852, item = "Dragonbreath Chili" },
|
||||
-- FOOD (buff names, not item names).
|
||||
-- "Well Fed" is the generic food aura: the ITEM is identified through
|
||||
-- the tooltip EFFECT line (scan/aura.lua auras.effects) against the
|
||||
-- per-variant discrim patterns below -- first match wins, no match =
|
||||
-- nil = the matrix falls back to the slot-generic icon ("food buff,
|
||||
-- unknown which"). The variant list is the food set of the KG
|
||||
-- stacking-rules atom plus the classic raid staples; the discrim
|
||||
-- patterns are best-effort data (recorded-tooltip verification via
|
||||
-- dev/raiddump pending) -- a wrong one is a one-line fix.
|
||||
-- ["Food"] (the eating tick) deliberately REMOVED: it is the
|
||||
-- sit-and-eat channel aura, not a food buff -- it made the FOOD cell
|
||||
-- green for anyone currently chewing (field-tested). Only lasting
|
||||
-- food buffs count.
|
||||
["Well Fed"] = { slot = "FOOD", spellID = 0, item = "food buff", icon = nil,
|
||||
variants = {
|
||||
-- ORDER MATTERS: first match wins, so specific effect lines
|
||||
-- (crit, ranged AP, spell damage) sit ABOVE the generic stat
|
||||
-- words they could otherwise be swallowed by.
|
||||
-- Several Turtle customs SHARE their aura spell with a classic
|
||||
-- staple (icon research 2026-08-11: Power Mushroom = 24800 =
|
||||
-- Dumplings, Sour Mountain Berry = 18230 = Grilled Squid,
|
||||
-- Juicy Striped Melon = 22731 = Runn Tum Tuber) -- those pairs
|
||||
-- are indistinguishable by effect line; the listed-first item
|
||||
-- is shown as the representative. Same slot, so harmless.
|
||||
{ item = "Danonzo's Tel'Abim Delight", icon = "inv_drink_17", discrim = "[Ss]pell [Dd]amage" },
|
||||
{ item = "Danonzo's Tel'Abim Medley", icon = "inv_misc_food_08", discrim = "[Hh]aste" },
|
||||
{ item = "Danonzo's Tel'Abim Surprise", icon = "inv_misc_food_09", discrim = "[Rr]anged [Aa]ttack [Pp]ower" },
|
||||
{ item = "Gurubashi Gumbo", icon = "inv_misc_food_64", discrim = "[Cc]rit" },
|
||||
{ item = "Le Fishe Au Chocolat", icon = nil, discrim = "[Dd]odge" }, -- icon: questionmark placeholder even upstream (octowow + wowauctions), stays nil
|
||||
{ item = "Empowering Herbal Salad", icon = "inv_misc_food_salad", discrim = "[Hh]ealing" },
|
||||
{ item = "Nightfin Soup", icon = "inv_drink_17", discrim = "8 [Mm]ana" },
|
||||
{ item = "Sagefish Delight", icon = "inv_misc_fish_21", discrim = "6 [Mm]ana" },
|
||||
-- +20 Strength: Dumplings and Power Mushroom, same spell 24800
|
||||
{ item = "Smoked Desert Dumplings", icon = "inv_misc_food_64", discrim = "[Ss]trength" },
|
||||
{ item = "Power Mushroom", icon = "inv_mushroom_11", discrim = "[Ss]trength" }, -- NOT "+10 all stats" (dead pattern until 2026-08-11); tooltip-verified +20 STR
|
||||
-- +10 Agility: Grilled Squid and Sour Mountain Berry, same spell 18230
|
||||
{ item = "Grilled Squid", icon = "inv_misc_fish_13", discrim = "[Aa]gility" },
|
||||
{ item = "Sour Mountain Berry", icon = "inv_misc_food_40", discrim = "[Aa]gility" },
|
||||
-- +10 Intellect: classic Tuber and Turtle Melon, same spell 22731
|
||||
{ item = "Runn Tum Tuber Surprise", icon = "inv_misc_food_63", discrim = "[Ii]ntellect" },
|
||||
{ item = "Juicy Striped Melon", icon = "inv_misc_food_22", discrim = "[Ii]ntellect" },
|
||||
-- generic stamina LAST: Gumbo's line also contains Stamina and
|
||||
-- must be caught by its crit pattern above, never by this one
|
||||
{ item = "Hardened Mushroom", icon = "inv_mushroom_11", discrim = "[Ss]tamina" }, -- NOT "+armor" (dead pattern until 2026-08-11); tooltip-verified +25 STA, spell 25660
|
||||
} },
|
||||
["Increased Stamina"] = { slot = "FOOD", spellID = 0, item = "stamina food", icon = nil },
|
||||
["Dragonbreath Chili"] = { slot = "FOOD", spellID = 15852, item = "Dragonbreath Chili", icon = "inv_drink_17" }, -- yes, the chili really uses a drink icon (wowhead-confirmed via og:image)
|
||||
|
||||
-- AP
|
||||
["Winterfall Firewater"] = { slot = "AP", spellID = 17038, item = "Winterfall Firewater" },
|
||||
["Juju Might"] = { slot = "AP", spellID = 16329, item = "Juju Might" },
|
||||
["Winterfall Firewater"] = { slot = "AP", spellID = 17038, item = "Winterfall Firewater", icon = "inv_potion_92" },
|
||||
["Juju Might"] = { slot = "AP", spellID = 16329, item = "Juju Might", icon = "inv_misc_monsterscales_07" },
|
||||
|
||||
-- STR
|
||||
["Juju Power"] = { slot = "STR", spellID = 16323, item = "Juju Power" },
|
||||
["Elixir of Giants"] = { slot = "STR", spellID = 11405, item = "Elixir of Giants" },
|
||||
["Elixir of the Giants"] = { slot = "STR", spellID = 11405, item = "Elixir of Giants" }, -- AURA name on this client (measured live: SpellInfo(11405) = "Elixir of the Giants"); the item is named without "the", and matching runs on the aura name first
|
||||
["Elixir of Brute Force"] = { slot = "STR", spellID = 17537, item = "Elixir of Brute Force" },
|
||||
["Strike of the Scorpok"] = { slot = "STR", spellID = 10669, item = "Strike of the Scorpok" }, -- measured live in a raid (26 players); was tallied as unknown
|
||||
["Rage of Ages"] = { slot = "STR", spellID = 0, item = "R.O.I.D.S." }, -- aura name differs from the item; own spell ID not measured yet
|
||||
["Juju Power"] = { slot = "STR", spellID = 16323, item = "Juju Power", icon = "inv_misc_monsterscales_11" },
|
||||
["Elixir of Giants"] = { slot = "STR", spellID = 11405, item = "Elixir of Giants", icon = "inv_potion_61" },
|
||||
["Elixir of the Giants"] = { slot = "STR", spellID = 11405, item = "Elixir of Giants", icon = "inv_potion_61" }, -- AURA name on this client (measured live: SpellInfo(11405) = "Elixir of the Giants"); the item is named without "the", and matching runs on the aura name first
|
||||
["Elixir of Brute Force"] = { slot = "STR", spellID = 17537, item = "Elixir of Brute Force", icon = "inv_potion_40" },
|
||||
|
||||
-- AGI
|
||||
["Elixir of the Mongoose"] = { slot = "AGI", spellID = 17538, item = "Elixir of the Mongoose" },
|
||||
["Elixir of Greater Agility"] = { slot = "AGI", spellID = 11334, item = "Elixir of Greater Agility" },
|
||||
["Greater Agility"] = { slot = "AGI", spellID = 11334, item = "Elixir of Greater Agility" }, -- AURA name (the item name never appears as a buff); was tallied as unknown 14x
|
||||
["Elixir of Agility"] = { slot = "AGI", spellID = 11328, item = "Elixir of Agility" },
|
||||
["Elixir of the Mongoose"] = { slot = "AGI", spellID = 17538, item = "Elixir of the Mongoose", icon = "inv_potion_32" },
|
||||
["Elixir of Greater Agility"] = { slot = "AGI", spellID = 11334, item = "Elixir of Greater Agility", icon = "inv_potion_94" },
|
||||
["Greater Agility"] = { slot = "AGI", spellID = 11334, item = "Elixir of Greater Agility", icon = "inv_potion_94" }, -- AURA name (the item name never appears as a buff); was tallied as unknown 14x
|
||||
["Elixir of Agility"] = { slot = "AGI", spellID = 11328, item = "Elixir of Agility", icon = "inv_potion_93" },
|
||||
|
||||
-- SP (merged: arcane_elixir + school_elixir + dreamtonic + dreamshard)
|
||||
["Greater Arcane Elixir"] = { slot = "SP", spellID = 17539, item = "Greater Arcane Elixir" },
|
||||
["Arcane Elixir"] = { slot = "SP", spellID = 11390, item = "Arcane Elixir" },
|
||||
["Elixir of Shadow Power"] = { slot = "SP", spellID = 11474, item = "Elixir of Shadow Power" },
|
||||
["Elixir of Frost Power"] = { slot = "SP", spellID = 21920, item = "Elixir of Frost Power" },
|
||||
["Greater Frost Power"] = { slot = "SP", spellID = 56544, item = "Elixir of Greater Frost Power" }, -- AURA name (TurtleWoW-added). NOT measured in-game: taken from a TurtleWoW spell database (56544 = "Greater Frost Power", "Increases frost spell damage by up to 40 for 3600 sec.") plus the item name in pfQuest-turtle's item DB; the aura drops "Elixir of" the same way Giants/Agility above do
|
||||
["Elixir of Greater Firepower"] = { slot = "SP", spellID = 26276, item = "Elixir of Greater Firepower" },
|
||||
["Elixir of Firepower"] = { slot = "SP", spellID = 7844, item = "Elixir of Firepower" },
|
||||
["Dreamtonic"] = { slot = "SP", spellID = 45489, item = "Dreamtonic" }, -- use-spell ID; the aura's own ID is unverified
|
||||
["Dreamshard Elixir"] = { slot = "SP", spellID = 45427, item = "Dreamshard Elixir" }, -- use-spell ID; the aura's own ID is unverified
|
||||
-- BL (Blasted Lands buff slot -- one at a time, pick one; the five
|
||||
-- items were split across STR/nowhere in the old model).
|
||||
-- Aura names + spell ids wowhead-confirmed 2026-08-11 ("Strike of the
|
||||
-- Scorpok" additionally measured live in a 26-man raid). The item
|
||||
-- names ride along as alias keys (spellID 0) because the Turtle
|
||||
-- client may surface the permanent aura under the item name (KG
|
||||
-- note); a key that never matches is harmless.
|
||||
["Rage of Ages"] = { slot = "BL", spellID = 10667, item = "R.O.I.D.S.", icon = "inv_stone_15" },
|
||||
["R.O.I.D.S."] = { slot = "BL", spellID = 0, item = "R.O.I.D.S.", icon = "inv_stone_15" },
|
||||
["Strike of the Scorpok"] = { slot = "BL", spellID = 10669, item = "Ground Scorpok Assay", icon = "inv_misc_dust_02" }, -- old model filed it under STR with the aura name as item -- both wrong (it is +25 AGI, and the item is the Assay)
|
||||
["Ground Scorpok Assay"] = { slot = "BL", spellID = 0, item = "Ground Scorpok Assay", icon = "inv_misc_dust_02" },
|
||||
["Spirit of Boar"] = { slot = "BL", spellID = 10668, item = "Lung Juice Cocktail", icon = "inv_drink_12" },
|
||||
["Lung Juice Cocktail"] = { slot = "BL", spellID = 0, item = "Lung Juice Cocktail", icon = "inv_drink_12" },
|
||||
["Infallible Mind"] = { slot = "BL", spellID = 10692, item = "Cerebral Cortex Compound", icon = "inv_potion_32" }, -- same icon as the Mongoose elixir -- genuine, wowhead-confirmed
|
||||
["Cerebral Cortex Compound"] = { slot = "BL", spellID = 0, item = "Cerebral Cortex Compound", icon = "inv_potion_32" },
|
||||
["Spiritual Domination"] = { slot = "BL", spellID = 10693, item = "Gizzard Gum", icon = "inv_misc_food_30" },
|
||||
["Gizzard Gum"] = { slot = "BL", spellID = 0, item = "Gizzard Gum", icon = "inv_misc_food_30" },
|
||||
|
||||
-- MP5
|
||||
["Mageblood Potion"] = { slot = "MP5", spellID = 24363, item = "Mageblood Potion" },
|
||||
["Mageblood"] = { slot = "MP5", spellID = 24363, item = "Mageblood Potion" }, -- TWoW buff-name variant (old Data.lua kept both)
|
||||
-- ZANZA (free-stacker; only one Zanza-potion effect at a time; buff
|
||||
-- name = item name, wowhead-confirmed)
|
||||
["Spirit of Zanza"] = { slot = "ZANZA", spellID = 24382, item = "Spirit of Zanza", icon = "inv_potion_30" },
|
||||
|
||||
-- GAE (split out of the old SP column)
|
||||
["Greater Arcane Elixir"] = { slot = "GAE", spellID = 17539, item = "Greater Arcane Elixir", icon = "inv_potion_25" },
|
||||
["Arcane Elixir"] = { slot = "GAE", spellID = 11390, item = "Arcane Elixir", icon = "inv_potion_30" },
|
||||
|
||||
-- SCHOOL (split out of the old SP column; one school elixir at a time)
|
||||
["Elixir of Shadow Power"] = { slot = "SCHOOL", spellID = 11474, item = "Elixir of Shadow Power", icon = "inv_potion_46" },
|
||||
["Elixir of Frost Power"] = { slot = "SCHOOL", spellID = 21920, item = "Elixir of Frost Power", icon = "inv_potion_03" },
|
||||
["Greater Frost Power"] = { slot = "SCHOOL", spellID = 56544, item = "Elixir of Greater Frost Power", icon = "inv_potion_13" }, -- AURA name (TurtleWoW-added). NOT measured in-game: taken from a TurtleWoW spell database (56544 = "Greater Frost Power") plus the item name in pfQuest-turtle's item DB; the aura drops "Elixir of" the same way Giants/Agility above do
|
||||
["Elixir of Greater Firepower"] = { slot = "SCHOOL", spellID = 26276, item = "Elixir of Greater Firepower", icon = "inv_potion_60" },
|
||||
["Elixir of Firepower"] = { slot = "SCHOOL", spellID = 7844, item = "Elixir of Firepower", icon = "inv_potion_33" },
|
||||
|
||||
-- DREAMT (TurtleWoW custom; own slot -- simultaneously active with
|
||||
-- GAE, SCHOOL and SHARD in the caster set)
|
||||
["Dreamtonic"] = { slot = "DREAMT", spellID = 45489, item = "Dreamtonic", icon = "inv_potion_10" }, -- use-spell ID; the aura's own ID is unverified
|
||||
|
||||
-- SHARD (TurtleWoW custom; own slot)
|
||||
["Dreamshard Elixir"] = { slot = "SHARD", spellID = 45427, item = "Dreamshard Elixir", icon = "inv_potion_12" }, -- use-spell ID; the aura's own ID is unverified
|
||||
|
||||
-- MP5 (narrowed: Nightfin Soup is a FOOD buff and lives there now)
|
||||
["Mageblood Potion"] = { slot = "MP5", spellID = 24363, item = "Mageblood Potion", icon = "inv_potion_45" },
|
||||
["Mageblood"] = { slot = "MP5", spellID = 24363, item = "Mageblood Potion", icon = "inv_potion_45" }, -- TWoW buff-name variant (old Data.lua kept both)
|
||||
|
||||
-- ARM
|
||||
["Elixir of Superior Defense"] = { slot = "ARM", spellID = 11348, item = "Elixir of Superior Defense" },
|
||||
["Elixir of Greater Defense"] = { slot = "ARM", spellID = 11349, item = "Elixir of Greater Defense" },
|
||||
["Elixir of Superior Defense"] = { slot = "ARM", spellID = 11348, item = "Elixir of Superior Defense", icon = "inv_potion_66" },
|
||||
["Elixir of Greater Defense"] = { slot = "ARM", spellID = 11349, item = "Elixir of Greater Defense", icon = "inv_potion_65" },
|
||||
|
||||
-- HP (merged: def_fortitude + alcohol STA variants)
|
||||
["Elixir of Fortitude"] = { slot = "HP", spellID = 3593, item = "Elixir of Fortitude" },
|
||||
["Health II"] = { slot = "HP", spellID = 3593, item = "Elixir of Fortitude" }, -- AURA name on this client (measured live: SpellInfo(3593) = "Health II")
|
||||
["Rumsey Rum Black Label"] = { slot = "HP", spellID = 25804, item = "Rumsey Rum Black Label" },
|
||||
["Rumsey Rum"] = { slot = "HP", spellID = 0, item = "Rumsey Rum" },
|
||||
["Rumsey Rum Light"] = { slot = "HP", spellID = 0, item = "Rumsey Rum Light" },
|
||||
["Medivh's Merlot"] = { slot = "HP", spellID = 57106, item = "Medivh's Merlot" }, -- use-spell ID; the aura's own ID is unverified
|
||||
["Gordok Green Grog"] = { slot = "HP", spellID = 0, item = "Gordok Green Grog" },
|
||||
-- HPELX (alcohol split out into ALC; Fortitude stacks with it)
|
||||
["Elixir of Fortitude"] = { slot = "HPELX", spellID = 3593, item = "Elixir of Fortitude", icon = "inv_potion_43" },
|
||||
["Health II"] = { slot = "HPELX", spellID = 3593, item = "Elixir of Fortitude", icon = "inv_potion_43" }, -- AURA name on this client (measured live: SpellInfo(3593) = "Health II")
|
||||
|
||||
-- FR
|
||||
["Greater Fire Protection"] = { slot = "FR", spellID = 17543, item = "Greater Fire Protection Potion" },
|
||||
["Fire Protection"] = { slot = "FR", spellID = 7233, item = "Fire Protection Potion" },
|
||||
-- ALC (alcohol buffs -- ONE column by design although different
|
||||
-- types stack; see the header compromise note). Buff names and spell
|
||||
-- ids wowhead/octowow-confirmed 2026-08-11; the "Blue Label" aura
|
||||
-- name differs from the Blue item name, both keys are kept.
|
||||
["Medivh's Merlot"] = { slot = "ALC", spellID = 57106, item = "Medivh's Merlot", icon = "inv_drink_waterskin_05" }, -- use-spell ID; the aura's own ID is unverified
|
||||
["Medivh's Merlot Blue"] = { slot = "ALC", spellID = 0, item = "Medivh's Merlot Blue", icon = "inv_drink_waterskin_01" },
|
||||
["Medivh's Merlot Blue Label"] = { slot = "ALC", spellID = 57107, item = "Medivh's Merlot Blue", icon = "inv_drink_waterskin_01" }, -- the AURA name (octowow: spell 57107 "Medivh's Merlot Blue Label")
|
||||
["Rumsey Rum Black Label"] = { slot = "ALC", spellID = 25804, item = "Rumsey Rum Black Label", icon = "inv_drink_04" },
|
||||
["Rumsey Rum"] = { slot = "ALC", spellID = 20875, item = "Rumsey Rum", icon = "inv_drink_03" },
|
||||
["Rumsey Rum Light"] = { slot = "ALC", spellID = 25037, item = "Rumsey Rum Light", icon = "inv_drink_08" },
|
||||
["Gordok Green Grog"] = { slot = "ALC", spellID = 22789, item = "Gordok Green Grog", icon = "inv_drink_03" }, -- same icon as Rumsey Rum -- genuine, wowhead-confirmed
|
||||
["Kreeg's Stout Beatdown"] = { slot = "ALC", spellID = 22790, item = "Kreeg's Stout Beatdown", icon = "inv_drink_05" }, -- +25 Spirit/-5 Int -- the healer set's alcohol, not a stamina drink
|
||||
|
||||
-- FR (protection-potion slot rules are an UNVERIFIED gap, see header)
|
||||
["Greater Fire Protection"] = { slot = "FR", spellID = 17543, item = "Greater Fire Protection Potion", icon = "inv_potion_24", unverified = true },
|
||||
["Fire Protection"] = { slot = "FR", spellID = 7233, item = "Fire Protection Potion", icon = "inv_potion_16", unverified = true },
|
||||
|
||||
-- FrR/NR/SR/HR/AR (same UNVERIFIED protection-potion gap as FR).
|
||||
-- Use-effect spell IDs + item icons wowhead-confirmed 2026-08-11.
|
||||
-- Tier coverage is asymmetric by design: no Lesser Arcane potion and
|
||||
-- no Greater Holy potion exist in vanilla 1.12. The aura on the
|
||||
-- client likely drops the "Greater" (wowhead names both tiers
|
||||
-- "<School> Protection"); the greater keys mirror the FR convention
|
||||
-- and the ID match carries them regardless of the live aura name.
|
||||
["Greater Frost Protection"] = { slot = "FrR", spellID = 17544, item = "Greater Frost Protection Potion", icon = "inv_potion_20", unverified = true },
|
||||
["Frost Protection"] = { slot = "FrR", spellID = 7239, item = "Frost Protection Potion", icon = "inv_potion_13", unverified = true },
|
||||
["Greater Nature Protection"] = { slot = "NR", spellID = 17546, item = "Greater Nature Protection Potion", icon = "inv_potion_22", unverified = true },
|
||||
["Nature Protection"] = { slot = "NR", spellID = 7254, item = "Nature Protection Potion", icon = "inv_potion_06", unverified = true }, -- live aura name carries a trailing space (SpellInfo(7254)); scan/aura.lua's trimmed retry covers it
|
||||
["Greater Shadow Protection"] = { slot = "SR", spellID = 17548, item = "Greater Shadow Protection Potion", icon = "inv_potion_23", unverified = true },
|
||||
["Shadow Protection"] = { slot = "SR", spellID = 7242, item = "Shadow Protection Potion", icon = "inv_potion_44", unverified = true }, -- NAME collides with the priest buff (data/classbuffs.lua SPROT, ids 976/10957/10958): ID-before-name keeps them apart; a name-only scan cannot tell the two, which SR's default-off expectation mitigates
|
||||
["Holy Protection"] = { slot = "HR", spellID = 7245, item = "Holy Protection Potion", icon = "inv_potion_09", unverified = true },
|
||||
["Greater Arcane Protection"] = { slot = "AR", spellID = 17549, item = "Greater Arcane Protection Potion", icon = "inv_potion_83", unverified = true },
|
||||
|
||||
-- WPN: intentionally NO aura entries - temp enchant is not a buff
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.ResolveItem(buffName, effectText) -> item, icon
|
||||
-- The PURE identification ladder (spec: "Identification ladder"):
|
||||
-- 1. unique buff name -> item/icon straight from the entry
|
||||
-- 2. generic buff name -> match effectText against the entry's
|
||||
-- variant discrim patterns (plain Lua 5.0 string.find, first match
|
||||
-- wins) -> that variant's item/icon
|
||||
-- 3. no match / no effect text -> nil, nil (the caller falls back to
|
||||
-- the slot-generic icon: "slot filled, item unknown")
|
||||
-- An entry-level discrim (no variants) gates rule 1 the same way: the
|
||||
-- item only resolves when the effect line matches. Offline-testable --
|
||||
-- no WoW API, no upvalues beyond the data table.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.ResolveItem(buffName, effectText)
|
||||
local def = DC.CONSUMABLES[buffName]
|
||||
if not def then
|
||||
return nil, nil
|
||||
end
|
||||
if def.variants then
|
||||
if type(effectText) == "string" then
|
||||
local n = table.getn(def.variants)
|
||||
for i = 1, n do
|
||||
local v = def.variants[i]
|
||||
if v.discrim and string.find(effectText, v.discrim) then
|
||||
return v.item, v.icon
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
if def.discrim then
|
||||
if type(effectText) == "string"
|
||||
and string.find(effectText, def.discrim) then
|
||||
return def.item, def.icon
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
return def.item, def.icon
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- spellID -> slotId map, generated from DC.CONSUMABLES (spellID > 0).
|
||||
-- Collision rule: at classify time an ID match beats a
|
||||
|
||||
+22
-8
@@ -1,5 +1,5 @@
|
||||
-- DopingControl data/enchants.lua
|
||||
-- Equipment tab: 14 enchant slots + 2 item-tile slots (both trinkets), in
|
||||
-- Equipment tab: 15 enchant slots + 2 item-tile slots (both trinkets), in
|
||||
-- paperdoll order, with 1.12 inventory slot IDs.
|
||||
-- Enchant columns are checked present/missing only (enchant ID in the item
|
||||
-- link ~= 0); an EMPTY enchant slot is always a gap (core/model.lua), so it
|
||||
@@ -7,13 +7,26 @@
|
||||
-- question is. Item-tile slots (trinkets) show the equipped item and
|
||||
-- ignore expectations entirely; only an EMPTY slot counts as a gap there.
|
||||
--
|
||||
-- Server note: neck and both rings ARE enchantable on this server (a
|
||||
-- server-custom feature beyond vanilla WoW, where they take no enchant) --
|
||||
-- NECK/R1/R2 are therefore kind="ench" like any armor slot, not item
|
||||
-- tiles. Their expectation defaults to OFF (data/expectations.lua): unlike
|
||||
-- the ARMOR enchant slots, a neck/ring enchant is not assumed baseline
|
||||
-- raid gear, so the columns only track it once a role/class opts in via
|
||||
-- the options grid.
|
||||
-- Server note: neck, both rings AND the waist ARE enchantable on this
|
||||
-- server (a server-custom feature beyond vanilla WoW, where none of them
|
||||
-- take an enchant) -- NECK/R1/R2/WAIST are therefore kind="ench" like any
|
||||
-- armor slot, not item tiles. The waist enchant is a "Belt Buckle" item
|
||||
-- (Copper/Bronze/Thorium/Dreamsteel/Bloody Belt Buckle),
|
||||
-- the same custom itemization line as the neck/ring gems. Their
|
||||
-- expectation defaults to OFF (data/expectations.lua): unlike the ARMOR
|
||||
-- enchant slots, a neck/ring/waist enchant is not assumed baseline raid
|
||||
-- gear, so the columns only track it once a role/class opts in via the
|
||||
-- options grid.
|
||||
--
|
||||
-- Bug history (2026-08-30): the waist (invSlot 6) was missing from this
|
||||
-- table entirely -- not a deliberate exclusion, an oversight that predates
|
||||
-- the Belt Buckle KG lookup above. Because scan/gear.lua's G.InvSlots()
|
||||
-- derives the full GetInventoryItemLink read list from THIS table, the
|
||||
-- omission silently dropped the waist from every equipment read: no raid
|
||||
-- dump ever carried a ";6=" entry, and the HIT tab under-counted any
|
||||
-- +hit/+resist on a belt (scan/hit.lua's H.SLOTS already listed 6 and
|
||||
-- expected scan/gear.lua's rawLinks to cover it -- see that file's
|
||||
-- ReadUnit comment). Adding WAIST here fixes both at the source.
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
@@ -30,6 +43,7 @@ DC.SLOTS_EQUIPMENT = {
|
||||
{ id = "SHLD", label = "SHO", sub = "slot 3", full = "Shoulders", kind = "ench", invSlot = 3 },
|
||||
{ id = "BACK", label = "BCK", sub = "slot 15", full = "Back", kind = "ench", invSlot = 15 },
|
||||
{ id = "CHST", label = "CHE", sub = "slot 5", full = "Chest", kind = "ench", invSlot = 5 },
|
||||
{ id = "WAIST", label = "WAI", sub = "slot 6", full = "Waist", kind = "ench", invSlot = 6 }, -- enchantable on this server (custom Belt Buckle line, unlike vanilla)
|
||||
{ id = "WRST", label = "WRI", sub = "slot 9", full = "Wrist", kind = "ench", invSlot = 9 },
|
||||
{ id = "HAND", label = "HND", sub = "slot 10", full = "Hands", kind = "ench", invSlot = 10 },
|
||||
{ id = "LEGS", label = "LEG", sub = "slot 7", full = "Legs", kind = "ench", invSlot = 7 },
|
||||
|
||||
+63
-32
@@ -15,7 +15,21 @@
|
||||
-- a CASTER/HEALER role default for every performing class.
|
||||
--
|
||||
-- Role-line choices:
|
||||
-- Consumables: standard raid role lines.
|
||||
-- Consumables: the four role sets of the KG stacking-rules atom
|
||||
-- (data-tables/consumable-stacking-rules, TurtleWoW
|
||||
-- 1.17.2 -- melee / caster / tank / healer, each "all
|
||||
-- active simultaneously") ARE the defaults now; see the
|
||||
-- spec 2026-08-11-slot-model-item-icons-design.md. RANGED
|
||||
-- has no KG set of its own and is derived from the melee
|
||||
-- line (no STR -- ranged AP scales off AGI -- and no
|
||||
-- FLASK). FR stays situational for all five roles
|
||||
-- (enable/disable per boss via the expectation matrix),
|
||||
-- WPN as before. The other school protection slots
|
||||
-- (FrR/NR/SR/HR/AR, added 2026-08-11) carry NO seed entry
|
||||
-- at all: default off for every role, so the dynamic-
|
||||
-- column rule hides them until someone enables them per
|
||||
-- role/class for a matching boss (same treatment as EMB
|
||||
-- and T1/T2 below) -- unlike FR, which ships seeded true.
|
||||
-- Class buffs: SOUL only HEALER, AI only CASTER/HEALER, SPROT situational
|
||||
-- for all five -- like FR. EMB (Emerald Blessing) has NO
|
||||
-- seed entry: the buff comes from a raid questline and most
|
||||
@@ -23,12 +37,16 @@
|
||||
-- until someone enables it per role/class in the options
|
||||
-- grid; the dynamic-column rule makes an unexpected slot
|
||||
-- disappear entirely (same treatment as T1/T2 below).
|
||||
-- Equipment: ALL armor enchant slots for ALL roles;
|
||||
-- Equipment: ALL armor enchant slots for ALL roles, WAIST included
|
||||
-- (owner decision, 2026-08-30: every role wears a belt,
|
||||
-- and it is enchantable on this server via the Belt
|
||||
-- Buckle item line -- so it is worn equipment like any
|
||||
-- other slot, not an optional extra like neck/rings);
|
||||
-- per-item exemptions (wand/holdable) are handled at
|
||||
-- classify time via DC.ENCH_EXEMPT, not here.
|
||||
-- NECK/R1/R2 (enchantable on this server, a custom feature
|
||||
-- beyond vanilla) default to OFF for every role: unlike
|
||||
-- the armor slots, a neck/ring enchant is not assumed
|
||||
-- NECK/R1/R2 (also enchantable on this server, a custom
|
||||
-- feature beyond vanilla) stay OFF by default for every
|
||||
-- role: unlike WAIST, a neck/ring enchant is not assumed
|
||||
-- baseline raid gear -- enable it per role/class in the
|
||||
-- options grid when it matters. T1/T2 (trinket item tiles)
|
||||
-- carry NO seed entry at all -- the model ignores
|
||||
@@ -37,19 +55,16 @@
|
||||
--
|
||||
-- Known debatable rows -- documented deliberately, the shipped defaults
|
||||
-- win:
|
||||
-- * TANK+AP: some tank consumable lists include Winterfall
|
||||
-- Firewater; the default has no AP for TANK.
|
||||
-- * MELEE+FLASK: some melee lists include Flask of the Titans; the
|
||||
-- default has no FLASK for MELEE.
|
||||
-- * MELEE/RANGED+HP: some melee lists have neither Fortitude nor
|
||||
-- alcohol; HP=true is the weakest row of the matrix.
|
||||
-- * RANGED+AP/WPN: Firewater/Juju Might are MELEE attack power; ranged
|
||||
-- AP comes from food. Arguably AP=false, WPN=false
|
||||
-- for RANGED; the default says true.
|
||||
-- * HEALER+SP: healer sets may carry Dreamshard (+healing), which the
|
||||
-- 11-slot merge puts into SP; the default has no SP for
|
||||
-- HEALER -> Dreamshard is invisible for healers by
|
||||
-- default.
|
||||
-- * TANK+AP / MELEE+FLASK: the OLD defaults deliberately excluded them;
|
||||
-- the KG role sets include them (tank runs Winterfall
|
||||
-- Firewater, melee runs Flask of the Titans), so both
|
||||
-- flipped to true with the v6 slot model.
|
||||
-- * RANGED line: derived, not KG-covered (see above) -- BL is included
|
||||
-- (Ground Scorpok Assay is +25 AGI), STR/FLASK are not.
|
||||
-- * HEALER+HPELX: the KG healer set carries no Elixir of Fortitude, so
|
||||
-- HPELX defaults off for HEALER while ALC (Kreeg's
|
||||
-- Stout Beatdown + Medivh's Merlot Blue) is on.
|
||||
-- * ZANZA: expected for every role (all four KG sets carry it).
|
||||
-- * Equipment: an alternative default would differentiate per role
|
||||
-- (OFFH only TANK/MELEE, RNGD only RANGED,
|
||||
-- casters/healers neither); deliberately flattened to
|
||||
@@ -65,9 +80,12 @@ local DC = DopingControl
|
||||
|
||||
DC.DEFAULT_EXPECT = {
|
||||
TANK = {
|
||||
-- consumables
|
||||
FLASK = true, FOOD = true, STR = true, AGI = true, ARM = true,
|
||||
HP = true, FR = true, WPN = true,
|
||||
-- consumables (KG tank set: Mongoose, Firewater, Juju Power,
|
||||
-- R.O.I.D.S., Zanza, Superior Defense, Fortitude, Titans, food,
|
||||
-- Merlot + Rumsey, sharpening stone; Gift of Arthas has no column)
|
||||
FLASK = true, FOOD = true, AP = true, STR = true, AGI = true,
|
||||
BL = true, ZANZA = true, ARM = true, HPELX = true, ALC = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment (RNGD default only for RANGED:
|
||||
@@ -75,48 +93,61 @@ DC.DEFAULT_EXPECT = {
|
||||
-- expectation matrix anytime)
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
MELEE = {
|
||||
-- consumables
|
||||
FOOD = true, AP = true, STR = true, AGI = true,
|
||||
HP = true, FR = true, WPN = true,
|
||||
-- consumables (KG melee set: Mongoose, Firewater/Juju Might,
|
||||
-- Juju Power/Giants, R.O.I.D.S./Scorpok, Zanza, Titans, food,
|
||||
-- sharpening stone; Juju Flurry has no column)
|
||||
FLASK = true, FOOD = true, AP = true, STR = true, AGI = true,
|
||||
BL = true, ZANZA = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
RANGED = {
|
||||
-- consumables
|
||||
FOOD = true, AP = true, AGI = true,
|
||||
HP = true, FR = true, WPN = true,
|
||||
-- consumables (derived from the melee line -- no KG set; no STR,
|
||||
-- no FLASK, see "Known debatable rows")
|
||||
FOOD = true, AP = true, AGI = true, BL = true, ZANZA = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
RNGD = true,
|
||||
},
|
||||
-- CASTER/HEALER: RNGD also dropped by role default (their classes are
|
||||
-- additionally exempt via ENCH_CLASS_EXEMPT - belt and suspenders)
|
||||
CASTER = {
|
||||
-- consumables
|
||||
FLASK = true, FOOD = true, SP = true, MP5 = true,
|
||||
-- consumables (KG caster set: Dreamshard, GAE, school elixir,
|
||||
-- Dreamtonic, Mageblood, wizard oil, Zanza, Supreme Power, food)
|
||||
FLASK = true, FOOD = true, GAE = true, SCHOOL = true,
|
||||
DREAMT = true, SHARD = true, MP5 = true, ZANZA = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
AI = true, MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
HEALER = {
|
||||
-- consumables
|
||||
FLASK = true, FOOD = true, MP5 = true,
|
||||
HP = true, FR = true, WPN = true,
|
||||
-- consumables (KG healer set: Dreamshard, Mageblood, mana oil,
|
||||
-- Zanza, Distilled Wisdom, food, Kreeg's + Merlot Blue)
|
||||
FLASK = true, FOOD = true, MP5 = true, SHARD = true, ZANZA = true,
|
||||
ALC = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs (SOUL: expected ONLY here)
|
||||
AI = true, MOTW = true, PWF = true, SPROT = true, SOUL = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
}
|
||||
|
||||
+36
-13
@@ -19,6 +19,12 @@
|
||||
-- resolved via SuperWoW SpellInfo(id) (return 3 = icon) when the function
|
||||
-- exists. textures is OPTIONAL/partial by design -- every consumer
|
||||
-- nil-guards, nothing downstream may require it.
|
||||
-- Buff EFFECT lines: auras.effects[name] = the tooltip's line 2 text
|
||||
-- (same hidden-tooltip read as the name, TextLeft2) for every path-A buff
|
||||
-- that yielded both a name and a non-empty line 2. This feeds the item
|
||||
-- identification ladder for generic buff names ("Well Fed" -> which food,
|
||||
-- via DC.ResolveItem's discrim patterns). effects is OPTIONAL/partial
|
||||
-- exactly like textures -- every consumer nil-guards.
|
||||
-- Disagreements between the two ID sets are counted (symmetric difference,
|
||||
-- only when BOTH paths yielded at least one ID -- an absent path is missing
|
||||
-- data, not a disagreement) and accumulated by the engine into
|
||||
@@ -68,8 +74,9 @@ local A = DC_Aura
|
||||
|
||||
-- Assemble(rawA, rawB, idNames) -> auras, aurasRead, disagreements, unknown
|
||||
-- rawA : array of { name = string|nil, id = number|nil,
|
||||
-- texture = string|nil } (path A; texture is the 1st
|
||||
-- UnitBuff return, optional)
|
||||
-- texture = string|nil, effect = string|nil }
|
||||
-- (path A; texture is the 1st UnitBuff return,
|
||||
-- effect the tooltip's line 2 -- both optional)
|
||||
-- rawB : array of spell ids (numbers, path B)
|
||||
-- idNames : OPTIONAL { [spellId] = "Buff Name" } for path-B ids,
|
||||
-- filled by the WoW-side caller (SuperWoW SpellInfo lookup --
|
||||
@@ -78,9 +85,10 @@ local A = DC_Aura
|
||||
-- as before (no behavior change without it).
|
||||
-- Returns:
|
||||
-- auras : { names = { [name]=true }, ids = { [id]=true },
|
||||
-- textures = { [name]=iconPath } }
|
||||
-- (store shape; textures may be empty/partial --
|
||||
-- consumers nil-guard)
|
||||
-- textures = { [name]=iconPath },
|
||||
-- effects = { [name]=effectLine } }
|
||||
-- (store shape; textures AND effects may be empty/
|
||||
-- partial -- consumers nil-guard)
|
||||
-- aurasRead : bool -- true iff at least one USABLE aura arrived: a
|
||||
-- path-A entry carrying a name or an id, or a path-B id.
|
||||
-- Entries with NEITHER (UnitBuff texture present but the
|
||||
@@ -131,6 +139,7 @@ function A.Assemble(rawA, rawB, idNames)
|
||||
local idsA = {}
|
||||
local idsB = {}
|
||||
local textures = {} -- [buffName] = icon path
|
||||
local effects = {} -- [buffName] = tooltip effect line (line 2)
|
||||
local namedIds = {} -- ids that arrived WITH a name (path A rows)
|
||||
local usable = 0 -- entries that carry actual evidence (see header)
|
||||
|
||||
@@ -144,6 +153,9 @@ function A.Assemble(rawA, rawB, idNames)
|
||||
if e.texture then
|
||||
textures[e.name] = e.texture
|
||||
end
|
||||
if type(e.effect) == "string" and e.effect ~= "" then
|
||||
effects[e.name] = e.effect
|
||||
end
|
||||
end
|
||||
if type(e.id) == "number" and e.id > 0 then
|
||||
idsA[e.id] = true
|
||||
@@ -259,7 +271,8 @@ function A.Assemble(rawA, rawB, idNames)
|
||||
end
|
||||
end
|
||||
|
||||
return { names = names, ids = ids, textures = textures },
|
||||
return { names = names, ids = ids, textures = textures,
|
||||
effects = effects },
|
||||
aurasRead, disagreements, unknown
|
||||
end
|
||||
|
||||
@@ -376,15 +389,22 @@ function A.GetScanTip()
|
||||
return ensureTip()
|
||||
end
|
||||
|
||||
-- Returns name (tooltip line 1) AND effect (tooltip line 2 -- the buff's
|
||||
-- effect text, feeds the item identification ladder for generic names).
|
||||
-- raises an error on bad units -> always called through pcall
|
||||
local function tipBuffName(unit, buffIndex)
|
||||
scanTip:ClearLines()
|
||||
scanTip:SetUnitBuff(unit, buffIndex)
|
||||
local name, effect = nil, nil
|
||||
local textObj = getglobal("DopingControlScanTipTextLeft1")
|
||||
if textObj then
|
||||
return textObj:GetText()
|
||||
name = textObj:GetText()
|
||||
end
|
||||
return nil
|
||||
local effObj = getglobal("DopingControlScanTipTextLeft2")
|
||||
if effObj then
|
||||
effect = effObj:GetText()
|
||||
end
|
||||
return name, effect
|
||||
end
|
||||
|
||||
-- debuff twin of tipBuffName (same hidden tooltip, SetUnitDebuff);
|
||||
@@ -420,20 +440,23 @@ function A.ReadUnit(unit, guid)
|
||||
if not ok or not texture then
|
||||
break
|
||||
end
|
||||
local name = nil
|
||||
local name, effect = nil, nil
|
||||
if tip then
|
||||
local ok2, nm = pcall(tipBuffName, unit, i)
|
||||
local ok2, nm, eff = pcall(tipBuffName, unit, i)
|
||||
if ok2 then
|
||||
name = nm
|
||||
effect = eff
|
||||
end
|
||||
end
|
||||
local id = nil
|
||||
if type(auraID) == "number" and auraID > 0 then
|
||||
id = auraID
|
||||
end
|
||||
-- texture (UnitBuff return 1) rides along so Assemble can key
|
||||
-- it under the tooltip name (auras.textures)
|
||||
table.insert(rawA, { name = name, id = id, texture = texture })
|
||||
-- texture (UnitBuff return 1) and the tooltip effect line ride
|
||||
-- along so Assemble can key them under the tooltip name
|
||||
-- (auras.textures / auras.effects)
|
||||
table.insert(rawA, { name = name, id = id, texture = texture,
|
||||
effect = effect })
|
||||
end
|
||||
end
|
||||
if UnitDebuff then
|
||||
|
||||
+21
-7
@@ -120,6 +120,15 @@ function E.NormalizeWeapon(isSelf, v, readable)
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- Stock 1.12 GetWeaponEnchantInfo takes NO arguments: on a client without
|
||||
-- SuperWoW the extra unit is ignored and the first return is the PLAYER's
|
||||
-- hasMainHandEnchant (1|nil). Only the SuperWoW name channel answers about
|
||||
-- the foreign unit, and it is always string-or-nil (measured: 11 names,
|
||||
-- 14 nils, "" never). A non-string therefore says nothing about this unit
|
||||
-- => not readable, never "has an imbue".
|
||||
if v ~= nil and type(v) ~= "string" then
|
||||
return nil
|
||||
end
|
||||
if v == nil then
|
||||
if readable == true then
|
||||
return false
|
||||
@@ -998,10 +1007,12 @@ if CreateFrame then
|
||||
end
|
||||
E.SweepLastKnown(DC.lastKnown, rosterSet, E.CACHE_CAP)
|
||||
local db = DopingControlDB
|
||||
if db and db.testMode then
|
||||
-- Test mode was switched ON while this scan was in flight: the
|
||||
-- simulator fully replaces the data source ("real
|
||||
-- data never beats the simulation") -- drop the result.
|
||||
if DC.SimOwnsStore(db) then
|
||||
-- Test mode OR demo mode was on (or switched on while this scan
|
||||
-- was in flight): the simulator fully replaces the data source
|
||||
-- ("real data never beats the simulation") -- drop the result.
|
||||
-- This is the load-bearing gate: it is the only thing between a
|
||||
-- finished scan and DC.store = E.NewStore(...) below.
|
||||
results = {}
|
||||
return
|
||||
end
|
||||
@@ -1055,8 +1066,11 @@ if CreateFrame then
|
||||
|
||||
-- Start(force) -> bool (scan started/restarted).
|
||||
-- Already running: no-op unless force, which restarts with a fresh
|
||||
-- roster snapshot. Test mode is NOT checked here -- an explicit Start
|
||||
-- is user intent; only the READY_CHECK auto-trigger honors testMode.
|
||||
-- roster snapshot. DC.SimOwnsStore is NOT checked here -- an explicit
|
||||
-- Start is user intent, and the callers that are NOT user intent
|
||||
-- (READY_CHECK, the roster-change and window-open auto-scans) check it
|
||||
-- themselves. The scan may therefore run in test/demo mode; what it may
|
||||
-- never do is publish, so finish() drops the result instead.
|
||||
function E.Start(force)
|
||||
if running then
|
||||
if not force then
|
||||
@@ -1087,7 +1101,7 @@ if CreateFrame then
|
||||
eventFrame:SetScript("OnEvent", function()
|
||||
if event == "READY_CHECK" then
|
||||
local db = DopingControlDB
|
||||
if db and db.readyCheckScan and not db.testMode then
|
||||
if db and db.readyCheckScan and not DC.SimOwnsStore(db) then
|
||||
E.Start()
|
||||
end
|
||||
return
|
||||
|
||||
+2
-2
@@ -29,9 +29,9 @@ local G = DC_Gear
|
||||
|
||||
-- Fallback inventory-slot list (= invSlot column of DC.SLOTS_EQUIPMENT,
|
||||
-- data/enchants.lua) so this file works standalone in tests.
|
||||
local FALLBACK_INV_SLOTS = { 1, 2, 3, 15, 5, 9, 10, 7, 8, 16, 17, 18, 11, 12, 13, 14 }
|
||||
local FALLBACK_INV_SLOTS = { 1, 2, 3, 15, 5, 6, 9, 10, 7, 8, 16, 17, 18, 11, 12, 13, 14 }
|
||||
|
||||
-- InvSlots() -> array of the 16 inventory slot numbers, in column order.
|
||||
-- InvSlots() -> array of the 17 inventory slot numbers, in column order.
|
||||
-- Prefers DC.SLOTS_EQUIPMENT (single source of truth once data/ is loaded).
|
||||
function G.InvSlots()
|
||||
if DC.SLOTS_EQUIPMENT then
|
||||
|
||||
+7
-6
@@ -122,9 +122,11 @@ DC_Hit = DC_Hit or {}
|
||||
local H = DC_Hit
|
||||
|
||||
-- Equipment slots that can carry +hit, in inventory-slot order. This is
|
||||
-- deliberately its OWN list and not DC_Gear.InvSlots(): the enchant tab
|
||||
-- has no waist column, but a belt most certainly can carry hit. Shirt (4)
|
||||
-- and tabard (19) never carry stats and are left out.
|
||||
-- deliberately its OWN list and not DC_Gear.InvSlots(): the two lists
|
||||
-- happen to agree today (both cover the waist, invSlot 6 -- see
|
||||
-- data/enchants.lua's bug-history note), but this file must not assume
|
||||
-- that stays true, so it keeps every hit-bearing slot spelled out here.
|
||||
-- Shirt (4) and tabard (19) never carry stats and are left out.
|
||||
H.SLOTS = { 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }
|
||||
|
||||
H.SLOT_MAIN = 16
|
||||
@@ -888,9 +890,8 @@ end
|
||||
-- ReadUnit(unit, rawLinks) -> hit, race
|
||||
-- rawLinks : OPTIONAL { [invSlot] = link } that scan/gear.lua already
|
||||
-- fetched for this unit. Slots it covers are taken from it
|
||||
-- (no second GetInventoryItemLink pass); slots outside its
|
||||
-- coverage -- the waist, which the enchant tab has no column
|
||||
-- for -- are fetched here.
|
||||
-- (no second GetInventoryItemLink pass); any slot H.SLOTS
|
||||
-- needs that rawLinks did not cover is fetched here instead.
|
||||
-- hit : the store shape documented in the file header
|
||||
-- race : race token from UnitRace (2nd return, e.g. "NightElf");
|
||||
-- needed for the racial weapon-skill estimate of foreign
|
||||
|
||||
+62
-6
@@ -67,6 +67,11 @@ T.ASK_GAP = 5
|
||||
-- do not ask the same player again for this long. Talents change on
|
||||
-- respec, which is rare and never mid-raid.
|
||||
T.REASK_AFTER = 1800
|
||||
-- how long an unfinished reply stays open. A whole reply lands within a
|
||||
-- fraction of a second, so a line arriving this much later cannot belong to
|
||||
-- it -- it is the start of a NEW reply and must not be folded into the old
|
||||
-- accumulator (see T.AccStale).
|
||||
T.ACC_TTL = 10
|
||||
|
||||
-- ==================================================================
|
||||
-- PURE SECTION (offline-testable)
|
||||
@@ -147,9 +152,24 @@ end
|
||||
-- spent = <sum of pointsSpent over the trees that reported>,
|
||||
-- sumRank = <sum of every rank we stored>,
|
||||
-- trees = <how many tab lines arrived>,
|
||||
-- complete = <INSTalentEND seen> }
|
||||
function T.NewAcc()
|
||||
return { ranks = {}, spent = 0, sumRank = 0, trees = 0, complete = false }
|
||||
-- complete = <INSTalentEND seen>,
|
||||
-- at = <when the first line arrived, nil offline> }
|
||||
function T.NewAcc(now)
|
||||
return { ranks = {}, spent = 0, sumRank = 0, trees = 0, complete = false,
|
||||
at = now }
|
||||
end
|
||||
|
||||
-- Has this accumulator been waiting so long that the next line must belong to
|
||||
-- a different reply? Only the END marker drops an accumulator, so a reply
|
||||
-- whose tail was lost would otherwise stay open forever and swallow the next
|
||||
-- one: tab lines add up twice while ranks are deduplicated by name, so
|
||||
-- T.Plausible rejects a perfectly good retry. Without a clock (offline) or an
|
||||
-- opening stamp nothing expires -- the old behaviour, unchanged.
|
||||
function T.AccStale(acc, now)
|
||||
if not acc or not acc.at or not now then
|
||||
return false
|
||||
end
|
||||
return (now - acc.at) > T.ACC_TTL
|
||||
end
|
||||
|
||||
-- Fold one parsed line into the accumulator. Returns the accumulator so
|
||||
@@ -281,6 +301,9 @@ if CreateFrame then
|
||||
return ok
|
||||
end
|
||||
|
||||
-- set below, once the frame exists: arms the paced sender
|
||||
local arm
|
||||
|
||||
-- Called by the scan with the players it can see. Nothing is sent here
|
||||
-- -- names are only lined up; the timer below paces them out.
|
||||
function T.Request(names)
|
||||
@@ -296,6 +319,9 @@ if CreateFrame then
|
||||
table.insert(queue, n)
|
||||
end
|
||||
end
|
||||
if arm and table.getn(queue) > 0 then
|
||||
arm()
|
||||
end
|
||||
end
|
||||
|
||||
local f = CreateFrame("Frame", "DopingControlTalentFrame")
|
||||
@@ -313,9 +339,14 @@ if CreateFrame then
|
||||
if not kind then
|
||||
return
|
||||
end
|
||||
local now = GetTime()
|
||||
local a = acc[sender]
|
||||
if a and T.AccStale(a, now) then
|
||||
-- the previous reply lost its END line; this one starts fresh
|
||||
a = nil
|
||||
end
|
||||
if not a then
|
||||
a = T.NewAcc()
|
||||
a = T.NewAcc(now)
|
||||
acc[sender] = a
|
||||
end
|
||||
T.Feed(a, kind, data)
|
||||
@@ -338,14 +369,26 @@ if CreateFrame then
|
||||
-- queue the scan filled. Deliberately a plain timer rather than a burst
|
||||
-- after each scan -- 40 requests in one frame is what a rate limit
|
||||
-- would punish, and nothing documents where that limit sits.
|
||||
--
|
||||
-- It is installed only while there is something to send and takes itself
|
||||
-- off again the moment the queue runs dry (or the feature is switched
|
||||
-- off): an OnUpdate runs 60+ times a second for the whole session, and a
|
||||
-- player who never scans must not pay for a queue that is always empty.
|
||||
-- Re-arming needs no extra wiring -- every scan calls T.Request.
|
||||
local elapsed = 0
|
||||
f:SetScript("OnUpdate", function()
|
||||
|
||||
local function disarm()
|
||||
f:SetScript("OnUpdate", nil)
|
||||
end
|
||||
|
||||
local function drain()
|
||||
elapsed = elapsed + (arg1 or 0)
|
||||
if elapsed < 1 then
|
||||
return
|
||||
end
|
||||
elapsed = 0
|
||||
if not enabled() or table.getn(queue) == 0 then
|
||||
disarm()
|
||||
return
|
||||
end
|
||||
local now = GetTime()
|
||||
@@ -359,10 +402,23 @@ if CreateFrame then
|
||||
T.Ask(name)
|
||||
end
|
||||
end
|
||||
end)
|
||||
if table.getn(queue) == 0 then
|
||||
disarm()
|
||||
end
|
||||
end
|
||||
|
||||
arm = function()
|
||||
if f:GetScript("OnUpdate") then
|
||||
return
|
||||
end
|
||||
-- a long idle gap must not fire an immediate burst
|
||||
elapsed = 0
|
||||
f:SetScript("OnUpdate", drain)
|
||||
end
|
||||
|
||||
-- forget everything (respec, or a deliberate re-read)
|
||||
function T.Clear()
|
||||
disarm()
|
||||
acc = {}
|
||||
ranks = {}
|
||||
readAt = {}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 256 KiB |
@@ -0,0 +1,118 @@
|
||||
-- DopingControl ui/cellsheet.lua
|
||||
--
|
||||
-- The matrix draws one small box per cell: a solid background with a 1px
|
||||
-- border whose color carries the state. Done with SetBackdrop that costs NINE
|
||||
-- texture regions per cell (measured in the client: bare frame 0, backdrop
|
||||
-- with bgFile only 1, backdrop with edgeFile 9). At 805 cells that is ~7200
|
||||
-- regions of pure decoration.
|
||||
--
|
||||
-- Instead every box is baked into one sheet and a cell owns a single texture
|
||||
-- region that picks its box with SetTexCoord. The border is part of the image,
|
||||
-- so the result is pixel-identical -- the sheet is generated from the same
|
||||
-- DC.COLORS palette by tools/gen-cellsheet.js.
|
||||
--
|
||||
-- Pure Lua on purpose: no WoW API at file scope, so the offline tests can
|
||||
-- dofile it under lua50.exe.
|
||||
|
||||
DC_Cells = {}
|
||||
|
||||
DC_Cells.SHEET = {
|
||||
path = "Interface\\AddOns\\DopingControl\\textures\\cells",
|
||||
size = 256, -- power of two, mandatory: a 12x12 TGA fails silently
|
||||
tileW = 64, -- holds the widest sprite (34) with room to spare
|
||||
tileH = 32, -- holds CELL_H (23)
|
||||
perRow = 4, -- 256 / 64
|
||||
}
|
||||
|
||||
-- Cell height never varies; only the width does (trinket columns are 34).
|
||||
DC_Cells.CELL_H = 23
|
||||
DC_Cells.WIDTHS = { 30, 34 }
|
||||
|
||||
-- Every (background, border) pair a CELL can take, in sheet order. Borders
|
||||
-- given as a number are DC.QUALITY indices, everything else is a DC.COLORS
|
||||
-- key. Order is the wire format of the sheet: appending is safe, reordering
|
||||
-- means regenerating the TGA.
|
||||
DC_Cells.VARIANTS = {
|
||||
{ name = "HAS", bg = "hatBg", border = "hatBorder" },
|
||||
{ name = "MISSING", bg = "fehltBg", border = "fehltBorder" },
|
||||
{ name = "UNKNOWN", bg = "unbekBg", border = "unbekBorder" },
|
||||
{ name = "SKIP", bg = "skip", border = "skipBorder" },
|
||||
{ name = "NEUTRAL", bg = "theadBg", border = "line" },
|
||||
{ name = "ITEM_HAS", bg = "theadBg", border = "hatBorder" },
|
||||
{ name = "ITEM_MISSING", bg = "theadBg", border = "fehltBorder" },
|
||||
{ name = "Q_RARE", bg = "theadBg", border = "rareBorder" },
|
||||
{ name = "Q_EPIC", bg = "theadBg", border = "epicBorder" },
|
||||
{ name = "Q_0", bg = "theadBg", border = 0 },
|
||||
{ name = "Q_1", bg = "theadBg", border = 1 },
|
||||
{ name = "Q_2", bg = "theadBg", border = 2 },
|
||||
{ name = "Q_5", bg = "theadBg", border = 5 },
|
||||
{ name = "Q_6", bg = "theadBg", border = 6 },
|
||||
}
|
||||
|
||||
DC_Cells.INDEX = {}
|
||||
for i = 1, table.getn(DC_Cells.VARIANTS) do
|
||||
DC_Cells.INDEX[DC_Cells.VARIANTS[i].name] = i
|
||||
end
|
||||
|
||||
-- Tile slot of a variant/width pair: each variant owns two consecutive tiles,
|
||||
-- one per width, in DC_Cells.WIDTHS order.
|
||||
local function slotOf(name, w)
|
||||
local vi = DC_Cells.INDEX[name]
|
||||
if not vi then
|
||||
return nil
|
||||
end
|
||||
for k = 1, table.getn(DC_Cells.WIDTHS) do
|
||||
if DC_Cells.WIDTHS[k] == w then
|
||||
return (vi - 1) * table.getn(DC_Cells.WIDTHS) + (k - 1)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
DC_Cells.SlotOf = slotOf
|
||||
|
||||
-- SetTexCoord arguments for one box. Returns four numbers or nil -- nil so a
|
||||
-- typo shows up as a missing box instead of silently sampling tile 0.
|
||||
function DC_Cells.Coords(name, w)
|
||||
local slot = slotOf(name, w)
|
||||
if not slot then
|
||||
return nil
|
||||
end
|
||||
local S = DC_Cells.SHEET
|
||||
local col = math.mod(slot, S.perRow)
|
||||
local row = math.floor(slot / S.perRow)
|
||||
local x0 = col * S.tileW
|
||||
local y0 = row * S.tileH
|
||||
return x0 / S.size,
|
||||
(x0 + w) / S.size,
|
||||
y0 / S.size,
|
||||
(y0 + DC_Cells.CELL_H) / S.size
|
||||
end
|
||||
|
||||
-- State + slot kind + item quality -> variant name. Mirrors exactly what the
|
||||
-- old SetBackdropG call sites in ui/matrix.lua chose -- which, for a nil
|
||||
-- quality, differed BY KIND (see ui/matrix.lua's M.PaintItemTile, old lines
|
||||
-- 1965-1990, confirmed at tools/luatests/test_cellsheet.lua):
|
||||
-- * trinket: M.qualityColors(item.quality) ran unconditionally, and for
|
||||
-- quality == nil that resolves to DC.QUALITY[1] (common/white) via the
|
||||
-- `q or 1` fallback -- state-INDEPENDENT.
|
||||
-- * ench: quality == nil skipped M.qualityColors and fell back to
|
||||
-- C.hatBorder/C.fehltBorder/C.line BY STATE -- exactly
|
||||
-- ITEM_HAS/ITEM_MISSING/NEUTRAL.
|
||||
-- So trinket's nil-quality case must NOT share ench's state-based fallback.
|
||||
function DC_Cells.VariantFor(state, kind, quality)
|
||||
if kind == "ench" or kind == "trinket" then
|
||||
if quality == 3 then return "Q_RARE" end
|
||||
if quality == 4 then return "Q_EPIC" end
|
||||
if quality ~= nil then return "Q_" .. quality end
|
||||
if kind == "trinket" then
|
||||
return "Q_1"
|
||||
end
|
||||
if state == "HAS" then return "ITEM_HAS" end
|
||||
if state == "MISSING" then return "ITEM_MISSING" end
|
||||
return "NEUTRAL"
|
||||
end
|
||||
if state == "HAS" then return "HAS" end
|
||||
if state == "MISSING" then return "MISSING" end
|
||||
if state == "UNKNOWN" then return "UNKNOWN" end
|
||||
return "SKIP"
|
||||
end
|
||||
+731
-58
File diff suppressed because it is too large
Load Diff
+68
-10
@@ -149,10 +149,12 @@ O.GRID_LABEL_W = 74 -- role/class label column (expectation grid)
|
||||
O.BOOK_NAME_W = 96 -- player-name column (skill book grid)
|
||||
O.ROW_H = 22 -- one grid row's height, both grids
|
||||
|
||||
-- equipment has the most configurable slots (11 armor enchants + neck +
|
||||
-- 2 rings; trinkets are item tiles and never configurable, see
|
||||
-- slotsForTab) -- the widest grid the pools have to serve.
|
||||
O.MAX_SLOT_COLS = 14
|
||||
-- consumables (v6 slot model) has the most configurable slots (22 incl.
|
||||
-- the five school protection columns);
|
||||
-- equipment follows with 14 (11 armor enchants + neck + 2 rings --
|
||||
-- trinkets are item tiles and never configurable, see slotsForTab).
|
||||
-- The widest grid the pools have to serve.
|
||||
O.MAX_SLOT_COLS = 22
|
||||
|
||||
-- x-offset of the 20 px check button inside its O.CELL_W-wide column
|
||||
O.CELL_INSET = math.floor((O.CELL_W - 20) / 2)
|
||||
@@ -489,6 +491,17 @@ function O.ClassTitle(class)
|
||||
.. string.lower(string.sub(class, 2))
|
||||
end
|
||||
|
||||
-- Label of the demo-mode button. Demo mode is TRANSIENT (core/demo.lua:
|
||||
-- never SavedVariables), so it gets a self-relabeling button rather than a
|
||||
-- checkbox -- the checkbox row above is bound to db fields, and a checkbox
|
||||
-- whose state dies on reload would read like a broken setting.
|
||||
function O.DemoButtonLabel()
|
||||
if DC.demoMode then
|
||||
return "Demo mode: ON (click to stop)"
|
||||
end
|
||||
return "Demo mode: OFF (click to start)"
|
||||
end
|
||||
|
||||
-- ==================================================================
|
||||
-- WOW SECTION -- frame, widgets, wiring
|
||||
-- ==================================================================
|
||||
@@ -504,7 +517,7 @@ if CreateFrame then
|
||||
-- O.GRID_LABEL_W label column O.COL_PITCH column pitch
|
||||
-- O.BOOK_NAME_W player-name column O.ROW_H row pitch
|
||||
-- O.CELL_INSET check button inset O.MAX_SLOT_COLS widest grid
|
||||
-- The two grids differ in column count (14 equipment slots vs. 15
|
||||
-- The two grids differ in column count (22 consumable slots vs. 15
|
||||
-- weapon skills); the frame is BUILT at the wider of the two and
|
||||
-- narrowed per tab by ApplyEditorWidth, so no widget is ever created
|
||||
-- against a width it later has to grow past.
|
||||
@@ -558,9 +571,9 @@ if CreateFrame then
|
||||
elseif tab == "CLASSBUFFS" then
|
||||
return DC.SLOTS_CLASSBUFFS or {}
|
||||
elseif tab == "EQUIPMENT" then
|
||||
-- only kind=="ench" slots are configurable here (14: the 11
|
||||
-- armor slots + neck/rings, which are enchantable on this
|
||||
-- server -- data/enchants.lua). T1/T2 (trinket item tiles)
|
||||
-- only kind=="ench" slots are configurable here (15: the 11
|
||||
-- armor slots + neck/rings/waist, which are enchantable on
|
||||
-- this server -- data/enchants.lua). T1/T2 (trinket item tiles)
|
||||
-- have no enchant question to ask and never appear: the
|
||||
-- matrix always shows them (item present/absent decides,
|
||||
-- expectations are never consulted for tiles).
|
||||
@@ -1536,6 +1549,42 @@ if CreateFrame then
|
||||
place(cbTest, -28)
|
||||
AddTooltip(cbTest, "Replace the scan with a fixed simulated raid.\nChat outputs are disabled while active.")
|
||||
|
||||
-- Demo mode sits right below the test-mode row: same idea (the
|
||||
-- simulated raid), plus a transient expectation layer that shows
|
||||
-- all six school protection columns at once. A BUTTON, not a
|
||||
-- checkbox -- the state is never saved, so it relabels itself
|
||||
-- instead of pretending to be a stored setting.
|
||||
local btnDemo = CreateButton(f, "DopingControlOptDemoBtn",
|
||||
O.DemoButtonLabel(), 200,
|
||||
function()
|
||||
-- Capture the widget BEFORE SetDemoMode runs. `this` is a
|
||||
-- plain global the 1.12 dispatcher sets per script call and
|
||||
-- never restores: SetDemoMode synchronously drives
|
||||
-- DC_Matrix.Refresh -> M.Render, whose leftover-hide loop
|
||||
-- Hide()s pooled rows and group heads that carry their own
|
||||
-- OnHide handlers (ui/matrix.lua M.RowOnHide /
|
||||
-- M.GroupHeadOnHide -- they read `this` themselves). After
|
||||
-- that nested dispatch `this` is the last-hidden widget, and
|
||||
-- a row frame is a plain Frame with no SetText. Same order
|
||||
-- the other handlers in this file already keep
|
||||
-- (GridCellOnClick, ResetOnClick): finish the `this` reads
|
||||
-- and writes before refreshing anything.
|
||||
local btn = this
|
||||
if DC.SetDemoMode then
|
||||
DC.SetDemoMode(not DC.demoMode)
|
||||
end
|
||||
btn:SetText(O.DemoButtonLabel())
|
||||
end)
|
||||
place(btnDemo, -28)
|
||||
AddTooltip(btnDemo, "Simulated raid with ALL SIX protection columns"
|
||||
.. " (fire, frost, nature, shadow, holy, arcane) shown at once."
|
||||
.. "\nYour saved expectations are left untouched, and demo mode"
|
||||
.. " is never saved - it ends with the next reload."
|
||||
-- the demo layer is snapshotted on entry (core/demo.lua), so
|
||||
-- grid edits made while it is armed do not reach the matrix
|
||||
.. "\nThe view is a snapshot: restart demo mode to pick up"
|
||||
.. " expectation changes made while it is running.")
|
||||
|
||||
local cbMinimap = CreateCheckbox(f, "Show minimap button",
|
||||
function() return db().showMinimapButton end,
|
||||
function(v)
|
||||
@@ -1788,6 +1837,11 @@ if CreateFrame then
|
||||
end
|
||||
RefreshEditor() -- grid/books pools + tab visibility + buttons
|
||||
ResetDisarm(getglobal("DopingControlOptResetBtn"))
|
||||
-- demo mode can have been toggled by /dc demo meanwhile
|
||||
local bd = getglobal("DopingControlOptDemoBtn")
|
||||
if bd then
|
||||
bd:SetText(O.DemoButtonLabel())
|
||||
end
|
||||
end)
|
||||
|
||||
-- ESC closes
|
||||
@@ -1827,14 +1881,18 @@ if CreateFrame then
|
||||
f:Show()
|
||||
end
|
||||
|
||||
-- external state changes (/dc test, minimap toggle) can resync an
|
||||
-- open options window
|
||||
-- external state changes (/dc test, /dc demo, minimap toggle) can
|
||||
-- resync an open options window
|
||||
function O.Refresh()
|
||||
if frame and frame:IsShown() then
|
||||
for i = 1, table.getn(checkboxes) do
|
||||
local cb = checkboxes[i]
|
||||
cb:SetChecked(cb.dcGet() and 1 or nil)
|
||||
end
|
||||
local bd = getglobal("DopingControlOptDemoBtn")
|
||||
if bd then
|
||||
bd:SetText(O.DemoButtonLabel())
|
||||
end
|
||||
RefreshEditor() -- a fresh scan can add or drop book rows
|
||||
end
|
||||
end
|
||||
|
||||
+29
-2
@@ -59,6 +59,22 @@ function R.SendDisabled(source)
|
||||
return source == "sim"
|
||||
end
|
||||
|
||||
-- The chat API rejects the WHOLE message with "Invalid escape code in chat
|
||||
-- message" when it meets a "|" that does not open a valid escape sequence
|
||||
-- (|c |r |H |h |T |t |n ||). Our header line uses " | " as a plain ASCII
|
||||
-- separator, so every report was refused. "||" is the escape for a literal
|
||||
-- pipe and still renders as a single "|", which is why this runs at the
|
||||
-- send point and NOT inside DC_Model.reportLines: the preview shows what
|
||||
-- reportLines returns and must keep showing the text as it APPEARS in chat.
|
||||
-- Report lines are pure ASCII by design (no item links, no colour codes),
|
||||
-- so doubling every pipe cannot damage a real escape sequence here.
|
||||
function R.ChatSafe(line)
|
||||
if not line then
|
||||
return line
|
||||
end
|
||||
return (string.gsub(line, "|", "||"))
|
||||
end
|
||||
|
||||
-- Create a drain queue from an array of lines. Takes an independent copy
|
||||
-- (the preview may be rebuilt while an old queue is still draining).
|
||||
function R.QueueCreate(lines)
|
||||
@@ -135,7 +151,18 @@ end
|
||||
function R.BuildLines(store, dbt, tab)
|
||||
store = store or DC.store or { players = {} }
|
||||
tab = tab or "CONSUMABLES"
|
||||
local expect = (dbt and dbt.expectations) or DC.DEFAULT_EXPECT or {}
|
||||
-- Same expectation resolver the matrix uses, or the report preview
|
||||
-- would show different columns and different gap totals than the grid
|
||||
-- it was opened from (demo mode). Safe for the SEND path too: the demo
|
||||
-- layer only applies over the "sim" store, and R.SendDisabled blocks
|
||||
-- sending on exactly that source. Guarded because ui/report.lua is
|
||||
-- loadable without ui/matrix.lua (tools/luatests/test_report_queue.lua).
|
||||
local expect
|
||||
if DC_Matrix and DC_Matrix.ExpectTable then
|
||||
expect = DC_Matrix.ExpectTable(dbt)
|
||||
else
|
||||
expect = (dbt and dbt.expectations) or DC.DEFAULT_EXPECT or {}
|
||||
end
|
||||
local function roleOf(p)
|
||||
return DC_Roles.resolve(dbt, store.source, p)
|
||||
end
|
||||
@@ -234,7 +261,7 @@ if CreateFrame then
|
||||
end
|
||||
local line = R.QueueTick(queue, GetTime(), currentSource())
|
||||
if line then
|
||||
SendChatMessage(line, R.CHAT_TYPE)
|
||||
SendChatMessage(R.ChatSafe(line), R.CHAT_TYPE)
|
||||
UpdateSendState()
|
||||
end
|
||||
if queue.done or queue.aborted then
|
||||
|
||||
+202
-12
@@ -44,6 +44,88 @@ W.SOLID_BACKDROP = {
|
||||
|
||||
W.WHITE = { r = 1, g = 1, b = 1 }
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Cell-box fallback palette: variant name -> (bg, border) color reference,
|
||||
-- used ONLY when ui/cellsheet.lua's baked sheet is unavailable (see
|
||||
-- W.HasCellSheet below). `border` is a DC.COLORS key, or a number that
|
||||
-- indexes DC.QUALITY -- same convention as DC_Cells.VARIANTS in
|
||||
-- ui/cellsheet.lua, which this table intentionally mirrors: colors are
|
||||
-- pulled LIVE from DC.COLORS/DC.QUALITY (core/const.lua, already loaded --
|
||||
-- ui/cellsheet.lua only bakes that same palette into a texture, it does
|
||||
-- not own it), so nothing here duplicates a color VALUE. The pairing
|
||||
-- itself (which key goes with which variant) can't be derived
|
||||
-- mechanically -- it is the one piece of information that has to exist
|
||||
-- twice given the failure mode this guards (ui/cellsheet.lua not loaded
|
||||
-- at all). tools/luatests/test_widgets.lua cross-checks every entry
|
||||
-- against the real DC_Cells.VARIANTS so the two cannot silently drift.
|
||||
W.CELL_FALLBACK_VARIANTS = {
|
||||
HAS = { bg = "hatBg", border = "hatBorder" },
|
||||
MISSING = { bg = "fehltBg", border = "fehltBorder" },
|
||||
UNKNOWN = { bg = "unbekBg", border = "unbekBorder" },
|
||||
SKIP = { bg = "skip", border = "skipBorder" },
|
||||
NEUTRAL = { bg = "theadBg", border = "line" },
|
||||
ITEM_HAS = { bg = "theadBg", border = "hatBorder" },
|
||||
ITEM_MISSING = { bg = "theadBg", border = "fehltBorder" },
|
||||
Q_RARE = { bg = "theadBg", border = "rareBorder" },
|
||||
Q_EPIC = { bg = "theadBg", border = "epicBorder" },
|
||||
Q_0 = { bg = "theadBg", border = 0 },
|
||||
Q_1 = { bg = "theadBg", border = 1 },
|
||||
Q_2 = { bg = "theadBg", border = 2 },
|
||||
Q_5 = { bg = "theadBg", border = 5 },
|
||||
Q_6 = { bg = "theadBg", border = 6 },
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC_Cells fallback shim -- covers a mid-session /reload where THIS file
|
||||
-- (ui/widgets.lua, an EXISTING file whose new content /reload happily
|
||||
-- re-executes) deploys, but ui/cellsheet.lua (a NEW file added in the same
|
||||
-- change) does not: 1.12's /reload re-runs existing files but never loads
|
||||
-- a file that was not already part of the running session (verified
|
||||
-- in-game). Left alone, DC_Cells stays nil, and it is not only
|
||||
-- W.MakeCell/W.SetCellBox below that touch it -- ui/matrix.lua calls
|
||||
-- DC_Cells.VariantFor(...) directly at two paint sites (M.PaintItemTile
|
||||
-- and the legacy trinket path, ui/matrix.lua ~2182/2381 -- out of scope
|
||||
-- for this change) and would error on the very first painted item/trinket
|
||||
-- cell regardless of anything done here.
|
||||
--
|
||||
-- The shim supplies ONLY the pure classification function -- state/kind/
|
||||
-- quality -> variant NAME string -- no sheet, no texture, no coordinate
|
||||
-- math, because none of that can work without ui/cellsheet.lua's baked
|
||||
-- sheet file. It is a verbatim copy of DC_Cells.VariantFor's logic (see
|
||||
-- ui/cellsheet.lua); test_widgets.lua cross-checks the two stay in sync.
|
||||
-- Installs only when DC_Cells does not already exist, so the normal
|
||||
-- (full-restart) load path -- where ui/cellsheet.lua runs before this file
|
||||
-- per DopingControl.toc and sets the real DC_Cells -- is untouched.
|
||||
if not DC_Cells then
|
||||
DC_Cells = {
|
||||
VariantFor = function(state, kind, quality)
|
||||
if kind == "ench" or kind == "trinket" then
|
||||
if quality == 3 then return "Q_RARE" end
|
||||
if quality == 4 then return "Q_EPIC" end
|
||||
if quality ~= nil then return "Q_" .. quality end
|
||||
if kind == "trinket" then
|
||||
return "Q_1"
|
||||
end
|
||||
if state == "HAS" then return "ITEM_HAS" end
|
||||
if state == "MISSING" then return "ITEM_MISSING" end
|
||||
return "NEUTRAL"
|
||||
end
|
||||
if state == "HAS" then return "HAS" end
|
||||
if state == "MISSING" then return "MISSING" end
|
||||
if state == "UNKNOWN" then return "UNKNOWN" end
|
||||
return "SKIP"
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
-- Whether ui/cellsheet.lua's baked sheet is actually available (as
|
||||
-- opposed to the shim above, which never sets .SHEET). One seam shared by
|
||||
-- W.MakeCell and W.SetCellBox, and directly testable offline without
|
||||
-- CreateFrame (unlike W.MakeCell itself).
|
||||
function W.HasCellSheet()
|
||||
return DC_Cells ~= nil and DC_Cells.SHEET ~= nil
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Font helper (we keep OUR pixel
|
||||
-- size -- the matrix layout depends on it -- and only take pfUI's font
|
||||
@@ -125,28 +207,117 @@ end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Cell factory: 30x23 state cell (the 30x23 size is part of the fixed
|
||||
-- visual layout). Carries one texture region (check/cross glyph OR trinket
|
||||
-- item icon) and one FontString (?, skip dot, trinket monogram); the
|
||||
-- matrix paint code shows exactly one of them per state.
|
||||
-- visual layout). The box (background + 1px border) is one texture region
|
||||
-- sampled from the baked sheet in ui/cellsheet.lua -- SetBackdrop with an
|
||||
-- edgeFile costs NINE texture regions per cell, this costs one. The icon
|
||||
-- (check/cross glyph OR trinket item icon) and FontString (?, skip dot,
|
||||
-- trinket monogram) are created on demand via W.CellIcon / W.CellText;
|
||||
-- the matrix paint code shows exactly one of them per state.
|
||||
-- Pool children stay anonymous (documented factory exception).
|
||||
-- ------------------------------------------------------------------
|
||||
function W.MakeCell(parent, name, w, h)
|
||||
local c = CreateFrame("Frame", name, parent)
|
||||
c:SetWidth(w or 30)
|
||||
c:SetHeight(h or 23)
|
||||
c:SetBackdrop(W.SOLID_BACKDROP)
|
||||
c:EnableMouse(true)
|
||||
c.icon = c:CreateTexture(nil, "ARTWORK")
|
||||
c.icon:SetPoint("CENTER", c, "CENTER", 0, 0)
|
||||
c.icon:SetWidth(16)
|
||||
c.icon:SetHeight(16)
|
||||
c.icon:Hide()
|
||||
c.text = c:CreateFontString(nil, "OVERLAY")
|
||||
W.ApplyFont(c.text, 9)
|
||||
c.text:SetPoint("CENTER", c, "CENTER", 0, 0)
|
||||
c.dcW = w or 30
|
||||
if W.HasCellSheet() then
|
||||
-- The state box (background + 1px border) comes from one baked sheet:
|
||||
-- SetBackdrop with an edgeFile costs NINE texture regions per cell,
|
||||
-- this costs one. See ui/cellsheet.lua.
|
||||
c.boxTex = c:CreateTexture(nil, "BACKGROUND")
|
||||
c.boxTex:SetAllPoints(c)
|
||||
c.boxTex:SetTexture(DC_Cells.SHEET.path)
|
||||
-- Without an initial texcoord the region would show the WHOLE sheet
|
||||
-- stretched across the cell until the first paint. Start on NEUTRAL.
|
||||
local l, r, t, b = DC_Cells.Coords("NEUTRAL", c.dcW)
|
||||
if l then
|
||||
c.boxTex:SetTexCoord(l, r, t, b)
|
||||
c.dcBoxVariant = "NEUTRAL"
|
||||
c.dcBoxW = c.dcW
|
||||
end
|
||||
else
|
||||
-- Fallback: ui/cellsheet.lua did not load this session (see
|
||||
-- W.HasCellSheet above) -- pre-refactor SetBackdrop path (no
|
||||
-- boxTex region at all) so the matrix still draws instead of
|
||||
-- erroring on a nil DC_Cells.SHEET. W.SetBackdropG and
|
||||
-- W.SOLID_BACKDROP are untouched by the sprite refactor, so this
|
||||
-- is exactly what W.MakeCell did before it.
|
||||
c:SetBackdrop(W.SOLID_BACKDROP)
|
||||
W.SetCellBox(c, "NEUTRAL")
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
-- Icon and FontString on demand: a cell shows one or the other, and in 1.12
|
||||
-- eagerly created regions cost even while hidden.
|
||||
function W.CellIcon(cell)
|
||||
if not cell.icon then
|
||||
cell.icon = cell:CreateTexture(nil, "ARTWORK")
|
||||
cell.icon:SetPoint("CENTER", cell, "CENTER", 0, 0)
|
||||
cell.icon:SetWidth(16)
|
||||
cell.icon:SetHeight(16)
|
||||
cell.icon:Hide()
|
||||
end
|
||||
return cell.icon
|
||||
end
|
||||
|
||||
function W.CellText(cell)
|
||||
if not cell.text then
|
||||
cell.text = cell:CreateFontString(nil, "OVERLAY")
|
||||
W.ApplyFont(cell.text, 9)
|
||||
cell.text:SetPoint("CENTER", cell, "CENTER", 0, 0)
|
||||
end
|
||||
return cell.text
|
||||
end
|
||||
|
||||
-- Guards on variant AND width (cell.dcBoxW, the width the box was last
|
||||
-- painted at) -- NOT variant alone. ui/matrix.lua resizes a pooled cell in
|
||||
-- place when its column width changes (trinket columns are 34, normal
|
||||
-- cells 30: see ui/matrix.lua's column-layout path around SetWidth(col.w))
|
||||
-- and only ever writes cell.dcW; it has no notion of dcBoxVariant and isn't
|
||||
-- expected to invalidate it. If this guard only checked variant, a cell
|
||||
-- that kept its variant across such a resize would keep a stale texcoord
|
||||
-- sized for the OLD width -- the baked border would sit off the new cell
|
||||
-- edge. Tracking dcBoxW here keeps the guard self-contained: correctness
|
||||
-- does not depend on any caller convention.
|
||||
function W.SetCellBox(cell, variant)
|
||||
if not W.HasCellSheet() then
|
||||
-- Fallback: same pre-refactor SetBackdrop recipe, colors pulled
|
||||
-- LIVE from DC.COLORS/DC.QUALITY via W.CELL_FALLBACK_VARIANTS
|
||||
-- above. Never touches cell.boxTex -- a fallback cell (see
|
||||
-- W.MakeCell) does not have one. W.SetBackdropG already
|
||||
-- change-guards by table reference, so no separate
|
||||
-- dcBoxVariant/dcBoxW bookkeeping is needed here.
|
||||
local v = W.CELL_FALLBACK_VARIANTS[variant]
|
||||
if not v then
|
||||
return
|
||||
end
|
||||
local bg = DC.COLORS[v.bg]
|
||||
local border = v.border
|
||||
if type(border) == "number" then
|
||||
border = DC.QUALITY[border]
|
||||
else
|
||||
border = DC.COLORS[border]
|
||||
end
|
||||
if not bg or not border then
|
||||
return
|
||||
end
|
||||
W.SetBackdropG(cell, bg, border)
|
||||
return
|
||||
end
|
||||
if cell.dcBoxVariant == variant and cell.dcBoxW == cell.dcW then
|
||||
return
|
||||
end
|
||||
local l, r, t, b = DC_Cells.Coords(variant, cell.dcW)
|
||||
if not l then
|
||||
return
|
||||
end
|
||||
cell.dcBoxVariant = variant
|
||||
cell.dcBoxW = cell.dcW
|
||||
cell.boxTex:SetTexCoord(l, r, t, b)
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Pill factory (ready pill, coverage pill): bordered mini frame with a
|
||||
-- centered FontString; width is adjusted by the caller to fit the text.
|
||||
@@ -247,6 +418,21 @@ function W.TipDouble(left, right, c, cr)
|
||||
GameTooltip:AddDoubleLine(left, right, r, g, b, r2, g2, b2)
|
||||
end
|
||||
|
||||
-- W.dcTipOwner: our own record of which of OUR frames currently owns
|
||||
-- GameTooltip. WoW 1.12's GameTooltip has no "GetOwner" method (confirmed
|
||||
-- against FrameXML-1.12.1: zero real hits -- the only "GetOwner" match
|
||||
-- anywhere in that tree is the unrelated global function
|
||||
-- GetOwnerAuctionItems). Every caller in this addon that ever needs "who
|
||||
-- currently owns the tooltip" goes through W.AttachTooltip to open one in
|
||||
-- the first place, so this is the single place that needs to record it.
|
||||
-- Set right after SetOwner
|
||||
-- (matching order: SetOwner, then the builder runs, then Show -- a builder
|
||||
-- that itself re-SetOwner()s the SAME frame, like M.CellTooltip's
|
||||
-- ANCHOR_LEFT override, does not change who owns it). Cleared in OnLeave,
|
||||
-- guarded on identity so a late/out-of-order OnLeave can never clobber a
|
||||
-- newer owner.
|
||||
W.dcTipOwner = nil
|
||||
|
||||
function W.AttachTooltip(frame, builder, anchor)
|
||||
frame.dcTipBuilder = builder
|
||||
frame.dcTipAnchor = anchor or "ANCHOR_LEFT"
|
||||
@@ -258,6 +444,7 @@ function W.AttachTooltip(frame, builder, anchor)
|
||||
end
|
||||
if GameTooltip and this.dcTipBuilder then
|
||||
GameTooltip:SetOwner(this, this.dcTipAnchor)
|
||||
W.dcTipOwner = this
|
||||
this.dcTipBuilder(this)
|
||||
GameTooltip:Show()
|
||||
end
|
||||
@@ -266,6 +453,9 @@ function W.AttachTooltip(frame, builder, anchor)
|
||||
if this.dcOldLeave then
|
||||
this.dcOldLeave()
|
||||
end
|
||||
if W.dcTipOwner == this then
|
||||
W.dcTipOwner = nil
|
||||
end
|
||||
if GameTooltip then
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user