Untitled
unknown
plain_text
10 days ago
131 kB
4
Indexable
Never
function(event, ...) local aura_env = aura_env -- Prevent code execution during loading screens or WA options window if event == "LOADING_SCREEN_DISABLED" then aura_env.world_loaded = true elseif event == "LOADING_SCREEN_ENABLED" then aura_env.world_loaded = false return false elseif event == "OPTIONS" or not aura_env.world_loaded then return false end local _GetTime = GetTime local GetTime = function() return aura_env.frameTime or _GetTime() end if event == "FRAME_UPDATE" then aura_env.frameTime = _GetTime() end -- This should never occur if not aura_env.CPlayer then return false end -- Cache Globals local Player = aura_env.CPlayer local Combat = Player.combat local spec = Player.spec local bit = bit local bit_band = bit.band local C_NamePlate = C_NamePlate local GetNamePlateForUnit = C_NamePlate.GetNamePlateForUnit local GetNamePlates = C_NamePlate.GetNamePlates local C_Scenario = C_Scenario local GetCriteriaInfo = C_Scenario.GetCriteriaInfo or C_ScenarioInfo.GetCriteriaInfo local GetInfo = C_Scenario.GetInfo local GetStepInfo = C_Scenario.GetStepInfo local C_UnitAuras = C_UnitAuras local GetAuraDataByAuraInstanceID = C_UnitAuras.GetAuraDataByAuraInstanceID local GetAuraDataBySlot = C_UnitAuras.GetAuraDataBySlot local CheckInteractDistance = CheckInteractDistance local COMBATLOG_OBJECT_AFFILIATION_MASK = COMBATLOG_OBJECT_AFFILIATION_MASK local COMBATLOG_OBJECT_AFFILIATION_MINE = COMBATLOG_OBJECT_AFFILIATION_MINE local CombatLogGetCurrentEventInfo = CombatLogGetCurrentEventInfo local debugprofilestart = debugprofilestart local debugprofilestop = debugprofilestop local floor = floor local frameTime = GetTime() local GetPowerRegen = GetPowerRegen local GetRaidTargetIndex = GetRaidTargetIndex local GetSpellCharges = GetSpellCharges or function( spellID ) if not spellID then return nil end local chargeInfo = C_Spell.GetSpellCharges( spellID ) if chargeInfo then return chargeInfo.currentCharges, chargeInfo.maxCharges, chargeInfo.cooldownStartTime, chargeInfo.cooldownDuration, chargeInfo.chargeModRate end end local GetSpellCooldown = GetSpellCooldown or function( spellID ) local info = C_Spell.GetSpellCooldown( spellID ) return info.startTime, info.duration, info.isEnabled end local GetSpellPowerCost = GetSpellPowerCost or C_Spell.GetSpellPowerCost local GetUnitSpeed = GetUnitSpeed local global_modifier = aura_env.global_modifier local InCombatLockdown = InCombatLockdown local ipairs = ipairs local IsEquippedItemType = IsEquippedItemType or C_Item.IsEquippedItemType local IsMounted = IsMounted local IsSpellKnown = IsSpellKnown local LE_SCENARIO_TYPE_CHALLENGE_MODE = LE_SCENARIO_TYPE_CHALLENGE_MODE local LibStub = LibStub local math = math local max = math.max local min = math.min local pairs = pairs local print = print local select = select local SetRaidTarget = SetRaidTarget local string = string local find = string.find local gsub = string.gsub local len = string.len local lower = string.lower local sub = string.sub local table = table local targetAuraEffect = aura_env.targetAuraEffect local tonumber = tonumber local tostring = tostring local type = type local UnitAffectingCombat = UnitAffectingCombat local UnitAuraSlots = UnitAuraSlots or C_UnitAuras.GetAuraSlots local UnitChannelInfo = UnitChannelInfo local UnitExists = UnitExists local UnitGetTotalAbsorbs = UnitGetTotalAbsorbs local UnitGetTotalHealAbsorbs = UnitGetTotalHealAbsorbs local UnitGUID = UnitGUID local UnitHealth = UnitHealth local UnitHealthMax = UnitHealthMax local UnitIsDead = UnitIsDead local UnitIsPlayer = UnitIsPlayer local UnitLevel = UnitLevel local UnitName = UnitName local UnitPower = UnitPower local UnitPowerMax = UnitPowerMax local UnitStagger = UnitStagger local UnitStat = UnitStat local UnitThreatSituation = UnitThreatSituation local WA_IterateGroupMembers = WA_IterateGroupMembers local WeakAuras = WeakAuras local gcdDuration = WeakAuras.gcdDuration local ScanEvents = WeakAuras.ScanEvents local insert = function ( t, v ) t[ #t + 1 ] = v end local next = next local t_sort = table.sort local sort = function( t, func ) if not func then t_sort( t ) else t_sort( t, function( l, r ) if not l then return false elseif not r then return true else return func( l, r ) end end ) end end local profiler_enabled = false aura_env.profiler_out = ( profiler_enabled and aura_env.profiler_out ) or {} local profilerStart = function() if profiler_enabled then debugprofilestart() end end local profilerEnd = function( desc ) if profiler_enabled then local profiler_t = debugprofilestop() if profiler_t and profiler_t > 0 then aura_env.profiler_out[ desc ] = profiler_t end end end -- Main worker thread if event == "FRAME_UPDATE" then local fullUpdate = false -- Development Debugging Option if profiler_enabled and next( aura_env.profiler_out ) ~= nil then ScanEvents( "JEREMYUI_PROFILER", aura_env.profiler_out ) end -- Modulate update speed based on framerate if aura_env.frame_pace then local delta = frameTime - aura_env.frame_pace local modulate = max( 0.0167, delta ) / 0.0167 aura_env.update_rate = min( 1, modulate * 0.5 ) end aura_env.frame_pace = frameTime if not aura_env.RECENT_UPDATE or aura_env.RECENT_UPDATE < frameTime - 0.5 then profilerStart() aura_env.RECENT_UPDATE = frameTime local recentDamage = aura_env.danger_a + aura_env.danger_b for index, damage in pairs( Combat.recent_damage ) do local expire = damage.expire if not expire or expire <= frameTime then Combat.recent_damage[ index ] = nil else recentDamage = recentDamage + ( damage.amount or 0 ) end end -- Used for Touch of Karma evaluation Player.recent_dtps = recentDamage / aura_env.RECENT_DURATION profilerEnd( "Parse Recent Damage" ) end if not aura_env.last or aura_env.fast or aura_env.tick_update and aura_env.tick_update < frameTime or aura_env.last < frameTime - aura_env.update_rate then -- Movement Rate / Speed if aura_env.last then local cur_speed = GetUnitSpeed( "player" ) if InCombatLockdown() and not IsMounted() and cur_speed > 0 then local delta_time = ( frameTime - aura_env.last ) Player.movement_yds = Player.movement_yds + ( cur_speed * delta_time ) Player.movement_t = Player.movement_t + delta_time Player.movement_rate = Player.movement_yds / Player.movement_t else Player.movement_yds = 0 Player.movement_t = 0 Player.movement_rate = 0 end Player.moving = Player.movement_t >= 1.25 end aura_env.fast = false aura_env.last = frameTime fullUpdate = true end aura_env.LBG = aura_env.LBG or LibStub( "LibCustomGlow-1.0" ) local LBG = aura_env.LBG local jeremy = aura_env.jeremy_update or {} local compression_value = 1000 local spellRaw = function ( name ) local raw = jeremy.raw[ name ] or 0 raw = raw * compression_value return raw end jeremy.raw = jeremy.raw or {} jeremy.rank = {} jeremy.scale = {} local spells = aura_env.spells local actionlist = {} local actionhash = {} local default_action = Player.default_action local rate_time = function() return ( aura_env.base_gm and Player.action_modifier / aura_env.base_gm ) or 1 end aura_env.action_cache = aura_env.action_cache or {} local action_set = function( tbl ) if not tbl.name then return end tbl.cb = tbl.cb or spells[ tbl.name ] tbl.raw = tbl.raw or 0 tbl.ticks = tbl.ticks or 1 tbl.damage = tbl.damage or 0 tbl.healing = tbl.healing or 0 tbl.group_healing = tbl.group_healing or 0 tbl.cooldown = tbl.cooldown or 0 tbl.cd_remains = tbl.cooldown_remains or 0 tbl.start_cd = tbl.starts_cooldown or {} tbl.execute_time = max( 0, tbl.execute_time or 0 ) tbl.cost = tbl.cost or 0 tbl.secondary_cost = tbl.secondary_cost or 0 tbl.t_amp = tbl.t_amp or 1.0 tbl.delay = max( 0, tbl.delay or 0 ) -- Snapshot Channel Information if tbl.cb and tbl.raw > 0 and Player.channel.spellID and Player.channel.spellID == tbl.cb.spellID then Player.channel.action = tbl.cb Player.channel.raw = tbl.raw Player.channel.ticks = tbl.ticks end local raw_comp = tbl.raw / compression_value jeremy.raw[ tbl.name ] = raw_comp > 1 and floor( raw_comp ) or raw_comp if not tbl.background then local deficit = ( Player.primary_resource and Player.primary_resource.deficit ) or 0 local secondary_deficit = ( Player.secondary_resource and Player.secondary_resource.deficit ) or 0 local dpet = tbl.raw / max( 1, 1 + ( tbl.execute_time + tbl.delay ) * rate_time() ) tbl.d_time = dpet - tbl.cost tbl.d_cost = dpet / max( 1, 1 + deficit + tbl.cost ) / max( 1, 1 + secondary_deficit + tbl.secondary_cost ) local index = actionhash[ tbl.name ] or #actionlist+1 actionlist[ index ] = tbl actionhash[ tbl.name ] = index end aura_env.action_cache[ tbl.name ] = tbl end if spec == 0 then return end local targets_tod_range = 0 local targetList = {} local demiseList = {} local healer_targets = {} if fullUpdate then profilerStart() Player.Update() profilerEnd( "Update Unit Info" ) if Player.channel.action then action_set( { type = "channel", name = "channel_remaining", raw = Player.channel.raw or 0, execute_time = Player.channel.remaining + Player.channel.latency, cb = Player.channel.action, }) end local ParseAuras = function( Enemy, unitID ) -- Faux auras aura_env.targetAuras[ unitID ] = {} aura_env.targetAuras[ unitID ][ "priority_check" ] = { amp = Enemy.priority_modifier, expire = frameTime + 3600 } if Player.role == "TANK" then local threat_status = UnitThreatSituation( "player", unitID ) or 0 aura_env.targetAuras[ unitID ][ "threat_check" ] = { amp = ( threat_status < 3 and 2.0 or 1.0 ), expire = frameTime + 3600 } end -- Windwalker Specific if spec == aura_env.SPEC_INDEX[ "MONK_WINDWALKER" ] then -- Mark of the Crane Enemy.auraExists( 228287, function( auraData ) if auraData.sourceUnit == "player" then Player.motc_targets = Player.motc_targets + 1 return true end end ) -- Shadowflame Vulnerability Enemy.auraExists( 411376, function( auraData ) if auraData.sourceUnit == "player" then Player.sfv_targets = Player.sfv_targets + 1 return true end end ) -- Keefer's Skyreach Enemy.auraExists( 393047, function( auraData ) if auraData.sourceUnit == "player" then local expires = auraData.expirationTime if expires == 0 then expires = frameTime + 3600 end -- 0 amplifier for tracking purposes aura_env.targetAuras[ unitID ][ auraData.spellId ] = { amp = 0, expire = expires } return true end end ) -- Fae Exposure / Jadefire Brand Enemy.auraExists( { 356773, 395414 }, function( auraData ) if auraData.sourceUnit == "player" then local expires = auraData.expirationTime if expires == 0 then expires = frameTime + 3600 end Player.jfh_targets = Player.jfh_targets + 1 Player.jfh_dur_total = max( 0, Player.jfh_dur_total + ( expires - frameTime ) ) aura_env.targetAuras[ unitID ][ auraData.spellId ] = { amp = 1.12, expire = expires } return true end end ) end -- Brewmaster Specific if spec == aura_env.SPEC_INDEX[ "MONK_BREWMASTER" ] then -- Weapons of Order Debuff Enemy.auraExists( { 312106, 387179 }, function( auraData ) if auraData.sourceUnit == "player" then local expires = auraData.expirationTime if expires == 0 then expires = frameTime + 3600 end local woo_count = auraData.applications or 0 local woo_amp = 1 + ( 0.08 * woo_count ) aura_env.woo_best = max( aura_env.woo_best, woo_count ) Player.woo_targets = Player.woo_targets + 1 Player.woo_dur_total = max( 0, Player.woo_dur_total + ( expires - frameTime ) ) aura_env.targetAuras[ unitID ][ auraData.spellId ] = { amp = woo_amp, expire = expires } return true end end ) -- Keg Smash Enemy.auraExists( 121253, function( auraData ) if auraData.sourceUnit == "player" then Player.ks_targets = Player.ks_targets + 1 return true end end ) -- Breath of Fire Enemy.auraExists( 123725, function( auraData ) if auraData.sourceUnit == "player" then Player.bof_targets = Player.bof_targets + 1 return true end end ) end -- Generic -- Others defined in Init for id, aura_amp in pairs( aura_env.aura_amps ) do local copies = 0 Enemy.auraExists( id, function( auraData ) local expires = auraData.expirationTime if expires == 0 then expires = frameTime + 3600 end if copies > 0 then aura_env.targetAuras[ unitID ][ id ] = { amp = aura_env.targetAuras[ unitID ][ id ].amp * aura_amp.modifier, expire = min( aura_env.targetAuras[ unitID ][ id ].expires, expires ) } else aura_env.targetAuras[ unitID ][ id ] = { amp = aura_amp.modifier, expire = expires } end copies = copies + 1 if copies >= aura_amp.copies then return true end end ) end end aura_env.combatHealthRemaining = 0 aura_env.targetHealthRemaining = 0 aura_env.in_keystone = select( 10, GetInfo() ) == LE_SCENARIO_TYPE_CHALLENGE_MODE local UpdateTargets = function() local count = 0 local fight_remains = 0 local finalBoss = false aura_env.boss_lockdown = false aura_env.woo_best = 0 Player.motc_targets = 0 Player.jfh_targets = 0 Player.jfh_dur_total = 0 Player.ks_targets = 0 Player.bof_targets = 0 Player.sfv_targets = 0 Player.woo_targets = 0 aura_env.targetAuras = {} healer_targets = {} if spec == aura_env.SPEC_INDEX["MONK_MISTWEAVER"] then -- Skip this loop for efficiency if not MW Spec for it in WA_IterateGroupMembers() do if UnitIsDead( it ) == false and CheckInteractDistance( it, 1 ) then local deficit = UnitHealthMax( it ) - UnitHealth( it ) + UnitGetTotalHealAbsorbs( it ) if deficit > 0 then -- Valid healing target healer_targets[ #healer_targets + 1 ] = { deficit = deficit, unitID = it, } end end end end local targetLimit = 10 local configLimit = aura_env.config.target_limit if configLimit then if configLimit == 1 then targetLimit = 5 elseif configLimit == 2 then targetLimit = 10 elseif configLimit == 3 then targetLimit = 15 else targetLimit = 20 end end local TargetFilter = function() local validTargets = {} local checkTod = spells[ "touch_of_death" ] and Player.getCooldown( "touch_of_death" ) == 0 and aura_env.config.tod_glow > 1 for _, nameplateframe in pairs( GetNamePlates() ) do local unitID = nameplateframe.namePlateUnitToken local unitGUID = UnitGUID( unitID ) if unitGUID == UnitGUID( "target" ) then unitID = "target" end local glowTod = false if aura_env.validTarget( unitID ) then local enemy = aura_env.GetEnemy( unitGUID ) enemy.unitID = unitID enemy.range = aura_env.unitRange( unitID ) -- Update BigWig Intermission Timers local intermission = nil for k, v in pairs( aura_env.BW_intermission_timers ) do local remaining = v.expire - frameTime if v.encounterId ~= aura_env.encounter_id or remaining <= 0 then aura_env.BW_intermission_timers[ k ] = nil elseif UnitGUID( v.unitid ) == unitGUID then intermission = ( intermission and min( intermission, remaining ) ) or remaining end end enemy.intermission = intermission local excluded = ( enemy.priority_modifier == 0 ) if not excluded and enemy.lastHealth then excluded = enemy.lastHealth < 0.01 end if not excluded then excluded = enemy.range > 8 end if not excluded then if enemy.needsFullUpdate then ScanEvents( "UNIT_AURA_FULL_UPDATE", unitID ) end excluded = ( next( enemy.auraExclusions ) ~= nil ) end if excluded then aura_env.targetAuras[unitID] = {} aura_env.targetAuras[unitID]["priority_check"] = { amp = ( unitID == "target" and 0.01 ) or 0, expire = frameTime + 3600 } else -- ToD off CD and option enabled if checkTod then -- Valid ToD Target if ( enemy.healthPct < 0.15 and Player.talent.improved_touch_of_death.ok and aura_env.config.tod_glow < 3 ) or UnitHealth( unitID ) < UnitHealthMax( "player" ) then glowTod = true targets_tod_range = targets_tod_range + 1 end end validTargets[ #validTargets + 1 ] = enemy targetList[ #targetList + 1 ] = enemy.npcid end end if nameplateframe then if glowTod then LBG.PixelGlow_Start( nameplateframe, { 1, 0, 0, 1 } ) else LBG.PixelGlow_Stop( nameplateframe ) end end end if #validTargets > targetLimit then sort( validTargets, function( l, r ) if l.unitID == "target" then return true elseif r.unitID == "target" then return false elseif l.isBoss and not r.isBoss then return true elseif r.isBoss and not l.isBoss then return false elseif l.ttd and r.ttd then return l.ttd > r.ttd else return ( l.lastHealth or 1 ) > ( r.lastHealth or 1 ) end end ) end return validTargets end for _, enemy in pairs( TargetFilter() ) do if not aura_env.boss_lockdown then if enemy.isBoss then aura_env.boss_lockdown = true if not finalBoss and aura_env.in_keystone then local steps = select( 3, GetStepInfo() ) if steps then local _, _, _, _, total, _, _, current = GetCriteriaInfo( steps ) if current then current = tonumber( sub( current, 1, len( current ) - 1 ) ) or 0 if ( current / total ) > 0.99 then finalBoss = true end end end end end end count = count + 1 aura_env.combatHealthRemaining = aura_env.combatHealthRemaining + enemy.healthActual fight_remains = max( enemy.ttd or 0, fight_remains ) local demise = max( 1, floor( enemy.ttd or 1 ) ) demiseList[ demise ] = ( demiseList[ demise ] or 0 ) + 1 if enemy.unitID == "target" then aura_env.targetHealthRemaining = enemy.healthActual Player.react.spellsteal = next( enemy.stealable_auras ) ~= nil end ParseAuras( enemy, enemy.unitID ) if count >= targetLimit then break end end if finalBoss then aura_env.fight_remains = 3600 else if fight_remains > 0 then aura_env.fight_remains = max( 1, floor( fight_remains + 0.5 ) ) else aura_env.fight_remains = 300 end end sort( healer_targets, function( l, r ) return l.deficit > r.deficit end) return max( 1, count ) end if not aura_env.target_update or aura_env.target_update < frameTime - 1 then profilerStart() -- Nameplate iterator aura_env.target_count = UpdateTargets() aura_env.healer_targets = healer_targets profilerEnd( "UpdateTargets()" ) -- Force ST Config Option if aura_env.config.targetting == 2 then aura_env.target_count = 1 end profilerStart() -- Initialize one-time pull data if aura_env.target_count > 1 then aura_env.pull_hash = aura_env.hashEnemyList( targetList ) -- Generate unique pull ID aura_env.pull_data = aura_env.get_pull_data( aura_env.pull_hash ) -- Initialize pull data --aura_env.nextPullListener( aura_env.pull_hash ) end profilerEnd( "Hash Pull Data" ) profilerStart() -- Time based spawn / demise aura_env.raid_events = aura_env.EventTimers( demiseList ) profilerEnd( "Update Event Timers" ) aura_env.target_update = frameTime end end local t_spells = {} for name, action in pairs( spells ) do t_spells[ #t_spells + 1 ] = { name = name, action = action, } end local n_spells = #t_spells if n_spells > 0 and ( fullUpdate or not aura_env.actionlist_update or aura_env.actionlist_update < frameTime - ( 0.5 / n_spells ) )then aura_env.actionlist_update = frameTime debugprofilestart() local cpu_target = aura_env.config.cpu_target or 1.5 -- target CPU processing time in miliseconds if aura_env.rank_cpu_average then cpu_target = cpu_target - aura_env.rank_cpu_average end local process_trees = 5 if aura_env.action_cpu_average then process_trees = max( 1, floor( cpu_target / aura_env.action_cpu_average ) ) end if not InCombatLockdown() then -- limited to 5 out of combat process_trees = min( process_trees, 5 ) end local debug_process_min = nil local debug_process_max = nil if not aura_env.spell_range_min or aura_env.spell_range_min >= n_spells then aura_env.spell_range_min = 1 end aura_env.spell_range_max = ( aura_env.spell_range_min + process_trees ) for spell_it, _spell in ipairs( t_spells ) do local name = _spell.name local action = _spell.action local continue = false if spell_it < aura_env.spell_range_min or spell_it > aura_env.spell_range_max then if aura_env.action_cache[ name ] then action_set( aura_env.action_cache[ name ] ) continue = true end end if not continue then debug_process_min = debug_process_min or spell_it debug_process_max = spell_it local spellID = action.spellID local action_type = action.type if spellID ~= nil then -- Cache AP/SP values -- these sometimes change in PvP combat so we'll cache that as well if not action.pve_ap_value and not Player.is_pvp then action.pve_ap_value = ( type( action.ap ) == "function" and action.ap() or action.ap ) or 0 elseif not action.pvp_ap_value and Player.is_pvp then action.pvp_ap_value = ( type( action.ap ) == "function" and action.ap() or action.ap ) or 0 end if not action.pve_sp_value and not Player.is_pvp then action.pve_sp_value = ( type( action.sp ) == "function" and action.sp() or action.sp ) or 0 elseif not action.pvp_sp_value and Player.is_pvp then action.pvp_sp_value = ( type( action.sp ) == "function" and action.sp() or action.sp ) or 0 end local tooltip = 0 local ticks = ( type( action.ticks ) == "function" and action.ticks() or action.ticks ) or 1 local sp_mod = Player.is_pvp and action.pvp_sp_value or action.pve_sp_value if sp_mod > 0 then tooltip = ticks * ( sp_mod * aura_env.spell_power ) else local composite_attack_power = function( ap_type ) local base_power_mod = 1 local weapon_power = Player.weapon_power.main_hand if IsEquippedItemType( "Two-Hand" ) then if ap_type == "BOTH" then base_power_mod = 0.98 end elseif ap_type == "BOTH" then weapon_power = Player.weapon_power.both elseif ap_type == "OFFHAND" then weapon_power = Player.weapon_power.off_hand elseif ap_type == "NONE" then weapon_power = Player.weapon_power.none end return floor( Player.attack_power + weapon_power + 0.5 ) * base_power_mod end local ap_mod = Player.is_pvp and action.pvp_ap_value or action.pve_ap_value local attack_power = composite_attack_power( action.ap_type ) tooltip = ticks * ( attack_power * ap_mod ) end local action_targets = max( 1, min( 20, ( action.target_count and action.target_count() ) or 1 ) ) local action_delay = 0 local start_cooldown = action.start_cooldown or {} local combo_base = action.combo_base local true_name = gsub( combo_base or name, "_cancel", "" ) local action_cooldown = Player.getBaseCooldown( true_name ) local action_cd_remains = Player.getCooldown( true_name ) if combo_base and not start_cooldown[ combo_base ] then start_cooldown[ #start_cooldown + 1 ] = combo_base end local ready = Player.ready( action ) local execute_time = action.execute_time() local cost = nil local secondary_cost = nil if Player.primary_resource then if not Player.secondary_resource then secondary_cost = 0 end local costTable = GetSpellPowerCost( action.replaces or spellID ) if costTable then for _, costInfo in pairs( costTable ) do if cost and secondary_cost then break end if not cost and costInfo.type == Player.primary_resource.type then cost = costInfo.cost elseif not secondary_cost and costInfo.type == Player.secondary_resource.type then secondary_cost = costInfo.cost end end end cost = cost or 0 secondary_cost = secondary_cost or 0 end local gain = action.chi_gain and action.chi_gain() or 0 -- TODO: Need to rewrite this as generic resource gain somehow local secondary_gain = 0 action.base_gain = gain action.base_cost = cost action.base_secondary_cost = secondary_cost action.base_execute_time = execute_time if not ready then action_set( { type = action_type, name = name, raw = 0, cost = cost, execute_time = execute_time, } ) action.result = nil end if ready and action.skip_calcs then action_set( { type = action_type, name = name, raw = 1, cost = cost, execute_time = execute_time, } ) action.result = { damage = 1, healing = 1, } elseif ready then -- Action is not ready but is also not a background action if not action.background then --and not IsUsableSpell( spellID ) then local usable_in = 0 -- Add resource regeneration to action delay if low on resources if Player.primary_resource and Player.primary_resource.regen > 0 then if action.base_cost > 0 and Player.primary_resource.current < action.base_cost then local resource_delta = action.base_cost - Player.primary_resource.current usable_in = ( resource_delta / Player.primary_resource.regen ) end elseif Player.secondary_resource and Player.secondary_resource.regen > 0 then if secondary_cost > 0 and Player.secondary_resource.current < secondary_cost then local resource_delta = secondary_cost - Player.secondary_resource.current usable_in = ( resource_delta / Player.secondary_resource.regen ) end end -- Add cooldown time to action delay if cooldown is shorter than GCD local cd_after_gcd = action_cd_remains - Player.gcd_remains local charges = GetSpellCharges( spellID ) if cd_after_gcd > 0 and ( not charges or charges == 0 ) then usable_in = max( usable_in, cd_after_gcd ) end action_delay = max( action_delay, usable_in ) -- Check if we're channeling and add channel latency if Player.channel.remaining and action_delay > Player.channel.remaining then local latency = Player.channel.latency or 0 -- TODO: Generic usable during channel? Is this achievable via the API or spell labels? if Player.channel.spellID == 101546 and action.usable_during_sck then latency = 0 end action_delay = max( action_delay, latency ) end end -- Calculate total mitigation from action local actionMitigation = function( action, state ) local state = state or nil -- Stagger local stagger_reduction = 0 if action.reduce_stagger and type( action.reduce_stagger ) == "function" then stagger_reduction = action.reduce_stagger( state ) stagger_reduction = min( stagger_reduction, Player.stagger ) -- Limited to current stagger end -- Mitigation local mitigation = stagger_reduction if action.mitigate and type( action.mitigate ) == "function" then mitigation = mitigation + ( action.mitigate( state ) or 0 ) end return mitigation end -- -------------- local mitigation = actionMitigation( action, nil ) -- -------------- -- Action Multiplier tooltip = tooltip * Player.action_multiplier( action ) -- Cache spec auras -- these sometimes change in PvP so we will cache both if not action.aura_modifier_pve and not Player.is_pvp or not action.aura_modifier_pvp and Player.is_pvp then local total_aura_effect = aura_env.auraEffectForSpell( action.triggerSpell or spellID ) if Player.is_pvp then action.aura_modifier_pvp = total_aura_effect else action.aura_modifier_pve = total_aura_effect end end tooltip = tooltip * ( Player.is_pvp and action.aura_modifier_pvp or action.aura_modifier_pve ) -- Bonus damage and healing not related to ap or sp modifier local bonus_damage = action.bonus_da and action.bonus_da() or 0 local bonus_healing = action.bonus_heal and action.bonus_heal() or 0 -- Damage value local damage = action_type == "damage" and tooltip or 0 damage = damage + bonus_damage -- Self-healing value local healing = action_type == "self_heal" and tooltip or 0 healing = healing + bonus_healing -- Targeted healing value local group_healing = action_type == "smart_heal" and tooltip or 0 group_healing = group_healing + bonus_healing -- Versatility damage = damage * Player.vers_bonus healing = healing * Player.vers_bonus group_healing = group_healing * Player.vers_bonus -- Critical modifiers local crit_damage = 0 local crit_healing = 0 local crit_group_healing = 0 local crit_rate = 0 local crit_mod = 0 if action.may_crit then crit_rate = action.critical_rate and action.critical_rate() or Player.crit_bonus crit_mod = action.critical_modifier and action.critical_modifier() or 1 if Player.is_pvp then local pvp_crit_modifier = Player.spell.pvp_enabled.effectN( 3 ).mod crit_mod = crit_mod * pvp_crit_modifier end local crit_effect = crit_rate * crit_mod crit_damage = damage * crit_effect crit_healing = healing * crit_effect crit_group_healing = group_healing * crit_effect damage = damage + crit_damage healing = healing + crit_healing group_healing = group_healing + crit_group_healing end -- Target Effects local temporary_amplifiers = 1 local temporaryAmplifiers = function( action ) return global_modifier( action, action_delay ) * targetAuraEffect( action, action_delay ) end local target_multiplier = 1 local targetMultiplier = function( action ) local mul = 1 if action_type == "smart_heal" then mul = 0 for t_it = 1, action_targets do local target = aura_env.healer_targets[ t_it ] mul = mul + ( action.target_multiplier and action.target_multiplier( target ) or 1 ) end elseif action.target_multiplier then mul = action.target_multiplier( action_targets ) end return mul end -- Ability triggers ( GotD, Resonant Fists, etc., also used for combos ) local applyTriggerSpells = function( ) local damage_out, healing_out, group_heal_out, mitigate_out = 0, 0, 0, 0 local trigger_gain, trigger_cost, trigger_secondary_cost = 0, 0, 0 local trigger_time, trigger_delay = 0, 0 local trigger_cdr = {} local driver = action local driverName = combo_base or name driver.trigger = driver.trigger or {} driver.tick_trigger = driver.tick_trigger or {} local state_table = {} local trigger_spells = {} local trigger_exists = {} local trigger_pushback = function( spell, enabled, periodic, recursive_callback, stack ) local _driver = recursive_callback and spells[ recursive_callback ] or driver local _driverName = recursive_callback or driverName local _stack = ( stack or _driverName ) .. " -> " .. spell trigger_exists[ spell.."-".._driverName ] = true if _driver.result then -- Allow multiple identical triggers spell = gsub( spell, "%-.*", "" ) if spell and spells[ spell ] and spells[ spell ].result and spellRaw( spell ) > 0 then local _this = {} local callback_stack = {} local stack_driver = nil local triggerReady = function( self, state ) if type( enabled ) == "function" then return enabled( self, state ) end return enabled end local h = 5381 local l = nil for cb in gsub( _stack .. "->", "%s+", "" ):gmatch( "(.-)->" ) do l = ( stack_driver and h ) or nil for c in cb:gmatch( "." ) do h = ( bit.lshift( h, 5 ) + h ) + string.byte( c ) end insert( callback_stack, { name = cb, spell = spells[ cb ] or nil, driverName = stack_driver, result = spells[ cb ] and spells[ cb ].result or nil, } ) stack_driver = cb end _this.ready = triggerReady _this.stack = _stack _this.spell = spell _this.onTick = periodic _this.icd = spells[ spell ].icd or 0 _this.state = { -- Pass driver callbacks to trigger callback_state = state_table[ l ] or { time = execute_time + action_delay, primary = min( Player.primary_resource.max, ( Player.primary_resource.current + gain ) ) - cost, secondary = min( Player.secondary_resource.max, ( Player.secondary_resource.current + secondary_gain ) ) - secondary_cost, buffs = {}, cooldown = { [ true_name ] = max( 0, Player.getBaseCooldown( true_name ) - execute_time ) }, invalid = false, }, callback_stack = callback_stack, callback_name = _driverName, callback = _driver, result = _driver.result, ticks = ( _driver.result.ticks or 1 ) * ( _driver.result.target_count or 1 ), pos = #callback_stack, time = 0, primary = 0, secondary = 0, buffs = {}, cooldown = {}, invalid = false, } state_table[ h ] = _this.state _this.rate = spells[ spell ].trigger_rate or 1 if type( _this.rate ) == "function" then _this.rate = _this.rate( _this.state ) end if _this.rate > 0 then if not periodic then _this.state.count = _this.rate else if _this.icd > 0 then _this.state.count = min( _this.state.ticks, _this.rate * floor( _this.state.result.execute_time / _this.icd ) ) else _this.state.count = _this.rate * _this.state.ticks end end if Player.ready( spells[ _this.spell ], _this.state ) then trigger_spells[ #trigger_spells + 1 ] = _this end end end end end for spell, enabled in pairs( driver.trigger ) do trigger_pushback( spell, enabled, false ) end for spell, enabled in pairs( driver.tick_trigger ) do trigger_pushback( spell, enabled, true ) end -- Recursive triggers local do_recursion = true while ( do_recursion ) do do_recursion = false for _, trigger in pairs( trigger_spells ) do local spell = spells[ trigger.spell ] if spell.trigger then for recursive_trigger, enabled in pairs( spell.trigger ) do if not trigger_exists[ recursive_trigger.."-"..trigger.spell ] then trigger_pushback( recursive_trigger, enabled, false, trigger.spell, trigger.stack ) do_recursion = true end end end if spell.tick_trigger then for recursive_trigger, enabled in pairs( spell.tick_trigger ) do if not trigger_exists[ recursive_trigger.."-"..trigger.spell ] then trigger_pushback( recursive_trigger, enabled, true, trigger.spell, trigger.stack ) do_recursion = true end end end end end -- Setup state position and index tables sort( trigger_spells, function( l, r ) return l.state.pos < r.state.pos end) for _, trigger in pairs( trigger_spells ) do -- TODO: - Rename shadowed variables -- Initialization local state = trigger.state local driver = state.callback local driverName = state.callback_name local driverState = state.callback_state -- Inherit state from Driver if driverState then local copyState = function( e ) state[ e ] = driverState[ e ] end copyState( "primary" ) copyState( "secondary" ) copyState( "time" ) copyState( "buffs" ) copyState( "cooldown" ) end local result = state.result local spell = spells[ trigger.spell ] local spell_result = spell.result local tick_count = state.count local duration = result.execute_time local tick_damage = 0 local tick_healing = 0 local tick_group_heal = 0 local trigger_mitigate = 0 local trigger_damage = 0 local trigger_healing = 0 local trigger_group_heal = 0 local trigger_cdr = {} local basePrimary = state.primary local baseSecondary = state.secondary local baseTime = state.time if not state.invalid then -- Driver is a channeled ability and trigger is not background action if driver.channeled and not spell.background then local latency = Player.channel.latency or 0 -- Currently only relevant instance of this is spinning crane kick but use a generic action variable at some point local use_during_channel = driver.spellID == 101546 and spell.usable_during_sck if use_during_channel then latency = 0 local gcd = driver.gcd( driver, state ) local base_et = driver.execute_time( state ) state.time = state.time - ( base_et - gcd ) end trigger_delay = max( action_delay, latency ) - action_delay end state.time = max( state.time, trigger_delay + trigger_time ) local _, stack = ipairs( state.callback_stack ) if #stack > 1 then local _prev = #stack - 1 local _prev_cb = stack[ _prev ] if _prev_cb and _prev_cb.spell then local _prev_spell = _prev_cb.spell if ( not spell.background or not _prev_spell.background ) and _prev_spell.onExecute then _prev_spell.onExecute( _prev_spell, state ) end end end end -- Check ready state state.invalid = state.invalid or not trigger.ready( spell, state ) -- Check ready state state.invalid = state.invalid or not Player.ready( spell, state ) -- set init trigger CD local trigger_cd_remains = Player.getCooldown( trigger.spell, state ) local trigger_cd = Player.getBaseCooldown( trigger.spell ) if not state.invalid then -- Start Cooldown state.cooldown[ trigger.spell ] = trigger_cd local ticks = spell_result.ticks or 1 trigger_mitigate = actionMitigation( spell, state ) -- Tick Function local ticks_remaining = ticks local tick_time = spell.execute_time( state ) / ticks while ticks_remaining > 0 do local tick_partition = ticks_remaining < 2 and ticks_remaining or 1 -- Trigger is non-background action with execute time if tick_time > 0 and not spell.background then state.time = state.time + ( tick_time * tick_partition ) end tick_damage = ( spell_result.damage or 0 ) / ticks tick_healing = ( spell_result.healing or 0 ) / ticks tick_group_heal = ( spell_result.group_healing or 0 ) / ticks if tick_damage > 0 then local spell_am = Player.action_multiplier( spell ) local am_delta = Player.action_multiplier( spell, state ) if spell_am > 0 then am_delta = am_delta / spell_am end tick_damage = tick_damage * am_delta end trigger_damage = trigger_damage + ( tick_damage * tick_partition ) trigger_healing = trigger_healing + ( tick_healing * tick_partition ) trigger_group_heal = trigger_group_heal + ( tick_group_heal * tick_partition ) if spell.onImpact then for t_idx = 1, ( spell_result.target_count or 1 ) do spell.onImpact( spell, state ) end end ticks_remaining = ticks_remaining - tick_partition end -- Background spell multipliers if spell.background then local spell_target_multiplier = targetMultiplier( spell ) local spell_temporary_amplifiers = temporaryAmplifiers( spell ) trigger_damage = trigger_damage * spell_target_multiplier * spell_temporary_amplifiers trigger_healing = trigger_healing * spell_temporary_amplifiers trigger_group_heal = trigger_group_heal * spell_target_multiplier * spell_temporary_amplifiers end -- Trigger reduces cooldown of spell if spell.reduces_cd then for cdr_spell, cdr_value in pairs( spell.reduces_cd ) do cdr_spell = gsub( cdr_spell, "%-.*", "" ) local cdr = cdr_value if type( cdr ) == "function" then cdr = cdr( spell, state ) end if state.cooldown[ cdr_spell ] then state.cooldown[ cdr_spell ] = max( 0, state.cooldown[ cdr_spell ] - cdr ) end local remaining, total = Player.getCooldown( cdr_spell, state ) if cdr > 0 and remaining > 0 and total > 0 then state.cooldown[ trigger.spell ] = max( 0, state.cooldown[ trigger.spell ] - cdr ) local spell_action = aura_env.spells[ cdr_spell ] if spell_action and not spell_action.background then local mod_rate = aura_env.actionModRate( spell_action ) if mod_rate < 1 then cdr = cdr * ( 1 - mod_rate ) end local id = cdr_spell .. "-010" .. state.pos trigger_cdr[ id ] = ( trigger_cdr[ id ] or 0 ) + cdr end end end end damage_out = damage_out + trigger_damage * tick_count healing_out = healing_out + trigger_healing * tick_count group_heal_out = group_heal_out + trigger_group_heal * tick_count mitigate_out = mitigate_out + trigger_mitigate * tick_count end -- Update cooldown information for chained abilities if action.combo and not spell.background then action_cooldown = max( action_cooldown, trigger_cd ) action_cd_remains = max ( action_cd_remains, trigger_cd_remains ) trigger_delay = max( action_delay, action_cd_remains ) - action_delay start_cooldown[ #start_cooldown + 1] = trigger.spell end -- Update Resources state.primary = state.primary - ( spell.base_cost or 0 ) state.primary = state.primary + ( tick_count * ( spell.chi_gain and spell.chi_gain( state ) or 0 ) ) state.primary = min( state.primary, Player.primary_resource.max ) state.secondary = state.secondary - ( spell.base_secondary_cost or 0 ) --state.secondary = state.secondary + TODO Secondary Gain function state.secondary = min( state.secondary, Player.secondary_resource.max ) -- Update Trigger results trigger_cost = trigger_cost + ( basePrimary - state.primary ) trigger_secondary_cost = trigger_secondary_cost + ( baseSecondary - state.secondary ) trigger_time = trigger_time + ( state.time - baseTime ) end return { cost = trigger_cost, secondary_cost = trigger_secondary_cost, damage = damage_out, self_healing = healing_out, mitigation = mitigate_out, group_healing = group_heal_out, execute_time = trigger_time, delay = trigger_delay, cdr = trigger_cdr, } end local resultMatrix = {} local timeMatrix = { action_delay } local targetMatrix = { [ action_delay ] = action_targets } local triggerResults = {} local cacheResults = function( t ) resultMatrix[ t ] = { amplifier = temporary_amplifiers, callback = action, ticks = ticks, target_count = action_targets, damage = damage, self_healing = healing, group_healing = group_healing, mitigation = mitigation, crit_rate = crit_rate, crit_mod = crit_mod, critical_damage = crit_damage, critical_healing = crit_healing, critical_group_healing = crit_group_healing, execute_time = execute_time, delay = action_delay, trigger_results = triggerResults, cost = cost, secondary_cost = secondary_cost, reduce_cd = {}, } end if not action.background then -- Raid Events if action_type == "damage" and name ~= "touch_of_death" and action_cooldown > 0 and action.target_multiplier and ( not Player.channel.spellID or Player.channel.spellID ~= spellID ) then for _, raid_event in pairs( aura_env.raid_events ) do local count = raid_event.count local t = floor( raid_event.adds_in ) if count > action_targets and action_cooldown > t and t > action_delay then timeMatrix[ #timeMatrix + 1 ] = t targetMatrix[ t ] = count end end end local base_damage = damage local base_healing = healing local base_ghealing = group_healing for _, delay in pairs( timeMatrix ) do action_delay = delay action_targets = targetMatrix[ delay ] -- Target Multiplier target_multiplier = targetMultiplier( action ) -- Target Auras temporary_amplifiers = temporaryAmplifiers( action ) damage = base_damage * target_multiplier * temporary_amplifiers healing = base_healing * temporary_amplifiers group_healing = base_ghealing * target_multiplier * temporary_amplifiers -- Expel Harm -- TODO: Move to post processor if name == "expel_harm" then -- Only Healing config option for Brewmaster if spec ~= 1 or aura_env.config.eh_mode ~= 2 then local eh_damage = max( 1, 0.1 * healing ) damage = damage + eh_damage end end triggerResults = applyTriggerSpells( ) cacheResults( action_delay ) end else cacheResults( action_delay ) end local tmpD = nil local resultIdx = 0 local adjusted = 0 for idx, results in pairs( resultMatrix ) do results.result_base = { } for k, v in pairs( results ) do results.result_base[ k ] = v end -- Post-processing effects aura_env.actionPostProcessor( results ) -- Trigger effects for k, v in pairs( results.trigger_results ) do if results[ k ] then results[ k ] = results[ k ] + v end end if results.trigger_results.cdr then for s, r in pairs( results.trigger_results.cdr ) do results.reduce_cd[ s ] = r end end -- Healing Caps results.self_healing = min( results.self_healing, Player.health_deficit ) results.self_healing = max( results.self_healing, 0 ) local groupdeficit = 0 for t_it = 1, action_targets do groupdeficit = groupdeficit + ( aura_env.healer_targets[ t_it ] and aura_env.healer_targets[ t_it ].deficit or 0 ) end results.group_healing = min( results.group_healing, groupdeficit ) results.group_healing = max( results.group_healing, 0 ) -- Damage Caps if results.target_count == 1 and aura_env.targetHealthRemaining > 0 then results.damage = min ( aura_env.targetHealthRemaining, results.damage ) end if aura_env.combatHealthRemaining > 0 then results.damage = min ( aura_env.combatHealthRemaining, results.damage ) end -- ----------------------------------------------------- -- Adjust damage / heal value based on spec roles local D = results.damage if Player.role ~= "DAMAGER" or not IsInGroup() then D = ( D * 0.5 ) + results.self_healing if Player.role == "TANK" then D = D + results.mitigation else D = D + results.group_healing end end results.adjusted = D D = D / max( 1, 1 + results.execute_time + results.delay ) if not tmpD or tmpD < D then tmpD = D resultIdx = idx end end local result = resultMatrix[ resultIdx ] adjusted = result.adjusted or 0 cost = result.cost secondary_cost = result.secondary_cost ticks = result.ticks action_targets = result.target_count damage = result.damage healing = result.self_healing group_healing = result.group_healing mitigation = result.mitigation crit_rate = result.crit_rate crit_mod = result.crit_mod crit_damage = result.critical_damage crit_healing = result.critical_healing crit_group_healing = result.critical_group_healing execute_time = result.execute_time action_delay = result.delay temporary_amplifiers = result.amplifier action.result = result.result_base for s, v in pairs( result.reduce_cd ) do action.reduces_cd[ s ] = v end -- Cache curent player amplifier if name == default_action then Player.action_modifier = temporary_amplifiers end -- ----------------------------------------------------- -- Generic Brew CDR if spec == aura_env.SPEC_INDEX["MONK_BREWMASTER"] and action.brew_cdr then local brew_cdr = action.brew_cdr or 0 local brew_list = { "purifying_brew", "celestial_brew", "fortifying_brew", "black_ox_brew" } if type( brew_cdr ) == "function" then brew_cdr = brew_cdr( action ) end if brew_cdr > 0 then -- initialize table action.reduces_cd = action.reduces_cd or {} for _, brew in pairs ( brew_list ) do if spells[brew] and spells[brew].spellID then -- 000 is a unique identifier for general brew cdr -- this allows usage of both brew_cdr and reduces_cd for brew spells action.reduces_cd[brew.."-000"] = brew_cdr end end end end -- CDR if action.reduces_cd then local cdr_value = 0 local cdr_cost = 0 local cdr_time = 0 for spell, value in pairs( action.reduces_cd ) do local useResult = find( spell, "-010" ) spell = gsub( spell, "%-.*", "" ) local cdr = value local total_cdr = 0 if type(cdr) == "function" then cdr = cdr( action ) end local spell_action = spells[ spell ] local base_cd = Player.getBaseCooldown( spell ) if base_cd > 1 then if spell_action and cdr > 0 then if useResult then total_cdr = cdr else local timeLeft = Player.getCooldown( spell ) if timeLeft > 0 then total_cdr = min( cdr, timeLeft - execute_time ) local mod_rate = aura_env.actionModRate( spell_action ) if mod_rate < 1 then total_cdr = total_cdr * ( 1 - mod_rate ) end end end end if total_cdr > 0 then local spell_raw = spellRaw( spell ) local spell_time = spell_action.time_total or spell_action.execute_time() if spell_raw > adjusted then spell_raw = spell_raw - adjusted cdr_value = cdr_value + ( total_cdr / base_cd * spell_raw ) cdr_cost = cdr_cost + ( max( 0, ( spell_action.cost_total or 0 ) ) * total_cdr / base_cd ) cdr_time = cdr_time + ( spell_time * total_cdr / base_cd ) end end end end adjusted = adjusted + cdr_value cost = cost + cdr_cost execute_time = execute_time + cdr_time end -- Action has increased recharge rate local actionModRate = aura_env.actionModRate( action ) if action_cd_remains == 0 and action_cooldown > 0 and actionModRate < 1 then local duration = action_cooldown local recharge_cdr = min( duration, max( 0, action_cooldown - execute_time ) ) * ( 1 - actionModRate ) if recharge_cdr > 0 then local recast = 1 + ( recharge_cdr / action_cooldown ) adjusted = adjusted * recast cost = cost * recast execute_time = execute_time * recast end end -- Resource Gain if Player.primary_resource then if Player.primary_resource.regen > 0 then gain = gain + ( Player.primary_resource.regen * ( execute_time + action_delay ) ) end gain = min( Player.primary_resource.deficit, gain ) if aura_env.fight_remains > 5 then if Player.secondary_resource then if Player.secondary_resource.regen > 0 then secondary_gain = secondary_gain + ( Player.secondary_resource.regen * ( execute_time + action_delay ) ) end secondary_cost = secondary_cost - min( Player.primary_resource.deficit, secondary_gain ) end end end cost = cost - gain secondary_cost = secondary_cost - secondary_gain action.cost_total = cost action.time_total = execute_time action_set( { type = action_type, name = name, raw = adjusted, ticks = ticks, cost = cost, secondary_cost = secondary_cost, damage = damage, healing = healing, group_healing = group_healing, cooldown = action_cooldown, cooldown_remains = action_cd_remains, starts_cooldown = start_cooldown, delay = action_delay, base_cost = action.base_cost or 0, execute_time = execute_time, background = action.background, t_amp = temporary_amplifiers, combo_base = combo_base, }) end end end -- continue end aura_env.spell_range_min = aura_env.spell_range_min + process_trees local action_cpu_time = debugprofilestop() local actions_current = debug_process_max - debug_process_min local action_cpu_avg = action_cpu_time / actions_current if aura_env.action_cpu_average then aura_env.action_cpu_average = ( aura_env.action_cpu_average + action_cpu_avg ) / 2 else aura_env.action_cpu_average = action_cpu_avg end if action_cpu_time > 0 and profiler_enabled then aura_env.profiler_out[ "Update ActionList" ] = action_cpu_time end end if fullUpdate and next( actionlist ) ~= nil then -- Sort and rank candidate actions -- ----------------------------------------------------------- local t = min( 5, aura_env.fight_remains ) local a = Player.action_modifier local c = 0 local o = 0 local s = 0 local n = #actionlist local ow = {} if n > 0 then debugprofilestart() local scale_mode = 1 local hashed = {} local function sortActionList( list ) local maximum_sequence = 5 local minimum_step = 1 local sequence_n = 0 local sequence_t = {} local previous_s = s local non_op = false local pool = false o = global_modifier( default_action, t, false ) * targetAuraEffect( default_action, t ) if o > a then pool = true else if o < a then local bi_t = t / 2 local bi_o = global_modifier( default_action, bi_t, false ) * targetAuraEffect( default_action, bi_t ) if bi_o < a then t = bi_t end end end if not pool then -- Sort actions by time sort( list, function( l, r ) return l.d_time > r.d_time end) local validActions = {} for i = 1, n do local action = list[ i ] if not action or not action.cb or not action.cb.spellID or action.background or action.raw <= 0 or action.delay > t or not IsSpellKnown( action.cb.replaces or action.cb.spellID ) then list[ i ] = nil validActions[ i ] = nil else validActions[ i ] = action hashed[ i ] = list[ i ] end end if not next( list ) then return {} end while true do local delta_s = s - previous_s if non_op or s >= t or c <= 0 or ( delta_s > 0 and delta_s < minimum_step ) then break end previous_s = s local n_actions = #validActions if n_actions <= 1 then return list end non_op = true for j, action in pairs( validActions ) do local cd_remaining = ( ow[ action.name ] or action.cd_remains ) - s if action.cost <= c and cd_remaining <= 0 and action.delay <= ( t - s ) then non_op = false sequence_n = sequence_n + 1 sequence_t[ #sequence_t + 1 ] = action.name s = s + action.execute_time c = c - action.cost if s >= t or action.execute_time == 0 or action.cost <= 0 or sequence_n >= maximum_sequence then Player.action_sequence = sequence_t return list end if c <= 0 then break end ow[ action.name ] = action.cooldown for _, v in pairs( action.start_cd ) do local trigger = hashed[ actionhash[ v ] ] if trigger then ow[ trigger.name ] = trigger.cooldown end end elseif cd_remaining > t or action.delay > t then validActions[ j ] = nil end end end end -- Adjust by cost if Player.primary_resource then sort( list, function( l, r ) return l.d_cost > r.d_cost end ) local _, start = next( list ) Player.action_sequence = next( sequence_t ) ~= nil and sequence_t or { start.name } scale_mode = 0 end return list end actionlist = sortActionList( actionlist ) local debug_out = {} local action_debug = false for k, v in pairs( actionlist ) do if not v.raw or v.raw == 0 then jeremy.rank[ v.name ] = 0 elseif v.base_cost and Player.primary_resource and v.base_cost > Player.primary_resource.current then jeremy.rank[ v.name ] = 0 else local action_name = gsub( v.combo_base and v.combo_base or v.name, "_cancel", "" ) jeremy.rank[ action_name ] = jeremy.rank[ action_name ] or k jeremy.scale[ action_name ] = jeremy.scale[ action_name ] or ( scale_mode == 1 and v.d_time or v.d_cost ) if action_debug and k <= 5 then debug_out[ k ] = { name = v.name, dpet = v.d_time, raw = v.raw, cost = v.cost, s_cost = v.secondary_cost, execute_time = v.execute_time, delay = v.delay, } end end end if next( debug_out ) ~= nil then ScanEvents( "JEREMY_DEBUG_PRIORITY", debug_out ) end local rank_cpu_time = debugprofilestop() local actions_current = n local rank_cpu_avg = rank_cpu_time / actions_current if aura_env.rank_cpu_average then aura_env.rank_cpu_average = ( aura_env.rank_cpu_average + rank_cpu_avg ) / 2 else aura_env.rank_cpu_average = rank_cpu_avg end if rank_cpu_time > 0 and profiler_enabled then aura_env.profiler_out[ "Rank Candidate Actions" ] = rank_cpu_time end end -- ----------------------------------------------------------- -- Ability options jeremy.boss_lockdown = aura_env.boss_lockdown jeremy.fight_remains = aura_env.fight_remains jeremy.target_ttd = aura_env.target_ttd if aura_env.target_count > 1 and targets_tod_range == 1 and aura_env.target_ttd <= 1 then jeremy.force_tod = true else jeremy.force_tod = false end local woo_buff = ( spec == aura_env.SPEC_INDEX[ "MONK_BREWMASTER" ] and Player.findAura( 387184 ) ) or nil if woo_buff and woo_buff.remaining <= ( 0.25 + Player.gcd_duration ) * ( 4 - aura_env.woo_best ) then jeremy.woo_prio = true else jeremy.woo_prio = false end -- end of ability options -- ------------------------------------------------------------- -- Passed Configuration Options jeremy.options = {} jeremy.is_beta = Player.is_beta() jeremy.options.limit = aura_env.config.limit or 5 jeremy.options.scaling = aura_env.config.scaling_option or 1 jeremy.options.inverse = ( aura_env.config.inverse == 2 ) or false jeremy.options.hold_sef = aura_env.config.hold_sef or 1 aura_env.jeremy_update = jeremy ScanEvents( "JEREMY_UPDATE", jeremy ) return true end return false end if event == "ENCOUNTER_START" then aura_env.encounter_id = tostring( ... ) return false elseif event == "ENCOUNTER_END" then aura_env.encounter_id = nil return false end if event == "JEREMY_STARTBAR" or event == "RELOE_SPELLCD_STATE_UPDATE" then local key, time, spell, srcGUID if event == "RELOE_SPELLCD_STATE_UPDATE" then spell, srcGUID, key, time = ... if not ( spell and time ) then return end else key = select( 3, ... ) time = select( 5, ... ) end key = tostring( key ) if key and time then local config = aura_env.bw_config[ key ] if config and config.enabled then local expirationTime = frameTime + time local config_type = config.type or "ERROR" local t_key = (srcGUID or "SB").."-"..key if config_type == "ADD_SPAWN" then aura_env.BW_add_timers[ t_key ] = { key = key, expire = expirationTime, count = config.count or 0, encounterId = aura_env.encounter_id or -1, srcGUID = srcGUID, type = config_type, } elseif config_type == "INTERMISSION" then aura_env.BW_intermission_timers[ t_key ] = { key = key, expire = expirationTime, unitid = config.unitid or "boss1", encounterId = aura_env.encounter_id or -1, srcGUID = srcGUID, type = config_type, } elseif config_type == "TANKBUSTER" then local affects = config.affects or 1 if affects == 1 and Player.role == "TANK" or affects == 2 and Player.role ~= "TANK" or affects == 3 then -- Send message to Raid Ability Timeline if event == "RELOE_SPELLCD_STATE_UPDATE" then local Enemy = aura_env.GetEnemy( srcGUID ) ScanEvents( "JEREMY_TIMELINE_UPDATE", spell, srcGUID, key, time, Enemy.marker ) end aura_env.BW_buster_timers [ t_key ] = { key = key, expire = expirationTime, damage_type = config.damage_type or 1, encounterId = aura_env.encounter_id or -1, srcGUID = srcGUID, type = config_type, } end end end end return false end if event == "UNIT_SPELLCAST_SUCCEEDED" then local _, _, spellID = ... if spellID == 137639 then aura_env.sef_fixate = nil elseif spellID == 221771 then local dstGUID = UnitGUID( "target" ) aura_env.sef_fixate = dstGUID aura_env.last_fixate_bonus = aura_env.forwardModifier( aura_env.spells["tiger_palm"], 1 ) end if spellID and aura_env.combo_strike[spellID] then if Player.last_combo_strike ~= spellID then Player.last_combo_strike = spellID aura_env.fast = true end end return false end if event == "COMBAT_RATING_UPDATE" or event == "PLAYER_ENTERING_WORLD" then aura_env.initGear() end if event == "TRAIT_CONFIG_UPDATED" or event == "PLAYER_SPECIALIZATION_CHANGED" or event == "PLAYER_ENTERING_WORLD" then aura_env.initSpecialization() end if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_REGEN_ENABLED" then aura_env.updateDB() Combat = { avg_level = 0, damage_by_level = 0, damage_taken = 0, damage_taken_avoidable = 0, damage_taken_unavoidable = 0, recent_damage = {}, } aura_env.CURRENT_MARKERS = {} end -- Everything below returns false -- ------------------------------ if event == "UNIT_HEALTH" or event == "UNIT_ABSORB_AMOUNT_CHANGED" then local unitID = ... local unitGUID = UnitGUID( unitID ) if aura_env.validTarget( unitID ) then local Enemy = aura_env.GetEnemy( unitGUID ) Enemy.healthActual = UnitHealth( unitID ) - ( ( aura_env.earlyDeath[ Enemy.npcid ] or 0 ) * UnitHealthMax( unitID ) ) + UnitGetTotalAbsorbs( unitID ) Enemy.healthPct = min( 1, Enemy.healthActual / UnitHealthMax( unitID ) ) if Enemy.lastHealth and Enemy.lastSeen then local difference = Enemy.lastHealth - Enemy.healthPct if difference > 0 then local elapsed = frameTime - Enemy.lastSeen if not Enemy.n or Enemy.n == 0 then Enemy.rate = difference / elapsed Enemy.n = 1 else local samples = min( Enemy.n, 9 ) local newRate = Enemy.rate * samples + ( difference / elapsed ) Enemy.n = samples + 1 Enemy.rate = newRate / Enemy.n end end end Enemy.lastHealth = Enemy.healthPct Enemy.lastSeen = frameTime if Enemy.rate then Enemy.ttd = Enemy.healthPct / Enemy.rate if Enemy.intermission then Enemy.ttd = min( Enemy.ttd, Enemy.intermission ) end if unitGUID == UnitGUID( "target" ) then aura_env.target_ttd = max( 1, floor( Enemy.ttd + 0.5 ) ) aura_env.taret_abs = UnitGetTotalAbsorbs( unitID ) or 0 end end end return false end if event == "NAME_PLATE_UNIT_ADDED" or event == "NAME_PLATE_UNIT_UPDATED" or event == "UPDATE_MOUSEOVER_UNIT" then local unitID, unitGUID if event == "UPDATE_MOUSEOVER_UNIT" then -- Only use this event if there is no nameplate available -- and there is a valid mouseover unitID = "mouseover" unitGUID = UnitGUID( "mouseover" ) if GetNamePlateForUnit( "mouseover" ) then return end else unitID = ... unitGUID = ( unitID and UnitGUID( unitID ) ) or nil end if not unitGUID then return end if UnitExists( unitID ) then local Enemy = aura_env.GetEnemy( unitGUID ) if not Enemy.isBoss then if unitGUID == UnitGUID( "boss1" ) or unitGUID == UnitGUID( "boss2" ) or unitGUID == UnitGUID( "boss3" ) or unitGUID == UnitGUID( "boss4" ) or unitGUID == UnitGUID( "boss5" ) then Enemy.isBoss = true end end local oldMarker = Enemy.marker Enemy.marker = GetRaidTargetIndex( unitID ) Enemy.level = UnitLevel( unitID ) Enemy.priority_modifier = aura_env.npc_priority[ Enemy.npcid ] or 1.0 local am_level = aura_env.config.automarker_enable local am_enable = am_level == 2 or ( am_level == 3 and Player.role == "TANK" ) or ( am_level == 4 and Player.leader ) if aura_env.AUTOMARKER and am_enable then local value = aura_env.AUTOMARKER[ UnitName( unitID ) ] or aura_env.AUTOMARKER[ Enemy.npcid ] if value then local mark = find( value, "MARK" ) ~= nil Enemy.interruptTarget = find( value, "INTERRUPT" ) ~= nil Enemy.stunTarget = find( value, "STUN" ) ~= nil local l, h = 1, 6 if find( value, "KILL" ) then l, h = 7, 8 end if Enemy.marker then aura_env.CURRENT_MARKERS[ Enemy.marker ] = unitGUID elseif mark == true then local marks_available = false for m = h, l, -1 do if aura_env.CURRENT_MARKERS[ m ] == nil then marks_available = true break end end for m = h, l, -1 do -- We need a mark, can we steal one? if not marks_available and aura_env.CURRENT_MARKERS[ m ] then local marked_enemy = aura_env.GetEnemy( aura_env.CURRENT_MARKERS[ m ] ) -- Do not steal from enemy in combat if not marked_enemy.inCombat then if Enemy.inCombat or Enemy.range < marked_enemy.range then aura_env.CURRENT_MARKERS[ m ] = nil end end end if aura_env.CURRENT_MARKERS[ m ] == nil then SetRaidTarget( unitID, m ) aura_env.CURRENT_MARKERS[ m ] = unitGUID Enemy.marker = m break end end end end end if Enemy.marker ~= oldMarker then ScanEvents( "JEREMY_MARKER_CHANGED", unitGUID, Enemy.marker ) end end return false end if event == "UNIT_AURA" or event == "UNIT_AURA_FULL_UPDATE" then local unitID, updateInfo = ... local unitGUID = UnitGUID( unitID ) if unitID == "player" or aura_env.validTarget( unitID ) then local Unit = ( unitID == "player" and Player ) or aura_env.GetEnemy( unitGUID ) local parseAuraData = function( auraData ) auraData.name = gsub( lower( auraData.name ), "%s+", "_" ) auraData.stacks = auraData.applications or 0 auraData.duration = auraData.duration or 0 return auraData end if event == "UNIT_AURA_FULL_UPDATE" then if not Unit.lastFullUpdate or Unit.lastFullUpdate < frameTime - aura_env.update_rate then Unit.auraExclusions = {} Unit.auraDataByInstance = {} Unit.auraInstancesByID = {} -- Monk Diffuse Magic if Unit == Player then Unit.diffuse_auras = {} Unit.diffuse_reflects = {} else Unit.stealable_auras = {} end local auraSlots = { UnitAuraSlots( unitID, "HELPFUL|HARMFUL" ) } for slot in pairs( auraSlots ) do local auraData = GetAuraDataBySlot( unitID, slot ) if auraData then local instanceID = auraData.auraInstanceID local spellId = auraData.spellId if aura_env.auraExclusions[ spellId ] then Unit.auraExclusions[ instanceID ] = spellId end -- Monk Diffuse Magic if Unit == Player then local valid_diffuse = aura_env.diffuse_options[ spellId ] if valid_diffuse and valid_diffuse.enabled then Unit.diffuse_auras[ instanceID ] = true if valid_diffuse.reflect then Unit.diffuse_reflects[ instanceID ] = true end end else if auraData.isStealable then Unit.stealable_auras[ instanceID ] = true end end Unit.auraInstancesByID[ spellId ] = Unit.auraInstancesByID[ spellId ] or {} Unit.auraInstancesByID[ spellId ][ instanceID ] = true Unit.auraDataByInstance[ instanceID ] = parseAuraData( auraData ) end end Unit.needsFullUpdate = false Unit.lastFullUpdate = frameTime end else if updateInfo.isFullUpdate then Unit.needsFullUpdate = true else if updateInfo.addedAuras then for _, auraData in pairs( updateInfo.addedAuras ) do local instanceID = auraData.auraInstanceID local spellId = auraData.spellId if aura_env.auraExclusions[ spellId ] then Unit.auraExclusions[ instanceID ] = spellId end -- Monk Diffuse Magic if Unit == Player then local valid_diffuse = aura_env.diffuse_options[ spellId ] if valid_diffuse and valid_diffuse.enabled then Unit.diffuse_auras[ instanceID ] = true if valid_diffuse.reflect then Unit.diffuse_reflects[ instanceID ] = true end end else if auraData.isStealable then Unit.stealable_auras[ instanceID ] = true end end Unit.auraInstancesByID[ spellId ] = Unit.auraInstancesByID[ spellId ] or {} Unit.auraInstancesByID[ spellId ][ instanceID ] = true Unit.auraDataByInstance[ instanceID ] = parseAuraData( auraData ) end end if updateInfo.updatedAuraInstanceIDs then for _, instanceID in pairs( updateInfo.updatedAuraInstanceIDs ) do local auraData = GetAuraDataByAuraInstanceID( unitID, instanceID ) if auraData then auraData.name = gsub( lower( auraData.name ), "%s+", "_" ) auraData.stacks = auraData.applications or 0 local spellId = auraData.spellId Unit.auraInstancesByID[ spellId ] = Unit.auraInstancesByID[ spellId ] or {} Unit.auraInstancesByID[ spellId ][ instanceID ] = true Unit.auraDataByInstance[ instanceID ] = parseAuraData( auraData ) end end end if updateInfo.removedAuraInstanceIDs then for _, instanceID in pairs( updateInfo.removedAuraInstanceIDs ) do Unit.auraExclusions[ instanceID ] = nil -- Monk Diffuse Magic if Unit == Player then Unit.diffuse_auras[ instanceID ] = nil Unit.diffuse_reflects[ instanceID ] = nil else Unit.stealable_auras[ instanceID ] = nil end if Unit.auraDataByInstance[ instanceID ] then local spellId = Unit.auraDataByInstance[ instanceID ].spellId Unit.auraInstancesByID[ spellId ][ instanceID ] = nil Unit.auraDataByInstance[ instanceID ] = nil end end end end end end return false end if event == "UNIT_THREAT_LIST_UPDATE" or event == "NAME_PLATE_UNIT_REMOVED" then local unitID = ... local unitGUID = UnitGUID( unitID ) if not unitGUID then return end if not UnitExists( unitID ) then aura_env.ResetEnemy( unitGUID ) else local Enemy = aura_env.GetEnemy( unitGUID ) if UnitAffectingCombat( unitID ) then Enemy.inCombat = true elseif Enemy.inCombat then local unitInCombat = false for it in WA_IterateGroupMembers() do if UnitThreatSituation( it, unitID ) then unitInCombat = true break end end if not unitInCombat then aura_env.ResetEnemy( unitGUID ) end end if Enemy.inCombat and not Enemy.combatStart then Enemy.combatStart = frameTime end end ScanEvents( "NAME_PLATE_UNIT_UPDATED", unitID ) return false end if event == "COMBAT_LOG_EVENT_UNFILTERED" then local LogDamage = function (_, eventtype, _, srcGUID, srcName, srcFlags, _, dstGUID, ...) if eventtype == nil or eventtype == "ENVIRONMENTAL_DAMAGE" or not find( eventtype, "_DAMAGE" ) then return false end local enemyGUID = nil if dstGUID == UnitGUID( "player" ) then enemyGUID = srcGUID else local mask = COMBATLOG_OBJECT_AFFILIATION_MASK local bitfield = bit_band( srcFlags, mask ) if srcGUID == UnitGUID( "player" ) or srcGUID == UnitGUID( "pet" ) or bitfield == COMBATLOG_OBJECT_AFFILIATION_MINE then enemyGUID = dstGUID end end if enemyGUID == nil then return false end local Enemy = aura_env.GetEnemy( enemyGUID ) local spellID, spellSchool, amount if eventtype == "SWING_DAMAGE" then _, _, _, amount = ... else _, _, _, spellID, _, spellSchool, amount = ... end spellSchool = spellSchool or 1 spellID = spellID or 0 if amount == nil or type( amount ) ~= "number" or amount == 0 then return false end if srcGUID == enemyGUID then if dstGUID == UnitGUID( "player" ) and Enemy.level > 0 then Combat.damage_by_level = Combat.damage_by_level + ( Enemy.level * amount ) Combat.damage_taken = Combat.damage_taken + amount Combat.avg_level = Combat.damage_by_level / Combat.damage_taken; Combat.recent_damage[ #Combat.recent_damage + 1 ] = { amount = amount, expire = frameTime + aura_env.RECENT_DURATION } if spellSchool == 1 and not find( eventtype, "PERIODIC" ) then Combat.damage_taken_avoidable = Combat.damage_taken_avoidable + amount else Combat.damage_taken_unavoidable = Combat.damage_taken_unavoidable + amount end end else -- Not pet damage if srcGUID == UnitGUID( "player" ) then -- spell tracking ... if spellID and aura_env.pull_hash ~= "" then aura_env.coneTickListener( spellID, enemyGUID ) end end end end local LogDeath = function (_, eventtype, _, srcGUID, _, _, _, dstGUID, ...) if eventtype == "UNIT_DIED" or eventtype == "UNIT_DESTROYED" or eventtype == "UNIT_DISSIPATES" then aura_env.ResetEnemy( dstGUID, true ) end end if InCombatLockdown() then LogDamage( CombatLogGetCurrentEventInfo() ) LogDeath( CombatLogGetCurrentEventInfo() ) end return false end return false end
Leave a Comment