DopingControl v0.6.4
This commit is contained in:
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user