- Added lib:UnitBuff(unit, index, filterCaster) to query buff data by slot
- Added lib:FindPlayerBuff(unit, spellID) to find player-cast buffs by spell ID
- Both functions return duration, timeleft, and caster information
- Mirrors the debuff tracking API (UnitDebuff/FindPlayerDebuff)
- Enables tracking buff ownership and timeleft for future projects
Updated GetActionTexture to fall back to the macro's icon in all code paths:
- When no action is active and no explicit tooltip (line 2444)
- When inventory slot is empty (line 2469)
- Final fallback for all other cases (line 2494)
This ensures the macro icon is always shown instead of the question mark
when no action/spell texture is available. The question mark should only
appear as an absolute last resort if GetMacroInfo fails.
Changes:
- Split lib.durations into lib.personalDebuffs and lib.sharedDebuffs
- Removed combo-scaling spells (Rupture, Rip, Kidney Shot) from static tables
since they are handled dynamically by ComboPointTracker
- Added lib:IsPersonalDebuff() helper to distinguish debuff types
- Updated ValidateUnitDebuff to auto-detect personal vs shared debuffs
- Personal debuffs (DoTs, poisons, most CC) now auto-filter to player by default
- Shared debuffs (Sunder, Faerie Fire, Hunter's Mark) remain unfiltered
- Maintained lib.durations for backwards compatibility
This prevents false positives when checking personal debuffs like Rupture
in multi-player scenarios while allowing shared debuffs like Sunder Armor
to work correctly.
- ValidateUnitDebuff now supports args.mine to filter player-cast debuffs
- Fixes issue where personal debuffs (Rupture, Rip) from other players
were incorrectly matched when checking timeleft
- Shared stacking debuffs (Sunder Armor) continue to work as expected
- Uses new lib:UnitDebuff filterCaster parameter internally
- Modified lib:UnitDebuff() to accept optional filterCaster parameter
- Added lib:FindPlayerDebuff() to search for player debuffs by spell ID
- Ensures debuffs in buff slots can be properly identified by owner
- Prevents false positives from other players' debuffs with same spell ID
Updated fallback logic to always use the macro's chosen icon (from the
macro frame) when no action is active. The priority is now:
1. Tooltip texture (from first action if #showtooltip is present)
2. Macro's icon (from macro frame)
3. Never question mark (unless GetMacroInfo fails, which shouldn't happen)
This ensures the icon properly shows the macro's icon when all
conditionals fail, instead of showing a question mark.
When #showtooltip is used without an explicit spell/item argument and all
conditionals fail, the icon now falls back to the tooltip texture (first
action) instead of immediately showing a question mark. This prevents the
question mark icon from appearing when the macro has valid actions but none
of their conditions are currently met.
Fixes issue where macro icon becomes question mark when no target and
Battle Shout buff is already active.
Previously, lib:UnitDebuff() callers only looped through indices 1-16, which
meant overflow debuffs in buff slots 17-32 were never detected.
Changed loop range from 1-16 to 1-32:
- _get_debuff_timeleft() loops (Conditionals.lua:93, 105)
- ValidateUnitDebuff() loop (Conditionals.lua:802)
- Added missing "if not effect then break end" check (Conditionals.lua:804)
How lib:UnitDebuff(unit, index) works:
- Index 1-16: Checks debuff slot, falls back to buff slot at same index
- Index 17-32: No debuff slots exist, so checks buff slots 17-32
Since a debuff can only exist in ONE location (either debuff slot OR buff slot),
checking 1-32 covers all 16 debuff slots + all 32 buff slots efficiently.
Previously, lib:UnitDebuff() callers only looped through indices 1-16, which
meant overflow debuffs in buff slots 17-32 were never detected.
Changed loop range from 1-16 to 1-32:
- _get_debuff_timeleft() loops (Conditionals.lua:93, 105)
- ValidateUnitDebuff() loop (Conditionals.lua:802)
- Added missing "if not effect then break end" check (Conditionals.lua:804)
How lib:UnitDebuff(unit, index) works:
- Index 1-16: Checks debuff slot, falls back to buff slot at same index
- Index 17-32: No debuff slots exist, so checks buff slots 17-32
Since a debuff can only exist in ONE location (either debuff slot OR buff slot),
checking 1-32 covers all 16 debuff slots + all 32 buff slots efficiently.
Applied the same overflow bug fixes to the combodurations branch to ensure
compatibility with combo point-scaled debuffs. This fixes three issues:
1. lib:UnitDebuff() (line 799): Fixed UnitBuff() return value capture from 4
values to 3 values (UnitBuff only returns texture, stacks, spellID). Also
changed overflow check from only checking static database to checking
lib:GetDuration() which includes combo durations, learned durations, and
static durations.
2. SeedUnit() debuff loop (line 837): Changed check from only lib.durations[]
to lib:GetDuration() so debuffs with learned or combo-scaled durations are
properly seeded into the tracking system.
3. SeedUnit() buff loop (line 867): Fixed UnitBuff() return value capture from
4 values to 3, and changed check from only lib.durations[] to
lib:GetDuration() to properly track overflow debuffs with combo-scaled or
learned durations.
These fixes ensure that:
- Overflow debuffs like Moonfire are properly detected by [nodebuff:] conditionals
- Duration tracking works for overflow debuffs with combo point scaling (Rip, Rupture)
- Duration tracking works for overflow debuffs with learned durations
- All existing combo point tracking functionality is preserved
Fixed three issues preventing overflowed debuffs from being properly tracked
with duration information, especially for debuffs with learned durations:
1. lib:UnitDebuff() (line 760): Changed overflow check from only checking
static database (lib.durations[spellID]) to also checking learned durations
via lib:GetDuration(spellID). This allows overflowed debuffs with learned
durations to be properly returned.
2. SeedUnit() UnitBuff loop (line 807): Fixed incorrect UnitBuff() return value
capture - was trying to get 4 values when it only returns 3, causing spellID
to always be nil.
3. SeedUnit() both loops (lines 797, 810): Changed to use lib:GetDuration()
instead of only checking lib.durations[], so both regular debuffs AND
overflowed debuffs with learned durations are properly seeded into the
tracking system.
This ensures debuff conditionals like [debuff:Moonfire<4] work correctly for
overflowed debuffs, even if the duration was learned rather than in the
static database.
Fixed a critical bug where UnitBuff() calls in Utility.lua were incorrectly
capturing 4 return values instead of 3, causing the spell ID variable to
receive nil instead of the actual spell ID. This broke debuff overflow
detection where debuffs shown as buffs would not be properly identified.
According to SuperWoW documentation, the API returns:
- UnitBuff(unit, index) → texture, stacks, spellID (3 values)
- UnitDebuff(unit, index) → texture, stacks, debuffType, spellID (4 values)
Changes:
- Fixed lib:UnitDebuff() overflow fallback in Utility.lua:758
- Fixed GetUnitBuffs() helper function in Utility.lua:1108
- Fixed buff checking in CheckImmunity() function in Utility.lua:1282
- Reverted incorrect changes to Conditionals.lua (was already correct)
This resolves the issue where macros like [nodebuff:Moonfire] would spam cast
when the debuff had overflowed and was showing as a buff on the target.
Prevent TrackComboPointCast from overwriting confirmed tracking with
unconfirmed evaluation data. Once DoCast confirms a cast, subsequent
macro evaluations will no longer overwrite the tracking entry.
This fixes the issue where pfUI's second AddEffect call would see
unconfirmed tracking because evaluations overwrote the confirmed data.
Don't clear the confirmed flag after first use since pfUI may call
AddEffect multiple times for the same spell. Instead, rely on the
time-based expiry (0.5s timeout) to prevent stale tracking data.
This fixes the issue where the second AddEffect call would see
unconfirmed tracking and ignore the correct duration.
The issue: None of the CastSpellByName/CastSpell hooks were firing because
CleveRoid macros go through DoCast, not the standard casting functions.
Evidence from user output:
- Many "ComboTrack: Rip cast with 5 CP" messages ✓ (TrackComboPointCast called)
- NO "[Confirmed]" messages ✗ (no hooks firing)
- "[pfUI AddEffect Hook] Ignoring Rip tracking (not confirmed)" ✗ (never confirmed)
The DoCast integration was calling TrackComboPointCast but NOT setting
confirmed=true, so pfUI would always see unconfirmed tracking data.
Fix: Add confirmation in DoCast hook, just like all the other cast hooks.
Now when user casts via CleveRoid macros:
1. DoCast fires → TrackComboPointCast → confirmed=true ✓
2. pfUI AddEffect fires → sees confirmed=true → uses duration ✓
CRITICAL FIX: For instant-cast spells, pfUI's AddEffect fires before
SPELLCAST_START, so confirmed flag was never set in time.
Event sequence for instant casts:
1. CastSpellByName hook → TrackComboPointCast (confirmed=false)
2. pfUI AddPending
3. SPELLCAST_STOP (instant cast completes)
4. pfUI PersistPending → AddEffect (confirmed still false!) ✗
5. SPELLCAST_START may not fire for instants
Solution: Confirm tracking immediately when cast functions are called:
- CastSpellByName_Hook → confirmed=true
- CastSpell_Hook → confirmed=true
- Hook global CastSpell → confirmed=true
- Hook global UseAction → confirmed=true
- SPELLCAST_START → confirmed=true (backup)
Now pfUI AddEffect (step 4) sees confirmed=true ✓
Added debug output showing:
"[Confirmed] Rip tracking confirmed (CastSpellByName)"
This ensures tracking is confirmed BEFORE pfUI tries to use it.
Prevent macro conditional evaluations from overwriting good CP data:
Problem: Macros like "[combo:>0 nodebuff]Rip /cast [combo:>0]Rip"
evaluate multiple times, calling TrackComboPointCast repeatedly:
1. Check [combo:>0 nodebuff] → TrackComboPointCast(0 CP)
2. Claw lands → player gains 1 CP
3. Check [combo:>0] → TrackComboPointCast(1 CP)
4. Spell casts → pfUI AddEffect fires → uses tracking data
If the 0 CP call happens after the 1 CP call, pfUI gets wrong data.
Solution: Only update tracking if:
- No existing data exists, OR
- Existing data is stale (>0.5s), OR
- New CP value >= existing CP value
This ensures we keep the highest (best) combo point count during
rapid macro evaluations, so pfUI always gets the correct duration.
Debug: Shows "Ignoring X with Y CP (have Z CP)" when preventing
overwrites of better data.
CRITICAL FIX: pfUI's libdebuff uses completely different data structures:
- Uses unit NAME (not GUID)
- Uses unit LEVEL as a key
- Uses spell NAME (effect, not ID)
- Storage: pflib.objects[unitName][unitLevel][effectName]
Fixed SyncComboDurationToPfUI:
- Convert GUID to unit name (check target, fallback to guidToName map)
- Get unit level from target or default to 0
- Convert spell ID to spell name using SpellInfo()
- Remove rank from spell name to match pfUI's format
- Search both specific level and level 0 (pfUI's fallback)
Fixed AddEffect hook:
- Updated parameters to match pfUI signature: (unit, unitlevel, effect, duration, caster)
- Check spell by name instead of ID
- Map spell name back to ID to find tracking data
This ensures pfUI's cooldown displays show correct combo durations.
After analyzing pfUI's cooldown module, we need to directly sync
combo durations to pfUI's libdebuff.objects storage:
- Added SyncComboDurationToPfUI function to force-update pfUI's
stored debuff duration after tracking combo spells
- Called from UNIT_CASTEVENT handler after AddEffect
- This ensures pfUI's cooldown display shows the correct combo
duration (e.g., 28s for 5 CP Rip) instead of base 12s
pfUI's cooldown module is just a display layer - it shows durations
from pfUI.api.libdebuff.objects. By directly updating this storage,
we ensure the correct duration is displayed regardless of timing.
Hook into pfUI.api.libdebuff to inject combo-based durations:
- Hook GetDuration: Returns learned combo durations for combo spells
- Hook AddEffect: Injects tracked combo durations when debuffs are applied
This ensures pfUI's cooldown module displays the correct duration
(e.g., 28s for 5 CP Rip instead of base 12s).
Debug messages show when pfUI hooks are triggered in debug mode.
The issue: UNIT_CASTEVENT fires AFTER combo points are consumed, so
GetComboPoints() returns 0. The lastComboPoints fallback wasn't always
set in time.
Solution: When TrackComboPointCastByID sees 0 combo points, it now:
1. First tries lastComboPoints (OnUpdate tracking)
2. If still 0, checks name-based tracking data from extension hooks
(which captured the CP count BEFORE the spell was cast)
3. Uses the CP count if the cast was within the last 0.5 seconds
This ensures we always get the correct combo point count for duration
calculation, regardless of timing.
Added debug output at all critical points in the duration tracking flow:
- UNIT_CASTEVENT handler: Shows calculated duration and CP count
- lib:GetDuration: Shows which source provided the duration (learned combo/caster/static)
- lib:AddEffect: Shows what duration was actually stored
- SeedUnit: Shows what duration is used when rescanning debuffs/buffs
This will help diagnose why combo durations aren't being applied correctly.
BUGFIX: Core.lua:2253 attempt to compare nil with number
The cleanup loop was checking 'if time > cast.expires' but some entries
in spell_tracking (like combo tracking data) don't have an expires field.
Added nil check: 'if cast.expires and time > cast.expires'
This prevents errors when combo tracking data is in spell_tracking without
an expiration time.