TotemBar v0.4.1
This commit is contained in:
@@ -2,6 +2,13 @@
|
||||
|
||||
All notable changes to TotemBar are documented here.
|
||||
|
||||
## v0.4.1 — 2026-08-31
|
||||
|
||||
### Fixed
|
||||
- A totem destroyed by the enemy inside the ~0.4s window between a Totemic Recall wipe and its
|
||||
refusal being attributed now stays dead instead of having its countdown resurrected by the
|
||||
refused-recall restore (`core/cast.lua`).
|
||||
|
||||
## v0.4.0 — 2026-08-30
|
||||
|
||||
### Added
|
||||
|
||||
@@ -11,18 +11,17 @@ server data, not guesswork. No dependencies; adopts the
|
||||
|
||||
<img src="screenshots/pulse-ui.png" alt="TotemBar in action: round slots with duration and pulse rings, totems placed in the world" width="100%">
|
||||
|
||||
## What's new — v0.4.0 · 2026-08-30
|
||||
## What's new — v0.4.1 · 2026-08-31
|
||||
|
||||
- **Destroyed totems are detected.** A totem killed before its timer ran out used to keep
|
||||
counting down; now the death line (and, with SuperWoW/nampower, the cast and death events)
|
||||
clears its countdown, ring, pulse and range tint. Live retest still pending.
|
||||
- **No phantom countdown on a cast refused for mana.** The Clearcasting exemption never
|
||||
applied to totems and had silently disabled the mana gate.
|
||||
- **A refused Totemic Recall keeps your timers.** Only the Recall that actually went out clears
|
||||
them; a press the server rejects restores what it had cleared.
|
||||
- **A totem destroyed inside the recall attribution window stays dead.** A totem the enemy
|
||||
destroyed in the ~0.4s window between a Totemic Recall wipe and its refusal being attributed
|
||||
no longer has its countdown resurrected by the refused-recall restore.
|
||||
|
||||
**Version history** — details in [CHANGELOG.md](CHANGELOG.md):
|
||||
|
||||
- **v0.4.1** (2026-08-31) — a totem destroyed by the enemy inside the recall attribution
|
||||
window now stays dead instead of coming back when a refused Totemic Recall restores its
|
||||
timers.
|
||||
- **v0.4.0** (2026-08-30) — a totem destroyed before its timer ran out is now
|
||||
detected and cleared (countdown, ring, pulse and range tint together) instead
|
||||
of counting down as if it still stood; a totem cast refused for lack of mana
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
## Title: TotemBar
|
||||
## Notes: Shaman totem bar - pick one totem per element, cast it, or spam-cycle all four from a macro.
|
||||
## Author: ShempError
|
||||
## Version: 0.4.0
|
||||
## Version: 0.4.1
|
||||
## SavedVariables: TotemBarDB
|
||||
|
||||
core\totemdata.lua
|
||||
|
||||
+128
-17
@@ -1307,6 +1307,21 @@ if CreateFrame then
|
||||
TotemBar.TOTEM_ELEMENTS, name, owner, playerName)
|
||||
if element then
|
||||
TotemBar.evictDestroyedTotem(element)
|
||||
return
|
||||
end
|
||||
-- Recall-attribution race (see "Recall-attribution vs.
|
||||
-- destruction race" above): activeTotems has nothing for
|
||||
-- this element because a recall wipe already cleared it and
|
||||
-- is still awaiting attribution -- re-run the same owner/
|
||||
-- name match against that pending snapshot so a later
|
||||
-- restoreRecalledTotems doesn't bring the dead totem back.
|
||||
local snapshotRecs = TotemBar.pendingRecallSnapshotRecs()
|
||||
if snapshotRecs then
|
||||
local snapElement = TotemBar.elementForOwnedTotemName(snapshotRecs,
|
||||
TotemBar.TOTEM_ELEMENTS, name, owner, playerName)
|
||||
if snapElement then
|
||||
TotemBar.evictFromRecallSnapshot(snapElement)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -1315,22 +1330,20 @@ if CreateFrame then
|
||||
local element = TotemBar.elementForDiesLineCandidate(TotemBar.activeTotems,
|
||||
TotemBar.TOTEM_ELEMENTS, diesName)
|
||||
if element then
|
||||
local rec = TotemBar.activeTotems[element]
|
||||
local exists, guid, health, deadOrGhost = nil, nil, nil, nil
|
||||
if type(UnitExists) == "function" then
|
||||
exists, guid = UnitExists(rec.guid)
|
||||
end
|
||||
if exists then
|
||||
if type(UnitHealth) == "function" then
|
||||
health = UnitHealth(rec.guid)
|
||||
end
|
||||
if type(UnitIsDeadOrGhost) == "function" then
|
||||
deadOrGhost = UnitIsDeadOrGhost(rec.guid)
|
||||
end
|
||||
end
|
||||
if TotemBar.totemDestroyed(exists, health, deadOrGhost) then
|
||||
if TotemBar.verifyDiesLineDestruction(TotemBar.activeTotems[element]) then
|
||||
TotemBar.evictDestroyedTotem(element)
|
||||
end
|
||||
return
|
||||
end
|
||||
-- Same recall-attribution race as the owned-name branch
|
||||
-- above, for the generic "<Name> dies." fallback.
|
||||
local snapshotRecs = TotemBar.pendingRecallSnapshotRecs()
|
||||
if snapshotRecs then
|
||||
local snapElement = TotemBar.elementForDiesLineCandidate(snapshotRecs,
|
||||
TotemBar.TOTEM_ELEMENTS, diesName)
|
||||
if snapElement and TotemBar.verifyDiesLineDestruction(snapshotRecs[snapElement]) then
|
||||
TotemBar.evictFromRecallSnapshot(snapElement)
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
@@ -1346,6 +1359,7 @@ if CreateFrame then
|
||||
-- events cannot cause a silent miss.
|
||||
local element = TotemBar.elementForGuid(TotemBar.activeTotems,
|
||||
TotemBar.TOTEM_ELEMENTS, arg1, TotemBar.guidsEqual)
|
||||
local unitName, ownerName, playerName = nil, nil, nil
|
||||
if not element and arg1 and type(UnitName) == "function" then
|
||||
-- GUID-free fallback (2026-08-20 event tap: UnitName/owner
|
||||
-- DO resolve at the exact death instant on this client, for
|
||||
@@ -1353,9 +1367,9 @@ if CreateFrame then
|
||||
-- see the CHAT_MSG_COMBAT_FRIENDLY_DEATH/UNIT_DIED fixture
|
||||
-- pair) -- covers a totem whose GUID was never latched by
|
||||
-- either latch path at all.
|
||||
local unitName = UnitName(arg1)
|
||||
local ownerName = UnitName(arg1 .. "owner")
|
||||
local playerName = UnitName("player")
|
||||
unitName = UnitName(arg1)
|
||||
ownerName = UnitName(arg1 .. "owner")
|
||||
playerName = UnitName("player")
|
||||
element = TotemBar.elementForOwnedTotemName(TotemBar.activeTotems,
|
||||
TotemBar.TOTEM_ELEMENTS, unitName, ownerName, playerName)
|
||||
end
|
||||
@@ -1368,6 +1382,29 @@ if CreateFrame then
|
||||
-- moment later" bug for the one case it exists to short-
|
||||
-- circuit fastest.
|
||||
TotemBar.evictDestroyedTotem(element)
|
||||
return
|
||||
end
|
||||
-- Recall-attribution race (see "Recall-attribution vs.
|
||||
-- destruction race" above): re-run both matches against the
|
||||
-- pending recall snapshot, reusing whatever UnitName lookups
|
||||
-- already ran above instead of re-querying the (by-now-gone)
|
||||
-- unit a second time.
|
||||
local snapshotRecs = TotemBar.pendingRecallSnapshotRecs()
|
||||
if snapshotRecs then
|
||||
local snapElement = TotemBar.elementForGuid(snapshotRecs,
|
||||
TotemBar.TOTEM_ELEMENTS, arg1, TotemBar.guidsEqual)
|
||||
if not snapElement and arg1 and type(UnitName) == "function" then
|
||||
if not unitName then
|
||||
unitName = UnitName(arg1)
|
||||
ownerName = UnitName(arg1 .. "owner")
|
||||
playerName = UnitName("player")
|
||||
end
|
||||
snapElement = TotemBar.elementForOwnedTotemName(snapshotRecs,
|
||||
TotemBar.TOTEM_ELEMENTS, unitName, ownerName, playerName)
|
||||
end
|
||||
if snapElement then
|
||||
TotemBar.evictFromRecallSnapshot(snapElement)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -1514,6 +1551,80 @@ function TotemBar.restoreRecalledTotems()
|
||||
return restored
|
||||
end
|
||||
|
||||
-- ===== Recall-attribution vs. destruction race =====
|
||||
--
|
||||
-- recallWipeActiveTotems above clears TotemBar.activeTotems immediately and
|
||||
-- keeps the pre-wipe records in TotemBar.lastRecallWipe.recs until the recall
|
||||
-- is attributed. If a totem that was JUST wiped dies server-side inside that
|
||||
-- same CAST_FAIL_WINDOW, the destruction handlers below (CHAT_MSG_COMBAT_
|
||||
-- FRIENDLY_DEATH / UNIT_DIED) look it up in TotemBar.activeTotems -- which is
|
||||
-- already empty for it -- so the eviction is a silent no-op and sets no
|
||||
-- tombstone. If the recall is THEN attributed as refused, restoreRecalledTotems
|
||||
-- puts the snapshot record back and the dead totem's countdown resumes. These
|
||||
-- two helpers let the destruction handlers reach into the still-pending
|
||||
-- snapshot with the exact same name/guid matches they already run against
|
||||
-- activeTotems, so a totem that died mid-attribution stays dead either way.
|
||||
|
||||
-- The pending recall snapshot's records table, or nil if there is no recall
|
||||
-- wipe awaiting attribution right now (none at all, or it is already older
|
||||
-- than CAST_FAIL_WINDOW -- same staleness rule restoreRecalledTotems uses;
|
||||
-- an old snapshot means any subsequent event is unrelated to it).
|
||||
function TotemBar.pendingRecallSnapshotRecs()
|
||||
local wiped = TotemBar.lastRecallWipe
|
||||
if not wiped or not wiped.at or not wiped.recs then
|
||||
return nil
|
||||
end
|
||||
if (GetTime() - wiped.at) > TotemBar.CAST_FAIL_WINDOW then
|
||||
return nil
|
||||
end
|
||||
return wiped.recs
|
||||
end
|
||||
|
||||
-- Evicts `element` from the pending recall snapshot (see above) instead of
|
||||
-- from TotemBar.activeTotems -- the wipe already cleared that table, so
|
||||
-- TotemBar.evictDestroyedTotem would no-op here. Removes the record from
|
||||
-- TotemBar.lastRecallWipe.recs so a later restoreRecalledTotems leaves this
|
||||
-- element alone, and tombstones it exactly like evictDestroyedTotem does, so
|
||||
-- pfUI's libtotem can't resurrect the display for it either.
|
||||
function TotemBar.evictFromRecallSnapshot(element)
|
||||
local wiped = TotemBar.lastRecallWipe
|
||||
if not wiped or not wiped.recs then
|
||||
return
|
||||
end
|
||||
local rec = wiped.recs[element]
|
||||
if not rec then
|
||||
return
|
||||
end
|
||||
if rec.start and rec.duration then
|
||||
TotemBar.destroyedTombstone[element] = rec.start + rec.duration
|
||||
end
|
||||
wiped.recs[element] = nil
|
||||
end
|
||||
|
||||
-- Pure-ish (queries the live unit): runs the SAME "already exists, and either
|
||||
-- zero health or dead-or-ghost" verification the CHAT_MSG_COMBAT_FRIENDLY_
|
||||
-- DEATH generic "<Name> dies." fallback used inline before this was pulled
|
||||
-- out -- shared now so the recall-snapshot branch below runs the identical
|
||||
-- check instead of a second hand-copied one.
|
||||
function TotemBar.verifyDiesLineDestruction(rec)
|
||||
if not rec then
|
||||
return false
|
||||
end
|
||||
local exists, guid, health, deadOrGhost = nil, nil, nil, nil
|
||||
if type(UnitExists) == "function" then
|
||||
exists, guid = UnitExists(rec.guid)
|
||||
end
|
||||
if exists then
|
||||
if type(UnitHealth) == "function" then
|
||||
health = UnitHealth(rec.guid)
|
||||
end
|
||||
if type(UnitIsDeadOrGhost) == "function" then
|
||||
deadOrGhost = UnitIsDeadOrGhost(rec.guid)
|
||||
end
|
||||
end
|
||||
return TotemBar.totemDestroyed(exists, health, deadOrGhost)
|
||||
end
|
||||
|
||||
-- Is at least one totem currently out? Used to avoid wasting Totemic Recall's
|
||||
-- own 6-second cooldown on a no-op cast: recalling with nothing out still puts
|
||||
-- Recall on cooldown, so a fresh set placed right after can't be recalled for
|
||||
|
||||
Reference in New Issue
Block a user