1274 lines
67 KiB
Lua
1274 lines
67 KiB
Lua
-- DopingControl core/sim.lua
|
|
-- Test mode: deterministic simulated raid.
|
|
-- DC_Sim.buildStore() returns a store in EXACTLY the shape the real scan
|
|
-- produces (source="sim") -- model, aggregation and
|
|
-- rendering run unchanged on it, so test mode exercises the real pipeline.
|
|
--
|
|
-- Fixed synthetic roster: 40 players (a FULL raid -- this server's raid
|
|
-- cap is 40, eight subgroups of five), all 9 classes, role distribution
|
|
-- TANK 4 / MELEE 11 / RANGED 6 / CASTER 11 / HEALER 8. No randomness --
|
|
-- every run looks the same, screenshots are comparable. All names start
|
|
-- with "Sim" so no synthetic name can be mistaken for (or whispered to) a
|
|
-- real player; chat outputs are dead in test mode anyway. Every player
|
|
-- also carries subgroup 1..8 (five each, real raid-group shape) so the
|
|
-- full-raid layout -- scroll behavior, group headers, column widths, the
|
|
-- render cost of forty rows -- is exercised at MAXIMUM occupancy.
|
|
--
|
|
-- Coverage built into the roster (every feature is showcased):
|
|
-- * every cell state in every tab (HAS / MISSING / UNKNOWN / NOTEXP)
|
|
-- * TANK group all readable with 0 gaps in all three tabs (green pill)
|
|
-- * exactly 3 offline + 2 read-failed players (5 of 40 unreadable); the
|
|
-- read-failed carry distance values (87 / 203) as diagnosis. All 5
|
|
-- unreadable players share subgroup 8 -- a raid subgroup that is
|
|
-- ENTIRELY unreadable, the opposite pill extreme from the all-green
|
|
-- TANK group above (both a fully-clean and a fully-blank group must
|
|
-- render correctly).
|
|
-- * 2 unconfirmed roles (Simfury, Simshade -> suggestion badges)
|
|
-- * trinkets: epic (quality 4), rare (quality 3), one EMPTY slot 14
|
|
-- * enchant gaps (enchant==0), EMPTY off hand (fury warrior), wand
|
|
-- wearer (ranged enchant NOTEXP), holdable off hand (NOTEXP)
|
|
-- * HAS via spell-ID match for some players (names empty) and via
|
|
-- name match for others (ids empty) -- both model paths are exercised
|
|
-- * whisper-eligible rows (readable + gaps) in every tab
|
|
-- * FULL icon coverage: every HAS-producing
|
|
-- aura name has a real-looking vanilla icon path in auras.textures
|
|
-- (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
|
|
-- Recklessness" + "Weakened Soul" (two columns from ONE player) =>
|
|
-- 3 columns total, count-desc then alpha: RS, CoR, WS. Every other
|
|
-- READABLE player gets an EMPTY debuffs table (legitimate clean state
|
|
-- = HAS cells); the 5 unreadable players keep debuffs nil (UNKNOWN).
|
|
-- * far flag: 2 READABLE players beyond DC.FAR_LIMIT=40
|
|
-- (Simhuntara 55, Simfrost 120) => coverage.far == 2; Simbow keeps
|
|
-- distance 31 (near), the read-failed distances 87/203 stay pure
|
|
-- diagnosis and never count as far.
|
|
-- * weapon enchant names: two players carry
|
|
-- weaponMainName -- Simtank "Dense Sharpening Stone", Simmage
|
|
-- "Brilliant Wizard Oil" (WPN HAS detail = the name); Simwall and
|
|
-- every one of the 40-man fill-in players below stay weaponMain=true
|
|
-- WITHOUT a name (generic detail path) -- exactly two named enchants
|
|
-- in the whole roster, deliberately.
|
|
-- * last-known showcase: Simblade (offline) carries
|
|
-- lastAuras + lastGear (At = now-540) so all three aura/equipment
|
|
-- tabs show dimmed last-known cells (HAS and MISSING both reachable;
|
|
-- the cached EMPTY off hand classifies NOTEXP => that cell stays a
|
|
-- plain "?"). Simhuntara is THE cross-continent case: online and
|
|
-- aura-readable but gearRead=false + gear nil, with lastGear
|
|
-- (At = now-1200) -- EQUIPMENT renders the "out of range" row note
|
|
-- plus dimmed tiles. IRON RULE: all of it is display-only, the cells
|
|
-- stay UNKNOWN in every count.
|
|
-- * deterministic clock: all At offsets and scanAt hang
|
|
-- off ONE base `now` -- GetTime() when the client provides it (test
|
|
-- mode shows plausible live ages), a FIXED 1000 offline so buildStore
|
|
-- stays byte-deterministic under the test harness.
|
|
-- * RESIST tab: every readable player carries resistsRead=true + a
|
|
-- resists table -- the 3 tanks fire 315/240/180 (a visible spread),
|
|
-- Simhuntara nature 120 (resist reads work even with gearRead=false --
|
|
-- an independent channel), Simmage frost 85 + arcane 45 (the two-
|
|
-- school showcase), everyone else a small deterministic per-school
|
|
-- formula (0s occur -- a legitimate resistance, distinct from the 4
|
|
-- unreadable players who carry no resist data at all -> UNKNOWN
|
|
-- cells). Simtank also carries a real NECK enchant (>0) -- neck/ring
|
|
-- enchants are a server-custom feature (data/enchants.lua) whose
|
|
-- expectation defaults to OFF, so this doubles as the "not expected,
|
|
-- item still shown" showcase.
|
|
-- * HIT tab (its own tab, eight columns: melee, ranged and one per
|
|
-- magic school): every showcase of the feature is in the roster --
|
|
-- MEASURED skill Simtank (skillExact 305 => +1.0% hit on the
|
|
-- player's side: melee 8 gear -> 9.0 against the flat 8.0 yellow
|
|
-- cap), ESTIMATED racial skill Simwall/Simsteel/Simrogue
|
|
-- (human/orc/gnome specializations), BOOK candidate Simsteel (ticking
|
|
-- "Axes" in the options adds +3 skill, worth +1.6 on his value, on
|
|
-- the next refresh -- the cap stays flat at 8.0 because skill counts
|
|
-- on the player's side, and the flag lives in the DB, never in this
|
|
-- store), AT CAP
|
|
-- Simtank/Simsteel/Simbow (melee + ranged), BELOW CAP Simwall (6)
|
|
-- and Simfrost (spell 8 of 16), DUAL WIELD Simrogue (dagger + sword:
|
|
-- yellow cap 8.0 met by 10, white cap 27.0), NO RANGED WEAPON
|
|
-- Simfrost (empty ranged slot) and every wand user (wands are a
|
|
-- spell-hit question) => NOTEXP cells, UNREADABLE Simhuntara
|
|
-- (hit = { hitRead = false } -- the item tooltips of an out-of-range
|
|
-- unit cannot be scanned, exactly like her gear) plus the offline/
|
|
-- read-failed players, who carry no hit table at all.
|
|
-- * PER-SCHOOL spread, the reason the tab exists: Simmage is a frost
|
|
-- mage with Arcane Focus 5/5 and Elemental Precision 3/3, so her
|
|
-- eight columns are NOT one number -- arcane 20.0 (10 generic + 10
|
|
-- talent), fire and frost 16.0 (10 + 6), holy/nature/shadow 10.0.
|
|
-- Simlock carries the GEAR side of the same idea: a +3% shadow item
|
|
-- line, so his shadow column reads 3 above his other schools.
|
|
-- * TALENT READABILITY: a foreign player's talents arrive over the
|
|
-- inspect protocol (scan/talents.lua) and may simply not be in yet,
|
|
-- which is why hit.talentsRead exists and why a tooltip without them
|
|
-- says the number excludes talents for now. Test mode deliberately
|
|
-- marks a HANDFUL of players talent-readable (Simtank, Simrogue,
|
|
-- Simmage) and leaves the rest false, so both tooltip states are
|
|
-- visible side by side in one screenshot -- it is simulated data, not
|
|
-- a claim that foreign talents are readable. Those three carry a
|
|
-- MEASURED weapon skill as well, because live both facts have the
|
|
-- same single source: being the own character.
|
|
-- Everyone else gets a deterministic filler pass (per-class main
|
|
-- weapon, ranged subtype from their equipped ranged item, small
|
|
-- per-index numbers, no race => plain base skill, talentsRead false).
|
|
-- All of it is display-only: a hit cell is never a gap, so pills stay
|
|
-- untouched.
|
|
-- * FULL-RAID FILL-IN (the fifteen players that take the roster from the
|
|
-- original 25-player sample to the 40-man cap, one per open role
|
|
-- slot): each is deliberately "clean" -- full consumables/class buffs
|
|
-- via one canonical name list (unexpected slots simply ignore names
|
|
-- they do not need, never a gap), default gearWith() (every enchant
|
|
-- slot filled, both trinkets present), weaponMain=true without a
|
|
-- name. They ride the SAME index-based filler passes below as every
|
|
-- other filler player in the original 25 for resist/hit/debuffs, so
|
|
-- their exact numbers are deliberately UNPINNED (only their existence
|
|
-- and readability matter) -- every pinned showcase value above still
|
|
-- lives on the unchanged original 25. Two of them (Simarrow,
|
|
-- Simscout) carry a real ranged weapon so the RANGED group's HIT tab
|
|
-- is not entirely NOTEXP; Simveil is the 3rd offline case. Column-
|
|
-- width stress: Simox is the shortest name in the roster (5
|
|
-- characters), Simthornweaver and Simshadowmender the longest
|
|
-- (14 / 15).
|
|
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
|
|
|
DopingControl = DopingControl or {}
|
|
local DC = DopingControl
|
|
|
|
DC_Sim = DC_Sim or {}
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- Built-in role confirmations. Everyone EXCEPT Simfury and Simshade --
|
|
-- those two stay unconfirmed so the suggestion flow (dashed badge, ?-mark,
|
|
-- reason tooltip) is visible in test mode. Copied into DC.simRoles by
|
|
-- buildStore(); NEVER into db.roles.
|
|
-- Simsham is a confirmed MELEE (enhancement) although the class heuristic
|
|
-- suggests HEALER -- showcases confirmed-beats-suggested.
|
|
-- ------------------------------------------------------------------
|
|
DC_Sim.ROLES = {
|
|
Simtank = "TANK", Simwall = "TANK", Simsteel = "TANK",
|
|
Simrogue = "MELEE", Simblade = "MELEE", Simstab = "MELEE",
|
|
Simkitty = "MELEE", Simsham = "MELEE",
|
|
Simhuntara = "RANGED", Simbow = "RANGED", Simtrue = "RANGED",
|
|
Simmage = "CASTER", Simfrost = "CASTER", Simzap = "CASTER",
|
|
Simlock = "CASTER", Simafflic = "CASTER", Simboom = "CASTER",
|
|
Simheala = "HEALER", Simlight = "HEALER", Simtree = "HEALER",
|
|
Simchain = "HEALER", Simholy = "HEALER", Simglow = "HEALER",
|
|
-- Simfury, Simshade intentionally absent -> suggestion badges
|
|
|
|
-- Full-raid fill-in (25 -> 40): one open role slot each, all
|
|
-- confirmed (no additional suggestion badges beyond the two above).
|
|
Simblock = "TANK",
|
|
Simgrit = "MELEE", Simslash = "MELEE", Simox = "MELEE",
|
|
Simjolt = "MELEE", Simzeal = "MELEE",
|
|
Simarrow = "RANGED", Simscout = "RANGED", Simveil = "RANGED",
|
|
Simember = "CASTER", Simdread = "CASTER", Simvoid = "CASTER",
|
|
Simthornweaver = "CASTER",
|
|
Simshadowmender = "HEALER", Simbloom = "HEALER",
|
|
}
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- Small builders (fresh tables on every call -- the store must never share
|
|
-- table references between builds or players).
|
|
-- ------------------------------------------------------------------
|
|
|
|
-- list -> set
|
|
local function set(list)
|
|
local t = {}
|
|
for i = 1, table.getn(list) do
|
|
t[list[i]] = true
|
|
end
|
|
return t
|
|
end
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- Canonical "clean" aura set for the 40-man fill-in players (see the
|
|
-- header): one name per consumable/class-buff slot, always paired with
|
|
-- a working ICONS entry (icon coverage stays satisfied). A slot the
|
|
-- player's role/class does not expect simply ignores its name -- an
|
|
-- unexpected slot is NOTEXP regardless of what auras are present -- so
|
|
-- 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", "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",
|
|
"Prayer of Shadow Protection", "Arcane Intellect",
|
|
"Soulstone Resurrection",
|
|
}
|
|
|
|
local function cleanAuras()
|
|
return { names = set(CLEAN_AURA_NAMES), ids = {} }
|
|
end
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- Aura icons: plausible vanilla icon paths for every
|
|
-- buff name the sim can turn into a HAS cell. Cosmetic sim data -- paths
|
|
-- only have to LOOK real, the client never loads them offline.
|
|
-- ------------------------------------------------------------------
|
|
local ICONS = {
|
|
-- FLASK
|
|
["Flask of the Titans"] = "Interface\\Icons\\INV_Potion_62",
|
|
["Flask of Supreme Power"] = "Interface\\Icons\\INV_Potion_41",
|
|
["Flask of Distilled Wisdom"] = "Interface\\Icons\\INV_Potion_97",
|
|
["Flask of Chromatic Resistance"] = "Interface\\Icons\\INV_Potion_48",
|
|
-- FOOD
|
|
["Well Fed"] = "Interface\\Icons\\Spell_Misc_Food",
|
|
["Dragonbreath Chili"] = "Interface\\Icons\\INV_Drink_17",
|
|
-- AP
|
|
["Winterfall Firewater"] = "Interface\\Icons\\INV_Potion_92",
|
|
["Juju Might"] = "Interface\\Icons\\INV_Misc_MonsterScales_07",
|
|
-- STR (aura-name variants share the item icon, as for Mageblood below)
|
|
["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",
|
|
-- 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",
|
|
-- 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",
|
|
-- 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",
|
|
["Mark of the Wild"] = "Interface\\Icons\\Spell_Nature_Regeneration",
|
|
["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",
|
|
}
|
|
|
|
-- Debuff cosmetics. Resurrection Sickness and Weakened Soul genuinely
|
|
-- share one icon on 1.12 (both use
|
|
-- Spell_Holy_AshesToAshes -- realism over showcase variety).
|
|
local DEBUFF_ICONS = {
|
|
["Resurrection Sickness"] = "Interface\\Icons\\Spell_Holy_AshesToAshes",
|
|
["Curse of Recklessness"] = "Interface\\Icons\\Spell_Shadow_UnholyStrength",
|
|
["Weakened Soul"] = "Interface\\Icons\\Spell_Holy_AshesToAshes",
|
|
}
|
|
|
|
-- debuff literal builder: names + ids given, textures from DEBUFF_ICONS
|
|
-- (fresh tables per call -- store builds must never share references)
|
|
local function debuffsOf(namesList, idsList)
|
|
local d = { names = {}, ids = {}, textures = {} }
|
|
for i = 1, table.getn(namesList) do
|
|
local nm = namesList[i]
|
|
d.names[nm] = true
|
|
if DEBUFF_ICONS[nm] then
|
|
d.textures[nm] = DEBUFF_ICONS[nm]
|
|
end
|
|
end
|
|
for i = 1, table.getn(idsList) do
|
|
d.ids[idsList[i]] = true
|
|
end
|
|
return d
|
|
end
|
|
|
|
-- auras -> textures table (store shape: auras.textures[name] = icon path).
|
|
-- Names are taken directly; ids are resolved against the REAL data tables
|
|
-- (every table name that carries one of the player's ids gets its icon),
|
|
-- so whatever alias DC_Model's id->name index picks, the texture is there.
|
|
local function texturesFor(auras)
|
|
local t = {}
|
|
for name in pairs(auras.names) do
|
|
if ICONS[name] then
|
|
t[name] = ICONS[name]
|
|
end
|
|
end
|
|
if DC.CONSUMABLES then
|
|
for name, def in pairs(DC.CONSUMABLES) do
|
|
if def.spellID and def.spellID > 0 and auras.ids[def.spellID]
|
|
and ICONS[name] then
|
|
t[name] = ICONS[name]
|
|
end
|
|
end
|
|
end
|
|
if DC.CLASSBUFFS then
|
|
for name, def in pairs(DC.CLASSBUFFS) do
|
|
if def.ids then
|
|
for i = 1, table.getn(def.ids) do
|
|
if auras.ids[def.ids[i]] and ICONS[name] then
|
|
t[name] = ICONS[name]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
-- Main-hand item subtype per class for the hit filler pass below (the
|
|
-- weapon skill the melee-hit cap is derived from). Plausible vanilla
|
|
-- choices, nothing more -- the showcase players carry explicit literals.
|
|
local HIT_MAIN_SUB = {
|
|
WARRIOR = "One-Handed Swords",
|
|
PALADIN = "Two-Handed Maces",
|
|
HUNTER = "Polearms",
|
|
ROGUE = "Daggers",
|
|
PRIEST = "Staves",
|
|
SHAMAN = "One-Handed Maces",
|
|
MAGE = "Staves",
|
|
WARLOCK = "Staves",
|
|
DRUID = "Staves",
|
|
}
|
|
|
|
-- 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).
|
|
local SLOT_BASE = {
|
|
[1] = { name = "Simulated Faceguard", quality = 3, texture = "Interface\\Icons\\INV_Helmet_09" },
|
|
[2] = { name = "Simulated Choker", quality = 3, texture = "Interface\\Icons\\INV_Jewelry_Necklace_07" },
|
|
[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" },
|
|
[8] = { name = "Simulated Sabatons", quality = 3, texture = "Interface\\Icons\\INV_Boots_Plate_06" },
|
|
[16] = { name = "Simulated Edge", quality = 4, texture = "Interface\\Icons\\INV_Sword_39" },
|
|
[17] = { name = "Simulated Barrier", quality = 3, texture = "Interface\\Icons\\INV_Shield_06" },
|
|
[18] = { name = "Simulated Repeater", quality = 2, texture = "Interface\\Icons\\INV_Weapon_Crossbow_02" },
|
|
[11] = { name = "Simulated Signet", quality = 3, texture = "Interface\\Icons\\INV_Jewelry_Ring_21" },
|
|
[12] = { name = "Simulated Band", quality = 2, texture = "Interface\\Icons\\INV_Jewelry_Ring_14" },
|
|
[13] = { name = "Simulated Charm", quality = 2, texture = "Interface\\Icons\\INV_Misc_OrnateBox" },
|
|
[14] = { name = "Simulated Insignia", quality = 3, texture = "Interface\\Icons\\INV_Jewelry_Talisman_07" },
|
|
}
|
|
|
|
-- Base gear: every slot filled and enchanted (enchant 900+invSlot except
|
|
-- neck/rings/trinkets), every entry with name/quality/texture from
|
|
-- 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
|
|
-- (their expectation defaults to OFF, so by default this renders the new
|
|
-- "not expected, item still shown" tile -- see core/model.lua).
|
|
local function gearWith(over)
|
|
local g = {}
|
|
for i = 1, table.getn(INV_SLOTS) do
|
|
local inv = INV_SLOTS[i]
|
|
local base = SLOT_BASE[inv]
|
|
local e = { id = 5000 + inv, enchant = 900 + inv,
|
|
quality = base.quality, name = base.name,
|
|
texture = base.texture }
|
|
if inv == 2 or inv == 11 or inv == 12 or inv == 13 or inv == 14 then
|
|
e.enchant = 0 -- neck/rings/trinkets: enchant-free baseline
|
|
end
|
|
g[inv] = e
|
|
end
|
|
if over then
|
|
for inv, v in pairs(over) do
|
|
if type(v) == "table" then
|
|
local base = SLOT_BASE[inv]
|
|
if v.name == nil then
|
|
v.name = base.name
|
|
end
|
|
if v.quality == nil then
|
|
v.quality = base.quality
|
|
end
|
|
if v.texture == nil then
|
|
v.texture = base.texture
|
|
end
|
|
end
|
|
g[inv] = v
|
|
end
|
|
end
|
|
return g
|
|
end
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- The roster. Deterministic literals only.
|
|
-- ------------------------------------------------------------------
|
|
function DC_Sim.buildStore()
|
|
-- deterministic clock (see header): base for scanAt
|
|
-- and the last-known At offsets below
|
|
local now = 1000
|
|
if type(GetTime) == "function" then
|
|
now = GetTime()
|
|
end
|
|
|
|
-- reset transient sim confirmations to the built-in set (fresh copy)
|
|
DC.simRoles = {}
|
|
for name, role in pairs(DC_Sim.ROLES) do
|
|
DC.simRoles[name] = role
|
|
end
|
|
|
|
local players = {}
|
|
local function add(p)
|
|
table.insert(players, p)
|
|
end
|
|
|
|
-- ================= TANKS (3) -- all readable, 0 gaps in every tab ====
|
|
|
|
-- Simtank: spell-ID-only auras (names empty) -> proves the ID match
|
|
-- path alone. Thrown weapon -> ranged enchant NOTEXP. Epic + rare
|
|
-- trinkets (WoW quality: 4 = epic, 3 = rare). Neck enchant demo (D6):
|
|
-- a real neck enchant>0 -- NECK's expectation defaults to OFF, so this
|
|
-- shows the "not expected, item still shown, bright, no marker" tile
|
|
-- by default; enabling the checkbox for TANK/WARRIOR would flip it to
|
|
-- HAS with a green corner marker (same data, no sim change needed).
|
|
-- Fire resistance 315 -- highest of the three tanks (showcase spread).
|
|
add{ name = "Simtank", class = "WARRIOR", online = true,
|
|
guid = "0xF530000000000001", subgroup = 1,
|
|
aurasRead = true,
|
|
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 },
|
|
-- hit: the MEASURED-skill showcase (what the own character looks
|
|
-- like) -- skillExact means the 305 came from the skill window,
|
|
-- which lowers the yellow cap to 7.0; melee 8 sits above it.
|
|
-- talentsRead with NO talent numbers is its own state and a real
|
|
-- one: a protection warrior has no hit talent at all, so this is
|
|
-- a VERIFIED zero rather than the "not received yet" case below.
|
|
race = "Human",
|
|
hit = { melee = 8, spell = 0, schools = {}, hitRead = true,
|
|
mainSub = "One-Handed Maces", offSub = "Shields",
|
|
rangedSub = "Thrown", skillName = "Maces",
|
|
skill = 305, skillExact = true, talentsRead = true },
|
|
gear = gearWith({
|
|
[17] = { id = 5117, enchant = 917, subType = "Shields", quality = 3 },
|
|
[18] = { id = 5118, enchant = 0, subType = "Thrown",
|
|
name = "Simulated Throwing Axe" },
|
|
[2] = { id = 5299, enchant = 950, name = "Simulated Neck Chain" },
|
|
[13] = { id = 5213, enchant = 0, quality = 4, name = "Simulated Drake Talisman",
|
|
texture = "Interface\\Icons\\INV_Misc_Bone_10" },
|
|
[14] = { id = 5214, enchant = 0, quality = 3, name = "Simulated Hand of Justice",
|
|
texture = "Interface\\Icons\\INV_Jewelry_Talisman_01" },
|
|
}) }
|
|
|
|
-- Simwall: name-only auras (ids empty = sparse) -> proves the name
|
|
-- fallback path alone. Gun with scope (enchant>0) -> ranged HAS.
|
|
-- Deliberately weaponMain=true WITHOUT weaponMainName (so
|
|
-- the generic "main hand temp enchant" detail path stays exercised).
|
|
-- Fire resistance 240.
|
|
add{ name = "Simwall", class = "WARRIOR", online = true,
|
|
guid = "0xF530000000000002", subgroup = 1,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Flask of the Titans", "Well Fed",
|
|
"Winterfall Firewater", "Juju Power", "Elixir of the Mongoose",
|
|
"R.O.I.D.S.", "Spirit of Zanza",
|
|
"Elixir of Superior Defense", "Elixir of Fortitude",
|
|
"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
|
|
-- specialization is ESTIMATED into the skill (300 + 5 => cap 7.0)
|
|
-- and 6 still falls short of it
|
|
race = "Human",
|
|
hit = { melee = 6, spell = 0, schools = {}, hitRead = true,
|
|
talentsRead = false,
|
|
mainSub = "One-Handed Swords", offSub = "Shields",
|
|
rangedSub = "Guns" },
|
|
gear = gearWith({
|
|
[17] = { id = 5127, enchant = 917, subType = "Shields", quality = 3 },
|
|
[18] = { id = 5128, enchant = 33, subType = "Guns",
|
|
name = "Simulated Blunderbuss" },
|
|
}) }
|
|
|
|
-- Simsteel: mixed matching -- ids for everything with a known spell ID,
|
|
-- "Well Fed" (id-less entry) by name. NOTE the model's strict rule: once
|
|
-- a player has ANY ids, names of id-bearing entries no longer count, so
|
|
-- a "mixed" player may only name-match id-less entries. Bow with scope.
|
|
-- Fire resistance 180 -- lowest of the three tanks.
|
|
add{ name = "Simsteel", class = "WARRIOR", online = true,
|
|
guid = "0xF530000000000003", subgroup = 1,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed" }),
|
|
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);
|
|
-- ticking "Axes" for this player in the options adds another +3
|
|
-- skill and drops the cap to 6.4 on the very next refresh (the
|
|
-- book flag lives in the DB, never in the store)
|
|
race = "Orc",
|
|
hit = { melee = 8, spell = 0, schools = {}, hitRead = true,
|
|
talentsRead = false,
|
|
mainSub = "One-Handed Axes", offSub = "Shields",
|
|
rangedSub = "Bows" },
|
|
gear = gearWith({
|
|
[17] = { id = 5137, enchant = 917, subType = "Shields", quality = 3 },
|
|
[18] = { id = 5138, enchant = 33, subType = "Bows",
|
|
name = "Simulated Longbow" },
|
|
}) }
|
|
|
|
-- ================= MELEE (6) =========================================
|
|
|
|
-- Simfury: UNCONFIRMED -> heuristic suggests MELEE ("class default";
|
|
-- no shield). EMPTY off hand (fury with a 2H habit) -> NOTEXP under the
|
|
-- model's exemption resolution (no item on an exemptable slot = nothing
|
|
-- to enchant). Missing HP + FR consumables, missing PWF + SPROT class
|
|
-- buffs, head enchant missing -> whisper-eligible in every tab.
|
|
add{ name = "Simfury", class = "WARRIOR", online = true,
|
|
guid = "0xF530000000000004", subgroup = 1,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed", "Juju Might", "Juju Power",
|
|
"Elixir of the Mongoose", "Mark of the Wild" }), ids = {} },
|
|
weaponMain = true, gearRead = true,
|
|
gear = gearWith({
|
|
[1] = { id = 5141, enchant = 0 }, -- head enchant missing
|
|
[17] = "EMPTY", -- empty off hand -> gap
|
|
[18] = { id = 5148, enchant = 0, subType = "Thrown" },
|
|
}) }
|
|
|
|
-- Simrogue: ID matches for everything id-bearing, "Well Fed" by name.
|
|
-- EMPTY trinket slot 14 (the one empty trinket of the roster).
|
|
-- FR + SPROT missing. Afflicted 1/3: Resurrection Sickness (shared
|
|
-- with Simtree -> RS afflicted=2 = first debuff column).
|
|
add{ name = "Simrogue", class = "ROGUE", online = true,
|
|
guid = "0xF530000000000005", subgroup = 2,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed" }),
|
|
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
|
|
-- carries the yellow cap (gnome dagger racial => 7.0, met by 9)
|
|
-- AND the white dual-wield cap (26.0) in its tooltip.
|
|
-- Also the TALENT showcase on the melee side: the 9 is 4 from
|
|
-- gear plus 5 from the rogue's 5/5 melee-hit talent, and the
|
|
-- tooltip names both parts instead of one lump sum.
|
|
race = "Gnome",
|
|
hit = { melee = 9, gearMelee = 4, talentMelee = 5,
|
|
talentsRead = true,
|
|
-- measured skill goes WITH the read talents: both come
|
|
-- from the own character in the live client, so a
|
|
-- talent-readable sim player carries a measured skill
|
|
-- too (305 is what the gnome dagger racial estimates
|
|
-- anyway, so the cap is the same either way)
|
|
skill = 305, skillExact = true, skillName = "Daggers",
|
|
spell = 0, schools = {}, hitRead = true,
|
|
mainSub = "Daggers", offSub = "One-Handed Swords",
|
|
rangedSub = "Thrown" },
|
|
gear = gearWith({
|
|
[18] = { id = 5158, enchant = 0, subType = "Thrown" },
|
|
[12] = "EMPTY", -- empty ring 2 -> gap
|
|
[14] = "EMPTY", -- empty trinket -> gap
|
|
}) }
|
|
|
|
-- Simblade: OFFLINE (auras/gear nil, weaponMain nil, no distance --
|
|
-- a disconnected unit cannot be measured). Carries the
|
|
-- runtime cache's LAST-KNOWN snapshot from before the disconnect --
|
|
-- lastAuras (HP/FR consumables + SPROT class buff absent => dimmed
|
|
-- MISSING reachable, the rest dimmed HAS) and lastGear (wrist
|
|
-- enchant 0 => dimmed MISSING; EMPTY off hand => cached classify
|
|
-- NOTEXP => NO cell.last, the plain "?" stays). No lastWeaponMain:
|
|
-- the WPN cell shows what a channel WITHOUT cache looks like.
|
|
-- IRON RULE: display-only -- Simblade stays UNKNOWN in every count.
|
|
local bladeAuras = { names = set({ "Well Fed", "Juju Might",
|
|
"Juju Power", "Elixir of the Mongoose", "Mark of the Wild",
|
|
"Power Word: Fortitude" }), ids = {} }
|
|
bladeAuras.textures = texturesFor(bladeAuras)
|
|
add{ name = "Simblade", class = "ROGUE", online = false,
|
|
guid = "0xF530000000000006", subgroup = 8,
|
|
aurasRead = false, gearRead = false,
|
|
unreadableReason = "offline",
|
|
lastAuras = bladeAuras, lastAurasAt = now - 540,
|
|
lastGear = gearWith({
|
|
[9] = { id = 5162, enchant = 0 }, -- wrist: dimmed MISSING
|
|
[17] = "EMPTY", -- cached NOTEXP -> no .last
|
|
[18] = { id = 5163, enchant = 0, subType = "Thrown" },
|
|
}),
|
|
lastGearAt = now - 540 }
|
|
|
|
-- Simstab: name matching, only gap on consumables = no temp weapon
|
|
-- enchant; class buffs complete; two enchant gaps.
|
|
add{ name = "Simstab", class = "ROGUE", online = true,
|
|
guid = "0xF530000000000007", subgroup = 2,
|
|
aurasRead = true,
|
|
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
|
|
[7] = { id = 5167, enchant = 0 }, -- legs enchant missing
|
|
[18] = { id = 5168, enchant = 0, subType = "Thrown" },
|
|
}) }
|
|
|
|
-- Simkitty: druid in Cat Form (confirmed MELEE anyway). AP/AGI/MOTW via
|
|
-- spell ID, FOOD + form by name; STR/HP/FR/WPN missing; druid: EMPTY
|
|
-- ranged + EMPTY off hand (staff) -> NOTEXP (exemptable slots).
|
|
add{ name = "Simkitty", class = "DRUID", online = true,
|
|
guid = "0xF530000000000008", subgroup = 2,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Cat Form", "Well Fed" }),
|
|
ids = set({ 16329, 17538, 9885 }) },
|
|
weaponMain = false, gearRead = true,
|
|
gear = gearWith({
|
|
[17] = "EMPTY",
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- Simsham: enhancement -- CONFIRMED MELEE although the heuristic would
|
|
-- say HEALER. Full consumables/class buffs (ids + "Well Fed" by name);
|
|
-- shaman: EMPTY ranged slot -> NOTEXP.
|
|
-- Note (roles stay as-is, no
|
|
-- shipped default changes): a shaman is also the per-class
|
|
-- expectation poster child -- a TANK-confirmed shaman would get
|
|
-- expectations.TANK.SHAMAN.AI = true (INT for the mana-fed tank kit)
|
|
-- flipped by the user in the options grid; AI ships as the CASTER/HEALER
|
|
-- role default for every performing class.
|
|
add{ name = "Simsham", class = "SHAMAN", online = true,
|
|
guid = "0xF530000000000009", subgroup = 2,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed" }),
|
|
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 },
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- ================= RANGED (3) ========================================
|
|
|
|
-- Simhuntara: AP/AGI/MOTW via spell ID; HP/FR + PWF/SPROT missing.
|
|
-- Far case 1/2: readable at distance 55 (>= FAR_LIMIT 40).
|
|
-- THE cross-continent case -- the aura paths reach
|
|
-- anywhere, gear inspection does not: gearRead=false + gear nil on an
|
|
-- ONLINE, aura-readable player, with the runtime cache's lastGear
|
|
-- attached (a 20-minutes-old snapshot). EQUIPMENT renders the
|
|
-- "out of range" row note + dimmed tiles; nothing counts (IRON RULE).
|
|
-- Nature resistance 120 -- resist reads are their OWN channel,
|
|
-- independent of gearRead=false above (a resistance read is neither
|
|
-- an aura nor a gear read).
|
|
add{ name = "Simhuntara", class = "HUNTER", online = true,
|
|
guid = "0xF53000000000000A", subgroup = 4, distance = 55,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed" }),
|
|
ids = set({ 16329, 17538, 9885 }) },
|
|
weaponMain = true, gearRead = false,
|
|
resistsRead = true, resists = { [3] = 120 },
|
|
-- hit: THE unreadable showcase -- hit is summed from item
|
|
-- tooltips, so it dies with the same range limit as the gear read
|
|
-- (resistances survive it, they are their own channel)
|
|
hit = { hitRead = false },
|
|
lastGear = gearWith({
|
|
[18] = { id = 5178, enchant = 33, subType = "Guns",
|
|
name = "Simulated Rifle" },
|
|
}),
|
|
lastGearAt = now - 1200 }
|
|
|
|
-- Simbow: fully clean row (green contrast inside a non-green group).
|
|
add{ name = "Simbow", class = "HUNTER", online = true,
|
|
guid = "0xF53000000000000B", subgroup = 3, distance = 31,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed", "Juju Might",
|
|
"Elixir of Greater Agility", "Ground Scorpok Assay",
|
|
"Spirit of Zanza", "Rumsey Rum",
|
|
"Greater Fire Protection", "Mark of the Wild",
|
|
"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
|
|
-- guns, not bows, so nothing lowers this one)
|
|
race = "Dwarf",
|
|
hit = { melee = 8, spell = 0, schools = {}, hitRead = true,
|
|
talentsRead = false,
|
|
mainSub = "Polearms", rangedSub = "Bows" },
|
|
gear = gearWith({
|
|
[18] = { id = 5188, enchant = 33, subType = "Bows",
|
|
name = "Simulated Warbow" },
|
|
}) }
|
|
|
|
-- Simtrue: READ FAILED (online, but the aura path returned zero auras).
|
|
-- distance recorded as diagnosis, never as filter.
|
|
add{ name = "Simtrue", class = "HUNTER", online = true,
|
|
guid = "0xF53000000000000C", subgroup = 8, distance = 203,
|
|
aurasRead = false, gearRead = false,
|
|
unreadableReason = "read failed" }
|
|
|
|
-- ================= CASTERS (7) =======================================
|
|
|
|
-- Simmage: everything id-bearing via spell ID, FOOD by name; FR
|
|
-- missing. Wand -> ranged NOTEXP, holdable frill -> off hand NOTEXP.
|
|
-- Frost 85 + arcane 45 -- the two-school showcase (every other
|
|
-- resist-carrying player below has only one school set explicitly).
|
|
add{ name = "Simmage", class = "MAGE", online = true,
|
|
guid = "0xF53000000000000D", subgroup = 4,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed" }),
|
|
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 },
|
|
-- hit: THE per-school showcase and the reason the tab has eight
|
|
-- columns. 10% generic spell hit from gear, and a frost mage's
|
|
-- two school talents on top: Arcane Focus 5/5 (+10 arcane) and
|
|
-- Elemental Precision 3/3 (+6 frost AND fire). Her columns
|
|
-- therefore read arcane 20.0, fire 16.0, frost 16.0, everything
|
|
-- else 10.0 -- three different numbers on ONE caster, which no
|
|
-- single spell-hit column could ever show. The wand makes the
|
|
-- ranged column NOTEXP and the orb in the off hand is not a
|
|
-- weapon, so no dual-wield note either.
|
|
race = "Gnome",
|
|
hit = { melee = 0, spell = 10, gearSpell = 10, schools = {},
|
|
talentsRead = true, talentSpell = 0,
|
|
talentSchools = { arcane = 10, fire = 6, frost = 6 },
|
|
-- measured skill rides along with the read talents (see
|
|
-- Simrogue): a staff has no racial, so 300 is also what
|
|
-- the estimate would have produced
|
|
skill = 300, skillExact = true, skillName = "Staves",
|
|
hitRead = true, mainSub = "Staves",
|
|
offSub = "Miscellaneous", rangedSub = "Wands" },
|
|
gear = gearWith({
|
|
[17] = { id = 5207, enchant = 0, subType = "Miscellaneous",
|
|
name = "Simulated Orb" },
|
|
[18] = { id = 5208, enchant = 0, subType = "Wands",
|
|
name = "Simulated Wand" },
|
|
}) }
|
|
|
|
-- Simfrost: name-only, several gaps (MP5/FR/WPN missing; MOTW/PWF/SPROT
|
|
-- missing; head+shoulder enchants missing; EMPTY ranged on a mage --
|
|
-- forgot the wand). Afflicted 2/3: Curse of Recklessness + Weakened
|
|
-- Soul (TWO debuff columns from one player). Far case 2/2: readable
|
|
-- at distance 120.
|
|
add{ name = "Simfrost", class = "MAGE", online = true,
|
|
guid = "0xF53000000000000E", subgroup = 4, distance = 120,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Flask of Supreme Power", "Well Fed",
|
|
"Arcane Elixir", "Arcane Intellect" }), ids = {} },
|
|
debuffs = debuffsOf({ "Curse of Recklessness", "Weakened Soul" },
|
|
{ 11717, 6788 }),
|
|
weaponMain = false, gearRead = true,
|
|
-- hit: BELOW the spell cap (8 of 16) and an EMPTY ranged slot --
|
|
-- the second NOTEXP path (no ranged item at all, not just a wand)
|
|
race = "Undead",
|
|
hit = { melee = 0, spell = 8, schools = {}, hitRead = true,
|
|
talentsRead = false, mainSub = "Staves" },
|
|
gear = gearWith({
|
|
[1] = { id = 5211, enchant = 0 },
|
|
[3] = { id = 5212, enchant = 0 },
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- Simzap: OFFLINE.
|
|
add{ name = "Simzap", class = "MAGE", online = false,
|
|
guid = "0xF53000000000000F", subgroup = 8,
|
|
aurasRead = false, gearRead = false,
|
|
unreadableReason = "offline" }
|
|
|
|
-- Simlock: THE wand warlock (ranged enchant NOTEXP via subtype).
|
|
-- Name-only reader (ids empty) so the "Mageblood" TWoW alias entry is
|
|
-- exercised via the name fallback. One gap: SPROT.
|
|
add{ name = "Simlock", class = "WARLOCK", online = true,
|
|
guid = "0xF530000000000010", subgroup = 5,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed", "Flask of Supreme Power",
|
|
"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 = {} },
|
|
weaponMain = true, gearRead = true,
|
|
-- hit: the school-specific GEAR showcase, and the counterpart to
|
|
-- Simmage's talents -- an item line worth +3% shadow spell hit
|
|
-- lifts his shadow column to 9.0 while every other school reads
|
|
-- 6.0. talentsRead stays false: this fixture models a player whose
|
|
-- inspect reply (scan/talents.lua) has not arrived yet, so the
|
|
-- number is honestly
|
|
-- gear-only and every tooltip on this row says so (a warlock
|
|
-- would realistically add shadow talents on top of this).
|
|
hit = { melee = 0, spell = 6, gearSpell = 6,
|
|
schools = { Shadow = 3 },
|
|
talentsRead = false,
|
|
hitRead = true, mainSub = "Staves", rangedSub = "Wands" },
|
|
gear = gearWith({
|
|
[18] = { id = 5218, enchant = 0, subType = "Wands",
|
|
name = "Simulated Ghastly Wand" },
|
|
}) }
|
|
|
|
-- Simafflic: many consumable gaps; Soulstone on a CASTER -> SOUL cell
|
|
-- is NOTEXP although the aura is present (expectation wins).
|
|
add{ name = "Simafflic", class = "WARLOCK", online = true,
|
|
guid = "0xF530000000000011", subgroup = 5,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed", "Soulstone Resurrection" }),
|
|
ids = {} },
|
|
weaponMain = false, gearRead = true,
|
|
gear = gearWith({
|
|
[8] = { id = 5228, enchant = 0 }, -- feet enchant missing
|
|
[15] = { id = 5225, enchant = 0 }, -- back enchant missing
|
|
[18] = { id = 5229, enchant = 0, subType = "Wands" },
|
|
}) }
|
|
|
|
-- Simshade: UNCONFIRMED shadow priest -> heuristic suggests CASTER
|
|
-- ("Shadowform at scan time"). Otherwise a completely clean row:
|
|
-- a suggestion badge is not a gap.
|
|
add{ name = "Simshade", class = "PRIEST", online = true,
|
|
guid = "0xF530000000000012", subgroup = 5,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Shadowform", "Flask of Supreme Power",
|
|
"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",
|
|
"Prayer of Shadow Protection" }), ids = {} },
|
|
weaponMain = true, gearRead = true,
|
|
gear = gearWith({
|
|
[18] = { id = 5238, enchant = 0, subType = "Wands" },
|
|
}) }
|
|
|
|
-- Simboom: moonkin (confirmed CASTER). FLASK/MP5/FR/WPN missing,
|
|
-- MOTW/PWF/SPROT missing, wrist enchant missing, EMPTY ranged.
|
|
add{ name = "Simboom", class = "DRUID", online = true,
|
|
guid = "0xF530000000000013", subgroup = 6,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Moonkin Form", "Well Fed",
|
|
"Greater Arcane Elixir", "Arcane Intellect" }), ids = {} },
|
|
weaponMain = false, gearRead = true,
|
|
gear = gearWith({
|
|
[9] = { id = 5249, enchant = 0 },
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- ================= HEALERS (6) =======================================
|
|
|
|
-- Simheala: complete healer, all id-bearing buffs via spell ID (epic
|
|
-- trinket, holdable off hand -> NOTEXP). Clean row.
|
|
add{ name = "Simheala", class = "PRIEST", online = true,
|
|
guid = "0xF530000000000014", subgroup = 6,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed" }),
|
|
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",
|
|
name = "Simulated Prayer Book" },
|
|
[18] = { id = 5258, enchant = 0, subType = "Wands" },
|
|
[13] = { id = 5263, enchant = 0, quality = 4, name = "Simulated Eye of the Naga",
|
|
texture = "Interface\\Icons\\INV_Jewelry_Talisman_06" },
|
|
}) }
|
|
|
|
-- Simlight: READ FAILED, distance 87.
|
|
add{ name = "Simlight", class = "PRIEST", online = true,
|
|
guid = "0xF530000000000015", subgroup = 8, distance = 87,
|
|
aurasRead = false, gearRead = false,
|
|
unreadableReason = "read failed" }
|
|
|
|
-- Simtree: HP/FR/WPN missing, SOUL missing, hands enchant missing,
|
|
-- EMPTY ranged. "Mageblood" alias again on the MP5 slot. Afflicted
|
|
-- 3/3: shares Resurrection Sickness with Simrogue (RS afflicted=2).
|
|
add{ name = "Simtree", class = "DRUID", online = true,
|
|
guid = "0xF530000000000016", subgroup = 6,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Flask of Distilled Wisdom", "Well Fed",
|
|
"Mageblood", "Arcane Intellect", "Mark of the Wild",
|
|
"Power Word: Fortitude",
|
|
"Prayer of Shadow Protection" }), ids = {} },
|
|
debuffs = debuffsOf({ "Resurrection Sickness" }, { 15007 }),
|
|
weaponMain = false, gearRead = true,
|
|
gear = gearWith({
|
|
[10] = { id = 5270, enchant = 0 },
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- Simchain: resto shaman, SPROT + SOUL missing, EMPTY ranged -> NOTEXP.
|
|
add{ name = "Simchain", class = "SHAMAN", online = true,
|
|
guid = "0xF530000000000017", subgroup = 7,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Well Fed" }),
|
|
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 },
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- Simholy: name-only healer, complete on auras incl. SOUL; chest
|
|
-- enchant missing, EMPTY ranged (paladin), holdable off hand NOTEXP.
|
|
add{ name = "Simholy", class = "PALADIN", online = true,
|
|
guid = "0xF530000000000018", subgroup = 7,
|
|
aurasRead = true,
|
|
auras = { names = set({ "Flask of Distilled Wisdom", "Well Fed",
|
|
"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",
|
|
"Prayer of Shadow Protection",
|
|
"Soulstone Resurrection" }), ids = {} },
|
|
weaponMain = true, gearRead = true,
|
|
gear = gearWith({
|
|
[5] = { id = 5285, enchant = 0 },
|
|
[17] = { id = 5287, enchant = 0, subType = "Miscellaneous",
|
|
name = "Simulated Libram of Hope" },
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- 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", "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 },
|
|
[18] = "EMPTY",
|
|
}) }
|
|
|
|
-- ================= FULL-RAID FILL-IN (25 -> 40, see header) =========
|
|
-- Every player below is deliberately "clean": cleanAuras() covers
|
|
-- every consumable/class-buff slot any role could expect, default
|
|
-- gearWith() leaves every enchant slot filled and both trinkets
|
|
-- present, weaponMain=true carries no name (the generic WPN detail
|
|
-- path, like Simwall -- keeps the "exactly two named enchants" count
|
|
-- unchanged). Resist/hit/debuffs come from the index-based filler
|
|
-- passes below, exactly like every other filler player in the
|
|
-- original 25 -- their numbers are deliberately unpinned.
|
|
|
|
-- Simblock: the 4th tank (protection paladin) -- keeps the TANK group
|
|
-- entirely readable with 0 gaps (the green-pill case stays a green
|
|
-- pill at 4 members). RNGD is class-exempt for paladins either way.
|
|
add{ name = "Simblock", class = "PALADIN", online = true,
|
|
guid = "0xF53000000000001A", subgroup = 1,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
add{ name = "Simgrit", class = "WARRIOR", online = true,
|
|
guid = "0xF53000000000001B", subgroup = 2,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
add{ name = "Simslash", class = "ROGUE", online = true,
|
|
guid = "0xF53000000000001C", subgroup = 3,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
-- Simox: melee-spec hunter (a real TWoW spec, see DC_Roles.CLASS_ROLES)
|
|
-- and the column-width showcase: the shortest name in the roster.
|
|
add{ name = "Simox", class = "HUNTER", online = true,
|
|
guid = "0xF53000000000001D", subgroup = 3,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
add{ name = "Simjolt", class = "SHAMAN", online = true,
|
|
guid = "0xF53000000000001E", subgroup = 3,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
add{ name = "Simzeal", class = "PALADIN", online = true,
|
|
guid = "0xF53000000000001F", subgroup = 3,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
-- Simarrow/Simscout: real ranged weapons (unlike the other fill-in
|
|
-- players' default ranged slot) so the RANGED group's HIT tab is not
|
|
-- entirely NOTEXP -- the hit filler pass below reads gear[18].subType.
|
|
add{ name = "Simarrow", class = "HUNTER", online = true,
|
|
guid = "0xF530000000000020", subgroup = 4,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true,
|
|
gear = gearWith({
|
|
[18] = { id = 5426, enchant = 33, subType = "Bows",
|
|
name = "Simulated Huntsbow" },
|
|
}) }
|
|
|
|
add{ name = "Simscout", class = "HUNTER", online = true,
|
|
guid = "0xF530000000000021", subgroup = 4,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true,
|
|
gear = gearWith({
|
|
[18] = { id = 5427, enchant = 33, subType = "Guns",
|
|
name = "Simulated Scattergun" },
|
|
}) }
|
|
|
|
-- Simveil: the 3rd offline case (with Simblade/Simzap: 3 offline + 2
|
|
-- read-failed = 5 of 40 unreadable). Deliberately in subgroup 8 with
|
|
-- the other 4 unreadable players -- a raid subgroup that is entirely
|
|
-- unreadable, the stress case from the header.
|
|
add{ name = "Simveil", class = "HUNTER", online = false,
|
|
guid = "0xF530000000000022", subgroup = 8,
|
|
aurasRead = false, gearRead = false,
|
|
unreadableReason = "offline" }
|
|
|
|
add{ name = "Simember", class = "MAGE", online = true,
|
|
guid = "0xF530000000000023", subgroup = 5,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
add{ name = "Simdread", class = "WARLOCK", online = true,
|
|
guid = "0xF530000000000024", subgroup = 5,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
add{ name = "Simvoid", class = "PRIEST", online = true,
|
|
guid = "0xF530000000000025", subgroup = 6,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
-- Simthornweaver: moonkin druid and one of the two long-name
|
|
-- column-width showcases (14 characters).
|
|
add{ name = "Simthornweaver", class = "DRUID", online = true,
|
|
guid = "0xF530000000000026", subgroup = 6,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
-- Simshadowmender: resto shaman and the other long-name showcase
|
|
-- (15 characters, the longest name in the roster).
|
|
add{ name = "Simshadowmender", class = "SHAMAN", online = true,
|
|
guid = "0xF530000000000027", subgroup = 7,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
add{ name = "Simbloom", class = "DRUID", online = true,
|
|
guid = "0xF530000000000028", subgroup = 7,
|
|
aurasRead = true, auras = cleanAuras(),
|
|
weaponMain = true, gearRead = true, gear = gearWith() }
|
|
|
|
-- Icon coverage pass: give every readable player
|
|
-- a textures table covering all of their HAS-producing buff names --
|
|
-- derived programmatically from names + ids, so both model match paths
|
|
-- deliver an icon. Deterministic (pure function of the literals above).
|
|
-- Debuff pass: every READABLE player without a debuff
|
|
-- literal gets the EMPTY debuffs shape -- zero debuffs is a legitimate
|
|
-- clean state (HAS cells), only the 5 unreadable players stay nil
|
|
-- (UNKNOWN cells).
|
|
-- Resist filler pass: every readable player who did NOT get an
|
|
-- explicit resists literal above (the 3 tanks + Simhuntara + Simmage)
|
|
-- gets small, deterministic, per-school-varied values -- a pure
|
|
-- function of the player's roster index so re-builds stay
|
|
-- byte-identical. Small mod bases (7/8/9/10/11) guarantee zeros show
|
|
-- up repeatedly across the roster (a legitimate 0 resistance, not a
|
|
-- read failure -- resistsRead is what carries that distinction). The 5
|
|
-- unreadable players (Simblade/Simzap/Simveil offline, Simtrue/
|
|
-- Simlight read failed) never get resistsRead/resists at all --
|
|
-- UNKNOWN cells.
|
|
for i = 1, table.getn(players) do
|
|
local p = players[i]
|
|
if p.auras then
|
|
p.auras.textures = texturesFor(p.auras)
|
|
end
|
|
if p.aurasRead and p.debuffs == nil then
|
|
p.debuffs = { names = {}, ids = {}, textures = {} }
|
|
end
|
|
if p.online ~= false and p.aurasRead and p.resistsRead == nil then
|
|
p.resistsRead = true
|
|
p.resists = {
|
|
[2] = math.mod(i * 3, 7),
|
|
[3] = math.mod(i * 3 + 2, 8),
|
|
[4] = math.mod(i * 3 + 4, 9),
|
|
[5] = math.mod(i * 3 + 6, 10),
|
|
[6] = math.mod(i * 3 + 8, 11),
|
|
}
|
|
end
|
|
-- Hit filler pass: every readable player WITHOUT an explicit hit
|
|
-- literal above gets a deterministic set (pure function of the
|
|
-- roster index, so re-builds stay byte-identical). Small mod
|
|
-- bases put a few players on the melee cap (8) and leave everyone
|
|
-- below the spell cap (17) -- the players AT the spell cap are
|
|
-- the explicit ones. The ranged subtype comes from the player's
|
|
-- own equipped ranged item, so a wand user stays NOTEXP and a
|
|
-- thrown-weapon user gets a real ranged column. The 5 unreadable
|
|
-- players keep no hit table at all -> UNKNOWN cells.
|
|
if p.online ~= false and p.aurasRead and p.hit == nil then
|
|
local rangedSub = nil
|
|
local rangedItem = p.gear and p.gear[18]
|
|
if type(rangedItem) == "table" then
|
|
rangedSub = rangedItem.subType
|
|
end
|
|
p.hit = {
|
|
melee = math.mod(i * 2, 9),
|
|
spell = math.mod(i * 3, 12),
|
|
schools = {},
|
|
hitRead = true,
|
|
mainSub = HIT_MAIN_SUB[p.class],
|
|
rangedSub = rangedSub,
|
|
-- the live starting state for everyone but the own
|
|
-- character: no inspect reply has arrived yet
|
|
-- (scan/talents.lua), so the numbers above are gear-only
|
|
-- and every tooltip on these rows says so
|
|
|
|
talentsRead = false,
|
|
}
|
|
-- no race token: the estimate then lands on the plain base
|
|
-- skill, which is exactly what an unknown/new race token does
|
|
-- in the live path too
|
|
end
|
|
end
|
|
|
|
return { scanAt = now, source = "sim", players = players }
|
|
end
|