-- SETTINGS START -- how often it should check if a spell is usable (in seconds) -- this could be subject to adjustment AutoReactiveClass_UpdateDelay = 0.5; -- SETTINGS END -- VARIABLES START AQ_AutoReactiveClass_Options = { }; AutoReactiveClass_LastUpdate = 0; AutoReactiveClass_LastId = nil; -- SETTINGS END -- FUNCTIONS START -- entry to add to the ActionQueue. Prepared with some concern. AutoReactiveClass_Queue_Entry = { id = AUTOREACTIVECLASS_ACTIONQUEUE_ID; spellId = nil; -- not known until we first try to cast it shouldExecuteFunc = ActionQueue_ShouldExecuteFunction_ASAP; executeFunc = ActionQueue_ExecuteFunction_Spell; name = AUTOREACTIVECLASS_ACTIONQUEUE_ID; }; function AutoReactiveClass_Queue_Interrupt_ShouldExecuteFunction(entry) if ( not ActionQueue_ShouldExecuteFunction_SpellCooldown(entry) ) then return false; end if ( not UnitExists("target") ) then ActionQueue_RemoveAction(entry.id); return false; end local name = UnitName("target"); if ( name ~= entry.caster ) then ActionQueue_RemoveAction(entry.id); return false; elseif ( ActionQueue_UnitIsStunned("target") ) then ActionQueue_RemoveAction(entry.id); return false; elseif ( ActionQueue_UnitIsSilenced("target") ) then ActionQueue_RemoveAction(entry.id); return false; end return true; end function AutoReactiveClass_Queue_Interrupt_ExecuteFunction(entry) local result, r1, r2, r3 = ActionQueue_ExecuteFunction_Spell(entry); entry.spellId = nil; return result, r1, r2, r3; end -- entry to add to the ActionQueue. Prepared with some concern. AutoReactiveClass_Queue_Interrupt_Entry = { id = AUTOREACTIVECLASS_ACTIONQUEUE_INTERRUPT_ID; spellId = nil; -- not known until we first try to cast it shouldExecuteFunc = AutoReactiveClass_Queue_Interrupt_ShouldExecuteFunction; executeFunc = AutoReactiveClass_Queue_Interrupt_ExecuteFunction; priority = ACTIONQUEUE_HIGHEST_PRIORITY; name = AUTOREACTIVECLASS_ACTIONQUEUE_INTERRUPT_ID; }; function AQ_AutoReactiveClass_OnLoad() if ( not AQ_AutoReactiveClass_Khaos() ) then AQ_AutoReactiveClass_Cosmos(); end -- make sure we get to know when the client variables are all loaded in and pretty local frame = AQ_AutoReactiveClassFrame; frame:RegisterEvent("VARIABLES_LOADED"); frame:RegisterEvent("SPELLS_CHANGED"); frame:RegisterEvent("UPDATE_BONUS_ACTIONBAR"); frame:RegisterEvent("UNIT_HEALTH"); if ( EnemySpellDetector_AddListener ) then EnemySpellDetector_AddListener(EnemySpellDetector_Type_BeginCast, "AQ_AutoReactiveClass_HandleBeginAction_Cast"); EnemySpellDetector_AddListener(EnemySpellDetector_Type_BeginPerform, "AQ_AutoReactiveClass_HandleBeginAction"); EnemySpellDetector_AddListener(EnemySpellDetector_Type_Cast, "AQ_AutoReactiveClass_HandleDoAction"); EnemySpellDetector_AddListener(EnemySpellDetector_Type_Perform, "AQ_AutoReactiveClass_HandleDoAction"); end end function AQ_AutoReactiveClass_HandleBeginAction_Cast(performer, action, target, p1, p2, p3, p4, p5) return AQ_AutoReactiveClass_HandleBeginAction(performer, action, target, EnemySpellDetector_Type_Cast, p1, p2, p3, p4, p5); end function AQ_AutoReactiveClass_HandleBeginAction_Perform(performer, action, target, p1, p2, p3, p4, p5) return AQ_AutoReactiveClass_HandleBeginAction(performer, action, target, EnemySpellDetector_Type_Perform, p1, p2, p3, p4, p5); end function AQ_AutoReactiveClass_HandleDoAction(performer, action, target, p1, p2, p3, p4, p5) if ( not AQ_AutoReactiveClass_Options.removeInterruptIfSpellCast ) then return false; end if ( not UnitExists("target") ) then return false; end if ( not UnitIsEnemy("target", "player") ) then return false; end local name = UnitName("target"); if ( not name ) or ( name == "" ) then return false; end local dequeue = false; if ( performer ) or ( performer == name ) then dequeue = true; end if ( target ) or ( target == name ) then dequeue = true; end if ( dequeue ) and ( ActionQueue_IsQueued(AUTOREACTIVECLASS_ACTIONQUEUE_INTERRUPT_ID) ) and ( AutoReactiveClass_Queue_Interrupt_Entry.caster == name ) then ActionQueue_RemoveAction(AUTOREACTIVECLASS_ACTIONQUEUE_INTERRUPT_ID); end return false; end function AQ_AutoReactiveClass_HandleBeginAction(performer, action, target, actionType, p1, p2, p3, p4, p5) local useGeneric = false; local _, class = UnitClass("player"); if ( class ) and ( class ~= UKNOWNBEING ) and ( class ~= UNKNOWN ) or ( class == UNKNOWNOBJECT ) then local funcName = "AQ_AutoReactiveClass_HandleBeginAction"; local classFunc = getglobal(funcName.."_"..class); if ( classFunc ) then setglobal(funcName, classFunc); end else --[[ local _, class = UnitClass("player"); if ( not class ) or ( class == UKNOWNBEING ) or ( class == UNKNOWN ) or ( class == UNKNOWNOBJECT ) then return false; else EnemySpellDetector_RemoveListener(EnemySpellDetector_Type_BeginCast, "AQ_AutoReactiveClass_HandleBeginAction_Cast"); EnemySpellDetector_RemoveListener(EnemySpellDetector_Type_BeginPerform, "AQ_AutoReactiveClass_HandleBeginAction_Perform"); end ]]-- --setglobal("AQ_AutoReactiveClass_HandleBeginAction", AQ_AutoReactiveClass_HandleBeginAction_ShowMessage); useGeneric = true; end local func = nil; if ( useGeneric ) then func = AQ_AutoReactiveClass_HandleBeginAction_Generic; else getglobal("AQ_AutoReactiveClass_HandleBeginAction"); end if ( not arg ) then arg = {}; end if ( not func ) then return nil; end return func(performer, action, target, actionType, p1, p2, p3, p4, p5); end function AQ_AutoReactiveClass_PlaySoundFile(snd) if ( AQ_AutoReactiveClass_Options.playSounds ) then PlaySoundFile("Interface\\AddOns\\AQ_AutoReactiveClass\\Sounds\\"..snd); end end function AQ_AutoReactiveClass_ShowMessage(msg) if ( not AQ_AutoReactiveClass_Options.showMessage ) then return; end if ( not AQ_AutoReactiveClass_Options.showActionMessage ) then return; end ActionQueue_ShowMessage(msg); end function AQ_AutoReactiveClass_HandleBeginAction_ShowMessage(performer, action, target, actionType, p1, p2, p3, p4, p5) if ( not AQ_AutoReactiveClass_Options.showMessage ) or ( not AQ_AutoReactiveClass_Options.showMessageWhenNoInterrupt ) then return; end if ( not AQ_AutoReactiveClass_Options.showActionMessage ) then return; end local msg = nil; local targetName = UnitName("target"); if ( not targetName ) or ( strlen(targetName) <= 0 ) then return; end if ( ( performer ) and ( targetName == performer ) ) or ( ( target ) and ( targetName == target ) ) then if ( performer ) and ( action ) and ( target ) then if ( actionType == "perform" ) then msg = string.format(AUTOREACTIVECLASS_PERFORMED_BY_ON_FORMAT, performer, action, target); else msg = string.format(AUTOREACTIVECLASS_CAST_BY_ON_FORMAT, performer, action, target); end elseif ( performer ) and ( action ) then if ( actionType == "perform" ) then msg = string.format(AUTOREACTIVECLASS_PERFORMED_BY_FORMAT, performer, action); else msg = string.format(AUTOREACTIVECLASS_CAST_BY_FORMAT, performer, action); end elseif ( target ) and ( action ) then if ( actionType == "perform" ) then msg = string.format(AUTOREACTIVECLASS_INVOLVING_FORMAT, target, action); else msg = string.format(AUTOREACTIVECLASS_CAST_FORMAT, target, action); end end if ( msg ) then ActionQueue_ShowMessage(msg); end end end function AQ_AutoReactiveClass_HandleBeginAction_ShowMessage_Interrupt(performer, action, target, actionType, p1, p2, p3, p4, p5) if ( not AQ_AutoReactiveClass_Options.showMessage ) then return; end if ( not AQ_AutoReactiveClass_Options.showActionMessage ) then return; end if ( AQ_AutoReactiveClass_Options.interruption.showInterruptName ) and ( AutoReactiveClass_Queue_Interrupt_Entry.spellId ) then local spellId = AutoReactiveClass_Queue_Interrupt_Entry.spellId; if ( spellId ) then local spellName = GetSpellName(spellId, "spell"); local msg = format(AUTOREACTIVECLASS_INTERRUPT_NAME, spellName, action); return ActionQueue_ShowMessage(msg); end end return AQ_AutoReactiveClass_HandleBeginAction_ShowMessage(performer, action, target, actionType, p1, p2, p3, p4, p5); end function AQ_AutoReactiveClass_HandleBeginAction_Generic(performer, action, target, actionType, p1, p2, p3, p4, p5) AQ_AutoReactiveClass_HandleBeginAction_ShowMessage(performer, action, target, actionType, p1, p2, p3, p4, p5); end function AQ_AutoReactiveClass_IsClass(classPlayer) local _, class = UnitClass("player"); if ( not class ) or ( class == UKNOWNBEING ) or ( class == UNKNOWN ) or ( class == UNKNOWNOBJECT ) then return false; end if ( class == classPlayer ) then return true; else return false; end end AQ_AutoReactiveClass_Messages = {}; function AQ_AutoReactiveClass_GetMessage(spellName) if ( not spellName ) then return ""; end local message = AQ_AutoReactiveClass_Messages[spellName]; if ( not message ) then message = string.format(AUTOREACTIVECLASS_MESSAGE_FORMAT, spellName); AQ_AutoReactiveClass_Messages[spellName] = message; end return message; end function AQ_AutoReactiveClass_MergeList(destination, source) local shouldAdd = false; local stuffToAdd = {}; for k, v in source do shouldAdd = true; for key, value in destination do if ( value == v ) then shouldAdd = false; break; end end if ( shouldAdd ) then table.insert(stuffToAdd, v); end end for k, v in stuffToAdd do table.insert(destination, v); end end function AQ_AutoReactiveClass_OnEvent() -- when client variables are loaded, start using Perception if ( event == "VARIABLES_LOADED" ) then local frame = AQ_AutoReactiveClassFrame; frame:UnregisterEvent(event); AQ_AutoReactiveClass_EnableUpdate(); ActionQueue_LoadDefaultsHandleArray(AQ_AUTOREACTIVECLASS_OPTIONS_DEFAULT, AQ_AutoReactiveClass_Options); if ( AQ_AutoReactiveClass_Options.interruption.version < AUTOREACTIVECLASS_INTERRUPTION_VERSION ) then AQ_AutoReactiveClass_MergeList(AQ_AutoReactiveClass_Options.interruption.spellsThatShouldBeIgnoredForInterruption, AUTOREACTIVECLASS_SPELLSTHATSHOULDBEIGNOREDFORINTERRUPTION); AQ_AutoReactiveClass_MergeList(AQ_AutoReactiveClass_Options.interruption.spellsThatNeedStunInterrupt, AUTOREACTIVECLASS_SPELLSTHATNEEDSTUNINTERRUPT); AQ_AutoReactiveClass_Options.interruption.version = AUTOREACTIVECLASS_INTERRUPTION_VERSION; ActionQueue_ShowMessage(AQ_AUTOREACTIVECLASS_MSG_INTERRUPTION_OUT_OF_DATE); if ( ChatFrame1 ) and ( ChatFrame1.AddMessage ) then ChatFrame1:AddMessage(AQ_AUTOREACTIVECLASS_MSG_INTERRUPTION_OUT_OF_DATE); end end return; end local _, class = UnitClass("player"); local funcName = "AQ_AutoReactiveClass_OnEvent_"..class; local func = getglobal(funcName); if ( func ) then func(); end if ( event == "SPELLS_CHANGED" ) then AQ_AutoReactiveClass_EnableUpdate(); return; end end -- Enable AQ_AutoReactiveClass. function AQ_AutoReactiveClass_EnableUpdate() -- show the frame to allow OnUpdates to occur local frame = AQ_AutoReactiveClassFrame; frame:Show(); end -- Disable AQ_AutoReactiveClass. function AQ_AutoReactiveClass_DisableUpdate() -- hide the frame to prevent OnUpdates from occurring (saving a few cycles) local frame = AQ_AutoReactiveClassFrame; frame:Hide(); end function AutoReactiveClass_GetActiveStance() local numForms = GetNumShapeshiftForms(); local texture, name, isActive, isCastable; for i=1, NUM_SHAPESHIFT_SLOTS do if ( i <= numForms ) then texture, name, isActive, isCastable = GetShapeshiftFormInfo(i); if ( isActive ) then return name; end end end return nil; end function AQ_AutoReactiveClass_IsActionIdMappedToSpellId(actionId, spellId, spellBook) if ( not spellBook ) then spellBook = "spell"; end local spellTexture = GetSpellTexture(spellId, spellBook); local actionTexture = GetActionTexture(actionId); if ( spellTexture ) and ( actionTexture ) then if ( spellTexture == actionTexture ) then return true; else return false; end else return false; end end function AutoReactiveClass_GetReactiveSpellName() local _, class = UnitClass("player"); if ( not class ) or ( class == UKNOWNBEING ) or ( class == UNKNOWN ) or ( class == UNKNOWNOBJECT ) then return nil; end local spell = nil; if ( class == AUTOREACTIVE_CLASS_WARRIOR ) then local activeStance = AutoReactiveClass_GetActiveStance(); if ( activeStance == AUTOREACTIVE_STANCE_BATTLE ) then spell = AUTOREACTIVECLASS_OVERPOWER_NAME; end if ( activeStance == AUTOREACTIVE_STANCE_DEFENSIVE ) then spell = AUTOREACTIVECLASS_REVENGE_NAME; end if ( activeStance == AUTOREACTIVE_STANCE_BERSERKER ) then spell = nil; end elseif ( class == AUTOREACTIVE_CLASS_HUNTER ) then spell = AUTOREACTIVECLASS_MONGOOSE_BITE_NAME; elseif ( class == AUTOREACTIVE_CLASS_ROGUE ) then spell = AUTOREACTIVECLASS_RIPOSTE_NAME; else AQ_AutoReactiveClass_DisableUpdate() end return spell; end function AQ_AutoReactiveClass_OnUpdate(elapsed) if ( not AQ_AutoReactiveClass_Options.enabled ) then AQ_AutoReactiveClass_DisableUpdate(); return; end local curTime = GetTime(); -- if more than AutoReactiveClass_UpdateDelay seconds has passed if ( ( curTime - AutoReactiveClass_LastUpdate ) > AutoReactiveClass_UpdateDelay ) then AutoReactiveClass_LastUpdate = curTime; if ( ActionQueue_IsMounted() ) then return false; end if ( ActionQueue_IsShadowmelded() ) then return false; end if ( ActionQueue_IsGlobalSpellCooldown() ) then return false; end if ( not UnitExists("target") ) or ( not UnitCanAttack("player", "target") ) or ( UnitIsDeadOrGhost("target") ) then return false; end if ( ActionQueue_UnitIsSleeping("target") ) then return false; end local reactiveSpellName = AutoReactiveClass_GetReactiveSpellName(); if ( not reactiveSpellName ) then return false; end -- skill not in queue: see if it's time to re-queue it local id = nil; -- optimization: if we retrieved the id before and it has not changed, why re-retreive it? if ( AutoReactiveClass_LastId ) then local name = GetSpellName(AutoReactiveClass_LastId, "spell"); if ( name == reactiveSpellName ) then id = AutoReactiveClass_LastId; end end if ( not id ) then id = ActionQueue_FindSpellId(reactiveSpellName); end -- OK, did we find a valid id? if ( id ) and ( id > 0 ) then -- make sure we cache the found spell AutoReactiveClass_LastId = id; -- skill found: queue it up AutoReactiveClass_Queue_Entry.spellId = id; local start, duration, enable = GetSpellCooldown(id, "spell"); local leftTime = 0; if ( start ) and ( duration ) then leftTime = start+duration-GetTime(); end local limit = AQ_AutoReactiveClass_Options.reactiveLagOffset; if ( not limit ) then limit = 0; end; if ( leftTime <= limit ) and ( enable == 1 ) then local actionId = AutoReactiveClass_LastActionId; if ( actionId ) then if ( not AQ_AutoReactiveClass_IsActionIdMappedToSpellId(actionId, id) ) then actionId = nil; end end if ( not actionId ) then actionId = ActionQueue_Util_RetrieveActionIdFromSpellId(id); AutoReactiveClass_LastActionId = actionId; end if ( actionId ) then local isUsable, notEnoughMana = IsUsableAction(actionId); if ( not isUsable ) or ( notEnoughMana ) then if ( isUsable ) and ( AQ_AutoReactiveClass_Options.showMessage ) then ActionQueue_ShowMessage(AQ_AutoReactiveClass_GetMessage(reactiveSpellName)); end return false; end else return false; end if ( AQ_AutoReactiveClass_Options.showMessage ) then ActionQueue_ShowMessage(AQ_AutoReactiveClass_GetMessage(reactiveSpellName)); end if ( AQ_AutoReactiveClass_Options.queueAction ) then AutoReactiveClass_Queue_Entry.spellId = id; if ( not ActionQueue_IsQueued(AUTOREACTIVECLASS_ACTIONQUEUE_ID) ) then ActionQueue_QueueAction(AutoReactiveClass_Queue_Entry); end end end else -- skill not found: disable the addon AQ_AutoReactiveClass_DisableUpdate() end end end function AQ_AutoReactiveClass_HandleBeginAction_Wards(performer, action, target, actionType, p1, p2, p3, p4, p5) if ( not AQ_AutoReactiveClass_Options.enabled ) or ( not AQ_AutoReactiveClass_Options.shouldInterrupt ) then return AQ_AutoReactiveClass_HandleBeginAction_ShowMessage(performer, action, target, actionType, p1, p2, p3, p4, p5); end if ( not arg ) then arg = {}; end local name = performer; if ( not name ) then name = target; end if ( UnitExists("target") ) then if ( UnitName("target") ~= name ) then return AQ_AutoReactiveClass_HandleBeginAction_ShowMessage(performer, action, target, actionType, p1, p2, p3, p4, p5); end end if ( ActionQueue_IsQueued(AUTOREACTIVECLASS_ACTIONQUEUE_INTERRUPT_ID) ) then AQ_AutoReactiveClass_HandleBeginAction_ShowMessage(performer, action, target, actionType, p1, p2, p3, p4, p5); return true; end if ( target ) and ( target ~= UnitName("player") ) then return false; end local _, class = UnitClass("player"); local arr = AQ_AutoReactiveClass_Class_Wards[class]; if ( arr ) then local match = false; local ignore = false; local actionLower = strlower(action); for spell, entry in arr do ignore = false; if ( entry.notSpells ) then for k, v in entry.notSpells do if ( actionLower == v ) then ignore = true; end end end if ( entry.spells ) and ( not ignore ) then for k, v in entry.spells do if ( actionLower == v ) then match = true; break; end end end if ( entry.keywords ) and ( not ignore ) then for k, v in entry.keywords do if ( strfind(actionLower, v) ) then match = true; break; end end end end if ( match ) then local spellId, actionId = ActionQueue_GetSpellAndActionIdForSpell(spell, nil, "ward_"..spell, true); if ( spellId ) and ( ActionQueue_SpellHasCooldownLessThan(spellId, nil, 3) ) then AutoReactiveClass_Queue_Interrupt_Entry.spellId = spellId; ActionQueue_QueueAction(AutoReactiveClass_Queue_Interrupt_Entry); AQ_AutoReactiveClass_HandleBeginAction_ShowMessage_Interrupt(performer, action, target, actionType, p1, p2, p3, p4, p5); return true; else AQ_AutoReactiveClass_HandleBeginAction_ShowMessage(performer, action, target, actionType, p1, p2, p3, p4, p5); end end end return false; end -- FUNCTIONS END function AQ_AutoReactiveClass_Toggle_Enabled() if ( AQ_AutoReactiveClass_Options.enabled ) then AQ_AutoReactiveClass_Set_Enabled(false); else AQ_AutoReactiveClass_Set_Enabled(true); end end function AQ_AutoReactiveClass_Toggle_Interrupts_Enabled() if ( AQ_AutoReactiveClass_Options.shouldInterrupt ) then AQ_AutoReactiveClass_Set_Interrupts_Enabled(false); else AQ_AutoReactiveClass_Set_Interrupts_Enabled(true); end end function AQ_AutoReactiveClass_Set_Enabled(state) if ( state == nil ) then state = false; end AQ_AutoReactiveClass_Options.enabled = state; if ( state ) then AQ_AutoReactiveClass_EnableUpdate(); else AQ_AutoReactiveClass_DisableUpdate(); end end function AQ_AutoReactiveClass_Set_Interrupts_Enabled(state) if ( state == nil ) then state = false; end AQ_AutoReactiveClass_Options.shouldInterrupt = state; end