From b5d8f6de00c0c4d330cfd4f30143b08f0c179db9 Mon Sep 17 00:00:00 2001 From: "Johnny C. Lam" Date: Fri, 19 Jul 2013 05:33:45 +0000 Subject: [PATCH] Major changes to OvaleFuture to properly handle spellcast event sequence. * Fix long-standing bug regarding UNIT_SPELLCAST_* events. The proper order order of events is actually: SENT > START > SUCCEEDED (cast-time spells) SENT > SUCCEEDED (instant-cast spells) Save the target, spell and lineID info from the SENT event to identify later events regarding the same spellcast. * Save the most recent spellcast information in Ovale.lastSpellcast, which includes a snapshot of the stats that are active for that spellcast. Copy this snapshot information into DoT auras applied by the spellcast. This assumes that the DoT being applied was caused by the most recent spellcast. * Auras changes that are simultaneous with a spellcast actually occur slightly after the UNIT_SPELLCAST_SUCCEEDED event and before the CLEU SPELL_DAMAGE (or other spell result) event. Capture new snapshots and adjust the spellcast snapshot information accordingly to ensure accuracy. This should fix the following tickets: * Ticket 260 where target.Debuff*() and Last*() script conditions don't always show the same player stats for the same spell. Both now pull from the same source so they show the same information. * Ticket 262 where the DebuffComboPoints() condition was always returning zero since previously OvaleAura didn't have access to combo point information. The combo point information is now saved with the spellcast snapshot that is copied into the DoT aura. git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@980 d5049fe3-3747-40f7-a4b5-f36d6801af5f --- Ovale.lua | 11 ++++- OvaleAura.lua | 27 ++++++----- OvaleCondition.lua | 2 +- OvaleFuture.lua | 135 ++++++++++++++++++++++++++++++++++------------------ OvalePaperDoll.lua | 50 +++++++++++-------- OvaleState.lua | 2 +- 6 files changed, 145 insertions(+), 82 deletions(-) diff --git a/Ovale.lua b/Ovale.lua index 66cc6df..d52e132 100644 --- a/Ovale.lua +++ b/Ovale.lua @@ -40,8 +40,8 @@ local OVALE_TRUE_STRING = tostring(true) Ovale.L = L --The current time, updated once per frame refresh. Ovale.now = API_GetTime() --- The spell ID of the most recent spell cast. -Ovale.lastSpellId = nil +-- The most recent spell cast. +Ovale.lastSpellcast = {} --The table of check boxes definition Ovale.casesACocher = {} --the frame with the icons @@ -173,6 +173,13 @@ function Ovale:ToggleOptions() self.frame:ToggleOptions() end +function Ovale:UpdateLastSpellcast(spellcast) + wipe(self.lastSpellcast) + for k, v in pairs(spellcast) do + self.lastSpellcast[k] = v + end +end + function Ovale:UpdateVisibility() local profile = OvaleOptions:GetProfile() diff --git a/OvaleAura.lua b/OvaleAura.lua index 61f0471..7a9456b 100644 --- a/OvaleAura.lua +++ b/OvaleAura.lua @@ -139,8 +139,8 @@ local function UnitGainedAura(event, guid, spellId, filter, casterGUID, icon, co ) local addAura = not existingAura or not auraIsUnchanged if addAura then - Ovale:DebugPrintf(OVALE_AURA_DEBUG, "%s: Adding %s %s (%s) to %s, aura.serial=%d", - event, filter, name, spellId, guid, aura.serial) + Ovale:DebugPrintf(OVALE_AURA_DEBUG, "%s: Adding %s %s (%s) to %s at %f, aura.serial=%d", + event, filter, name, spellId, guid, Ovale.now, aura.serial) aura.icon = icon aura.stacks = count aura.debuffType = debuffType @@ -158,20 +158,22 @@ local function UnitGainedAura(event, guid, spellId, filter, casterGUID, icon, co aura.name = name aura.value = value + -- Snapshot stats for DoTs. if mine then local si = OvaleData.spellInfo[spellId] - if si then + if si and si.tick then -- Only set the initial tick information for new auras. - if not existingAura and si.tick then + if not existingAura then aura.ticksSeen = 0 aura.tick = OvaleData:GetTickLength(spellId) end -- Determine whether to snapshot player stats for the aura or to keep the existing stats. - if Ovale.lastSpellId and OvaleData:NeedNewSnapshot(spellId, Ovale.lastSpellId) then - Ovale:DebugPrintf(OVALE_AURA_DEBUG, "%s: Snapshot stats for %s %s (%s) on %s at %f, gain=%f, aura.serial=%d", - event, filter, name, spellId, guid, Ovale.now, aura.gain, aura.serial) - OvalePaperDoll:SnapshotStats(aura) - aura.damageMultiplier = self:GetDamageMultiplier(spellId) + local lastSpellcast = Ovale.lastSpellcast + local lastSpellId = lastSpellcast and lastSpellcast.spellId + if lastSpellId and OvaleData:NeedNewSnapshot(spellId, lastSpellId) then + Ovale:DebugPrintf(OVALE_AURA_DEBUG, "%s: Snapshot stats for %s %s (%s) on %s from %f, now=%f, aura.serial=%d", + event, filter, name, spellId, guid, lastSpellcast.snapshotTime, Ovale.now, aura.serial) + OvalePaperDoll:SnapshotStats(aura, Ovale.lastSpellcast) end end end @@ -185,9 +187,10 @@ local function UnitGainedAura(event, guid, spellId, filter, casterGUID, icon, co end local function RemoveAuraIfExpired(guid, spellId, filter, aura, serial) + local self = OvaleAura if aura and serial and aura.serial ~= serial then - Ovale:DebugPrintf(OVALE_AURA_DEBUG, "Removing expired %s %s (%s) from %s, serial=%d aura.serial=%d", - filter, aura.name, spellId, guid, serial, aura.serial) + Ovale:DebugPrintf(OVALE_AURA_DEBUG, "Removing expired %s %s (%s) from %s at %f, serial=%d aura.serial=%d", + filter, aura.name, spellId, guid, Ovale.now, serial, aura.serial) self:SendMessage("Ovale_AuraRemoved", guid, spellId, aura.source) self_pool:Release(aura) return true @@ -332,7 +335,7 @@ local function UpdateAuraTick(guid, spellId, timestamp) aura.lastTickTime = timestamp aura.tick = tick aura.ticksSeen = ticksSeen - Ovale:DebugPrintf(OVALE_AURA_DEBUG, "Updating %s %s (%s) on %s, tick=%f", filter, aura.name, spellId, guid, tick) + Ovale:DebugPrintf(OVALE_AURA_DEBUG, "Updating %s %s (%s) on %s at %f, tick=%f", filter, aura.name, spellId, guid, Ovale.now, tick) end end -- diff --git a/OvaleCondition.lua b/OvaleCondition.lua index ea735b0..b11c796 100644 --- a/OvaleCondition.lua +++ b/OvaleCondition.lua @@ -622,7 +622,7 @@ OvaleCondition.conditions.debuffrangedattackpower = OvaleCondition.conditions.bu OvaleCondition.conditions.buffcombopoints = function(condition) self_auraFound.comboPoints = nil local start, ending = GetAura(condition, self_auraFound) - local comboPoints = self_auraFound.comboPoints or 0 + local comboPoints = self_auraFound.comboPoints or 1 if start and ending and start <= ending then return start, ending, comboPoints, start, 0 else diff --git a/OvaleFuture.lua b/OvaleFuture.lua index 4029d3d..7128c42 100644 --- a/OvaleFuture.lua +++ b/OvaleFuture.lua @@ -26,6 +26,7 @@ local ipairs = ipairs local pairs = pairs local select = select local tinsert = table.insert +local tostring = tostring local tremove = table.remove local API_UnitCastingInfo = UnitCastingInfo local API_UnitChannelInfo = UnitChannelInfo @@ -40,14 +41,20 @@ local self_activeSpellcast = {} local self_lastSpellcast = {} local self_pool = OvalePool:NewPool("OvaleFuture_pool") --- Used to track the most recent target of a spellcast matching self_lastLineID. -local self_lastTarget = nil +-- Used to track the most recent spellcast started with UNIT_SPELLCAST_SENT. local self_lastLineID = nil +local self_lastSpell = nil +local self_lastTarget = nil + +-- Time at which a player aura was last added. +local self_timeAuraAdded = nil -- The spell requests that have been sent to the server and are awaiting a reply. -- self_sentSpellcast[lineId] = timestamp local self_sentSpellcast = {} +local OVALE_UNKNOWN_GUID = 0 + -- These CLEU events are eventually received after a successful spellcast. local OVALE_CLEU_SPELLCAST_RESULTS = { SPELL_AURA_APPLIED = true, @@ -71,8 +78,10 @@ OvaleFuture.traceSpellId = nil -- local function TracePrintf(spellId, ...) local self = OvaleFuture - if self.traceSpellId and self.traceSpellId == spellId then - Ovale:FormatPrint(...) + if self.traceSpellId then + if (self.traceSpellId == spellId) or (type(spellId) == "string" and OvaleData:GetSpellName(self.traceSpellId) == spellId) then + Ovale:FormatPrint(...) + end end end @@ -98,9 +107,9 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a spellcast.stop = endTime spellcast.channeled = channeled spellcast.allowRemove = allowRemove - --TODO unable to know what is the real target - if lineId == self_lastLineID and self_lastTarget then - -- Ovale:FormatPrint("found lineId %d, target is %s", lineId, self_lastTarget) + + -- Set the target from the data taken from UNIT_SPELLCAST_SENT if it's the same spellcast. + if lineId == self_lastLineID and OvaleData:GetSpellName(spellId) == self_lastSpell then spellcast.target = self_lastTarget else spellcast.target = API_UnitGUID("target") @@ -109,10 +118,8 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a Ovale.now, OvaleData:GetSpellName(spellId), spellId, lineId, startTime, endTime, spellcast.target) -- Snapshot the current stats for the spellcast. - Ovale.lastSpellId = spellId OvalePaperDoll:SnapshotStats(spellcast) spellcast.damageMultiplier = OvaleAura:GetDamageMultiplier(spellId) - tinsert(self_activeSpellcast, spellcast) local si = OvaleData.spellInfo[spellId] if si then @@ -157,6 +164,8 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a spellcast.removeOnSuccess = true end + tinsert(self_activeSpellcast, spellcast) + ScoreSpell(spellId) Ovale.refreshNeeded["player"] = true end @@ -174,20 +183,41 @@ local function RemoveSpellFromQueue(spellId, lineId) Ovale.refreshNeeded["player"] = true end +-- UpdateLastSpellInfo() is called at the end of the event handler for OVALE_CLEU_SPELLCAST_RESULTS[]. +-- It saves the given spellcast as the most recent one on its target and ensures that the spellcast +-- snapshot values are correctly adjusted for buffs that are added or cleared simultaneously with the +-- spellcast. local function UpdateLastSpellInfo(spellcast) - if spellcast then - local targetGUID = spellcast.target - local spellId = spellcast.spellId - if targetGUID and spellId then - if not self_lastSpellcast[targetGUID] then - self_lastSpellcast[targetGUID] = {} - end - local oldSpellcast = self_lastSpellcast[targetGUID][spellId] - if oldSpellcast then - self_pool:Release(oldSpellcast) + local targetGUID = spellcast.target + local spellId = spellcast.spellId + if targetGUID and spellId then + if not self_lastSpellcast[targetGUID] then + self_lastSpellcast[targetGUID] = {} + end + local oldSpellcast = self_lastSpellcast[targetGUID][spellId] + if oldSpellcast then + self_pool:Release(oldSpellcast) + end + self_lastSpellcast[targetGUID][spellId] = spellcast + + --[[ + If any auras have been added between the start of the spellcast and this event, + then take a more recent snapshot of the player stats for this spellcast. + + This is needed to see any auras that were applied at the same time as the + spellcast, e.g., potions or other on-use abilities or items. + ]]-- + if self_timeAuraAdded then + if self_timeAuraAdded >= spellcast.start and self_timeAuraAdded - spellcast.stop < 1 then + OvalePaperDoll:SnapshotStats(spellcast) + spellcast.damageMultiplier = OvaleAura:GetDamageMultiplier(spellId) + TracePrintf(spellId, " Updated spell info for %s (%d) to snapshot from %f.", + OvaleData:GetSpellName(spellId), spellId, spellcast.snapshotTime) end - self_lastSpellcast[targetGUID][spellId] = spellcast end + + -- Save this most recent spellcast. + Ovale:UpdateLastSpellcast(spellcast) end end -- @@ -203,6 +233,7 @@ function OvaleFuture:OnEnable() self:RegisterEvent("UNIT_SPELLCAST_SENT") self:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED") self:RegisterEvent("UNIT_SPELLCAST_START") + self:RegisterMessage("Ovale_AuraAdded") self:RegisterMessage("Ovale_InactiveUnit") end @@ -215,17 +246,20 @@ function OvaleFuture:OnDisable() self:UnregisterEvent("UNIT_SPELLCAST_SENT") self:UnregisterEvent("UNIT_SPELLCAST_START") self:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED") + self:UnregisterMessage("Ovale_AuraAdded") self:UnregisterMessage("Ovale_InactiveUnit") end function OvaleFuture:PLAYER_ENTERING_WORLD(event) -- Empty out self_lastSpellcast. - for guid, spellTable in pairs(self_lastSpellcast) do - for spellId, spellcast in pairs(spellTable) do - spellTable[spellId] = nil - self_pool:Release(spellcast) - end - self_lastSpellcast[guid] = nil + for guid in pairs(self_lastSpellcast) do + self:Ovale_InactiveUnit(event, guid) + end +end + +function OvaleFuture:Ovale_AuraAdded(event, guid, spellId, caster) + if guid == OvaleGUID:GetGUID("player") then + self_timeAuraAdded = Ovale.now end end @@ -275,43 +309,54 @@ function OvaleFuture:UNIT_SPELLCAST_INTERRUPTED(event, unit, name, rank, lineId, end end --- UNIT_SPELLCAST_SENT is triggered when the spellcast has finished. --- Look up the active spellcast and fix up the target of the spell. +-- UNIT_SPELLCAST_SENT is triggered when the spellcast is sent to the server. +-- This is sent before all other UNIT_SPELLCAST_* events for the same spellcast. function OvaleFuture:UNIT_SPELLCAST_SENT(event, unit, spell, rank, target, lineId) if unit == "player" then + self_lastLineID = lineId + self_lastSpell = spell + -- UNIT_TARGET may arrive out of order with UNIT_SPELLCAST* events, so we can't track -- the target in an event handler. - local targetGUID - if target == API_UnitName("target") then - targetGUID = API_UnitGUID("target") - else - targetGUID = OvaleGUID:GetGUIDForName(target) - end - self_lastTarget = targetGUID - self_lastLineID = lineId - TracePrintf(spellId, "%s: %f %s, lineId=%d, targetGUID=%s", event, Ovale.now, spell, lineId, targetGUID) - for _, spellcast in ipairs(self_activeSpellcast) do - if spellcast.lineId == lineId then - spellcast.target = targetGUID - -- Update spellcast stats to the latest snapshot of the player's stats. - OvalePaperDoll:SnapshotStats(spellcast) - spellcast.damageMultiplier = OvaleAura:GetDamageMultiplier(spellId) + if target then + if target == API_UnitName("target") then + self_lastTarget = API_UnitGUID("target") + else + self_lastTarget = OvaleGUID:GetGUIDForName(target) end + else + self_lastTarget = OVALE_UNKNOWN_GUID end + TracePrintf(spell, "%s: %f %s on %s, lineId=%d", event, Ovale.now, spell, self_lastTarget, lineId) self_sentSpellcast[lineId] = Ovale.now end end +-- UNIT_SPELLCAST_SUCCEEDED is triggered when a spellcast successfully completes. +--[[ + For a cast-time spell: + UNIT_SPELLCAST_SENT + UNIT_SPELLCAST_START + UNIT_SPELLCAST_SUCCEEDED + For an instant-cast spell: + UNIT_SPELLCAST_SENT + UNIT_SPELLCAST_SUCCEEDED +]]-- function OvaleFuture:UNIT_SPELLCAST_SUCCEEDED(event, unit, name, rank, lineId, spellId) if unit == "player" then TracePrintf(spellId, "%s: %f %d, lineId=%d", event, Ovale.now, spellId, lineId) + -- Search for a cast-time spell matching this spellcast that was added by UNIT_SPELLCAST_START. for _, spellcast in ipairs(self_activeSpellcast) do if spellcast.lineId == lineId then spellcast.allowRemove = true + -- Take a more recent snapshot of the player stats for this cast-time spell. + OvalePaperDoll:SnapshotStats(spellcast) + spellcast.damageMultiplier = OvaleAura:GetDamageMultiplier(spellId) return end end + --[[ This spell was an instant-cast spell, but only add it to the queue if it's not part of a channeled spell. A channeled spell is actually two separate spells, an @@ -382,7 +427,7 @@ function OvaleFuture:COMBAT_LOG_EVENT_UNFILTERED(event, ...) if sourceGUID == OvaleGUID:GetGUID("player") then if OVALE_CLEU_SPELLCAST_RESULTS[event] then local spellId, spellName = select(12, ...) - TracePrintf(spellId, "%s: %f %s (%d), lineId=%d", event, Ovale.now, spellName, spellId, lineId) + TracePrintf(spellId, "%s: %f %s (%d)", event, Ovale.now, spellName, spellId) for index, spellcast in ipairs(self_activeSpellcast) do if spellcast.allowRemove and (spellcast.spellId == spellId or spellcast.auraSpellId == spellId) then if not spellcast.channeled and (spellcast.removeOnSuccess or event ~= "SPELL_CAST_SUCCESS") then @@ -415,7 +460,7 @@ function OvaleFuture:ApplyInFlightSpells(now, ApplySpell) ApplySpell(spellcast.spellId, spellcast.start, spellcast.stop, spellcast.stop, spellcast.nocd, spellcast.target, spellcast) else tremove(self_activeSpellcast, index) - UpdateLastSpellInfo(spellcast) + self_pool:Release(spellcast) -- Decrement current index since item was removed and rest of items shifted up. index = index - 1 end diff --git a/OvalePaperDoll.lua b/OvalePaperDoll.lua index 467f671..ed6f708 100644 --- a/OvalePaperDoll.lua +++ b/OvalePaperDoll.lua @@ -319,32 +319,40 @@ function OvalePaperDoll:SnapshotStats(t, source) source = self.stat end for k in pairs(self.stat) do - t[k] = source[k] + if source[k] then + t[k] = source[k] + end + end + -- Copy other properties that are relevant for auras that might be present. + -- TODO: Holy power? + if source.comboPoints then + t.comboPoints = source.comboPoints end end -function OvalePaperDoll:Debug() +function OvalePaperDoll:Debug(stat) + stat = stat or self.stat Ovale:FormatPrint("Class: %s", self.class) Ovale:FormatPrint("Level: %d", self.level) Ovale:FormatPrint("Specialization: %s", self.specialization) - Ovale:FormatPrint("Agility: %d", self.stat.agility) - Ovale:FormatPrint("Intellect: %d", self.stat.intellect) - Ovale:FormatPrint("Spirit: %d", self.stat.spirit) - Ovale:FormatPrint("Stamina: %d", self.stat.stamina) - Ovale:FormatPrint("Strength: %d", self.stat.strength) - Ovale:FormatPrint("AP: %d", self.stat.attackPower) - Ovale:FormatPrint("RAP: %d", self.stat.rangedAttackPower) - Ovale:FormatPrint("Spell bonus damage: %d", self.stat.spellBonusDamage) - Ovale:FormatPrint("Spell bonus healing: %d", self.stat.spellBonusHealing) - Ovale:FormatPrint("Spell critical strike effect: %f%%", self.stat.spellCrit) - Ovale:FormatPrint("Spell haste effect: %f%%", self.stat.spellHaste) - Ovale:FormatPrint("Melee critical strike effect: %f%%", self.stat.meleeCrit) - Ovale:FormatPrint("Melee haste effect: %f%%", self.stat.meleeHaste) - Ovale:FormatPrint("Ranged critical strike effect: %f%%", self.stat.rangedCrit) - Ovale:FormatPrint("Ranged haste effect: %f%%", self.stat.rangedHaste) - Ovale:FormatPrint("Mastery effect: %f%%", self.stat.masteryEffect) - Ovale:FormatPrint("Damage multiplier: %f", self.stat.damageMultiplier) - Ovale:FormatPrint("Weapon damage (mainhand): %f", self.stat.mainHandWeaponDamage) - Ovale:FormatPrint("Weapon damage (offhand): %f", self.stat.offHandWeaponDamage) + Ovale:FormatPrint("Agility: %d", stat.agility) + Ovale:FormatPrint("Intellect: %d", stat.intellect) + Ovale:FormatPrint("Spirit: %d", stat.spirit) + Ovale:FormatPrint("Stamina: %d", stat.stamina) + Ovale:FormatPrint("Strength: %d", stat.strength) + Ovale:FormatPrint("Attack power: %d", stat.attackPower) + Ovale:FormatPrint("Ranged attack power: %d", stat.rangedAttackPower) + Ovale:FormatPrint("Spell bonus damage: %d", stat.spellBonusDamage) + Ovale:FormatPrint("Spell bonus healing: %d", stat.spellBonusHealing) + Ovale:FormatPrint("Spell critical strike effect: %f%%", stat.spellCrit) + Ovale:FormatPrint("Spell haste effect: %f%%", stat.spellHaste) + Ovale:FormatPrint("Melee critical strike effect: %f%%", stat.meleeCrit) + Ovale:FormatPrint("Melee haste effect: %f%%", stat.meleeHaste) + Ovale:FormatPrint("Ranged critical strike effect: %f%%", stat.rangedCrit) + Ovale:FormatPrint("Ranged haste effect: %f%%", stat.rangedHaste) + Ovale:FormatPrint("Mastery effect: %f%%", stat.masteryEffect) + Ovale:FormatPrint("Damage multiplier: %f", stat.damageMultiplier) + Ovale:FormatPrint("Weapon damage (mainhand): %f", stat.mainHandWeaponDamage) + Ovale:FormatPrint("Weapon damage (offhand): %f", stat.offHandWeaponDamage) end -- diff --git a/OvaleState.lua b/OvaleState.lua index 4949f00..75f1467 100644 --- a/OvaleState.lua +++ b/OvaleState.lua @@ -153,7 +153,7 @@ function OvaleState:UpdatePowerRates() end function OvaleState:Reset() - self.lastSpellId = Ovale.lastSpellId + self.lastSpellId = Ovale.lastSpellcast and Ovale.lastSpellcast.spellId self.serial = self.serial + 1 self.currentTime = self.maintenant Ovale:Logf("Reset state with current time = %f", self.currentTime) -- 1.7.9.5