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:
Horyoshi
2026-08-20 16:52:02 +02:00
parent 879620200e
commit f38395a95c
4 changed files with 140 additions and 59 deletions
+21 -20
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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