Fix profsync guild-wide lag, price MC trash, bid on 0 GP items (10.20.5)
profsync: stop expanding skill headers on every read. Expand/CollapseSkillHeader fire SKILL_LINES_CHANGED, which this addon also listens to, so each read re-armed its own debounced check -- a permanent 15s cycle that dragged pfQuest's quest-giver rebuild and Cartographer's addon walk along with it on every guild member, whether or not they ever opened a profession. Reads are now plain scans that report whether a collapsed header may have censored them; an expand happens only where the difference actually matters (a known profession vanishing, the open window's own rank, /status), and is bracketed by a grace window so our own echo is ignored. A drop is never announced off an incomplete read. loot: only skip items with NO price entry. A price of 0 is a known item that just hasn't been priced yet, and it was silently skipping the bid/trade window. prices: Molten Core trash belts and bracers 0 -> 67 GP, Boots of Blistering Flames and Core Forged Helmet 100 -> 67, add Core Forged Greaves. minep default 300. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+21
-20
@@ -147,6 +147,7 @@ local prices = {
|
||||
[18808] = {125,"T1"}; --Gloves of the Hypnotic Flame
|
||||
[18809] = {125,"T1"}; --Sash of Whispered Secrets
|
||||
[18812] = {125,"T1"}; --Wristguards of True Flight
|
||||
[18806] = {9,"T1"}; --Core Forged Greaves
|
||||
[19140] = {150,"T1"}; --Cauterizing Band
|
||||
[18805] = {150,"T1"}; --Core Hound Tooth
|
||||
[18803] = {300,"T1"}; --Finkle's Lava Dredger
|
||||
@@ -180,27 +181,27 @@ local prices = {
|
||||
[92080] = {9999,"T1"}; --Molten Corehound
|
||||
|
||||
--Trash Mobs
|
||||
[16817] = {0,"T1"}; --Girdle of Prophecy
|
||||
[16802] = {0,"T1"}; --Arcanist Belt
|
||||
[16806] = {0,"T1"}; --Felheart Belt
|
||||
[16827] = {0,"T1"}; --Nightslayer Belt
|
||||
[16828] = {0,"T1"}; --Cenarion Belt
|
||||
[16851] = {0,"T1"}; --Giantstalker's Belt
|
||||
[16838] = {0,"T1"}; --Earthfury Belt
|
||||
[16858] = {0,"T1"}; --Lawbringer Belt
|
||||
[16864] = {0,"T1"}; --Belt of Might
|
||||
[16817] = {67,"T1"}; --Girdle of Prophecy
|
||||
[16802] = {67,"T1"}; --Arcanist Belt
|
||||
[16806] = {67,"T1"}; --Felheart Belt
|
||||
[16827] = {67,"T1"}; --Nightslayer Belt
|
||||
[16828] = {67,"T1"}; --Cenarion Belt
|
||||
[16851] = {67,"T1"}; --Giantstalker's Belt
|
||||
[16838] = {67,"T1"}; --Earthfury Belt
|
||||
[16858] = {67,"T1"}; --Lawbringer Belt
|
||||
[16864] = {67,"T1"}; --Belt of Might
|
||||
[81260] = {150,"T1"}; --Lavashard Axe
|
||||
[16819] = {0,"T1"}; --Vambraces of Prophecy
|
||||
[16799] = {0,"T1"}; --Arcanist Bindings
|
||||
[16804] = {0,"T1"}; --Felheart Bracers
|
||||
[16825] = {0,"T1"}; --Nightslayer Bracelets
|
||||
[16830] = {0,"T1"}; --Cenarion Bracers
|
||||
[16850] = {0,"T1"}; --Giantstalker's Bracers
|
||||
[16840] = {0,"T1"}; --Earthfury Bracers
|
||||
[16857] = {0,"T1"}; --Lawbringer Bracers
|
||||
[16861] = {0,"T1"}; --Bracers of Might
|
||||
[81261] = {100,"T1"}; --Boots of Blistering Flames
|
||||
[81262] = {100,"T1"}; --Core Forged Helmet
|
||||
[16819] = {67,"T1"}; --Vambraces of Prophecy
|
||||
[16799] = {67,"T1"}; --Arcanist Bindings
|
||||
[16804] = {67,"T1"}; --Felheart Bracers
|
||||
[16825] = {67,"T1"}; --Nightslayer Bracelets
|
||||
[16830] = {67,"T1"}; --Cenarion Bracers
|
||||
[16850] = {67,"T1"}; --Giantstalker's Bracers
|
||||
[16840] = {67,"T1"}; --Earthfury Bracers
|
||||
[16857] = {67,"T1"}; --Lawbringer Bracers
|
||||
[16861] = {67,"T1"}; --Bracers of Might
|
||||
[81261] = {67,"T1"}; --Boots of Blistering Flames
|
||||
[81262] = {67,"T1"}; --Core Forged Helmet
|
||||
[81263] = {125,"T1"}; --Lost Dark Iron Chain
|
||||
[81264] = {125,"T1"}; --Shoulderpads of True Flight
|
||||
[81265] = {125,"T1"}; --Ashskin Belt
|
||||
|
||||
+109
-35
@@ -109,6 +109,11 @@ local LEARN_SETTLE = 12 -- quiet time after the last "you have learned"
|
||||
local LEARN_MAX_WAIT = 90 -- ...but never postpone a learn batch longer than this
|
||||
local RANK_MIN_GAP = 300 -- min seconds between two skill-level-only messages
|
||||
local SKILL_DEBOUNCE = 15 -- settle time after SKILL_LINES_CHANGED
|
||||
-- ⚠ Expanding or collapsing a skill header FIRES SKILL_LINES_CHANGED — see the
|
||||
-- warning above ReadSkills. This is how long the addon disbelieves that event
|
||||
-- after having caused it itself. A genuine skill-up landing inside the window is
|
||||
-- simply missed, which costs a cosmetic rank line until the next idle sweep.
|
||||
local SKILL_ECHO_GRACE = 1
|
||||
local IDLE_CHECK = 300 -- background skill re-check cadence
|
||||
local ACK_TIMEOUT = 60 -- how long a batch may sit unacknowledged
|
||||
local RETRY_DELAY = 600 -- pause everything after finding the relay offline
|
||||
@@ -186,6 +191,7 @@ local pending = {} -- [profId] = batch awaiting acknowledgement
|
||||
local metaCache = {} -- [profId][recipeId] = { n = name, i = icon }
|
||||
local metaRounds = 0
|
||||
local skillsCheckAt = nil -- debounced SKILL_LINES_CHANGED handler
|
||||
local skillEchoUntil = nil -- ignore SKILL_LINES_CHANGED we caused ourselves
|
||||
local nextIdleCheck = nil
|
||||
local learnSettleAt = nil -- debounced "you have learned" burst
|
||||
local learnFirstAt = nil -- start of the current burst, bounds the debounce
|
||||
@@ -523,32 +529,26 @@ local function RestoreSkillHeaders(collapsed)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns [profId] = { r = rank, m = max } for every tracked profession this
|
||||
-- character has, or nil when the pane could not be read cleanly right now.
|
||||
local function ReadSkills()
|
||||
local anyCollapsed = false
|
||||
local function AnyCollapsedHeader()
|
||||
for i = 1, GetNumSkillLines() do
|
||||
local _, isHeader, isExpanded = GetSkillLineInfo(i)
|
||||
if isHeader and not isExpanded then anyCollapsed = true break end
|
||||
if isHeader and not isExpanded then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local restore = nil
|
||||
if anyCollapsed then
|
||||
restore = ExpandSkillHeaders()
|
||||
if not restore then return nil end
|
||||
end
|
||||
|
||||
-- ⚠ A NAME CAN APPEAR TWICE. "Survival" is both a Turtle profession and a
|
||||
-- hunter's class skill line, and a hunter sees both in this pane, so the last
|
||||
-- one read must not win by accident — the higher cap does, which is always the
|
||||
-- profession. A hunter who does NOT have the profession is caught later and
|
||||
-- more cheaply: nothing in their spellbook opens it, so it is never scanned
|
||||
-- and never reported.
|
||||
--
|
||||
-- Deliberately no minimum-cap gate here. It would be a second way to tell the
|
||||
-- two apart, but it is also a single condition that could silently blank every
|
||||
-- profession on a client whose returns differ, and the spellbook check already
|
||||
-- covers the case.
|
||||
-- ⚠ A NAME CAN APPEAR TWICE. "Survival" is both a Turtle profession and a
|
||||
-- hunter's class skill line, and a hunter sees both in this pane, so the last
|
||||
-- one read must not win by accident — the higher cap does, which is always the
|
||||
-- profession. A hunter who does NOT have the profession is caught later and
|
||||
-- more cheaply: nothing in their spellbook opens it, so it is never scanned
|
||||
-- and never reported.
|
||||
--
|
||||
-- Deliberately no minimum-cap gate here. It would be a second way to tell the
|
||||
-- two apart, but it is also a single condition that could silently blank every
|
||||
-- profession on a client whose returns differ, and the spellbook check already
|
||||
-- covers the case.
|
||||
local function ScanSkillLines()
|
||||
local out = {}
|
||||
for i = 1, GetNumSkillLines() do
|
||||
local name, isHeader, _, rank, _, _, maxRank = GetSkillLineInfo(i)
|
||||
@@ -562,11 +562,48 @@ local function ReadSkills()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RestoreSkillHeaders(restore)
|
||||
return out
|
||||
end
|
||||
|
||||
--[[ ⚠ EXPANDING A SKILL HEADER IS EXPENSIVE FOR THE WHOLE UI, NOT JUST FOR US.
|
||||
|
||||
ExpandSkillHeader/CollapseSkillHeader fire SKILL_LINES_CHANGED — FrameXML's own
|
||||
expand button calls nothing else and relies on that event to redraw the pane —
|
||||
and that event goes to EVERY addon. On a normal OctoWoW install it lands on
|
||||
pfQuest, whose handler compares the visible skill-line names and, because
|
||||
collapsing a header hides its children from GetSkillLineInfo, sees a change
|
||||
every single time and runs a full pfDatabase:SearchQuests() quest-giver rebuild;
|
||||
and on Cartographer's Professions module, which walks GetNumAddOns() calling
|
||||
GetAddOnMetadata on every installed addon.
|
||||
|
||||
Worse, it used to feed itself: this addon listens to SKILL_LINES_CHANGED too,
|
||||
so its own expand/collapse re-armed the debounced check, which read the skills
|
||||
again, which expanded again — a permanent 15-second cycle of quest-giver
|
||||
rebuilds on every guild member, whether or not they ever opened a profession.
|
||||
That is what made the whole guild lag (2026-08-09).
|
||||
|
||||
So: the plain read NEVER touches a header, and callers that can live with a
|
||||
partial answer ask for one. `mayExpand` is for the rare case where the
|
||||
difference actually matters — telling "profession abandoned" apart from
|
||||
"profession hidden under a collapsed header" — and it brackets the mutation in
|
||||
skillEchoUntil so our own echo is not mistaken for a real skill change.
|
||||
|
||||
Returns skills, complete. `complete` false means a collapsed header may be
|
||||
hiding professions, so a MISSING one proves nothing. ]]
|
||||
local function ReadSkills(mayExpand)
|
||||
local skills = ScanSkillLines()
|
||||
if not AnyCollapsedHeader() then return skills, true end
|
||||
if not mayExpand then return skills, false end
|
||||
|
||||
skillEchoUntil = GetTime() + SKILL_ECHO_GRACE
|
||||
local restore = ExpandSkillHeaders() -- nil when the Skills pane is open
|
||||
if not restore then return skills, false end
|
||||
skills = ScanSkillLines()
|
||||
RestoreSkillHeaders(restore)
|
||||
skillEchoUntil = GetTime() + SKILL_ECHO_GRACE
|
||||
return skills, true
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Reading an open profession window
|
||||
-- ---------------------------------------------------------------------------
|
||||
@@ -790,13 +827,31 @@ end
|
||||
|
||||
local function CheckSkills()
|
||||
if not CanSend() then return end
|
||||
local skills = ReadSkills()
|
||||
if not skills then
|
||||
skillsCheckAt = GetTime() + SKILL_DEBOUNCE -- Skills pane open, come back
|
||||
return
|
||||
end
|
||||
local skills, complete = ReadSkills(false)
|
||||
local db = DB()
|
||||
|
||||
-- ⚠ Only pay for a header expand when a profession we ALREADY KNOW ABOUT has
|
||||
-- vanished from the read, because that is the single decision a partial read
|
||||
-- cannot make (see ReadSkills for what an expand costs the rest of the UI). A
|
||||
-- rank read off a visible line is trustworthy on its own, and a BRAND NEW
|
||||
-- profession hidden under a collapsed header is not worth an expand either:
|
||||
-- opening its window is the only way to use it, and that reports the rank
|
||||
-- alongside the recipes.
|
||||
if not complete then
|
||||
local suspect = false
|
||||
for id in pairs(db.profs) do
|
||||
if PROF_BY_ID[id] and not skills[id] then suspect = true break end
|
||||
end
|
||||
if suspect then
|
||||
local full, ok = ReadSkills(true)
|
||||
if not ok then
|
||||
skillsCheckAt = GetTime() + SKILL_DEBOUNCE -- Skills pane open, come back
|
||||
return
|
||||
end
|
||||
skills, complete = full, ok
|
||||
end
|
||||
end
|
||||
|
||||
-- Gone from the pane means abandoned. The row is kept, flagged, and only
|
||||
-- deleted once the website confirms — otherwise a lost "dropped" message
|
||||
-- would leave the site advertising recipes the player no longer has.
|
||||
@@ -809,7 +864,10 @@ local function CheckSkills()
|
||||
db.profs[id] = nil
|
||||
pending[id] = nil
|
||||
elseif not skills[id] then
|
||||
if not state.gone or not pending[id] then
|
||||
-- Belt and braces: the suspect check above should have upgraded the read
|
||||
-- to a complete one before we get here. Never announce a drop off a read
|
||||
-- that a collapsed header could have censored.
|
||||
if complete and (not state.gone or not pending[id]) then
|
||||
state.gone = true
|
||||
pending[id] = { drop = true, at = GetTime() }
|
||||
Send("X", tostring(id))
|
||||
@@ -1043,8 +1101,16 @@ local function OnWindowVisible(isCraft)
|
||||
|
||||
if prof then
|
||||
local state = ProfState(prof.id)
|
||||
local skills = ReadSkills()
|
||||
-- The cheap read first; only if THIS profession is the one hidden under a
|
||||
-- collapsed header is an expand worth it — and here it is, because the
|
||||
-- alternative is publishing a rank of 0/0. This path already only runs when
|
||||
-- the recipe list actually changed, so it is rare.
|
||||
local skills = ReadSkills(false)
|
||||
local info = skills and skills[prof.id]
|
||||
if not info then
|
||||
skills = ReadSkills(true)
|
||||
info = skills and skills[prof.id]
|
||||
end
|
||||
-- The stored rank is -1 until the website has confirmed one, which is not
|
||||
-- a number to put on the wire; fall back to 0 when the pane is unreadable.
|
||||
local rank = (info and info.r) or state.r
|
||||
@@ -1107,6 +1173,12 @@ f:SetScript("OnEvent", function()
|
||||
end
|
||||
|
||||
if event == "SKILL_LINES_CHANGED" then
|
||||
-- ⚠ IGNORE OUR OWN ECHO. Expanding or collapsing a header to read the pane
|
||||
-- fires this event, so without the grace window the check that did the
|
||||
-- expanding re-arms itself and runs again forever, every SKILL_DEBOUNCE
|
||||
-- seconds, dragging pfQuest's quest-giver rebuild along with it. See
|
||||
-- ReadSkills.
|
||||
if skillEchoUntil and GetTime() < skillEchoUntil then return end
|
||||
-- Fires on every point of skill-up, so it only arms a debounced check; that
|
||||
-- check sends nothing unless a profession appeared, vanished, or moved.
|
||||
skillsCheckAt = GetTime() + SKILL_DEBOUNCE
|
||||
@@ -1198,10 +1270,12 @@ local function PrintStatus()
|
||||
or (online and " online" or " OFFLINE"))
|
||||
Say("guild " .. guild .. " | relay " .. relay)
|
||||
|
||||
local skills = ReadSkills()
|
||||
if not skills then
|
||||
Say("could not read the Skills pane (close the character window)")
|
||||
else
|
||||
-- A human typed this, so the expand is worth it here whatever it costs.
|
||||
local skills, complete = ReadSkills(true)
|
||||
if not complete then
|
||||
Say("partial reading — close the character window for the full picture")
|
||||
end
|
||||
do
|
||||
local parts, any = {}, false
|
||||
for id, info in pairs(skills) do
|
||||
local prof = PROF_BY_ID[id]
|
||||
|
||||
+9
-3
@@ -10,7 +10,7 @@ local T = AceLibrary("Tablet-2.0")
|
||||
local L = AceLibrary("AceLocale-2.2"):new("shootyepgp")
|
||||
sepgp.VARS = {
|
||||
basegp = 300,
|
||||
minep = 0,
|
||||
minep = 300,
|
||||
baseaward_ep = 100,
|
||||
decay = 0.9,
|
||||
max = 1000,
|
||||
@@ -2250,7 +2250,10 @@ function sepgp:tradeLoot(playerState,targetState)
|
||||
local link_found, _, itemColor, itemString, itemName = string.find(itemLink, "^(|c%x+)|H(.+)|h(%[.+%])")
|
||||
if (link_found) then
|
||||
local price = sepgp_prices:GetPrice(itemString,sepgp_progress)
|
||||
if not (price) or price == 0 then
|
||||
-- Only skip items with NO price entry (untracked trash). A price of 0
|
||||
-- means a known item that just hasn't been priced yet (Gear Points seeds
|
||||
-- new items at 0) -- still open the window so it can be handed out/logged.
|
||||
if not (price) then
|
||||
return
|
||||
end
|
||||
local bind = self:itemBinding(itemString)
|
||||
@@ -2380,7 +2383,10 @@ function sepgp:processLoot(player,itemLink,source)
|
||||
local bind = self:itemBinding(itemString)
|
||||
if not (bind) then return end
|
||||
local price = sepgp_prices:GetPrice(itemString,sepgp_progress)
|
||||
if (not (price)) or (price == 0) then
|
||||
-- Only skip items with NO price entry (untracked trash). A price of 0 means
|
||||
-- a known item that just hasn't been priced yet (Gear Points seeds new items
|
||||
-- at 0) -- still open the MS/OS window so it can be handed out/logged.
|
||||
if (not (price)) then
|
||||
return
|
||||
end
|
||||
local class,_
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
## X-Website: https://github.com/cinecom/shootyepgp/
|
||||
## X-Category: Guild
|
||||
## X-Credits: shooty, Qcat(zhCN)
|
||||
## Version: 10.20.3 <ERROR>
|
||||
## Version: 10.20.5 <ERROR>
|
||||
## X-Alpha:
|
||||
## OptionalDeps: Ace2
|
||||
## SavedVariables: sepgp_saychannel, sepgp_groupbyclass, sepgp_groupbyarmor, sepgp_groupbyrole, sepgp_raidonly, sepgp_dbver, sepgp_debug, sepgp_fubar, sepgp_sound, RaidStrikeDB, RaidStrikeResetTime
|
||||
|
||||
Reference in New Issue
Block a user