commit fb359993b2b8cb79f617d12407593280c6dba95d Author: Jrc13245 Date: Mon Aug 18 16:15:25 2025 -0400 Initial Commit diff --git a/CleveRoidMacros.toc b/CleveRoidMacros.toc new file mode 100644 index 0000000..3e5c960 --- /dev/null +++ b/CleveRoidMacros.toc @@ -0,0 +1,34 @@ +## Interface: 11200 +## Author: DWG, _brain, Torio, yani9o +## Title: [Super]CleveRoidMacros +## Notes: /cleveroid for settings +## Version: 1.2 +## OptionalDeps: ClassicFocus, FocusFrame, pfUI, SuperMacro, Bongos_ActionBar +## SavedVariables: CleveRoidMacros +Localization.lua +Init.lua +Utility.lua +Core.lua +Conditionals.lua +Console.lua +ExtensionsManager.lua + +Compatibility\SuperMacro.lua +Compatibility\pfUI.lua +Compatibility\Bongos.lua + +Extensions\MacroLengthWarn.lua +Extensions\Mouseover\GameTooltip.lua +Extensions\Mouseover\Blizzard.lua +Extensions\Mouseover\CT_RaidAssist.lua +Extensions\Mouseover\CT_UnitFrames.lua +Extensions\Mouseover\DiscordUnitFrames.lua +Extensions\Mouseover\FocusFrame.lua +Extensions\Mouseover\Grid.lua +Extensions\Mouseover\NotGrid.lua +Extensions\Mouseover\PerfectRaid.lua +Extensions\Mouseover\pfUI.lua +Extensions\Mouseover\sRaidFrames.lua +Extensions\Mouseover\ag_UnitFrames.lua + +Extensions\Tooltip\Generic.lua diff --git a/Compatibility/Bongos.lua b/Compatibility/Bongos.lua new file mode 100644 index 0000000..1d2774e --- /dev/null +++ b/Compatibility/Bongos.lua @@ -0,0 +1,8 @@ +CleveRoids.RegisterActionEventHandler(function(slot, event) + if not slot or not BActionButton or not BActionBar then return end + + local button = getglobal("BActionButton" .. slot) + if button then + BActionButton.Update(button) + end +end) diff --git a/Compatibility/SuperMacro.lua b/Compatibility/SuperMacro.lua new file mode 100644 index 0000000..14d3db3 --- /dev/null +++ b/Compatibility/SuperMacro.lua @@ -0,0 +1,21 @@ +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +local Extension = CleveRoids.RegisterExtension("Compatibility_SuperMacro") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.RunMacro(name) + CleveRoids.ExecuteMacroByName(name) +end + +function Extension.OnLoad() + if not SuperMacroFrame then + return + end + + Extension.Hook("RunMacro", "RunMacro", true) + Extension.UnregisterEvent("ADDON_LOADED", "Onload") +end + + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Compatibility/pfUI.lua b/Compatibility/pfUI.lua new file mode 100644 index 0000000..d1cc15b --- /dev/null +++ b/Compatibility/pfUI.lua @@ -0,0 +1,37 @@ +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +local Extension = CleveRoids.RegisterExtension("Compatibility_pfUI") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") +Extension.Debug = false + +function Extension.RunMacro(name) + CleveRoids.ExecuteMacroByName(name) +end + +function Extension.DLOG(msg) + if Extension.Debug then + DEFAULT_CHAT_FRAME:AddMessage("|cffcccc33[R]: |cffffff55" .. ( msg )) + end +end + +function Extension.FocusNameHook() + local hook = Extension.internal.memberHooks[CleveRoids]["GetFocusName"] + local target = hook.original() + + if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.unitname then + target = pfUI.uf.focus.unitname + end + + --Extension.DLOG(target) + + return target +end + +function Extension.OnLoad() + Extension.DLOG("Extension pfUI Loaded.") + Extension.HookMethod(CleveRoids, "GetFocusName", "FocusNameHook", true) + Extension.UnregisterEvent("ADDON_LOADED", "Onload") +end + +_G["CleveRoids"] = CleveRoids diff --git a/Conditionals.lua b/Conditionals.lua new file mode 100644 index 0000000..631ea80 --- /dev/null +++ b/Conditionals.lua @@ -0,0 +1,1321 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +--This table maps stat keys to the functions that retrieve their values. +local stat_checks = { + -- Base Stats (Corrected to use the 'effective' stat with gear) + str = function() local _, effective = UnitStat("player", 1); return effective end, + strength = function() local _, effective = UnitStat("player", 1); return effective end, + agi = function() local _, effective = UnitStat("player", 2); return effective end, + agility = function() local _, effective = UnitStat("player", 2); return effective end, + stam = function() local _, effective = UnitStat("player", 3); return effective end, + stamina = function() local _, effective = UnitStat("player", 3); return effective end, + int = function() local _, effective = UnitStat("player", 4); return effective end, + intellect = function() local _, effective = UnitStat("player", 4); return effective end, + spi = function() local _, effective = UnitStat("player", 5); return effective end, + spirit = function() local _, effective = UnitStat("player", 5); return effective end, + + -- Combat Ratings (Corrected to use UnitAttackPower and UnitRangedAttackPower) + ap = function() local base, pos, neg = UnitAttackPower("player"); return base + pos + neg end, + attackpower = function() local base, pos, neg = UnitAttackPower("player"); return base + pos + neg end, + rap = function() local base, pos, neg = UnitRangedAttackPower("player"); return base + pos + neg end, + rangedattackpower = function() local base, pos, neg = UnitRangedAttackPower("player"); return base + pos + neg end, + healing = function() return GetBonusHealing() end, + healingpower = function() return GetBonusHealing() end, + + -- Bonus Spell Damage by School + arcane_power = function() return GetSpellBonusDamage(6) end, + fire_power = function() return GetSpellBonusDamage(3) end, + frost_power = function() return GetSpellBonusDamage(4) end, + nature_power = function() return GetSpellBonusDamage(2) end, + shadow_power = function() return GetSpellBonusDamage(5) end, + + -- Defensive Stats + armor = function() local _, effective = UnitArmor("player"); return effective end, + defense = function() return GetDefense() end, + + -- Resistances + arcane_res = function() local _, val = UnitResistance("player", 7); return val end, + fire_res = function() local _, val = UnitResistance("player", 3); return val end, + frost_res = function() local _, val = UnitResistance("player", 5); return val end, + nature_res = function() local _, val = UnitResistance("player", 4); return val end, + shadow_res = function() local _, val = UnitResistance("player", 6); return val end +} + +local function And(t,func) + if type(func) ~= "function" then return false end + if type(t) ~= "table" then + t = { [1] = t } + end + for k,v in pairs(t) do + if not func(v) then + return false + end + end + return true +end + +local function Or(t,func) + if type(func) ~= "function" then return false end + if type(t) ~= "table" then + t = { [1] = t } + end + for k,v in pairs(t) do + if func(v) then + return true + end + end + return false +end + + +-- Validates that the given target is either friend (if [help]) or foe (if [harm]) +-- target: The unit id to check +-- help: Optional. If set to 1 then the target must be friendly. If set to 0 it must be an enemy. +-- remarks: Will always return true if help is not given +-- returns: Whether or not the given target can either be attacked or supported, depending on help +function CleveRoids.CheckHelp(target, help) + if help == nil then return true end + if help then + return UnitCanAssist("player", target) + else + return UnitCanAttack("player", target) + end +end + +-- Ensures the validity of the given target +-- target: The unit id to check +-- help: Optional. If set to 1 then the target must be friendly. If set to 0 it must be an enemy +-- returns: Whether or not the target is a viable target +function CleveRoids.IsValidTarget(target, help) + -- If the conditional is not for @mouseover, use the existing logic. + if target ~= "mouseover" then + if not UnitExists(target) or not CleveRoids.CheckHelp(target, help) then + return false + end + return true + end + + -- --- START OF PATCH --- + -- New logic to handle [@mouseover] with pfUI compatibility. + + local effectiveMouseoverUnit = "mouseover" -- Start with the default game token. + + -- Check if the default mouseover exists. If not, check pfUI's internal data, + -- which is necessary because pfUI frames don't always update the default token. + if not UnitExists(effectiveMouseoverUnit) then + if pfUI and pfUI.uf and pfUI.uf.mouseover and pfUI.uf.mouseover.unit and UnitExists(pfUI.uf.mouseover.unit) then + -- If pfUI has a valid mouseover unit recorded, use that instead. + effectiveMouseoverUnit = pfUI.uf.mouseover.unit + else + -- If neither the default token nor the pfUI unit exists, there's no valid mouseover. + return false + end + end + -- --- END OF PATCH --- + + -- Finally, perform the help/harm check on the determined mouseover unit (either from the game or from pfUI). + if not UnitExists(effectiveMouseoverUnit) or not CleveRoids.CheckHelp(effectiveMouseoverUnit, help) then + return false + end + + return true +end + +-- Returns the current shapeshift / stance index +-- returns: The index of the current shapeshift form / stance. 0 if in no shapeshift form / stance +function CleveRoids.GetCurrentShapeshiftIndex() + if CleveRoids.playerClass == "PRIEST" then + return CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Shadowform"]) and 1 or 0 + elseif CleveRoids.playerClass == "ROGUE" then + return CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Stealth"]) and 1 or 0 + end + for i=1, GetNumShapeshiftForms() do + _, _, active = GetShapeshiftFormInfo(i) + if active then + return i + end + end + + return 0 +end + +function CleveRoids.CancelAura(auraName) + local ix = 0 + auraName = string.lower(string.gsub(auraName, "_"," ")) + while true do + local aura_ix = GetPlayerBuff(ix,"HELPFUL") + ix = ix + 1 + if aura_ix == -1 then break end + + if CleveRoids.hasSuperwow then + local bid = GetPlayerBuffID(aura_ix) + bid = (bid < -1) and (bid + 65536) or bid + if string.lower(SpellInfo(bid)) == auraName then + CancelPlayerBuff(aura_ix) + return true + end + else + AuraScanTooltip:SetPlayerBuff(aura_ix) + local name = string.lower(getglobal("AuraScanTooltipTextLeft1"):GetText()) + if name == auraName then + CancelPlayerBuff(aura_ix) + break + end + end + + end + return false +end + +function CleveRoids.HasGearEquipped(gearId) + if not gearId then return false end + + local wantId = tonumber(gearId) + local wantName = (type(gearId) == "string") and string.lower(gearId) or nil + + for slot = 1, 19 do + local link = GetInventoryItemLink("player", slot) + if link then + -- Lua 5.0: use string.find with captures + local _, _, id = string.find(link, "item:(%d+)") + local _, _, nameInBrackets = string.find(link, "%[(.+)%]") + + if wantId and id and tonumber(id) == wantId then + return true + end + + if wantName and nameInBrackets and string.lower(nameInBrackets) == wantName then + return true + end + + -- Fallback: resolve via GetItemInfo using the numeric id if we have it + if wantName and not nameInBrackets and id then + local itemName = GetItemInfo(tonumber(id)) -- may be nil if not cached + if itemName and string.lower(itemName) == wantName then + return true + end + end + end + end + + return false +end + + +-- Checks whether or not the given weaponType is currently equipped +-- weaponType: The name of the weapon's type (e.g. Axe, Shield, etc.) +-- returns: True when equipped, otherwhise false +function CleveRoids.HasWeaponEquipped(weaponType) + if not CleveRoids.WeaponTypeNames[weaponType] then + return false + end + + local slotName = CleveRoids.WeaponTypeNames[weaponType].slot + local localizedName = CleveRoids.WeaponTypeNames[weaponType].name + local slotId = GetInventorySlotInfo(slotName) + local slotLink = GetInventoryItemLink("player",slotId) + + if not slotLink then + return false + end + + local _,_,itemId = string.find(slotLink,"item:(%d+)") + if not itemId then -- Also good to check if itemId was found + return false + end + local _name,_link,_,_lvl,_type,subtype = GetItemInfo(itemId) + -- just had to be special huh? + local fist = string.find(subtype,"^Fist") + -- drops things like the One-Handed prefix + local _,_,subtype = string.find(subtype,"%s?(%S+)$") + + if subtype == localizedName or (fist and (CleveRoids.WeaponTypeNames[weaponType].name == CleveRoids.Localized.FistWeapon)) then + return true + end + + return false +end + +-- Checks whether or not the given UnitId is in your party or your raid +-- target: The UnitId of the target to check +-- groupType: The name of the group type your target has to be in ("party" or "raid") +-- returns: True when the given target is in the given groupType, otherwhise false +function CleveRoids.IsTargetInGroupType(target, groupType) + local groupSize = (groupType == "raid") and 40 or 5 + + for i = 1, groupSize do + if UnitIsUnit(groupType..i, target) then + return true + end + end + + return false +end + +function CleveRoids.GetSpammableConditional(name) + return CleveRoids.spamConditions[name] or "nomybuff" +end + +-- Checks whether or not we're currently casting a channeled spell +function CleveRoids.CheckChanneled(channeledSpell) + if not channeledSpell then return false end + + -- Remove the "(Rank X)" part from the spells name in order to allow downranking + local spellName = string.gsub(CleveRoids.CurrentSpell.spellName, "%(.-%)%s*", "") + local channeled = string.gsub(channeledSpell, "%(.-%)%s*", "") + + if CleveRoids.CurrentSpell.type == "channeled" and spellName == channeled then + return false + end + + if channeled == CleveRoids.Localized.Attack then + return not CleveRoids.CurrentSpell.autoAttack + end + + if channeled == CleveRoids.Localized.AutoShot then + return not CleveRoids.CurrentSpell.autoShot + end + + if channeled == CleveRoids.Localized.Shoot then + return not CleveRoids.CurrentSpell.wand + end + + CleveRoids.CurrentSpell.spellName = channeled + return true +end + +function CleveRoids.ValidateComboPoints(operator, amount) + if not operator or not amount then return false end + local points = GetComboPoints() + + if CleveRoids.operators[operator] then + return CleveRoids.comparators[operator](points, amount) + end + + return false +end + +function CleveRoids.ValidateKnown(args) + if not args then + return false + end + if table.getn(CleveRoids.Talents) == 0 then + CleveRoids.IndexTalents() + end + + local effective_name_to_check + local original_args_for_rank_check = args + + if type(args) ~= "table" then + effective_name_to_check = args + args = { name = args } + else + effective_name_to_check = args.name + end + + local spell = CleveRoids.GetSpell(effective_name_to_check) + local talent_points = nil + + if not spell then + talent_points = CleveRoids.GetTalent(effective_name_to_check) + end + + if not spell and talent_points == nil then + return false + end + + local arg_amount = nil + local arg_operator = nil + if type(original_args_for_rank_check) == "table" then + arg_amount = original_args_for_rank_check.amount + arg_operator = original_args_for_rank_check.operator + end + + if spell then + local spell_rank_str = spell.rank or (spell.highest and spell.highest.rank) or "" + -- FLEXIBLY extract just the number from the rank string + local _, _, spell_rank_num_str = string.find(spell_rank_str, "(%d+)") + + if not arg_amount and not arg_operator then + return true + elseif arg_amount and arg_operator and CleveRoids.operators[arg_operator] and spell_rank_num_str and spell_rank_num_str ~= "" then + local numeric_rank = tonumber(spell_rank_num_str) + if numeric_rank then + return CleveRoids.comparators[arg_operator](numeric_rank, arg_amount) + else + return false + end + else + return false + end + elseif talent_points ~= nil then + if not arg_amount and not arg_operator then + return talent_points > 0 + elseif arg_amount and arg_operator and CleveRoids.operators[arg_operator] then + return CleveRoids.comparators[arg_operator](talent_points, arg_amount) + else + return false + end + end + + return false +end + +function CleveRoids.ValidateResting() + return IsResting() +end + + +-- TODO: refactor numeric comparisons... + +-- Checks whether or not the given unit has power in percent vs the given amount +-- unit: The unit we're checking +-- operator: valid comparitive operator symbol +-- amount: The required amount +-- returns: True or false +function CleveRoids.ValidatePower(unit, operator, amount) + if not unit or not operator or not amount then return false end + local powerPercent = 100 / UnitManaMax(unit) * UnitMana(unit) + + if CleveRoids.operators[operator] then + return CleveRoids.comparators[operator](powerPercent, amount) + end + + return false +end + +-- Checks whether or not the given unit has current power vs the given amount +-- unit: The unit we're checking +-- operator: valid comparitive operator symbol +-- amount: The required amount +-- returns: True or false +function CleveRoids.ValidateRawPower(unit, operator, amount) + if not unit or not operator or not amount then return false end + local power = UnitMana(unit) + + if CleveRoids.operators[operator] then + return CleveRoids.comparators[operator](power, amount) + end + + return false +end + +-- Checks whether or not the given unit has a power deficit vs the amount specified +-- unit: The unit we're checking +-- operator: valid comparitive operator symbol +-- amount: The required amount +-- returns: True or false +function CleveRoids.ValidatePowerLost(unit, operator, amount) + if not unit or not operator or not amount then return false end + local powerLost = UnitManaMax(unit) - UnitMana(unit) + + if CleveRoids.operators[operator] then + return CleveRoids.comparators[operator](powerLost, amount) + end + + return false +end + +-- Checks whether or not the given unit has hp in percent vs the given amount +-- unit: The unit we're checking +-- operator: valid comparitive operator symbol +-- amount: The required amount +-- returns: True or false +function CleveRoids.ValidateHp(unit, operator, amount) + if not unit or not operator or not amount then return false end + local hpPercent = 100 / UnitHealthMax(unit) * UnitHealth(unit) + + if CleveRoids.operators[operator] then + return CleveRoids.comparators[operator](hpPercent, amount) + end + + return false +end + +-- Checks whether or not the given unit has hp vs the given amount +-- unit: The unit we're checking +-- operator: valid comparitive operator symbol +-- amount: The required amount +-- returns: True or false +function CleveRoids.ValidateRawHp(unit, operator, amount) + if not unit or not operator or not amount then return false end + local rawhp = UnitHealth(unit) + + if CleveRoids.operators[operator] then + return CleveRoids.comparators[operator](rawhp, amount) + end + + return false +end + +-- Checks whether or not the given unit has an hp deficit vs the amount specified +-- unit: The unit we're checking +-- operator: valid comparitive operator symbol +-- amount: The required amount +-- returns: True or false +function CleveRoids.ValidateHpLost(unit, operator, amount) + if not unit or not operator or not amount then return false end + local hpLost = UnitHealthMax(unit) - UnitHealth(unit) + + if CleveRoids.operators[operator] then + return CleveRoids.comparators[operator](hpLost, amount) + end + + return false +end + +-- Checks whether the given creatureType is the same as the target's creature type +-- creatureType: The type to check +-- target: The target's unitID +-- returns: True or false +-- remarks: Allows for both localized and unlocalized type names +function CleveRoids.ValidateCreatureType(creatureType, target) + if not target then return false end + local targetType = UnitCreatureType(target) + if not targetType then return false end -- ooze or silithid etc + local ct = string.lower(creatureType) + local cl = UnitClassification(target) + if (ct == "boss" and "worldboss" or ct) == cl then + return true + end + if string.lower(creatureType) == "boss" then creatureType = "worldboss" end + local englishType = CleveRoids.Localized.CreatureTypes[targetType] + return ct == string.lower(targetType) or creatureType == englishType +end + +-- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed +function CleveRoids.ValidateCooldown(args, ignoreGCD) + if not args then return false end + if type(args) ~= "table" then + args = {name = args} + end + + local expires = CleveRoids.GetCooldown(args.name, ignoreGCD) + + if not args.operator and not args.amount then + return expires > 0 + elseif CleveRoids.operators[args.operator] then + return CleveRoids.comparators[args.operator](expires - GetTime(), args.amount) + end +end + +function CleveRoids.GetPlayerAura(index, isbuff) + if not index then return false end + + local buffType = isbuff and "HELPFUL" or "HARMFUL" + local bid = GetPlayerBuff(index, buffType) + if bid < 0 then return end + + local spellID = CleveRoids.hasSuperwow and GetPlayerBuffID(bid) + + return GetPlayerBuffTexture(bid), GetPlayerBuffApplications(bid), spellID, GetPlayerBuffTimeLeft(bid) +end + +function CleveRoids.ValidateAura(unit, args, isbuff) + if not args or not UnitExists(unit) then return false end + + if not CleveRoids.hasSuperwow then + return false + end + + if type(args) ~= "table" then + args = {name = args} + end + + local isPlayer = UnitIsUnit(unit, "player") + local found = false + local stacks, remaining + local i = isPlayer and 0 or 1 + + while true do + local texture + local current_spellID = nil + + if isPlayer then + texture, stacks, current_spellID, remaining = CleveRoids.GetPlayerAura(i, isbuff) + else + local returns = isbuff and {UnitBuff(unit, i)} or {UnitDebuff(unit, i)} + texture = returns[1] + if texture then + for _, value in ipairs(returns) do + if type(value) == "number" and SpellInfo(value) then + current_spellID = value + stacks = isbuff and returns[2] or returns[4] + break + end + end + end + end + + if not texture then break end + + if current_spellID then + local auraName = SpellInfo(current_spellID) + if auraName then + if string.find(string.lower(auraName), string.lower(args.name), 1, true) then + found = true + break + end + end + end + + i = i + 1 + end + + local ops = CleveRoids.operators + if not args.amount and not args.operator and not args.checkStacks then + return found + elseif isPlayer and not args.checkStacks and args.amount and ops[args.operator] then + return CleveRoids.comparators[args.operator](remaining or -1, args.amount) + elseif args.amount and args.checkStacks and ops[args.operator] then + return CleveRoids.comparators[args.operator](stacks or -1, args.amount) + else + return false + end +end + +function CleveRoids.ValidateUnitBuff(unit, args) + return CleveRoids.ValidateAura(unit, args, true) +end + +function CleveRoids.ValidateUnitDebuff(unit, args) + if not args or not UnitExists(unit) then return false end + + if type(args) ~= "table" then + args = {name = args} + end + + local found = false + local texture, stacks, spellID, remaining + local i + + -- Step 1: Search DEBUFFS first + i = (unit == "player") and 0 or 1 + while true do + if unit == "player" then + texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, false) + else + texture, stacks, _, spellID = UnitDebuff(unit, i) + end + + if not texture then break end + if (CleveRoids.hasSuperwow and args.name == SpellInfo(spellID)) or (not CleveRoids.hasSuperwow and texture == CleveRoids.auraTextures[args.name]) then + found = true + break + end + i = i + 1 + end + + -- Step 2: If not found, search BUFFS + if not found then + i = (unit == "player") and 0 or 1 + while true do + if unit == "player" then + texture, stacks, spellID, remaining = CleveRoids.GetPlayerAura(i, true) + else + texture, stacks, spellID = UnitBuff(unit, i) + end + + if not texture then break end + if (CleveRoids.hasSuperwow and args.name == SpellInfo(spellID)) or (not CleveRoids.hasSuperwow and texture == CleveRoids.auraTextures[args.name]) then + found = true + break + end + i = i + 1 + end + end + + -- Step 3: Correctly perform conditional validation + local ops = CleveRoids.operators + + -- Case A: No numeric/stack condition, just check for existence. + if not args.amount and not args.operator and not args.checkStacks then + return found + end + + -- Case B: Numeric/stack condition exists. + if args.amount and ops[args.operator] then + -- If aura was found, perform the comparison on its stacks/time. + if found then + if args.checkStacks then + return CleveRoids.comparators[args.operator](stacks or 0, args.amount) + elseif unit == "player" then -- Time check only for player + return CleveRoids.comparators[args.operator](remaining or 0, args.amount) + else + -- This case is for a numeric check on a non-player unit that isn't a stack check. + -- Debuff checks on NPCs/other players are typically for existence or stacks. + -- Return true if found, as there's no other metric to compare. + return true + end + -- If aura was NOT found, compare against default values. + else + if args.checkStacks then + -- Compare stacks (0) against the amount. e.g., (0 < 5) is true. + return CleveRoids.comparators[args.operator](0, args.amount) + elseif unit == "player" then + -- Compare time (0) against the amount. + return CleveRoids.comparators[args.operator](0, args.amount) + else + return false + end + end + end + + return false -- Fallback +end + +function CleveRoids.ValidatePlayerBuff(args) + return CleveRoids.ValidateAura("player", args, true) +end + +function CleveRoids.ValidatePlayerDebuff(args) + return CleveRoids.ValidateAura("player", args, false) +end + +-- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed +function CleveRoids.GetCooldown(name, ignoreGCD) + if not name then return 0 end + local expires = CleveRoids.GetSpellCooldown(name, ignoreGCD) + local spell = CleveRoids.GetSpell(name) + if not spell then expires = CleveRoids.GetItemCooldown(name, ignoreGCD) end + if expires > GetTime() then + -- CleveRoids.Cooldowns[name] = expires + return expires + end + + return 0 +end + +-- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed +-- Returns the cooldown of the given spellName or nil if no such spell was found +function CleveRoids.GetSpellCooldown(spellName, ignoreGCD) + if not spellName then return 0 end + + local spell = CleveRoids.GetSpell(spellName) + if not spell then return 0 end + + local start, cd = GetSpellCooldown(spell.spellSlot, spell.bookType) + if ignoreGCD and cd and cd > 0 and cd == 1.5 then + return 0 + else + return (start + cd) + end +end + +-- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed +function CleveRoids.GetItemCooldown(itemName, ignoreGCD) + if not itemName then return 0 end + + local item = CleveRoids.GetItem(itemName) + if not item then return 0 end + + local start, cd, expires + if item.inventoryID then + start, cd = GetInventoryItemCooldown("player", item.inventoryID) + elseif item.bagID then + start, cd = GetContainerItemCooldown(item.bagID, item.slot) + end + + if ignoreGCD and cd and cd > 0 and cd == 1.5 then + return 0 + else + return (start + cd) + end +end + +function CleveRoids.IsReactive(name) + return CleveRoids.reactiveSpells[spellName] ~= nil +end + +function CleveRoids.GetActionButtonInfo(slot) + local macroName, actionType, id = GetActionText(slot) + if actionType == "MACRO" then + return actionType, id, macroName + elseif actionType == "SPELL" and id then + local spellName, rank = SpellInfo(id) + return actionType, id, spellName, rank + elseif actionType == "ITEM" and id then + local item = CleveRoids.GetItem(id) + return actionType, id, (item and item.name), (item and item.id) + end +end + +function CleveRoids.IsReactiveUsable(spellName) + if not CleveRoids.reactiveSlots[spellName] then return false end + local actionSlot = CleveRoids.reactiveSlots[spellName] + + local isUsable, oom = CleveRoids.Hooks.OriginalIsUsableAction(actionSlot) + local start, duration = GetActionCooldown(actionSlot) + + if isUsable and (start == 0 or duration == 1.5) then -- 1.5 just means gcd is active + return 1 + else + return nil, oom + end +end + +function CleveRoids.CheckSpellCast(unit, spell) + if not CleveRoids.hasSuperwow then return false end + + local spell = spell or "" + local _,guid = UnitExists(unit) + if not guid or (guid and not CleveRoids.spell_tracking[guid]) then + return false + else + -- are we casting a specific spell, or any spell + if spell == SpellInfo(CleveRoids.spell_tracking[guid].spell_id) or (spell == "") then + return true + end + return false + end +end + +-- A list of Conditionals and their functions to validate them +CleveRoids.Keywords = { + exists = function(conditionals) + return UnitExists(conditionals.target) + end, + + noexists = function(conditionals) + return not UnitExists(conditionals.target) + end, + + help = function(conditionals) + return conditionals.help and conditionals.target and UnitExists(conditionals.target) and UnitCanAssist("player", conditionals.target) + end, + + harm = function(conditionals) + return conditionals.harm and conditionals.target and UnitExists(conditionals.target) and UnitCanAttack("player", conditionals.target) + end, + + stance = function(conditionals) + local i = CleveRoids.GetCurrentShapeshiftIndex() + return Or(conditionals.stance, function (v) + return (i == tonumber(v)) + end) + end, + + nostance = function(conditionals) + local i = CleveRoids.GetCurrentShapeshiftIndex() + local forbiddenStances = conditionals.nostance + if type(forbiddenStances) ~= "table" then + return i == 0 + end + return And(forbiddenStances, function (v) + return (i ~= tonumber(v)) + end) + end, + + noform = function(conditionals) + local i = CleveRoids.GetCurrentShapeshiftIndex() + local forbiddenForms = conditionals.noform + if type(forbiddenForms) ~= "table" then + return i == 0 + end + return And(forbiddenForms, function (v) + return (i ~= tonumber(v)) + end) + end, + + form = function(conditionals) + local i = CleveRoids.GetCurrentShapeshiftIndex() + return Or(conditionals.form, function (v) + return (i == tonumber(v)) + end) + end, + + mod = function(conditionals) + if type(conditionals.mod) ~= "table" then + return CleveRoids.kmods.mod() + end + return Or(conditionals.mod, function(mod) + return CleveRoids.kmods[mod]() + end) + end, + + nomod = function(conditionals) + if type(conditionals.nomod) ~= "table" then + return CleveRoids.kmods.nomod() + end + return And(conditionals.nomod, function(mod) + return not CleveRoids.kmods[mod]() + end) + end, + + target = function(conditionals) + return CleveRoids.IsValidTarget(conditionals.target, conditionals.help) + end, + + combat = function(conditionals) + -- Check if an argument like :target or :focus was provided. The parser turns this into a table. + if type(conditionals.combat) == "table" then + -- If so, run the check on the provided unit(s). + return Or(conditionals.combat, function(unit) + return UnitExists(unit) and UnitAffectingCombat(unit) + end) + else + -- Otherwise, this is a bare [combat]. The value might be 'true' or a spell name. + -- In either case, it should safely default to checking the player. + return UnitAffectingCombat("player") + end + end, + + nocombat = function(conditionals) + -- Check if an argument like :target or :focus was provided. + if type(conditionals.nocombat) == "table" then + -- If so, run the check on the provided unit(s). + return And(conditionals.nocombat, function(unit) + if not UnitExists(unit) then + return true + end + return not UnitAffectingCombat(unit) + end) + else + -- Otherwise, this is a bare [nocombat]. Default to checking the player. + return not UnitAffectingCombat("player") + end + end, + + stealth = function(conditionals) + return ( + (CleveRoids.playerClass == "ROGUE" and CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Stealth"])) + or (CleveRoids.playerClass == "DRUID" and CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Prowl"])) + ) + end, + + nostealth = function(conditionals) + return ( + (CleveRoids.playerClass == "ROGUE" and not CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Stealth"])) + or (CleveRoids.playerClass == "DRUID" and not CleveRoids.ValidatePlayerBuff(CleveRoids.Localized.Spells["Prowl"])) + ) + end, + + casting = function(conditionals) + if type(conditionals.casting) ~= "table" then return CleveRoids.CheckSpellCast(conditionals.target, "") end + return Or(conditionals.casting, function (spell) + return CleveRoids.CheckSpellCast(conditionals.target, spell) + end) + end, + + nocasting = function(conditionals) + if type(conditionals.nocasting) ~= "table" then return CleveRoids.CheckSpellCast(conditionals.target, "") end + return And(conditionals.nocasting, function (spell) + return not CleveRoids.CheckSpellCast(conditionals.target, spell) + end) + end, + + zone = function(conditionals) + local zone = GetRealZoneText() + local sub_zone = GetSubZoneText() + return Or(conditionals.zone, function (v) + return (sub_zone ~= "" and (v == sub_zone) or (v == zone)) + end) + end, + + nozone = function(conditionals) + local zone = GetRealZoneText() + local sub_zone = GetSubZoneText() + return And(conditionals.nozone, function (v) + return not ((sub_zone ~= "" and v == sub_zone)) or (v == zone) + end) + end, + + equipped = function(conditionals) + return And(conditionals.equipped, function (v) + return (CleveRoids.HasWeaponEquipped(v) or CleveRoids.HasGearEquipped(v)) + end) + end, + + noequipped = function(conditionals) + return And(conditionals.noequipped, function (v) + return not (CleveRoids.HasWeaponEquipped(v) or CleveRoids.HasGearEquipped(v)) + end) + end, + + dead = function(conditionals) + if not conditionals.target then return false end + return UnitIsDeadOrGhost(conditionals.target) + end, + + alive = function(conditionals) + if not conditionals.target then return false end + return not UnitIsDeadOrGhost(conditionals.target) + end, + + noalive = function(conditionals) + if not conditionals.target then return false end + return UnitIsDeadOrGhost(conditionals.target) + end, + + nodead = function(conditionals) + if not conditionals.target then return false end + return not UnitIsDeadOrGhost(conditionals.target) + end, + + reactive = function(conditionals) + return Or(conditionals.reactive, function (v) + return CleveRoids.IsReactiveUsable(v) + end) + end, + + noreactive = function(conditionals) + return And(conditionals.noreactive,function (v) + return not CleveRoids.IsReactiveUsable(v) + end) + end, + + member = function(conditionals) + return Or(conditionals.member, function(v) + return + CleveRoids.IsTargetInGroupType(conditionals.target, "party") + or CleveRoids.IsTargetInGroupType(conditionals.target, "raid") + end) + end, + + party = function(conditionals) + return CleveRoids.IsTargetInGroupType(conditionals.target, "party") + end, + + noparty = function(conditionals) + return not CleveRoids.IsTargetInGroupType(conditionals.target, "party") + end, + + raid = function(conditionals) + return CleveRoids.IsTargetInGroupType(conditionals.target, "raid") + end, + + noraid = function(conditionals) + return not CleveRoids.IsTargetInGroupType(conditionals.target, "raid") + end, + + group = function(conditionals) + if type(conditionals.group) ~= "table" then + conditionals.group = { "party", "raid" } + end + return Or(conditionals.group, function(groups) + if groups == "party" then + return GetNumPartyMembers() > 0 + elseif groups == "raid" then + return GetNumRaidMembers() > 0 + end + end) + end, + + checkchanneled = function(conditionals) + return And(conditionals.checkchanneled, function(channeledSpells) + return CleveRoids.CheckChanneled(channeledSpells) + end) + end, + + buff = function(conditionals) + return And(conditionals.buff, function(v) + return CleveRoids.ValidateUnitBuff(conditionals.target, v) + end) + end, + + nobuff = function(conditionals) + return And(conditionals.nobuff, function(v) + return not CleveRoids.ValidateUnitBuff(conditionals.target, v) + end) + end, + + debuff = function(conditionals) + return And(conditionals.debuff, function(v) + return CleveRoids.ValidateUnitDebuff(conditionals.target, v) + end) + end, + + nodebuff = function(conditionals) + return And(conditionals.nodebuff, function(v) + return not CleveRoids.ValidateUnitDebuff(conditionals.target, v) + end) + end, + + mybuff = function(conditionals) + return And(conditionals.mybuff, function(v) + return CleveRoids.ValidatePlayerBuff(v) + end) + end, + + nomybuff = function(conditionals) + return And(conditionals.nomybuff, function(v) + return not CleveRoids.ValidatePlayerBuff(v) + end) + end, + + mydebuff = function(conditionals) + return And(conditionals.mydebuff, function(v) + return CleveRoids.ValidatePlayerDebuff(v) + end) + end, + + nomydebuff = function(conditionals) + return And(conditionals.nomydebuff, function(v) + return not CleveRoids.ValidatePlayerDebuff(v) + end) + end, + + power = function(conditionals) + return And(conditionals.power, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidatePower(conditionals.target, args.operator, args.amount) + end) + end, + + mypower = function(conditionals) + return And(conditionals.mypower, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidatePower("player", args.operator, args.amount) + end) + end, + + rawpower = function(conditionals) + return And(conditionals.rawpower, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateRawPower(conditionals.target, args.operator, args.amount) + end) + end, + + myrawpower = function(conditionals) + return And(conditionals.myrawpower, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateRawPower("player", args.operator, args.amount) + end) + end, + + powerlost = function(conditionals) + return And(conditionals.powerlost, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidatePowerLost(conditionals.target, args.operator, args.amount) + end) + end, + + mypowerlost = function(conditionals) + return And(conditionals.mypowerlost, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidatePowerLost("player", args.operator, args.amount) + end) + end, + + hp = function(conditionals) + return And(conditionals.hp, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateHp(conditionals.target, args.operator, args.amount) + end) + end, + + myhp = function(conditionals) + return And(conditionals.myhp, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateHp("player", args.operator, args.amount) + end) + end, + + rawhp = function(conditionals) + return And(conditionals.rawhp, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateRawHp(conditionals.target, args.operator, args.amount) + end) + end, + + myrawhp = function(conditionals) + return And(conditionals.myrawhp, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateRawHp("player", args.operator, args.amount) + end) + end, + + hplost = function(conditionals) + return And(conditionals.hplost, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateHpLost(conditionals.target, args.operator, args.amount) + end) + end, + + myhplost = function(conditionals) + return And(conditionals.myhplost, function(args) + if type(args) ~= "table" then return false end + return CleveRoids.ValidateHpLost("player", args.operator, args.amount) + end) + end, + + type = function(conditionals) + return Or(conditionals.type, function(unittype) + return CleveRoids.ValidateCreatureType(unittype, conditionals.target) + end) + end, + + notype = function(conditionals) + return And(conditionals.notype, function(unittype) + return not CleveRoids.ValidateCreatureType(unittype, conditionals.target) + end) + end, + + cooldown = function(conditionals) + return And(conditionals.cooldown,function (v) + return CleveRoids.ValidateCooldown(v, true) + end) + end, + + nocooldown = function(conditionals) + return And(conditionals.nocooldown,function (v) + return not CleveRoids.ValidateCooldown(v, true) + end) + end, + + cdgcd = function(conditionals) + return And(conditionals.cdgcd,function (v) + return CleveRoids.ValidateCooldown(v, false) + end) + end, + + nocdgcd = function(conditionals) + return And(conditionals.nocdgcd,function (v) + return not CleveRoids.ValidateCooldown(v, false) + end) + end, + + channeled = function(conditionals) + return CleveRoids.CurrentSpell.type == "channeled" + end, + + nochanneled = function(conditionals) + return CleveRoids.CurrentSpell.type ~= "channeled" + end, + + targeting = function(conditionals) + return Or(conditionals.targeting, function (unit) + return (UnitIsUnit("targettarget", unit) == 1) + end) + end, + + notargeting = function(conditionals) + return And(conditionals.notargeting, function (unit) + return UnitIsUnit("targettarget", unit) ~= 1 + end) + end, + + isplayer = function(conditionals) + return UnitIsPlayer(conditionals.target) + end, + + isnpc = function(conditionals) + return not UnitIsPlayer(conditionals.target) + end, + + inrange = function(conditionals) + if not IsSpellInRange then return end + return And(conditionals.inrange, function(spellName) + return IsSpellInRange(spellName or conditionals.action, conditionals.target) == 1 + end) + end, + + noinrange = function(conditionals) + if not IsSpellInRange then return end + return And(conditionals.noinrange, function(spellName) + return IsSpellInRange(spellName or conditionals.action, conditionals.target) == 0 + end) + end, + + combo = function(conditionals) + return And(conditionals.combo, function(args) + return CleveRoids.ValidateComboPoints(args.operator, args.amount) + end) + end, + + nocombo = function(conditionals) + return And(conditionals.nocombo, function(args) + return not CleveRoids.ValidateComboPoints(args.operator, args.amount) + end) + end, + + known = function(conditionals) + return And(conditionals.known, function(args) + return CleveRoids.ValidateKnown(args) + end) + end, + + noknown = function(conditionals) + return And(conditionals.noknown, function(args) + return not CleveRoids.ValidateKnown(args) + end) + end, + + resting = function() + return IsResting() == 1 + end, + + noresting = function() + return IsResting() == nil + end, + + stat = function(conditionals) + return And(conditionals.stat, function(args) + if type(args) ~= "table" or not args.name or not args.operator or not args.amount then + return false -- Malformed arguments from the parser. + end + + local stat_key = string.lower(args.name) + local get_stat_func = stat_checks[stat_key] + + if not get_stat_func then + return false -- The requested stat key is invalid. + end + + local current_value = get_stat_func() + if not current_value then return false end + + -- Use the addon's existing logic to compare the numbers. + return CleveRoids.comparators[args.operator](current_value, args.amount) + end) + end, + + class = function(conditionals) + -- Determine which unit to check. Defaults to 'target' if no @unitid was specified. + local unitToCheck = conditionals.target or "target" + + -- The conditional must fail if the unit doesn't exist OR is not a player. + if not UnitExists(unitToCheck) or not UnitIsPlayer(unitToCheck) then + return false + end + + -- Get the player's class. + local localizedClass, englishClass = UnitClass(unitToCheck) + if not localizedClass then return false end -- Failsafe for unusual cases + + -- The "Or" helper handles multiple values like [class:Warrior/Druid]. + return Or(conditionals.class, function(requiredClass) + return strlower(requiredClass) == strlower(localizedClass) or strlower(requiredClass) == strlower(englishClass) + end) + end, + + noclass = function(conditionals) + -- Determine which unit to check. Defaults to 'target' if no @unitid was specified. + local unitToCheck = conditionals.target or "target" + + -- A unit that doesn't exist cannot have a specific player class. + if not UnitExists(unitToCheck) then + return true + end + + -- An NPC cannot have a specific player class. + if not UnitIsPlayer(unitToCheck) then + return true + end + + -- If we get here, the unit is a player. Now check their class. + local localizedClass, englishClass = UnitClass(unitToCheck) + -- A player should always have a class, but if not, this condition is still met. + if not localizedClass then return true end + + -- The "And" helper ensures the player's class is not any of the forbidden classes. + return And(conditionals.noclass, function(forbiddenClass) + return strlower(forbiddenClass) ~= strlower(localizedClass) and strlower(forbiddenClass) ~= strlower(englishClass) + end) + end +} diff --git a/Console.lua b/Console.lua new file mode 100644 index 0000000..17ac7fc --- /dev/null +++ b/Console.lua @@ -0,0 +1,204 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +SLASH_PETATTACK1 = "/petattack" + +SlashCmdList.PETATTACK = function(msg) CleveRoids.DoPetAction(PetAttack, msg); end + +SLASH_PETFOLLOW1 = "/petfollow" + +SlashCmdList.PETFOLLOW = function(msg) CleveRoids.DoPetAction(PetFollow, msg); end + +SLASH_PETWAIT1 = "/petwait" + +SlashCmdList.PETWAIT = function(msg) CleveRoids.DoPetAction(PetWait, msg); end + +SLASH_PETPASSIVE1 = "/petpassive" + +SlashCmdList.PETPASSIVE = function(msg) CleveRoids.DoPetAction(PetPassiveMode, msg); end + +SLASH_PETAGGRESSIVE1 = "/petaggressive" + +SlashCmdList.PETAGGRESSIVE = function(msg) CleveRoids.DoPetAction(PetAggressiveMode, msg); end + +SLASH_PETDEFENSIVE1 = "/petdefensive" + +SlashCmdList.PETDEFENSIVE = function(msg) CleveRoids.DoPetAction(PetDefensiveMode, msg); end + +SLASH_RELOAD1 = "/rl" + +SlashCmdList.RELOAD = function() ReloadUI(); end + +SLASH_USE1 = "/use" + +SlashCmdList.USE = CleveRoids.DoUse + +SLASH_EQUIP1 = "/equip" + +SlashCmdList.EQUIP = CleveRoids.DoUse +-- take back supermacro and pfUI /equip +SlashCmdList.SMEQUIP = CleveRoids.DoUse +SlashCmdList.PFEQUIP = CleveRoids.DoUse + +SLASH_EQUIPMH1 = "/equipmh" +SlashCmdList.EQUIPMH = CleveRoids.DoEquipMainhand + +SLASH_EQUIPOH1 = "/equipoh" +SlashCmdList.EQUIPOH = CleveRoids.DoEquipOffhand + +SLASH_UNSHIFT1 = "/unshift" + +SlashCmdList.UNSHIFT = CleveRoids.DoUnshift + +SLASH_UNQUEUE1 = "/unqueue" +SlashCmdList.UNQUEUE = SpellStopCasting + +-- TODO make this conditional too +SLASH_CANCELAURA1 = "/cancelaura" +SLASH_CANCELAURA2 = "/unbuff" + +SlashCmdList.CANCELAURA = CleveRoids.DoConditionalCancelAura + +-- Define original implementations before hooking them. +-- This ensures we have a fallback for non-conditional use. +local StartAttack = function(msg) + if not UnitExists("target") or UnitIsDead("target") then TargetNearestEnemy() end + if not CleveRoids.CurrentSpell.autoAttack and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player","target") then + CleveRoids.CurrentSpell.autoAttackLock = true + CleveRoids.autoAttackLockElapsed = GetTime() + AttackTarget() + end +end + +local StopAttack = function(msg) + if CleveRoids.CurrentSpell.autoAttack and UnitExists("target") then + AttackTarget() + CleveRoids.CurrentSpell.autoAttack = false + end +end + +-- Register slash commands and assign original handlers. +-- These will be hooked immediately after. +SLASH_STARTATTACK1 = "/startattack" +SlashCmdList.STARTATTACK = StartAttack + +SLASH_STOPATTACK1 = "/stopattack" +SlashCmdList.STOPATTACK = StopAttack + +SLASH_STOPCASTING1 = "/stopcasting" +SlashCmdList.STOPCASTING = SpellStopCasting + +---------------------------------- +-- HOOK DEFINITIONS START +---------------------------------- + +-- /startattack hook +CleveRoids.Hooks.STARTATTACK_SlashCmd = SlashCmdList.STARTATTACK +SlashCmdList.STARTATTACK = function(msg) + msg = msg or "" + if string.find(msg, "%[") then + CleveRoids.DoConditionalStartAttack(msg) + else + CleveRoids.Hooks.STARTATTACK_SlashCmd(msg) + end +end + +-- /stopattack hook +CleveRoids.Hooks.STOPATTACK_SlashCmd = SlashCmdList.STOPATTACK +SlashCmdList.STOPATTACK = function(msg) + msg = msg or "" + if string.find(msg, "%[") then + -- If conditionals are present, let the function handle it. + -- It will only stop the attack if the conditions are met. + CleveRoids.DoConditionalStopAttack(msg) + else + -- If no conditionals, run the original command. + CleveRoids.Hooks.STOPATTACK_SlashCmd(msg) + end +end + +-- /stopcasting hook +CleveRoids.Hooks.STOPCASTING_SlashCmd = SlashCmdList.STOPCASTING +SlashCmdList.STOPCASTING = function(msg) + msg = msg or "" + if string.find(msg, "%[") then + -- If conditionals are present, let the function handle it. + -- It will only stop the cast if the conditions are met. + CleveRoids.DoConditionalStopCasting(msg) + else + -- If no conditionals, run the original command. + CleveRoids.Hooks.STOPCASTING_SlashCmd() + end +end + +CleveRoids.Hooks.UNQUEUE_SlashCmd = SlashCmdList.UNQUEUE +SlashCmdList.UNQUEUE = function(msg) + msg = msg or "" + if string.find(msg, "%[") then + -- If conditionals are present, let the function handle it. + CleveRoids.DoConditionalStopCasting(msg) + else + -- If no conditionals, run the original command. + CleveRoids.Hooks.UNQUEUE_SlashCmd() + end +end + +-- /cast hook +CleveRoids.Hooks.CAST_SlashCmd = SlashCmdList.CAST +SlashCmdList.CAST = function(msg) + if msg and string.find(msg, "[%[%?!~{]") then + CleveRoids.DoCast(msg) + else + CleveRoids.Hooks.CAST_SlashCmd(msg) + end +end + +CleveRoids.Hooks.TARGET_SlashCmd = SlashCmdList.TARGET +CleveRoids.TARGET_SlashCmd = function(msg) + tmsg = CleveRoids.Trim(msg) + + if tmsg ~= "" and not string.find(tmsg, "%[") and not string.find(tmsg, "@") then + CleveRoids.Hooks.TARGET_SlashCmd(tmsg) + return + end + + if CleveRoids.DoTarget(tmsg) then + if UnitExists("target") then + return + end + end + CleveRoids.Hooks.TARGET_SlashCmd(msg) +end +SlashCmdList.TARGET = CleveRoids.TARGET_SlashCmd + + +SLASH_CASTSEQUENCE1 = "/castsequence" +SlashCmdList.CASTSEQUENCE = function(msg) + msg = CleveRoids.Trim(msg) + local sequence = CleveRoids.GetSequence(msg) + if not sequence then return end + -- if not sequence.active then return end + + CleveRoids.DoCastSequence(sequence) +end + + +SLASH_RUNMACRO1 = "/runmacro" +SlashCmdList.RUNMACRO = function(msg) + return CleveRoids.ExecuteMacroByName(CleveRoids.Trim(msg)) +end + +SLASH_RETARGET1 = "/retarget" +SlashCmdList.RETARGET = function(msg) + CleveRoids.DoRetarget() +end + +SLASH_STOPMACRO1 = "/stopmacro" +SlashCmdList.STOPMACRO = function(msg) + CleveRoids.DoStopMacro(msg) +end + diff --git a/Core.lua b/Core.lua new file mode 100644 index 0000000..0bded8f --- /dev/null +++ b/Core.lua @@ -0,0 +1,2146 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny / brian / Mewtiny + License: MIT License +]] + +-- Setup to wrap our stuff in a table so we don't pollute the global environment +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +_G.CleveRoids = CleveRoids +CleveRoids.lastItemIndexTime = 0 +CleveRoids.initializationTimer = nil +CleveRoids.isActionUpdateQueued = true -- Flag to check if a full action update is needed + +local frame = CreateFrame("Frame") +frame:RegisterEvent("VARIABLES_LOADED") +frame:SetScript("OnEvent", function() + if not CleveRoidMacros then CleveRoidMacros = {} end + + if type(CleveRoidMacros.realtime) ~= "number" then + CleveRoidMacros.realtime = 0 + end + + if type(CleveRoidMacros.refresh) ~= "number" then + CleveRoidMacros.refresh = 5 + end +end) + +-- Queues a full update of all action bars. +-- This is called by game event handlers to avoid running heavy logic inside the event itself. +function CleveRoids.QueueActionUpdate() + if CleveRoidMacros.realtime == 0 then + CleveRoids.isActionUpdateQueued = true + end +end + +function CleveRoids.GetSpellCost(spellSlot, bookType) + CleveRoids.Frame:SetOwner(WorldFrame, "ANCHOR_NONE") + CleveRoids.Frame:SetSpell(spellSlot, bookType) + local _, _, cost = string.find(CleveRoids.Frame.costFontString:GetText() or "", "^(%d+) [^ys]") + local _, _, reagent = string.find(CleveRoids.Frame.reagentFontString:GetText() or "", "^Reagents: (.*)") + if reagent and string.sub(reagent, 1, 2) == "|c" then + reagent = string.sub(reagent, 11, -3) + end + + return (cost and tonumber(cost) or 0), (reagent and tostring(reagent) or nil) +end + +function CleveRoids.GetProxyActionSlot(slot) + if not slot then return end + return CleveRoids.actionSlots[slot] or CleveRoids.actionSlots[slot.."()"] +end + +function CleveRoids.TestForActiveAction(actions) + if not actions then return end + local currentActive = actions.active + local currentSequence = actions.sequence + local hasActive = false + local newActiveAction = nil + local newSequence = nil + + if actions.tooltip and table.getn(actions.list) == 0 then + if CleveRoids.TestAction(actions.cmd or "", actions.args or "") then + + hasActive = true + actions.active = actions.tooltip + end + else + for _, action in ipairs(actions.list) do + -- break on first action that passes tests + if CleveRoids.TestAction(action.cmd, action.args) then + hasActive = true + if action.sequence then + newSequence = action.sequence + newActiveAction = CleveRoids.GetCurrentSequenceAction(newSequence) + if not newActiveAction then hasActive = false end + else + newActiveAction = action + end + if hasActive then break end + end + end + end + + local changed = false + if currentActive ~= newActiveAction or currentSequence ~= newSequence then + actions.active = newActiveAction + actions.sequence = newSequence + changed = true + end + + if not hasActive then + if actions.active ~= nil or actions.sequence ~= nil then + actions.active = nil + actions.sequence = nil + changed = true + end + return changed + end + + if actions.active then + local previousUsable = actions.active.usable + local previousOom = actions.active.oom + local previousInRange = actions.active.inRange + + if actions.active.spell then + actions.active.inRange = 1 + + -- nampower range check (rebuild name(rank) like DoWithConditionals) + if IsSpellInRange then + local unit = actions.active.conditionals and actions.active.conditionals.target or "target" + if unit == "focus" and pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id then + unit = pfUI.uf.focus.label .. pfUI.uf.focus.id + end + local castName = actions.active.action + if actions.active.spell and actions.active.spell.name then + local rank = actions.active.spell.rank + or (actions.active.spell.highest and actions.active.spell.highest.rank) + if rank and rank ~= "" then + castName = actions.active.spell.name .. "(" .. rank .. ")" + end + end + if UnitExists(unit) then + local r = IsSpellInRange(castName, unit) + if r ~= nil then + actions.active.inRange = r + end + end + end + + actions.active.oom = (UnitMana("player") < actions.active.spell.cost) + + local start, duration = GetSpellCooldown(actions.active.spell.spellSlot, actions.active.spell.bookType) + local onCooldown = (start > 0 and duration > 0) + + if actions.active.isReactive then + if not CleveRoids.IsReactiveUsable(actions.active.action) then + actions.active.oom = false + actions.active.usable = nil + else + actions.active.usable = (pfUI and pfUI.bars) and nil or 1 + end + elseif actions.active.inRange ~= 0 and not actions.active.oom then + actions.active.usable = 1 + + -- pfUI:actionbar.lua -- update usable [out-of-range = 1, oom = 2, not-usable = 3, default = 0] + elseif pfUI and pfUI.bars and actions.active.oom then + actions.active.usable = 2 + else + actions.active.usable = nil + end + else + actions.active.inRange = 1 + actions.active.usable = 1 + end + if actions.active.usable ~= previousUsable or + actions.active.oom ~= previousOom or + actions.active.inRange ~= previousInRange then + changed = true + end + end + return changed +end + +function CleveRoids.TestForAllActiveActions() + for slot, actions in pairs(CleveRoids.Actions) do + local stateChanged = CleveRoids.TestForActiveAction(actions) + if stateChanged then + CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot) + end + end +end + +function CleveRoids.ClearAction(slot) + if not CleveRoids.Actions[slot] then return end + CleveRoids.Actions[slot].active = nil + CleveRoids.Actions[slot] = nil +end + +function CleveRoids.GetAction(slot) + if not slot or not CleveRoids.ready then return end + + local actions = CleveRoids.Actions[slot] + if actions then return actions end + + local text = GetActionText(slot) + + if text then + local macro = CleveRoids.GetMacro(text) + if macro then + actions = macro.actions + + CleveRoids.TestForActiveAction(actions) + CleveRoids.Actions[slot] = actions + CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot) + return actions + end + end +end + +function CleveRoids.GetActiveAction(slot) + local action = CleveRoids.GetAction(slot) + return action and action.active +end + +function CleveRoids.SendEventForAction(slot, event, ...) + local old_this = this + + local original_global_args = {} + for i = 1, 10 do + original_global_args[i] = _G["arg" .. i] + end + + if type(arg) == "table" then + + local n_varargs_from_arg_table = arg.n or 0 + for i = 1, 10 do + if i <= n_varargs_from_arg_table then + _G["arg" .. i] = arg[i] + else + _G["arg" .. i] = nil + end + end + else + for i = 1, 10 do + _G["arg" .. i] = nil + end + end + + local button_to_call_event_on + local page = floor((slot - 1) / NUM_ACTIONBAR_BUTTONS) + 1 + local pageSlot = slot - (page - 1) * NUM_ACTIONBAR_BUTTONS + + if slot >= 73 then + button_to_call_event_on = _G["BonusActionButton" .. pageSlot] + elseif slot >= 61 then + button_to_call_event_on = _G["MultiBarBottomLeftButton" .. pageSlot] + elseif slot >= 49 then + button_to_call_event_on = _G["MultiBarBottomRightButton" .. pageSlot] + elseif slot >= 37 then + button_to_call_event_on = _G["MultiBarLeftButton" .. pageSlot] + elseif slot >= 25 then + button_to_call_event_on = _G["MultiBarRightButton" .. pageSlot] + end + + if button_to_call_event_on then + this = button_to_call_event_on + ActionButton_OnEvent(event) + end + + if page == CURRENT_ACTIONBAR_PAGE then + local main_bar_button = _G["ActionButton" .. pageSlot] + if main_bar_button and main_bar_button ~= button_to_call_event_on then + this = main_bar_button + ActionButton_OnEvent(event) + elseif not button_to_call_event_on and main_bar_button then + this = main_bar_button + ActionButton_OnEvent(event) + end + end + + this = old_this + + for i = 1, 10 do + _G["arg" .. i] = original_global_args[i] + end + + if type(arg) == "table" and arg.n then + + if arg.n == 0 then + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event) end + elseif arg.n == 1 then + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event, arg[1]) end + elseif arg.n == 2 then + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event, arg[1], arg[2]) end + elseif arg.n == 3 then + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event, arg[1], arg[2], arg[3]) end + elseif arg.n == 4 then + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event, arg[1], arg[2], arg[3], arg[4]) end + elseif arg.n == 5 then + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event, arg[1], arg[2], arg[3], arg[4], arg[5]) end + elseif arg.n == 6 then + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event, arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]) end + else + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do fn_h(slot, event, arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7]) end + end + else + + for _, fn_h in ipairs(CleveRoids.actionEventHandlers) do + fn_h(slot, event) + end + end +end + +-- Executes the given Macro's body +-- body: The Macro's body +function CleveRoids.ExecuteMacroBody(body,inline) + local lines = CleveRoids.splitString(body, "\n") + if inline then lines = CleveRoids.splitString(body, "\\n"); end + + for k,v in pairs(lines) do + if CleveRoids.stopmacro then + CleveRoids.stopmacro = false + return true + end + ChatFrameEditBox:SetText(v) + ChatEdit_SendText(ChatFrameEditBox) + end + return true +end + +-- Gets the body of the Macro with the given name +-- name: The name of the Macro +-- returns: The body of the macro +function CleveRoids.GetMacroBody(name) + local macro = CleveRoids.GetMacro(name) + return macro and macro.body +end + +-- Attempts to execute a macro by the given name +-- name: The name of the macro +-- returns: Whether the macro was executed or not +function CleveRoids.ExecuteMacroByName(name) + local body = CleveRoids.GetMacroBody(name) + if not body then + return false + end + + CleveRoids.ExecuteMacroBody(body) + return true +end + +function CleveRoids.SetHelp(conditionals) + if conditionals.harm then + conditionals.help = false + end +end + +function CleveRoids.FixEmptyTarget(conditionals) + if not conditionals.target then + if UnitExists("target") then + conditionals.target = "target" + elseif GetCVar("autoSelfCast") == "1" and not conditionals.target == "help" then + conditionals.target = "player" + end + end + return false +end + +-- Fixes the conditionals' target by targeting the target with the given name +-- conditionals: The conditionals containing the current target +-- name: The name of the player to target +-- hook: The target hook +-- returns: Whether or not we've changed the player's current target +function CleveRoids.FixEmptyTargetSetTarget(conditionals, name, hook) + if not conditionals.target then + hook(name) + conditionals.target = "target" + return true + end + return false +end + +-- Returns the name of the focus target or nil +function CleveRoids.GetFocusName() + -- 1. Add specific compatibility for pfUI. + -- pfUI stores its focus unit information in a table. + if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.unitname then + return pfUI.uf.focus.unitname + end + + -- Fallback for other focus addons + if ClassicFocus_CurrentFocus then + return ClassicFocus_CurrentFocus + elseif CURR_FOCUS_TARGET then + return CURR_FOCUS_TARGET + end + + return nil +end + +-- Attempts to target the focus target. +-- returns: Whether or not it succeeded +function CleveRoids.TryTargetFocus() + local name = CleveRoids.GetFocusName() + + if not name then + return false + end + + TargetByName(name, true) + + if not UnitExists("target") or (string.lower(UnitName("target")) ~= name) then + -- The target switch failed (out of range, LoS, etc.) + return false + end + + return true +end + +function CleveRoids.GetMacroNameFromAction(text) + if string.sub(text, 1, 1) == "{" and string.sub(text, -1) == "}" then + local name + if string.sub(text, 2, 2) == "\"" and string.sub(text, -2, -2) == "\"" then + return string.sub(text, 3, -3) + else + return string.sub(text, 2, -2) + end + end +end + +function CleveRoids.CreateActionInfo(action, conditionals) + local _, _, text = string.find(action, "!?%??~?(.*)") + local spell = CleveRoids.GetSpell(text) + local item, macroName, macro, macroTooltip, actionType, texture + + -- NEW: Check if the action is a slot number + local slotId = tonumber(text) + if slotId and slotId >= 1 and slotId <= 19 then + actionType = "item" + -- Use the most reliable method first to get the texture for an equipped item. + local itemTexture = GetInventoryItemTexture("player", slotId) + + -- Check if the texture was successfully found. + if itemTexture then + texture = itemTexture + else + -- If the primary method fails, fall back to the unknown texture. + -- This prevents errors if the slot is empty or the item data is unusual. + texture = CleveRoids.unknownTexture + end + else + -- Original logic for named items and spells + if not spell then + item = CleveRoids.GetItem(text) + end + if not item then + macroName = CleveRoids.GetMacroNameFromAction(text) + macro = CleveRoids.GetMacro(macroName) + macroTooltip = (macro and macro.actions) and macro.actions.tooltip + end + + if spell then + actionType = "spell" + texture = spell.texture or CleveRoids.unknownTexture + elseif item then + actionType = "item" + texture = (item and item.texture) or CleveRoids.unknownTexture + elseif macro then + actionType = "macro" + texture = (macro.actions and macro.actions.tooltip and macro.actions.tooltip.texture) + or (macro and macro.texture) + or CleveRoids.unknownTexture + end + end + + local info = { + action = text, + item = item, + spell = spell, + macro = macroTooltip, + type = actionType, + texture = texture, + conditionals = conditionals, + } + + return info +end + +function CleveRoids.SplitCommandAndArgs(text) + local _, _, cmd, args = string.find(text, "(/%w+%s?)(.*)") + if cmd and args then + cmd = CleveRoids.Trim(cmd) + text = CleveRoids.Trim(args) + end + return cmd, args +end + +function CleveRoids.ParseSequence(text) + local args = string.gsub(text, "(%s*,%s*)", ",") + local _, c, cond = string.find(args, "(%[.*%])") + local _, r, reset, resetVal = string.find(args, "(%s*%]*%s*reset=([%w/]+)%s+)") + + actionSeq = CleveRoids.Trim((r and string.sub(args, r+1)) or (c and string.sub(args, c+1)) or args) + args = (cond or "") .. actionSeq + + if not actionSeq then + return + end + + local sequence = { + index = 1, + reset = {}, + status = 0, + list = {}, + lastUpdate = 0, + cond = cond, + args = args, + cmd = "/castsequence" + } + if resetVal then + for _, rule in ipairs(CleveRoids.Split(resetVal, "/")) do + local secs = tonumber(rule) + if secs and secs > 0 then + sequence.reset.secs = secs + else + sequence.reset[string.lower(rule)] = true + end + end + end + + for _, a in ipairs(CleveRoids.Split(actionSeq, ",")) do + local sa = CleveRoids.CreateActionInfo(CleveRoids.GetParsedMsg(a)) + table.insert(sequence.list, sa) + end + CleveRoids.Sequences[text] = sequence + + return sequence +end + +function CleveRoids.ParseMacro(name) + if not name then return end + + local macroID = GetMacroIndexByName(name) + if not macroID then return end + + local _, texture, body = GetMacroInfo(macroID) + + if not body and GetSuperMacroInfo then + _, texture, body = GetSuperMacroInfo(name) + end + + if not texture or not body then return end + + + local macro = { + id = macroID, + name = name, + texture = texture, + body = body, + actions = {}, + } + macro.actions.list = {} + + -- build a list of testable actions for the macro + for i, line in ipairs(CleveRoids.splitString(body, "\n")) do + line = CleveRoids.Trim(line) + local cmd, args = CleveRoids.SplitCommandAndArgs(line) + + -- check for #showtooltip + if i == 1 then + local _, _, st, _, tt = string.find(line, "(#showtooltip)(%s?(.*))") + + -- if no #showtooltip, nothing to keep track of + if not st then + break + end + tt = CleveRoids.Trim(tt) + + -- #showtooltip and item/spell/macro specified, only use this tooltip + if st and tt ~= "" then + for _, arg in ipairs(CleveRoids.splitStringIgnoringQuotes(tt)) do + macro.actions.tooltip = CleveRoids.CreateActionInfo(arg) + local action = CleveRoids.CreateActionInfo(CleveRoids.GetParsedMsg(arg)) + action.cmd = "/cast" + action.args = arg + action.isReactive = CleveRoids.reactiveSpells[action.action] + table.insert(macro.actions.list, action) + end + break + end + else + -- make sure we have a testable action + if line ~= "" and args ~= "" and CleveRoids.dynamicCmds[cmd] then + for _, arg in ipairs(CleveRoids.splitStringIgnoringQuotes(args)) do + + local action = CleveRoids.CreateActionInfo(CleveRoids.GetParsedMsg(arg)) + + if cmd == "/castsequence" then + local sequence = CleveRoids.GetSequence(args) + if sequence then + action.sequence = sequence + end + end + action.cmd = cmd + action.args = arg + action.isReactive = CleveRoids.reactiveSpells[action.action] + table.insert(macro.actions.list, action) + end + end + end + end + + CleveRoids.Macros[name] = macro + return macro +end + +function CleveRoids.ParseMsg(msg) + if not msg then return end + local conditionals = {} + + msg, conditionals.ignoretooltip = string.gsub(CleveRoids.Trim(msg), "^%?", "") + local _, cbEnd, conditionBlock = string.find(msg, "%[(.+)%]") + local _, _, noSpam, cancelAura, action = string.find(string.sub(msg, (cbEnd or 0) + 1), "^%s*(!?)(~?)([^!~]+.*)") + action = CleveRoids.Trim(action or "") + + -- Store the action along with the conditionals incase it's needed + conditionals.action = action + -- FLEXIBLY remove rank text like "(Rank 9)" to get the base spell name + action = string.gsub(action, "%s*%(.-%)%s*$", "") + + if noSpam and noSpam ~= "" then + local spamCond = CleveRoids.GetSpammableConditional(action) + if spamCond then + conditionals[spamCond] = { action } + end + end + if cancelAura and cancelAura ~= "" then + conditionals.cancelaura = action + end + + if not conditionBlock then + return conditionals.action, conditionals + end + + -- Set the action's target to @unitid if found + local _, _, target = string.find(conditionBlock, "(@[^%s,]+)") + if target then + conditionBlock = CleveRoids.Trim(string.gsub(conditionBlock, target, "")) + conditionals.target = string.sub(target, 2) + end + + if conditionBlock and action then + -- Split the conditional block by comma or space + for _, conditionGroups in CleveRoids.splitStringIgnoringQuotes(conditionBlock, {",", " "}) do + if conditionGroups ~= "" then + -- Split conditional groups by colon + local conditionGroup = CleveRoids.splitStringIgnoringQuotes(conditionGroups, ":") + local condition, args = conditionGroup[1], conditionGroup[2] + + -- No args, the action is the implicit argument + if not args or args == "" then + if not conditionals[condition] then + conditionals[condition] = action + else + if type(conditionals[condition]) ~= "table" then + conditionals[condition] = { conditionals[condition] } + end + table.insert(conditionals[condition], action) + end + else + -- Has args. Ensure the key's value is a table and add the new arguments. + if not conditionals[condition] then + conditionals[condition] = {} + elseif type(conditionals[condition]) ~= "table" then + conditionals[condition] = { conditionals[condition] } + end + + -- Split the args by / for multiple values + for _, arg_item in CleveRoids.splitString(args, "/") do + local processed_arg = CleveRoids.Trim(arg_item) + + processed_arg = string.gsub(processed_arg, '"', "") + processed_arg = string.gsub(processed_arg, "_", " ") + processed_arg = CleveRoids.Trim(processed_arg) + + local arg_for_find = processed_arg + arg_for_find = string.gsub(arg_for_find, "^#(%d+)$", "=#%1") + arg_for_find = string.gsub(arg_for_find, "([^>~=<]+)#(%d+)", "%1=#%2") + + -- FIXED: This regex now accepts decimal numbers + local _, _, name, operator, amount = string.find(arg_for_find, "([^>~=<]*)([>~=<]+)(#?%d*%.?%d+)") + if not operator or not amount then + table.insert(conditionals[condition], processed_arg) + else + local name_to_use = (name and name ~= "") and name or conditionals.action + + local final_amount_str, num_replacements = string.gsub(amount, "#", "") + local should_check_stacks = (num_replacements == 1) + + table.insert(conditionals[condition], { + name = CleveRoids.Trim(name_to_use), + operator = operator, + amount = tonumber(final_amount_str), + checkStacks = should_check_stacks + }) + end + end + end + end + end + return conditionals.action, conditionals + end +end + +-- Get previously parsed or parse, store and return +function CleveRoids.GetParsedMsg(msg) + if not msg then return end + + if CleveRoids.ParsedMsg[msg] then + return CleveRoids.ParsedMsg[msg].action, CleveRoids.ParsedMsg[msg].conditionals + end + + CleveRoids.ParsedMsg[msg] = {} + CleveRoids.ParsedMsg[msg].action, CleveRoids.ParsedMsg[msg].conditionals = CleveRoids.ParseMsg(msg) + + return CleveRoids.ParsedMsg[msg].action, CleveRoids.ParsedMsg[msg].conditionals +end + +function CleveRoids.GetMacro(name) + return CleveRoids.Macros[name] or CleveRoids.ParseMacro(name) +end + +function CleveRoids.GetSequence(args) + return CleveRoids.Sequences[args] or CleveRoids.ParseSequence(args) +end + +function CleveRoids.GetCurrentSequenceAction(sequence) + return sequence.list[sequence.index] +end + +function CleveRoids.ResetSequence(sequence) + sequence.index = 1 +end + +function CleveRoids.AdvanceSequence(sequence) + if sequence.index < table.getn(sequence.list) then + sequence.index = sequence.index + 1 + else + CleveRoids.ResetSequence(sequence) + end +end + +function CleveRoids.TestAction(cmd, args) + local msg, conditionals = CleveRoids.GetParsedMsg(args) + + if string.find(msg, "#showtooltip") or conditionals.ignoretooltip == 1 then + return + end + + if not conditionals then + if not msg then + return + else + -- action is a {macro} or item/spell + return CleveRoids.GetMacroNameFromAction(msg) or msg + end + end + + local origTarget = conditionals.target + if cmd == "" or not CleveRoids.dynamicCmds[cmd] then + -- untestables + return + end + + if conditionals.target == "focus" then + local focusUnitId = nil + -- First, try to get the specific UnitID from pfUI's data for reliability. + if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id and UnitExists(pfUI.uf.focus.label .. pfUI.uf.focus.id) then + focusUnitId = pfUI.uf.focus.label .. pfUI.uf.focus.id + end + + if focusUnitId then + -- If we found a specific unit (e.g., "party1", "raid2"), use it for the test. + conditionals.target = focusUnitId + else + -- If we only have a name, check if that name exists. If not, the condition fails. + if not CleveRoids.GetFocusName() then + return -- No focus exists, so this action is not valid. + end + -- Fallback for testing: We can't safely target by name here, so we default to "target". + -- This part of the original logic remains as a fallback, but the pfUI check above will handle most cases. + conditionals.target = "target" + end + end + + + if conditionals.target == "mouseover" then + if not CleveRoids.IsValidTarget("mouseover", conditionals.help) then + return false + end + end + + CleveRoids.FixEmptyTarget(conditionals) + -- CleveRoids.SetHelp(conditionals) + + for k, v in pairs(conditionals) do + if not CleveRoids.ignoreKeywords[k] then + if not CleveRoids.Keywords[k] or not CleveRoids.Keywords[k](conditionals) then + -- failed test + conditionals.target = origTarget + return + end + end + end + + -- tests passed + conditionals.target = origTarget + return CleveRoids.GetMacroNameFromAction(msg) or msg +end + +function CleveRoids.DoWithConditionals(msg, hook, fixEmptyTargetFunc, targetBeforeAction, action) + local msg, conditionals = CleveRoids.GetParsedMsg(msg) + + -- No conditionals. Just exit. + if not conditionals then + if not msg then -- if not even an empty string + return false + else + if string.sub(msg, 1, 1) == "{" and string.sub(msg, -1) == "}" then + if string.sub(msg, 2, 2) == "\"" and string.sub(msg, -2, -2) == "\"" then + return CleveRoids.ExecuteMacroBody(string.sub(msg, 3, -3), true) + else + return CleveRoids.ExecuteMacroByName(string.sub(msg, 2, -2)) + end + end + + if hook then + hook(msg) + end + return true + end + end + + if conditionals.cancelaura then + if CleveRoids.CancelAura(conditionals.cancelaura) then + return true + end + end + + local origTarget = conditionals.target + if conditionals.target == "mouseover" then + if UnitExists("mouseover") then + conditionals.target = "mouseover" + elseif CleveRoids.mouseoverUnit and UnitExists(CleveRoids.mouseoverUnit) then + conditionals.target = CleveRoids.mouseoverUnit + else + conditionals.target = "mouseover" + end + end + + local needRetarget = false + if fixEmptyTargetFunc then + needRetarget = fixEmptyTargetFunc(conditionals, msg, hook) + end + + -- CleveRoids.SetHelp(conditionals) + + if conditionals.target == "focus" then + local focusUnitId = nil + + -- Attempt to get the direct UnitID from pfUI's focus frame data. This is more reliable. + if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.label and pfUI.uf.focus.id and UnitExists(pfUI.uf.focus.label .. pfUI.uf.focus.id) then + focusUnitId = pfUI.uf.focus.label .. pfUI.uf.focus.id + end + + if focusUnitId then + -- If we found a valid UnitID, we will use it for all subsequent checks and the final cast. + -- This avoids changing the player's actual target. + conditionals.target = focusUnitId + needRetarget = false + else + -- return false if pfUI is installed and no focus is set instead of "invalid target" + if (pfUI.uf.focus.label == nil or pfUI.uf.focus.label == "") and pfUI then return false end + -- If the direct UnitID isn't found, fall back to the original (but likely failing) method of targeting by name. + if not CleveRoids.TryTargetFocus() then + UIErrorsFrame:AddMessage(SPELL_FAILED_BAD_TARGETS, 1.0, 0.0, 0.0, 1.0) + conditionals.target = origTarget + return false + end + conditionals.target = "target" + needRetarget = true + end + end + + for k, v in pairs(conditionals) do + if not CleveRoids.ignoreKeywords[k] then + if not CleveRoids.Keywords[k] or not CleveRoids.Keywords[k](conditionals) then + if needRetarget then + TargetLastTarget() + needRetarget = false + end + conditionals.target = origTarget + return false + end + end + end + + if conditionals.target ~= nil and targetBeforeAction and not (CleveRoids.hasSuperwow and action == CastSpellByName) then + if not UnitIsUnit("target", conditionals.target) then + if SpellIsTargeting() then + SpellStopCasting() + end + TargetUnit(conditionals.target) + needRetarget = true + else + if needRetarget then needRetarget = false end + end + elseif needRetarget then + TargetLastTarget() + needRetarget = false + end + + if action == "STOPMACRO" then + CleveRoids.stopmacro = true + return true + end + + local result = true + if string.sub(msg, 1, 1) == "{" and string.sub(msg, -1) == "}" then + if string.sub(msg, 2, 2) == "\"" and string.sub(msg, -2,-2) == "\"" then + result = CleveRoids.ExecuteMacroBody(string.sub(msg, 3, -3), true) + else + result = CleveRoids.ExecuteMacroByName(string.sub(msg, 2, -2)) + end + else -- This 'else' corresponds to 'if string.sub(msg, 1, 1) == "{"...' + local castMsg = msg + -- FLEXIBLY check for any rank text like "(Rank 9)" before adding the highest rank + if action == CastSpellByName and not string.find(msg, "%(.*%)") then + local sp = CleveRoids.GetSpell(msg) + local rank = sp and (sp.rank or (sp.highest and sp.highest.rank)) + if rank and rank ~= "" then + castMsg = msg .. "(" .. rank .. ")" + end + end + if CleveRoids.hasSuperwow and action == CastSpellByName and conditionals.target then + CastSpellByName(castMsg, conditionals.target) -- SuperWoW handles targeting via argument + elseif action == CastSpellByName then + action(castMsg) + else + -- For other actions like UseContainerItem etc. + action(msg) + end + end + + if needRetarget then + TargetLastTarget() + end + + conditionals.target = origTarget + return result +end + +function CleveRoids.DoCast(msg) + local handled = false + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + if CleveRoids.DoWithConditionals(v, CleveRoids.Hooks.CAST_SlashCmd, CleveRoids.FixEmptyTarget, not CleveRoids.hasSuperwow, CastSpellByName) then + -- If a spell is successfully attempted, immediately exit to prevent casting multiple spells. + return true + end + end + return false +end + +-- Target using GUIDs (actually unit tokens) and correct targeting. +function CleveRoids.DoTarget(msg) + local action, conditionals = CleveRoids.GetParsedMsg(msg) + + if action ~= "" or not next(conditionals) then + CleveRoids.Hooks.TARGET_SlashCmd(msg) + return true + end + + -- Validate a *unit token* against parsed conditionals + local function IsGuidValid(unitTok, conds) + if not unitTok or not UnitExists(unitTok) or UnitIsDeadOrGhost(unitTok) then + return false + end + local orig = conds.target + conds.target = unitTok + local ok = true + for k, _ in pairs(conds) do + if not CleveRoids.ignoreKeywords[k] then + local fn = CleveRoids.Keywords[k] + if not fn or not fn(conds) then ok = false; break end + end + end + conds.target = orig + return ok + end + + ---------------------------------------------------------------- + -- FAST-PATH: explicit @unit (e.g. [@mouseover], [@focus], [@party1]) + ---------------------------------------------------------------- + do + local unitTok = conditionals.target + + -- Resolve @mouseover to an actual unit token that exists (works on pfUI frames) + if unitTok == "mouseover" then + if UnitExists("mouseover") then + unitTok = "mouseover" + elseif CleveRoids.mouseoverUnit and UnitExists(CleveRoids.mouseoverUnit) then + unitTok = CleveRoids.mouseoverUnit + elseif pfUI and pfUI.uf and pfUI.uf.mouseover and pfUI.uf.mouseover.unit + and UnitExists(pfUI.uf.mouseover.unit) then + unitTok = pfUI.uf.mouseover.unit + else + unitTok = nil + end + end + + -- Resolve @focus via pfUI focus emulation if present + if unitTok == "focus" and pfUI and pfUI.uf and pfUI.uf.focus + and pfUI.uf.focus.label and pfUI.uf.focus.id then + local fTok = pfUI.uf.focus.label .. pfUI.uf.focus.id + if UnitExists(fTok) then unitTok = fTok else unitTok = nil end + end + + -- If explicit unit resolves and passes conditionals, target it now (works out of range) + if unitTok and UnitExists(unitTok) and IsGuidValid(unitTok, conditionals) then + TargetUnit(unitTok) + return true + end + end + ---------------------------------------------------------------- + + -- 1) Keep current target if already valid + if UnitExists("target") and IsGuidValid("target", conditionals) then + return true + end + + -- 2) Build candidates: party1..4 and raid1..40 (not mutually exclusive) + local candidates = {} + + -- Party + for i = 1, 4 do + local u = "party"..i + if UnitExists(u) then table.insert(candidates, { unitId = u }) end + end + + -- Raid (all 1..40) + for i = 1, 40 do + local u = "raid"..i + if UnitExists(u) then table.insert(candidates, { unitId = u }) end + end + + -- Optional: also consider targettarget and mouseover if present + if UnitExists("targettarget") then table.insert(candidates, { unitId = "targettarget" }) end + if UnitExists("mouseover") then table.insert(candidates, { unitId = "mouseover" }) end + + -- 3) Find first valid and target it + for _, c in ipairs(candidates) do + if IsGuidValid(c.unitId, conditionals) then + TargetUnit(c.unitId) + return true + end + end + + -- 4) Nothing found; preserve original target + return true +end + + +-- Attempts to attack a unit by a set of conditionals +-- msg: The raw message intercepted from a /petattack command +function CleveRoids.DoPetAction(action, msg) + local handled = false + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + if CleveRoids.DoWithConditionals(v, action, CleveRoids.FixEmptyTarget, true, action) then + handled = true + break + end + end + return handled +end + +-- Attempts to conditionally start an attack. Returns false if no conditionals are found. +function CleveRoids.DoConditionalStartAttack(msg) + if not string.find(msg, "%[") then return false end + + local handled = false + local action = function() + if not UnitExists("target") or UnitIsDead("target") then TargetNearestEnemy() end + if not CleveRoids.CurrentSpell.autoAttack and not CleveRoids.CurrentSpell.autoAttackLock and UnitExists("target") and UnitCanAttack("player", "target") then + CleveRoids.CurrentSpell.autoAttackLock = true + CleveRoids.autoAttackLockElapsed = GetTime() + AttackTarget() + end + end + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + -- We pass 'nil' for the hook, so DoWithConditionals does nothing if it fails to parse conditionals. + if CleveRoids.DoWithConditionals(v, nil, CleveRoids.FixEmptyTarget, false, action) then + handled = true + break + end + end + return handled +end + +-- Attempts to conditionally stop an attack. Returns false if no conditionals are found. +function CleveRoids.DoConditionalStopAttack(msg) + if not string.find(msg, "%[") then return false end + + local handled = false + local action = function() + if CleveRoids.CurrentSpell.autoAttack and UnitExists("target") then + AttackTarget() + CleveRoids.CurrentSpell.autoAttack = false + end + end + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + if CleveRoids.DoWithConditionals(v, nil, CleveRoids.FixEmptyTarget, false, action) then + handled = true + break + end + end + return handled +end + +-- Attempts to conditionally stop casting. Returns false if no conditionals are found. +function CleveRoids.DoConditionalStopCasting(msg) + if not string.find(msg, "%[") then return false end + + local handled = false + local action = function() + SpellStopCasting() + end + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + if CleveRoids.DoWithConditionals(v, nil, CleveRoids.FixEmptyTarget, false, action) then + handled = true + break + end + end + return handled +end + + +-- Attempts to use or equip an item from the player's inventory by a set of conditionals +-- Also checks if a condition is a spell so that you can mix item and spell use +-- msg: The raw message intercepted from a /use or /equip command +function CleveRoids.DoUse(msg) + local handled = false + + local action = function(msg) + -- Try to interpret the message as a direct inventory slot ID first. + local slotId = tonumber(msg) + if slotId and slotId >= 1 and slotId <= 19 then -- Character slots are 1-19 + UseInventoryItem(slotId) + return -- Exit after using the item by slot. + end + + -- Original logic: if it's not a slot number, try to resolve by name. + local item = CleveRoids.GetItem(msg) + + if item and item.inventoryID then + -- This is for using an already-equipped item (like a trinket). + -- This action does not cause an inventory change that needs a fast re-index. + return UseInventoryItem(item.inventoryID) + elseif item and item.bagID then + -- This will use an item from a bag. It could be a potion (use) or a weapon (equip). + -- We need to check if it's an equippable item before using it. + local isEquippable = false + local itemName, _, _, _, _, _, _, _, itemEquipLoc = GetItemInfo(msg) + if itemName and itemEquipLoc and itemEquipLoc ~= "" then + isEquippable = true + end + + CleveRoids.GetNextBagSlotForUse(item, msg) + UseContainerItem(item.bagID, item.slot) + + -- If it was an equippable item, force a cache refresh on the next inventory event. + if isEquippable then + CleveRoids.lastItemIndexTime = 0 + end + return + end + + if (MerchantFrame:IsVisible() and MerchantFrame.selectedTab == 1) then return end + end + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + v = string.gsub(v, "^%?", "") + local subject = v + local _,e = string.find(v,"%]") + if e then subject = CleveRoids.Trim(string.sub(v,e+1)) end + + local wasHandled = false + -- If the subject is not a number, check if it's a spell. + if (not tonumber(subject)) and CleveRoids.GetSpell(subject) then + wasHandled = CleveRoids.DoWithConditionals(v, CleveRoids.Hooks.CAST_SlashCmd, CleveRoids.FixEmptyTarget, not CleveRoids.hasSuperwow, CastSpellByName) + else + -- Otherwise, treat it as an item (by name or slot ID). + wasHandled = CleveRoids.DoWithConditionals(v, action, CleveRoids.FixEmptyTarget, false, action) + end + if wasHandled then + handled = true + break + end + end + return handled -- Corrected typo from 'Handled' to 'handled' +end + +function CleveRoids.EquipBagItem(msg, offhand) + -- First, get item data from the addon's own reliable cache. + local item = CleveRoids.GetItem(msg) + if not item or not item.name then + -- If the addon can't find the item at all, do nothing. + return false + end + + local invslot = offhand and 17 or 16 -- 17 is off-hand, 16 is main-hand + + -- Now, check the currently equipped item using live game data. + local currentItemLink = GetInventoryItemLink("player", invslot) + if currentItemLink then + local currentItemName = GetItemInfo(currentItemLink) + if currentItemName and currentItemName == item.name then + -- The correct item is already in the correct slot. Stop here. + return true + end + end + + -- If the check above fails, proceed with the original logic to equip the item. + -- We can reuse the 'item' object we already found. + if not item.bagID and not item.inventoryID then + return false + end + + if item.bagID then + CleveRoids.GetNextBagSlotForUse(item, msg) + PickupContainerItem(item.bagID, item.slot) + else + PickupInventoryItem(item.inventoryID) + end + + EquipCursorItem(invslot) + ClearCursor() + + CleveRoids.lastItemIndexTime = 0 + + return true +end + +-- TODO: Refactor all these DoWithConditionals sections +function CleveRoids.DoEquipMainhand(msg) + local handled = false + + local action = function(msg) + return CleveRoids.EquipBagItem(msg, false) + end + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + v = string.gsub(v, "^%?", "") + + if CleveRoids.DoWithConditionals(v, action, CleveRoids.FixEmptyTarget, false, action) then + handled = true + break + end + end + return handled +end + +function CleveRoids.DoEquipOffhand(msg) + local handled = false + + local action = function(msg) + return CleveRoids.EquipBagItem(msg, true) + end + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + v = string.gsub(v, "^%?", "") + + if CleveRoids.DoWithConditionals(v, action, CleveRoids.FixEmptyTarget, false, action) then + handled = true + break + end + end + return handled +end + +function CleveRoids.DoUnshift(msg) + local handled + + local action = function(msg) + local currentShapeshiftIndex = CleveRoids.GetCurrentShapeshiftIndex() + if currentShapeshiftIndex ~= 0 then + CastShapeshiftForm(currentShapeshiftIndex) + end + end + + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(msg)) do + handled = false + if CleveRoids.DoWithConditionals(v, action, CleveRoids.FixEmptyTarget, false, action) then + handled = true + break + end + end + + if handled == nil then + action() + end + + return handled +end + +function CleveRoids.DoRetarget() + if GetUnitName("target") == nil + or UnitHealth("target") == 0 + or not UnitCanAttack("player", "target") + then + ClearTarget() + TargetNearestEnemy() + end +end + +-- Attempts to stop macro + function CleveRoids.DoStopMacro(msg) + local handled = false + for k, v in pairs(CleveRoids.splitStringIgnoringQuotes(CleveRoids.Trim(msg))) do + if CleveRoids.DoWithConditionals(msg, nil, nil, not CleveRoids.hasSuperwow, "STOPMACRO") then + handled = true -- we parsed at least one command + break + end + end + return handled +end + +function CleveRoids.DoCastSequence(sequence) + if not CleveRoids.hasSuperwow then + CleveRoids.Print("|cFFFF0000/castsequence|r requires |cFF00FFFFSuperWoW|r.") + return + end + + if CleveRoids.currentSequence and not CleveRoids.CheckSpellCast("player") then + CleveRoids.currentSequence = nil + elseif CleveRoids.currentSequence then + return + end + + if sequence.index > 1 then + if sequence.reset then + for k, _ in sequence.reset do + if CleveRoids.kmods[k] and CleveRoids.kmods[k]() then + CleveRoids.ResetSequence(sequence) + end + end + end + end + + local active = CleveRoids.GetCurrentSequenceAction(sequence) + if active and active.action then + sequence.status = 0 + sequence.lastUpdate = GetTime() + sequence.expires = 0 + + CleveRoids.currentSequence = sequence + + local action = (sequence.cond or "") .. active.action + local result = CleveRoids.DoWithConditionals(action, nil, nil, not CleveRoids.hasSuperwow, CastSpellByName) + + return result + end +end + +CleveRoids.DoConditionalCancelAura = function(msg) + local trimmedMsg = CleveRoids.Trim(msg or "") + + if trimmedMsg == "" then + return false + end + if CleveRoids.DoWithConditionals(trimmedMsg, nil, CleveRoids.FixEmptyTarget, false, CleveRoids.CancelAura) then + return true + else + return false + end +end + +function CleveRoids.OnUpdate(self) + local time = GetTime() + local refreshRate = CleveRoidMacros.refresh or 5 + refreshRate = 1/refreshRate + if CleveRoids.initializationTimer and time >= CleveRoids.initializationTimer then + CleveRoids.IndexItems() + CleveRoids.IndexActionBars() + CleveRoids.ready = true + CleveRoids.initializationTimer = nil + CleveRoids.TestForAllActiveActions() + CleveRoids.lastUpdate = time + return + end + if not CleveRoids.ready then return end + + -- Throttle the update loop to avoid excessive CPU usage. + if (time - CleveRoids.lastUpdate) < refreshRate then return end + CleveRoids.lastUpdate = time + -- Check the saved variable to decide which update mode to use. + if CleveRoidMacros.realtime == 1 then + -- Realtime Mode: Force an update on every throttled tick for maximum responsiveness. + CleveRoids.TestForAllActiveActions() + else + -- Event-Driven Mode (Default): Only update if a relevant game event has queued it. + if CleveRoids.isActionUpdateQueued then + CleveRoids.TestForAllActiveActions() + CleveRoids.isActionUpdateQueued = false -- Reset the flag after updating + end + end + + -- The rest of this function handles time-based logic that must always run. + if CleveRoids.CurrentSpell.autoAttackLock and (time - CleveRoids.autoAttackLockElapsed) > refreshRate then + CleveRoids.CurrentSpell.autoAttackLock = false + CleveRoids.autoAttackLockElapsed = nil + end + + for _, sequence in pairs(CleveRoids.Sequences) do + if sequence.index > 1 and sequence.reset.secs and (time - (sequence.lastUpdate or 0)) >= sequence.reset.secs then + CleveRoids.ResetSequence(sequence) + end + end + + for guid,cast in pairs(CleveRoids.spell_tracking) do + if time > cast.expires then + CleveRoids.spell_tracking[guid] = nil + end + end +end + +-- Initialize the nested table for the GameTooltip hooks if it doesn't exist +if not CleveRoids.Hooks.GameTooltip then CleveRoids.Hooks.GameTooltip = {} end + +-- Save the original GameTooltip.SetAction function before we override it +CleveRoids.Hooks.GameTooltip.SetAction = GameTooltip.SetAction + +-- Now, define our custom version of the function +function GameTooltip.SetAction(self, slot) + local actions = CleveRoids.GetAction(slot) + + local action_to_display_info = nil + if actions then + if actions.active then + action_to_display_info = actions.active + elseif actions.tooltip then + action_to_display_info = actions.tooltip + end + end + + if action_to_display_info and action_to_display_info.action then + local action_name = action_to_display_info.action + + -- NEW: Check if action is a slot ID for tooltip + local slotId = tonumber(action_name) + if slotId and slotId >= 1 and slotId <= 19 then + -- Use the more specific SetInventoryItem function to prevent conflicts with other addons. + GameTooltip:SetInventoryItem("player", slotId) + GameTooltip:Show() + return + end + -- End new logic + + local current_spell_data = CleveRoids.GetSpell(action_name) + if current_spell_data then + GameTooltip:SetSpell(current_spell_data.spellSlot, current_spell_data.bookType) + local rank_info = current_spell_data.rank or (current_spell_data.highest and current_spell_data.highest.rank) + if rank_info and rank_info ~= "" then + GameTooltipTextRight1:SetText("|cff808080" .. rank_info .. "|r") + else + GameTooltipTextRight1:SetText("") + end + GameTooltipTextRight1:Show() + GameTooltip:Show() + return + end + + local current_item_data = CleveRoids.GetItem(action_name) + if current_item_data then + -- Use specific functions based on where the item is located. + if current_item_data.inventoryID then + GameTooltip:SetInventoryItem("player", current_item_data.inventoryID) + elseif current_item_data.bagID and current_item_data.slot then + GameTooltip:SetBagItem(current_item_data.bagID, current_item_data.slot) + else + -- Fallback to the original method if location is unknown. + GameTooltip:SetHyperlink(current_item_data.link) + end + GameTooltip:Show() + return + end + + if action_to_display_info.macro and type(action_to_display_info.macro) == "table" then + local nested_action_info = action_to_display_info.macro + local nested_action_name = nested_action_info.action + + current_spell_data = CleveRoids.GetSpell(nested_action_name) + if current_spell_data then + GameTooltip:SetSpell(current_spell_data.spellSlot, current_spell_data.bookType) + local rank_info = current_spell_data.rank or (current_spell_data.highest and current_spell_data.highest.rank) + if rank_info and rank_info ~= "" then + GameTooltipTextRight1:SetText("|cff808080" .. rank_info .. "|r") + else + GameTooltipTextRight1:SetText("") + end + GameTooltipTextRight1:Show() + GameTooltip:Show() + return + end + + current_item_data = CleveRoids.GetItem(nested_action_name) + if current_item_data then + if current_item_data.inventoryID then + GameTooltip:SetInventoryItem("player", current_item_data.inventoryID) + elseif current_item_data.bagID and current_item_data.slot then + GameTooltip:SetBagItem(current_item_data.bagID, current_item_data.slot) + else + GameTooltip:SetHyperlink(current_item_data.link) + end + GameTooltip:Show() + return + end + end + end + + -- If none of our custom logic handled it, call the original function we saved earlier. + CleveRoids.Hooks.GameTooltip.SetAction(self, slot) +end + +CleveRoids.Hooks.PickupAction = PickupAction +function PickupAction(slot) + CleveRoids.ClearAction(slot) + CleveRoids.ClearSlot(CleveRoids.actionSlots, slot) + CleveRoids.ClearAction(CleveRoids.reactiveSlots, slot) + return CleveRoids.Hooks.PickupAction(slot) +end + +CleveRoids.Hooks.ActionHasRange = ActionHasRange +function ActionHasRange(slot) + local actions = CleveRoids.GetAction(slot) + if actions and actions.active then + return (1 and actions.active.inRange ~= -1 or nil) + else + return CleveRoids.Hooks.ActionHasRange(slot) + end +end + +CleveRoids.Hooks.IsActionInRange = IsActionInRange +function IsActionInRange(slot, unit) + local actions = CleveRoids.GetAction(slot) + if actions and actions.active and actions.active.type == "spell" then + return actions.active.inRange + else + return CleveRoids.Hooks.IsActionInRange(slot, unit) + end +end + +CleveRoids.Hooks.OriginalIsUsableAction = IsUsableAction +CleveRoids.Hooks.IsUsableAction = IsUsableAction +function IsUsableAction(slot, unit) + local actions = CleveRoids.GetAction(slot) + if actions and actions.active then + return actions.active.usable, actions.active.oom + else + return CleveRoids.Hooks.IsUsableAction(slot, unit) + end +end + +CleveRoids.Hooks.IsCurrentAction = IsCurrentAction +function IsCurrentAction(slot) + local active = CleveRoids.GetActiveAction(slot) + + if not active then + return CleveRoids.Hooks.IsCurrentAction(slot) + else + local name + if active.spell then + local rank = active.spell.rank or active.spell.highest.rank + name = active.spell.name..(rank and ("("..rank..")")) + elseif active.item then + name = active.item.name + end + + return CleveRoids.Hooks.IsCurrentAction(CleveRoids.GetProxyActionSlot(name) or slot) + end +end + +CleveRoids.Hooks.GetActionTexture = GetActionTexture +function GetActionTexture(slot) + local actions = CleveRoids.GetAction(slot) + + if actions and (actions.active or actions.tooltip) then + local proxySlot = (actions.active and actions.active.spell) and CleveRoids.GetProxyActionSlot(actions.active.spell.name) + if proxySlot and CleveRoids.Hooks.GetActionTexture(proxySlot) ~= actions.active.spell.texture then + return CleveRoids.Hooks.GetActionTexture(proxySlot) + else + return (actions.active and actions.active.texture) or (actions.tooltip and actions.tooltip.texture) or CleveRoids.unknownTexture + end + end + return CleveRoids.Hooks.GetActionTexture(slot) +end + +-- TODO: Look into https://github.com/Stanzilla/WoWUIBugs/issues/47 if needed +CleveRoids.Hooks.GetActionCooldown = GetActionCooldown +function GetActionCooldown(slot) + local actions = CleveRoids.GetAction(slot) + -- Check for actions.active OR actions.tooltip + if actions and (actions.active or actions.tooltip) then + -- Prioritize the active action, but fall back to the tooltip action + local a = actions.active or actions.tooltip + + local slotId = tonumber(a.action) + if slotId and slotId >= 1 and slotId <= 19 then + return GetInventoryItemCooldown("player", slotId) + end + + if a.spell then + return GetSpellCooldown(a.spell.spellSlot, a.spell.bookType) + elseif a.item then + if a.item.bagID and a.item.slot then + return GetContainerItemCooldown(a.item.bagID, a.item.slot) + elseif a.item.inventoryID then + return GetInventoryItemCooldown("player", a.item.inventoryID) + end + end + return 0, 0, 0 + else + return CleveRoids.Hooks.GetActionCooldown(slot) + end +end + +CleveRoids.Hooks.GetActionCount = GetActionCount +function GetActionCount(slot) + local action = CleveRoids.GetAction(slot) + local count + if action and action.active then + + local slotId = tonumber(action.active.action) + if slotId and slotId >= 1 and slotId <= 19 then + return GetInventoryItemCount("player", slotId) + end + + if action.active.item then + count = action.active.item.count + elseif action.active.spell and action.active.spell.reagent then + local reagent = CleveRoids.GetItem(action.active.spell.reagent) + count = reagent and reagent.count + end + end + + return count or CleveRoids.Hooks.GetActionCount(slot) +end + +CleveRoids.Hooks.IsConsumableAction = IsConsumableAction +function IsConsumableAction(slot) + local action = CleveRoids.GetAction(slot) + if action and action.active then + + local slotId = tonumber(action.active.action) + if slotId and slotId >= 1 and slotId <= 19 then + local _, count = GetInventoryItemCount("player", slotId) + if count and count > 0 then return 1 end + end + + if action.active.item and + (CleveRoids.countedItemTypes[action.active.item.type] + or CleveRoids.countedItemTypes[action.active.item.name]) + then + return 1 + end + + + if action.active.spell and action.active.spell.reagent then + return 1 + end + end + + return CleveRoids.Hooks.IsConsumableAction(slot) +end + +-- Create a hidden tooltip frame to read buff names +if not AuraScanTooltip and not CleveRoids.hasSuperwow then + CreateFrame("GameTooltip", "AuraScanTooltip") + AuraScanTooltip:SetOwner(WorldFrame, "ANCHORNONE") + AuraScanTooltip:AddFontStrings( + AuraScanTooltip:CreateFontString("$parentTextLeft1", nil, "GameTooltipText"), + AuraScanTooltip:CreateFontString("$parentTextRight1", nil, "GameTooltipText") + ) +end + +-- This single dummy frame handles events AND serves as our tooltip scanner. +CleveRoids.Frame = CreateFrame("GameTooltip") + +-- Create the extra font strings needed for other functions like GetSpellCost. +CleveRoids.Frame.costFontString = CleveRoids.Frame:CreateFontString() +CleveRoids.Frame.rangeFontString = CleveRoids.Frame:CreateFontString() +CleveRoids.Frame.reagentFontString = CleveRoids.Frame:CreateFontString() +CleveRoids.Frame:AddFontStrings(CleveRoids.Frame:CreateFontString(), CleveRoids.Frame:CreateFontString()) +CleveRoids.Frame:AddFontStrings(CleveRoids.Frame.costFontString, CleveRoids.Frame.rangeFontString) +CleveRoids.Frame:AddFontStrings(CleveRoids.Frame:CreateFontString(), CleveRoids.Frame:CreateFontString()) +CleveRoids.Frame:AddFontStrings(CleveRoids.Frame.reagentFontString, CleveRoids.Frame:CreateFontString()) + +CleveRoids.Frame:SetScript("OnUpdate", CleveRoids.OnUpdate) +CleveRoids.Frame:SetScript("OnEvent", function(...) + CleveRoids.Frame[event](this,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9,arg10) +end) + +-- == CORE EVENT REGISTRATION == +CleveRoids.Frame:RegisterEvent("PLAYER_LOGIN") +CleveRoids.Frame:RegisterEvent("ADDON_LOADED") +CleveRoids.Frame:RegisterEvent("UPDATE_MACROS") +CleveRoids.Frame:RegisterEvent("SPELLS_CHANGED") +CleveRoids.Frame:RegisterEvent("ACTIONBAR_SLOT_CHANGED") +CleveRoids.Frame:RegisterEvent("BAG_UPDATE") +CleveRoids.Frame:RegisterEvent("UNIT_INVENTORY_CHANGED") + +-- == STATE CHANGE EVENT REGISTRATION (for performance) == +CleveRoids.Frame:RegisterEvent("PLAYER_TARGET_CHANGED") +CleveRoids.Frame:RegisterEvent("PLAYER_FOCUS_CHANGED") -- For focus addons +CleveRoids.Frame:RegisterEvent("PLAYER_ENTER_COMBAT") +CleveRoids.Frame:RegisterEvent("PLAYER_LEAVE_COMBAT") +CleveRoids.Frame:RegisterEvent("UPDATE_SHAPESHIFT_FORM") +CleveRoids.Frame:RegisterEvent("SPELL_UPDATE_COOLDOWN") +CleveRoids.Frame:RegisterEvent("UNIT_AURA") +CleveRoids.Frame:RegisterEvent("UNIT_HEALTH") +CleveRoids.Frame:RegisterEvent("UNIT_POWER") +if CleveRoids.hasSuperwow then + CleveRoids.Frame:RegisterEvent("UNIT_CASTEVENT") +end +CleveRoids.Frame:RegisterEvent("START_AUTOREPEAT_SPELL") +CleveRoids.Frame:RegisterEvent("STOP_AUTOREPEAT_SPELL") +CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_START") +CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_STOP") + +function CleveRoids.Frame:PLAYER_LOGIN() + _, CleveRoids.playerClass = UnitClass("player") + _, CleveRoids.playerGuid = UnitExists("player") + CleveRoids.IndexSpells() + CleveRoids.initializationTimer = GetTime() + 1.5 + if not CleveRoids.hasSuperwow or not IsSpellInRange then + if not CleveRoids.hasSuperwow then + CleveRoids.Print("|cFFFF0000CleveRoidMacros|r requires |cFF00FFFFbalakethelock's SuperWoW|r:") + CleveRoids.Print("https://github.com/balakethelock/SuperWoW") + end + if not IsSpellInRange then + CleveRoids.Print("|cFFFF0000CleveRoidMacros|r requires |cFF00FFFFpepopo978's Nampower|r:") + CleveRoids.Print("https://github.com/pepopo978/nampower") + end + CleveRoids.DisableAddon("Missing Requirements") + return + else + CleveRoids.Print("|cFF4477FFCleveR|r|cFFFFFFFFoid Macros|r |cFF00FF00Loaded|r - See the README.") + end +end + +function CleveRoids.Frame:ADDON_LOADED(addon) + if addon ~= "CleveRoidMacros" then + return + end + + CleveRoids.InitializeExtensions() + + if SuperMacroFrame then + local hooks = { + cast = { action = CleveRoids.DoCast }, + target = { action = CleveRoids.DoTarget }, + use = { action = CleveRoids.DoUse }, + castsequence = { action = CleveRoids.DoCastSequence } + } + + -- Hook SuperMacro's RunLine to stay compatible + CleveRoids.Hooks.RunLine = RunLine + CleveRoids.RunLine = function(...) + for i = 1, arg.n do + if CleveRoids.stopmacro then + CleveRoids.stopmacro = false + return true + end + local intercepted = false + local text = arg[i] + + for k,v in pairs(hooks) do + local begin, _end = string.find(text, "^/"..k.."%s+[!%[]") + if begin then + local msg = string.sub(text, _end) + v.action(msg) + intercepted = true + break + end + end + + if not intercepted then + CleveRoids.Hooks.RunLine(text) + end + end + end + RunLine = CleveRoids.RunLine + end +end + +function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time) + if action == "MAINHAND" or action == "OFFHAND" then return end + + -- handle cast spell tracking + local cast = CleveRoids.spell_tracking[caster] + if cast_time > 0 and action == "START" or action == "CHANNEL" then + CleveRoids.spell_tracking[caster] = { spell_id = spell_id, expires = GetTime() + cast_time/1000, type = action } + elseif cast + and ( + (cast.spell_id == spell_id and (action == "FAIL" or action == "CAST")) + or (GetTime() > cast.expires) + ) + then + CleveRoids.spell_tracking[caster] = nil + end + + -- handle cast sequence + if CleveRoids.currentSequence and caster == CleveRoids.playerGuid then + local active = CleveRoids.GetCurrentSequenceAction(CleveRoids.currentSequence) + + local name, rank = SpellInfo(spell_id) + local isSeqSpell = (active.action == name or active.action == (name.."("..rank..")")) + if isSeqSpell then + local status = CleveRoids.currentSequence.status + if status == 0 and (action == "START" or action == "CHANNEL") and cast_time > 0 then + CleveRoids.currentSequence.status = 1 + CleveRoids.currentSequence.expires = GetTime() + cast_time - 2000 + elseif (status == 0 and action == "CAST" and cast_time == 0) + or (status == 1 and action == "CAST" and CleveRoids.currentSequence.expires) + then + CleveRoids.currentSequence.status = 2 + CleveRoids.currentSequence.lastUpdate = GetTime() + CleveRoids.AdvanceSequence(CleveRoids.currentSequence) + CleveRoids.currentSequence = nil + elseif action == "INTERRUPTED" or action == "FAILED" then + CleveRoids.currentSequence.status = 1 + end + end + end + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:SPELLCAST_CHANNEL_START() + CleveRoids.CurrentSpell.type = "channeled" + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:SPELLCAST_CHANNEL_STOP() + CleveRoids.CurrentSpell.type = "" + CleveRoids.CurrentSpell.spellName = "" + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:PLAYER_ENTER_COMBAT() + CleveRoids.CurrentSpell.autoAttack = true + CleveRoids.CurrentSpell.autoAttackLock = false + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:PLAYER_LEAVE_COMBAT() + CleveRoids.CurrentSpell.autoAttack = false + CleveRoids.CurrentSpell.autoAttackLock = false + for _, sequence in pairs(CleveRoids.Sequences) do + if CleveRoids.currentSequence ~= sequence and sequence.index > 1 and sequence.reset.combat then + CleveRoids.ResetSequence(sequence) + end + end + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:PLAYER_TARGET_CHANGED() + CleveRoids.CurrentSpell.autoAttack = false + CleveRoids.CurrentSpell.autoAttackLock = false + + for _, sequence in pairs(CleveRoids.Sequences) do + if CleveRoids.currentSequence ~= sequence and sequence.index > 1 and sequence.reset.target then + CleveRoids.ResetSequence(sequence) + end + end + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:UPDATE_MACROS() + CleveRoids.currentSequence = nil + -- Explicitly nil tables before re-assignment + CleveRoids.ParsedMsg = nil; + CleveRoids.ParsedMsg = {} + + CleveRoids.Macros = nil; + CleveRoids.Macros = {} + + CleveRoids.Actions = nil; + CleveRoids.Actions = {} + + CleveRoids.Sequences = nil; + CleveRoids.Sequences = {} + + CleveRoids.IndexSpells() + CleveRoids.IndexTalents() + CleveRoids.IndexActionBars() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:SPELLS_CHANGED() + CleveRoids.Frame:UPDATE_MACROS() +end + +function CleveRoids.Frame:ACTIONBAR_SLOT_CHANGED() + CleveRoids.ClearAction(arg1) + CleveRoids.IndexActionSlot(arg1) + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:BAG_UPDATE() + local now = GetTime() + -- Only index items if more than 1 second has passed since the last index + if (now - (CleveRoids.lastItemIndexTime or 0)) > 1.0 then + CleveRoids.lastItemIndexTime = now + CleveRoids.IndexItems() + + -- Directly clear all relevant caches and force a UI refresh for all buttons. + CleveRoids.Actions = {} + --CleveRoids.Macros = {} + --CleveRoids.ParsedMsg = {} + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end + end +end + +function CleveRoids.Frame:UNIT_INVENTORY_CHANGED() + if arg1 ~= "player" then return end + CleveRoids.Frame:BAG_UPDATE() +end + +function CleveRoids.Frame:START_AUTOREPEAT_SPELL() + local _, className = UnitClass("player") + if className == "HUNTER" then + CleveRoids.CurrentSpell.autoShot = true + else + CleveRoids.CurrentSpell.wand = true + end + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +function CleveRoids.Frame:STOP_AUTOREPEAT_SPELL() + local _, className = UnitClass("player") + if className == "HUNTER" then + CleveRoids.CurrentSpell.autoShot = false + else + CleveRoids.CurrentSpell.wand = false + end + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + +-- Generic event handlers that just queue an update +function CleveRoids.Frame:PLAYER_FOCUS_CHANGED() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end +function CleveRoids.Frame:UPDATE_SHAPESHIFT_FORM() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end +function CleveRoids.Frame:SPELL_UPDATE_COOLDOWN() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end +function CleveRoids.Frame:UNIT_AURA() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end +function CleveRoids.Frame:UNIT_HEALTH() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end +function CleveRoids.Frame:UNIT_POWER() + if CleveRoidMacros.realtime == 0 then + CleveRoids.QueueActionUpdate() + end +end + + +CleveRoids.Hooks.SendChatMessage = SendChatMessage +function SendChatMessage(msg, ...) + if msg and string.find(msg, "^#showtooltip") then + return + end + CleveRoids.Hooks.SendChatMessage(msg, unpack(arg)) +end + +CleveRoids.RegisterActionEventHandler = function(fn) + if type(fn) == "function" then + table.insert(CleveRoids.actionEventHandlers, fn) + end +end + +CleveRoids.RegisterMouseOverResolver = function(fn) + if type(fn) == "function" then + table.insert(CleveRoids.mouseOverResolvers, fn) + end +end + + +-- Bandaid so pfUI doesn't need to be edited +-- pfUI/modules/thirdparty-vanilla.lua:914 +CleverMacro = true + +---- START of pfUI Focus Fix ---- +do + local f = CreateFrame("Frame") + f:SetScript("OnEvent", function(self, event, arg1) + if event == "PLAYER_LOGIN" then + -- This ensures we wait until the player is fully in the world and all addons are loaded. + self:UnregisterEvent("PLAYER_LOGIN") + + -- Ensure both pfUI and its focus module are loaded before attempting to hook. + -- This also checks that the slash command we want to modify exists. + if pfUI and pfUI.uf and pfUI.uf.focus and SlashCmdList.PFFOCUS then + + local original_PFFOCUS_Handler = SlashCmdList.PFFOCUS + SlashCmdList.PFFOCUS = function(msg) + -- First, execute the original /focus command from pfUI to set the unit name. + original_PFFOCUS_Handler(msg) + + -- Now, if a focus name was set, find the corresponding UnitID. + if pfUI.uf.focus.unitname then + local focusName = pfUI.uf.focus.unitname + local found_label, found_id = nil, nil + + -- This function iterates through all known friendly units to find a + -- name match and return its specific UnitID components. + local function findUnitID() + -- Check party members and their pets + for i = 1, GetNumPartyMembers() do + if strlower(UnitName("party"..i) or "") == focusName then + return "party", i + end + if UnitExists("partypet"..i) and strlower(UnitName("partypet"..i) or "") == focusName then + return "partypet", i + end + end + + -- Check raid members and their pets + for i = 1, GetNumRaidMembers() do + if strlower(UnitName("raid"..i) or "") == focusName then + return "raid", i + end + if UnitExists("raidpet"..i) and strlower(UnitName("raidpet"..i) or "") == focusName then + return "raidpet", i + end + end + + -- Check player and pet + if strlower(UnitName("player") or "") == focusName then return "player", nil end + if UnitExists("pet") and strlower(UnitName("pet") or "") == focusName then return "pet", nil end + + return nil, nil + end + + found_label, found_id = findUnitID() + + -- Store the found label and ID. CleveRoids' Core.lua will use this + -- for a direct, reliable cast without needing to change your target. + pfUI.uf.focus.label = found_label + pfUI.uf.focus.id = found_id + else + -- Focus was cleared (e.g., via /clearfocus), so ensure our cached data is cleared too. + pfUI.uf.focus.label = nil + pfUI.uf.focus.id = nil + end + end + end + end + end) + f:RegisterEvent("PLAYER_LOGIN") + end + ---- END of pfUI Focus Fix ---- + + +SLASH_CLEVEROID1 = "/cleveroid" +SLASH_CLEVEROID2 = "/cleveroidmacros" + +SlashCmdList["CLEVEROID"] = function(msg) + if type(msg) ~= "string" then + msg = "" + end + + local cmd, val + local s, e, a, b = string.find(msg, "^(%S*)%s*(%S*)$") + if a then cmd = a else cmd = "" end + if b then val = b else val = "" end + + -- No command: show current value + if cmd == "" then + CleveRoids.Print("Current Settings:") + DEFAULT_CHAT_FRAME:AddMessage("realtime (force fast updates, CPU intensive) = " .. CleveRoidMacros.realtime .. " (Default: 0)") + DEFAULT_CHAT_FRAME:AddMessage("refresh (updates per second) = " .. CleveRoidMacros.refresh .. " (Default: 5)") + return + end + + -- realtime + if cmd == "realtime" then + local num = tonumber(val) + if num == 0 or num == 1 then + CleveRoidMacros.realtime = num + CleveRoids.Print("realtime set to " .. num) + else + CleveRoids.Print("Usage: /cleveroid realtime 0 or 1 - Force realtime updates rather than event based updates (Default: 0. 1 = on, increases CPU load.)") + CleveRoids.Print("Current realtime = " .. tostring(CleveRoidMacros.realtime)) + end + return + end + + -- refresh + if cmd == "refresh" then + local num = tonumber(val) + if num and num >= 1 and num <= 10 then + CleveRoidMacros.refresh = num + CleveRoids.Print("refresh set to " .. num .. " times per second") + else + CleveRoids.Print("Usage: /cleveroid refresh X - Set refresh rate. (1 to 10 updates per second. Default: 5)") + CleveRoids.Print("Current refresh = " .. tostring(CleveRoidMacros.refresh) .. " times per second") + end + return + end + + -- Unknown command fallback + CleveRoids.Print("Usage:") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid realtime 0 or 1 - Force realtime updates rather than event based updates (Default: 0. 1 = on, increases CPU load.)") + DEFAULT_CHAT_FRAME:AddMessage("/cleveroid refresh X - Set refresh rate. (1 to 10 updates per second. Default: 5)") +end diff --git a/Extensions/MacroLengthWarn.lua b/Extensions/MacroLengthWarn.lua new file mode 100644 index 0000000..cf17f1d --- /dev/null +++ b/Extensions/MacroLengthWarn.lua @@ -0,0 +1,41 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +-- CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("MacroLengthWarn") + +local edit_orig = EditMacro + +function Extension.SafeEditMacro(macro_id, x, y, body) + if SuperMacroFrame ~= nil and x and y and body == nil then + -- super will handle macro + edit_orig(macro_id, x, y, body) + return + end + + if not body then -- can't be too long without a body + edit_orig(macro_id, x, y, body) + return + end + + for line in string.gfind(body, "([^\n]+)") do + if string.len(line) > 261 then + DEFAULT_CHAT_FRAME:AddMessage("ERROR: A line in the macro < |cffffffff"..GetMacroInfo(macro_id).."|r > is too long and will |cffff0000CRASH|r the client and may |cffff0000DELETE|r all macros on exit! Max line length: |cffffffff261|r",1,1,0) + return + end + end + edit_orig(macro_id, x, y, body) +end + + +function Extension.OnLoad() + -- how do I use this with a global function? + -- CleveRoids.RegisterMethodHook("MacroLengthWarn", _G, "EditMacro", "SafeEditMacro", true) + EditMacro = Extension.SafeEditMacro +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Extensions/Mouseover/Blizzard.lua b/Extensions/Mouseover/Blizzard.lua new file mode 100644 index 0000000..fef1ac9 --- /dev/null +++ b/Extensions/Mouseover/Blizzard.lua @@ -0,0 +1,77 @@ +--[[ + Author: Fondlez + + This extension adds improved support for mouseover macros in Roid-Macros + for the default Blizzard frames. +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("Blizzard") + +function Extension.RegisterMouseoverForFrame(frame, unit) + if not frame then return end + + local onenter = frame:GetScript("OnEnter") + local onleave = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + CleveRoids.mouseoverUnit = unit + if onenter then + onenter() + end + end) + + frame:SetScript("OnLeave", function() + CleveRoids.mouseoverUnit = nil + if onleave then + onleave() + end + end) +end + +do + local frames = { + ["PlayerFrame"] = "player", + ["PetFrame"] = "pet", + ["TargetFrame"] = "target", + ["PartyMemberFrame1"] = "party1", + ["PartyMemberFrame2"] = "party2", + ["PartyMemberFrame3"] = "party3", + ["PartyMemberFrame4"] = "party4", + ["PartyMemberFrame1PetFrame"] = "party1", + ["PartyMemberFrame2PetFrame"] = "party2", + ["PartyMemberFrame3PetFrame"] = "party3", + ["PartyMemberFrame4PetFrame"] = "party4", + } + + local bars = { + "HealthBar", + "ManaBar", + } + + local allFrames = {} + for name, unit in pairs(frames) do + allFrames[name] = unit + for i, bar in ipairs(bars) do + allFrames[name .. bar] = unit + end + end + + -- Inconsisent naming for TargetofTarget required + allFrames["TargetofTargetFrame"] = "targettarget" + allFrames["TargetofTargetHealthBar"] = "targettarget" + allFrames["TargetofTargetManaBar"] = "targettarget" + + function Extension.OnLoad() + local frame + for name, unit in pairs(allFrames) do + frame = _G[name] + + if frame then + Extension.RegisterMouseoverForFrame(frame, unit) + end + end + end +end diff --git a/Extensions/Mouseover/CT_RaidAssist.lua b/Extensions/Mouseover/CT_RaidAssist.lua new file mode 100644 index 0000000..4e3337c --- /dev/null +++ b/Extensions/Mouseover/CT_RaidAssist.lua @@ -0,0 +1,62 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("CT_RaidAssist") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.OnEnter() + local tempOptions = CT_RAMenu_Options["temp"] + if ( SpellIsTargeting() ) then + SetCursor("CAST_CURSOR") + end + local parent = this.frameParent + local id = parent.id + if ( strsub(parent.name, 1, 12) == "CT_RAMTGroup" ) then + local name + if ( CT_RA_MainTanks[id] ) then + name = CT_RA_MainTanks[id] + end + for i = 1, GetNumRaidMembers(), 1 do + local memberName = GetRaidRosterInfo(i) + if ( name == memberName ) then + id = i + break + end + end + elseif ( strsub(parent.name, 1, 12) == "CT_RAPTGroup" ) then + local name + if ( CT_RA_PTargets[id] ) then + name = CT_RA_PTargets[id] + end + for i = 1, GetNumRaidMembers(), 1 do + local memberName = GetRaidRosterInfo(i) + if ( name == memberName ) then + id = i + break + end + end + end + CleveRoids.mouseoverUnit = "raid"..id +end + +function Extension.OnLeave() + CleveRoids.mouseoverUnit = nil +end + +function Extension.OnLoad() + if arg1 ~= "CT_RaidAssist" then + return + end + + Extension.Hook("CT_RA_MemberFrame_OnEnter", "OnEnter") + Extension.HookMethod(_G["GameTooltip"], "Hide", "OnLeave") + Extension.HookMethod(_G["GameTooltip"], "FadeOut", "OnLeave") + +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Extensions/Mouseover/CT_UnitFrames.lua b/Extensions/Mouseover/CT_UnitFrames.lua new file mode 100644 index 0000000..da7db4d --- /dev/null +++ b/Extensions/Mouseover/CT_UnitFrames.lua @@ -0,0 +1,39 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("CT_UnitFrames") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.SetHook(widget) + local hookedOnEnter = widget:GetScript("OnEnter") + local hookedOnLeave = widget:GetScript("OnLeave") + + widget:SetScript("OnEnter", function() + hookedOnEnter() + CleveRoids.mouseoverUnit = "targettarget" + end) + + widget:SetScript("OnLeave", function() + hookedOnLeave() + CleveRoids.mouseoverUnit = nil + end) +end + +function Extension.OnLoad() + if arg1 ~= "CT_UnitFrames" or not CT_AssistFrame then + return + end + CleveRoids.Print("CT_UnitFrames module loaded.") + + Extension.SetHook(CT_AssistFrame) + Extension.SetHook(CT_AssistFrameHealthBar) + Extension.SetHook(CT_AssistFrameManaBar) + Extension.SetHook(CT_AssistFrame_Drag) +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Extensions/Mouseover/DiscordUnitFrames.lua b/Extensions/Mouseover/DiscordUnitFrames.lua new file mode 100644 index 0000000..fa43dee --- /dev/null +++ b/Extensions/Mouseover/DiscordUnitFrames.lua @@ -0,0 +1,41 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("DiscordUnitFrames") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.OnEnterFrame() + CleveRoids.mouseoverUnit = this.unit +end + +function Extension.OnLeaveFrame() + CleveRoids.mouseoverUnit = nil +end + +function Extension.OnEnterElement() + CleveRoids.mouseoverUnit = this:GetParent().unit +end + +function Extension.OnLeaveElement() + CleveRoids.mouseoverUnit = nil +end + +function Extension.OnLoad() + if arg1 ~= "DiscordUnitFrames" then + return + end + + CleveRoids.ClearHooks() + Extension.Hook("DUF_UnitFrame_OnEnter", "OnEnterFrame") + Extension.Hook("DUF_UnitFrame_OnLeave", "OnLeaveFrame") + + Extension.Hook("DUF_Element_OnEnter", "OnEnterElement") + Extension.Hook("DUF_Element_OnLeave", "OnLeaveElement") +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Extensions/Mouseover/FocusFrame.lua b/Extensions/Mouseover/FocusFrame.lua new file mode 100644 index 0000000..fce6358 --- /dev/null +++ b/Extensions/Mouseover/FocusFrame.lua @@ -0,0 +1,42 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("FocusFrame") + +function Extension.RegisterMouseoverForFrame(frame) + local onenter = frame:GetScript("OnEnter") + local onleave = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + CleveRoids.mouseoverUnit = "focus" + if onenter then + onenter() + end + end) + + frame:SetScript("OnLeave", function() + CleveRoids.mouseoverUnit = nil + if onleave then + onleave() + end + end) +end + +function Extension.OnLoad() + if not FocusFrameHealthBar + or not FocusFrameManaBar + or not FocusFrameTextureFrame + or not FocusFrame then + return + end + + Extension.RegisterMouseoverForFrame(FocusFrameHealthBar) + Extension.RegisterMouseoverForFrame(FocusFrameManaBar) + Extension.RegisterMouseoverForFrame(FocusFrameTextureFrame) + Extension.RegisterMouseoverForFrame(FocusFrame) +end \ No newline at end of file diff --git a/Extensions/Mouseover/GameTooltip.lua b/Extensions/Mouseover/GameTooltip.lua new file mode 100644 index 0000000..b9acfa8 --- /dev/null +++ b/Extensions/Mouseover/GameTooltip.lua @@ -0,0 +1,29 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +local Extension = CleveRoids.RegisterExtension("GameTooltipMouseover") + +local lastUnit, setByTooltip = nil, false + +function Extension.SetUnit(_, unit) + if unit and unit ~= "" then + CleveRoids.SetMouseoverFrom("tooltip", unit) + end +end + +function Extension.OnClose() + CleveRoids.ClearMouseoverFrom("tooltip") +end + + +function Extension.OnLoad() + Extension.HookMethod(_G["GameTooltip"], "SetUnit", "SetUnit") + Extension.HookMethod(_G["GameTooltip"], "Hide", "OnClose") + Extension.HookMethod(_G["GameTooltip"], "FadeOut", "OnClose") +end + +_G["CleveRoids"] = CleveRoids + diff --git a/Extensions/Mouseover/Grid.lua b/Extensions/Mouseover/Grid.lua new file mode 100644 index 0000000..6795832 --- /dev/null +++ b/Extensions/Mouseover/Grid.lua @@ -0,0 +1,114 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +CleveRoids.Hooks = CleveRoids.Hooks or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("Grid") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.OnEnter(unit) + CleveRoids.mouseoverUnit = unit +end + +function Extension.OnLeave() + CleveRoids.mouseoverUnit = nil +end + +function Extension.OnLoad() + if arg1 ~= "Grid" then + return + end + + CleveRoids.Hooks.Grid = { CreateFrames = GridFrame.frameClass.prototype.CreateFrames} + GridFrame.frameClass.prototype.CreateFrames = CleveRoids.GrdCreateFrames +end + +-- Taken from GridFrame.lua and added OnEnter and OnLeave scripts +function CleveRoids:GrdCreateFrames() + -- create frame + --self.frame = CreateFrame("Button", nil, UIParent) + local f = self.frame + + f:SetScript("OnEnter", function() + Extension.OnEnter(self.unit) + end) + f:SetScript("OnLeave", function() + Extension.OnLeave() + end) + + -- f:Hide() + f:EnableMouse(true) + f:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up") + f:SetWidth(GridFrame:GetFrameSize()) + f:SetHeight(GridFrame:GetFrameSize()) + + -- only use SetScript pre-TBC + if Grid.isTBC then + f:SetAttribute("type1", "target") + else + f:SetScript("OnClick", function () GridFrame_OnClick(f, arg1) end) + f:SetScript("OnAttributeChanged", GridFrame_OnAttributeChanged) + end + + -- create border + f:SetBackdrop({ + bgFile = "Interface\\Addons\\Grid\\white16x16", tile = true, tileSize = 16, + edgeFile = "Interface\\Addons\\Grid\\white16x16", edgeSize = 1, + insets = {left = 1, right = 1, top = 1, bottom = 1}, + }) + f:SetBackdropBorderColor(0,0,0,0) + f:SetBackdropColor(0,0,0,1) + + -- create bar BG (which users will think is the real bar, as it is the one that has a shiny color) + -- this is necessary as there's no other way to implement status bars that grow in the other direction than normal + f.BarBG = f:CreateTexture() + f.BarBG:SetTexture("Interface\\Addons\\Grid\\gradient32x32") + f.BarBG:SetPoint("CENTER", f, "CENTER") + f.BarBG:SetWidth(GridFrame:GetFrameSize()-4) + f.BarBG:SetHeight(GridFrame:GetFrameSize()-4) + + -- create bar + f.Bar = CreateFrame("StatusBar", nil, f) + f.Bar:SetStatusBarTexture("Interface\\Addons\\Grid\\gradient32x32") + f.Bar:SetOrientation("VERTICAL") + f.Bar:SetMinMaxValues(0,100) + f.Bar:SetValue(100) + f.Bar:SetStatusBarColor(0,0,0,0.8) + f.Bar:SetPoint("CENTER", f, "CENTER") + f.Bar:SetFrameLevel(4) + f.Bar:SetWidth(GridFrame:GetFrameSize()-4) + f.Bar:SetHeight(GridFrame:GetFrameSize()-4) + + -- create center text + f.Text = f.Bar:CreateFontString(nil, "ARTWORK") + f.Text:SetFontObject(GameFontHighlightSmall) + f.Text:SetFont(STANDARD_TEXT_FONT,8) + f.Text:SetJustifyH("CENTER") + f.Text:SetJustifyV("CENTER") + f.Text:SetPoint("CENTER", f, "CENTER") + + -- create icon + f.Icon = f.Bar:CreateTexture("Icon", "OVERLAY") + f.Icon:SetWidth(16) + f.Icon:SetHeight(16) + f.Icon:SetPoint("CENTER", f, "CENTER") + f.Icon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + f.Icon:SetTexture(1,1,1,0) --"Interface\\Icons\\INV_Misc_Orb_02" + + -- set texture + f:SetNormalTexture(1,1,1,0) + f:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") + + self.frame = f + + -- set up click casting + ClickCastFrames = ClickCastFrames or {} + ClickCastFrames[self.frame] = true +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Extensions/Mouseover/NotGrid.lua b/Extensions/Mouseover/NotGrid.lua new file mode 100644 index 0000000..74128b8 --- /dev/null +++ b/Extensions/Mouseover/NotGrid.lua @@ -0,0 +1,54 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +local CreateFrames = nil +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("NotGrid") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.OnEnter() + CleveRoids.mouseoverUnit = this.unit +end + +function Extension.OnLeave() + CleveRoids.mouseoverUnit = nil +end + +function CleveRoids:NotGrid_CreateFrames() + CreateFrames(NotGrid); -- call the original + + -- NotGrid stores all of it's frames in the NotGrid.UnitFrames table. + for k, frame in pairs(NotGrid.UnitFrames) do + local enter = frame:GetScript("OnEnter") + local leave = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + enter() + Extension.OnEnter() + end) + + frame:SetScript("OnLeave", function() + leave() + Extension.OnLeave() + end) + end +end + +function Extension.OnLoad() + -- NotGrid loads before CleveRoids, so if NotGrid is enabled, then it's global will exist. + if not NotGrid then + return + end + -- Hooking manually as we need a post hook. + CreateFrames = NotGrid.CreateFrames + NotGrid.CreateFrames = CleveRoids.NotGrid_CreateFrames + + Extension.UnregisterEvent("ADDON_LOADED", "Onload") +end + +_G["CleveRoids"] = CleveRoids diff --git a/Extensions/Mouseover/PerfectRaid.lua b/Extensions/Mouseover/PerfectRaid.lua new file mode 100644 index 0000000..152420d --- /dev/null +++ b/Extensions/Mouseover/PerfectRaid.lua @@ -0,0 +1,137 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +CleveRoids.Hooks = CleveRoids.Hooks or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("PerfectRaid") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.OnEnter(unit) + CleveRoids.mouseoverUnit = unit +end + +function Extension.OnLeave() + CleveRoids.mouseoverUnit = nil +end + +function Extension.OnLoad() + if arg1 ~= "PerfectRaid" then + return + end + + CleveRoids.Hooks.PerfectRaid = { CreateFrame = PerfectRaid.CreateFrame } + PerfectRaid.CreateFrame = CleveRoids.PerfectRaidCreateFrame +end + +function CleveRoids.PerfectRaidCreateFrame(self, num) + -- We need to allocate up to num frames + + if self.poolsize >= num then return end + +--[[ + local mem,thr = gcinfo() + self:Msg("Memory Usage Before: %s [%s].", mem, thr) +--]] + local side = self.opt.Align + + local justify,point,relative,offset + + if side == "left" then + justify = "RIGHT" + point = "LEFT" + relative = "RIGHT" + offset = 5 + elseif side == "right" then + justify = "LEFT" + point = "RIGHT" + relative = "LEFT" + offset = -5 + end + + for i=(self.poolsize + 1),num do + local frame = CreateFrame("Button", nil, PerfectRaidFrame) + frame:EnableMouse(true) + frame.unit = "raid"..i + frame.id = i + frame:SetWidth(225) + frame:SetHeight(13) + frame:SetMovable(true) + frame:RegisterForDrag("LeftButton") + frame:SetScript("OnDragStart", function() self["master"]:StartMoving() end) + frame:SetScript("OnDragStop", function() self["master"]:StopMovingOrSizing() self:SavePosition() end) + frame:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up") + frame:SetScript("OnClick", self.OnClick) + frame:SetScript("OnEnter", function() + Extension.OnEnter(this.unit) + end) + frame:SetScript("OnLeave", function() + Extension.OnLeave() + end) + frame:SetParent(self.master) + + local font = frame:CreateFontString(nil, "ARTWORK") + font:SetFontObject(GameFontHighlightSmall) + font:SetText("WW") + font:SetJustifyH("CENTER") + font:SetWidth(font:GetStringWidth()) + font:SetHeight(14) + font:Show() + font:ClearAllPoints() + font:SetPoint(point, frame, relative,0, 0) + -- Add this font string to the frame + frame.Prefix = font + + font = frame:CreateFontString(nil, "ARTWORK") + font:SetFontObject(GameFontHighlightSmall) + font:SetText() + font:SetJustifyH(justify) + font:SetWidth(55) + font:SetHeight(12) + font:Show() + font:ClearAllPoints() + font:SetPoint(point, frame.Prefix, relative, offset, 0) + -- Add this font string to the frame + frame.Name = font + + local bar = CreateFrame("StatusBar", nil, frame) + bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar") + bar:SetMinMaxValues(0,100) + bar:ClearAllPoints() + bar:SetPoint(point, frame.Name, relative, offset, 0) + bar:SetWidth(60) + bar:SetHeight(7) + bar:Show() + -- Add this status bar to the frame + frame.Bar = bar + + font = frame:CreateFontString(nil, "ARTWORK") + font:SetFontObject(GameFontHighlightSmall) + font:SetText("") + font:SetJustifyH(justify) + font:SetWidth(font:GetStringWidth()) + font:SetHeight(12) + font:Show() + font:ClearAllPoints() + font:SetPoint(point, frame.Bar, relative, offset, 0) + -- Add this font string to the frame + frame.Status = font + + -- Lets set the frame in the indexed array + self.frames[i] = frame + self.frames["raid"..i] = frame + self.poolsize = i + end + + mem2,thr2 = gcinfo() +--[[ + self:Msg("Memory Usage After: %s [%s].", mem2, thr2) + self:Msg("Frame creation change: %s [%s].", mem2 - mem, thr2 - thr) +--]] +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Extensions/Mouseover/ag_UnitFrames.lua b/Extensions/Mouseover/ag_UnitFrames.lua new file mode 100644 index 0000000..c429480 --- /dev/null +++ b/Extensions/Mouseover/ag_UnitFrames.lua @@ -0,0 +1,44 @@ +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +CleveRoids.Hooks = CleveRoids.Hooks or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("ag_UnitFrames") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension.OnEnter(unit) + CleveRoids.mouseoverUnit = unit +end + +function Extension.OnLeave() + CleveRoids.mouseoverUnit = nil +end + +-- Because AddOns are loaded in alphabetical order, this callback will never see the aUF loaded message, had to do a workaround... +function Extension.OnLoad() + if not aUF then + return + end + CleveRoids.Hooks.ag_UnitFrames = { OnEnter = aUF.classes.aUFunit.prototype.OnEnter, OnLeave = aUF.classes.aUFunit.prototype.OnLeave} + aUF.classes.aUFunit.prototype.OnEnter = CleveRoids.aUFOnEnter + aUF.classes.aUFunit.prototype.OnLeave = CleveRoids.aUFOnLeave + Extension.UnregisterEvent("ADDON_LOADED", "Onload") +end + +-- Taken from ag_UnitClass.lua +function CleveRoids:aUFOnEnter() + Extension.OnEnter(self.unit) + self.frame.unit = self.unit + self:UpdateHighlight(true) + UnitFrame_OnEnter() +end + +function CleveRoids:aUFOnLeave() + Extension.OnLeave() + self:UpdateHighlight() + UnitFrame_OnLeave() +end + + +_G["CleveRoids"] = CleveRoids diff --git a/Extensions/Mouseover/pfUI.lua b/Extensions/Mouseover/pfUI.lua new file mode 100644 index 0000000..787f9e3 --- /dev/null +++ b/Extensions/Mouseover/pfUI.lua @@ -0,0 +1,299 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License + + Fixes pfUI mouseover issues by: + - Using a unique source key per pfUI frame (e.g., "pfui:party3", "pfui:raid7") + - Pairing Set/Clear with the same per-frame key + - Resolving a real UnitID when .unit isn't set + - Properly hooking party group[0] (your own party slot) with a safe closure and defaulting to "player" +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("pfUI") +Extension.RegisterEvent("PLAYER_ENTERING_WORLD", "PLAYER_ENTERING_WORLD") + +function Extension.OnLoad() +end + +-- Resolve a pfUI unitframe (focus / focustarget and fallback for party/raid) +local function ResolvePfUnit(frame, fallbackName) + if not frame then return nil end + if frame.label and frame.id then + return frame.label .. frame.id + end + + local name = fallbackName or frame.unitname + if not name or name == "" then return nil end + name = strlower(name) + + local candidates = { "target", "targettarget", "player", "pet" } + local i + for i = 1, 4 do + table.insert(candidates, "party"..i) + table.insert(candidates, "partypet"..i) + end + for i = 1, 40 do + table.insert(candidates, "raid"..i) + table.insert(candidates, "raidpet"..i) + end + + for _,u in ipairs(candidates) do + if UnitExists(u) and strlower(UnitName(u) or "") == name then + return u + end + end + + return nil +end + +-- Unique key per pfUI frame so leave events can't clear another frame's hover +local function PfSourceKey(frame) + if not frame then return "pfui:unknown" end + if frame.label and frame.id then + return "pfui:" .. frame.label .. frame.id + end + if frame.unit and frame.unit ~= "" then + return "pfui:" .. frame.unit + end + return "pfui:" .. tostring(frame) -- stable per-frame string in 1.12 +end + +-- Resolve a *real* UnitID for a pfUI frame +local function PfResolveUnit(frame, defaultUnit) + if frame and frame.unit and frame.unit ~= "" then + return frame.unit + end + if frame and frame.label and frame.id then + return frame.label .. frame.id + end + return ResolvePfUnit(frame) or defaultUnit +end + +-- Helper to set with per-frame key +local function PfSet(frame, defaultUnit) + local key = PfSourceKey(frame) + local unit = PfResolveUnit(frame, defaultUnit) + if unit then + frame.__cr_src = key + CleveRoids.SetMouseoverFrom(key, unit) + end +end + +-- Helper to clear with the same per-frame key +local function PfClear(frame) + if frame and frame.__cr_src then + CleveRoids.ClearMouseoverFrom(frame.__cr_src) + frame.__cr_src = nil + end +end + +-- PLAYER +function Extension.RegisterPlayerScripts() + if not pfUI or not pfUI.uf or not pfUI.uf.player then return end + local frame = pfUI.uf.player + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + PfSet(this, "player") + if onEnterFunc then onEnterFunc(this) end + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) +end + +-- TARGET +function Extension.RegisterTargetScripts() + if not pfUI or not pfUI.uf or not pfUI.uf.target then return end + local frame = pfUI.uf.target + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + PfSet(this, "target") + if onEnterFunc then onEnterFunc(this) end + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) +end + +-- TARGETTARGET +function Extension.RegisterTargetTargetScripts() + if not pfUI or not pfUI.uf or not pfUI.uf.targettarget then return end + local frame = pfUI.uf.targettarget + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + PfSet(this, "targettarget") + if onEnterFunc then onEnterFunc(this) end + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) +end + +-- PARTY (pfUI.uf.group[0..4]) -- include 0 to cover your own party slot +function Extension.RegisterPartyScripts() + if not pfUI or not pfUI.uf or not pfUI.uf.group then return end + + local i + for i = 0, 4 do + local frame = pfUI.uf.group[i] + if frame then + -- bind loop index for closures (Vanilla-safe) + local idx = i + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + -- For group[0] (your own party frame), default to "player" + local defaultUnit = (idx == 0) and "player" or nil + PfSet(this, defaultUnit) + if onEnterFunc then onEnterFunc(this) end + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) + end + end +end + +-- RAID (pfUI.uf.raid[1..40]) +function Extension.RegisterRaidScripts() + if not pfUI or not pfUI.uf or not pfUI.uf.raid then return end + + local i + for i = 1, 40 do + local frame = pfUI.uf.raid[i] + if frame then + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + PfSet(this) + if onEnterFunc then onEnterFunc(this) end + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) + end + end +end + +-- FOCUS +function Extension.RegisterFocusScripts() + if not pfUI or not pfUI.uf or not pfUI.uf.focus then return end + local frame = pfUI.uf.focus + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + PfSet(this) -- ResolvePfUnit handles focus emulation + if onEnterFunc then onEnterFunc(this) end + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) +end + +-- FOCUSTARGET (if your pfUI build provides it) +function Extension.RegisterFocusTargetScripts() + if not pfUI or not pfUI.uf or not pfUI.uf.focustarget then return end + local frame = pfUI.uf.focustarget + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + PfSet(this) + if onEnterFunc then onEnterFunc(this) end + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) +end + +-- PARTYTARGET (party1target..party4target, plus player's target) +function Extension.RegisterPartyTargetScripts() + if not pfUI or not pfUI.uf then return end + + -- This helper function is used to hook any given frame. + local function hookFrame(frame, defaultUnit) + if not frame then return end + local onEnterFunc = frame:GetScript("OnEnter") + local onLeaveFunc = frame:GetScript("OnLeave") + + frame:SetScript("OnEnter", function() + PfSet(this, defaultUnit) + -- We remove the call to the original onEnterFunc to prevent overwritten tooltips. + end) + + frame:SetScript("OnLeave", function() + PfClear(this) + if onLeaveFunc then onLeaveFunc(this) end + end) + end + + -- This helper function is specifically for party member targets (1-4) + local function hookPartyMemberTarget(i, frame) + if not frame then return end + local defaultUnit = "party" .. i .. "target" + hookFrame(frame, defaultUnit) + end + + -- Case A: Hook dedicated arrays for party members 1-4 + if pfUI.uf.grouptarget then + for i = 1, 4 do hookPartyMemberTarget(i, pfUI.uf.grouptarget[i]) end + end + if pfUI.uf.partytarget then + for i = 1, 4 do hookPartyMemberTarget(i, pfUI.uf.partytarget[i]) end + end + + -- Case B: Hook child target frames for party members 1-4 + if pfUI.uf.group then + for i = 1, 4 do + local g = pfUI.uf.group[i] + if g and g.target then hookPartyMemberTarget(i, g.target) end + end + end + + --- START OF FIX to include party0target --- + -- Case C: Specifically find and hook the player's own target frame (group[0].target) + if pfUI.uf.group and pfUI.uf.group[0] and pfUI.uf.group[0].target then + -- The player's target UnitID is always "target", not "party0target". + hookFrame(pfUI.uf.group[0].target, "target") + end + --- END OF FIX --- +end + +function Extension.PLAYER_ENTERING_WORLD() + if not pfUI or not pfUI.uf then return end + Extension.RegisterPlayerScripts() + Extension.RegisterTargetScripts() + Extension.RegisterTargetTargetScripts() + Extension.RegisterPartyScripts() + Extension.RegisterPartyTargetScripts() + Extension.RegisterRaidScripts() + Extension.RegisterFocusScripts() + Extension.RegisterFocusTargetScripts() +end diff --git a/Extensions/Mouseover/sRaidFrames.lua b/Extensions/Mouseover/sRaidFrames.lua new file mode 100644 index 0000000..a4ec4df --- /dev/null +++ b/Extensions/Mouseover/sRaidFrames.lua @@ -0,0 +1,24 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil + +local Extension = CleveRoids.RegisterExtension("sRaidFrames") +Extension.RegisterEvent("ADDON_LOADED", "OnLoad") + +function Extension:OnEnter(frame) + CleveRoids.mouseoverUnit = frame.unit +end + +function Extension.OnLoad() + if arg1 ~= "sRaidFrames" then + return + end + + Extension.HookMethod(sRaidFrames, "UnitTooltip", "OnEnter") +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/Extensions/Tooltip/Generic.lua b/Extensions/Tooltip/Generic.lua new file mode 100644 index 0000000..4a4249c --- /dev/null +++ b/Extensions/Tooltip/Generic.lua @@ -0,0 +1,259 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + + +-- Indexes all spells the current player and pet knows +function CleveRoids.IndexSpells() + local spells = {} + local i = 0 + local book = 1 + local bookType = CleveRoids.bookTypes[book] + local maxBooks = table.getn(CleveRoids.bookTypes) + + spells[bookType] = {} + while true do + i = i + 1 + + local spellName, spellRank = GetSpellName(i, bookType) + if not spellName then + i = 0 + book = book + 1 + if book > maxBooks then + break + end + + bookType = CleveRoids.bookTypes[book] + spells[bookType] = {} + else + local cost, reagent = CleveRoids.GetSpellCost(i, bookType) + local texture = GetSpellTexture(i, bookType) + if not spells[bookType][spellName] then + spells[bookType][spellName] = { + spellSlot = i, + name = spellName, + bookType = bookType, + texture = texture, + cost = cost, + reagent = reagent, + } + end + if spellRank and not spells[bookType][spellName][spellRank] then + spells[bookType][spellName][spellRank] = { + spellSlot = i, + name = spellName, + rank = spellRank, + bookType = bookType, + texture = texture, + cost = cost, + reagent = reagent + } + spells[bookType][spellName].highest = spells[bookType][spellName][spellRank] + end + + if reagent then + CleveRoids.countedItemTypes[reagent] = true + end + end + end + + CleveRoids.Spells = spells +end + +function CleveRoids.IndexTalents() + local talents = {[1] = true} + for tab = 1, GetNumTalentTabs() do + for i = 1, GetNumTalents(tab) do + local name, _, _, _, rank = GetTalentInfo(tab, i) + talents[name] = tonumber(rank) + end + end + CleveRoids.Talents = talents +end + + +function CleveRoids.IndexItems() + local items = {} + for bagID = 0, NUM_BAG_SLOTS do + for slot = GetContainerNumSlots(bagID), 1, -1 do + local link = GetContainerItemLink(bagID, slot) + if link then + local _, _, itemID = string.find(link, "item:(%d+)") + local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID) + + if name then + local _, count = GetContainerItemInfo(bagID, slot) + if not items[name] then + items[name] = { + bagID = bagID, + slot = slot, + id = itemID, + name = name, + type = itemType, + count = count, + texture = texture, + link = link, + bagSlots = {{bagID, slot}}, + slotsIndex = 1, + } + items[itemID] = name + else + items[name].count = (items[name].count or 0) + (count or 0) + table.insert(items[name].bagSlots, {bagID, slot}) + end + end + end + end + end + + for inventoryID = 0, 19 do + local link = GetInventoryItemLink("player", inventoryID) + if link then + local _, _, itemID = string.find(link, "item:(%d+)") + local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID) + if name then + local count = GetInventoryItemCount("player", inventoryID) + if not items[name] then + items[name] = { + inventoryID = inventoryID, + id = itemID, + name = name, + count = count, + texture = texture, + link = link, + } + items[itemID] = name + else + items[name].inventoryID = inventoryID + items[name].count = (items[name].count or 0) + (count or 0) + end + end + end + end + + CleveRoids.lastGetItem = nil + CleveRoids.Items = items + + -- relink an item action to the item if it wasn't in inventory/bags before + for slot, actions in CleveRoids.Actions do + for i, action in actions.list do + if action.item then + action.item = CleveRoids.GetItem(action.action) + end + end + end +end + +function CleveRoids.ClearSlot(slots, slot) + if slots[slot] then + slots[slots[slot]] = nil + end + slots[slot] = nil +end + +function CleveRoids.IndexActionSlot(slot) + if not HasAction(slot) then + CleveRoids.Actions[slot] = nil + CleveRoids.ClearSlot(CleveRoids.reactiveSlots, slot) + CleveRoids.ClearSlot(CleveRoids.actionSlots, slot) + else + local actionType, _, name, rank = CleveRoids.GetActionButtonInfo(slot) + if name then + local reactiveName = CleveRoids.reactiveSpells[name] and name + local actionSlotName = name..(rank and ("("..rank..")") or "") + + if reactiveName then + if not CleveRoids.reactiveSlots[reactiveName] then + CleveRoids.reactiveSlots[reactiveName] = slot + CleveRoids.reactiveSlots[slot] = reactiveName + end + elseif not reactiveName then + CleveRoids.ClearSlot(CleveRoids.reactiveSlots, slot) + end + + if actionType == "SPELL" or actionType == "ITEM" then + if not CleveRoids.actionSlots[actionSlotName] then + CleveRoids.actionSlots[actionSlotName] = slot + CleveRoids.actionSlots[slot] = actionSlotName + end + end + end + end + + CleveRoids.TestForActiveAction(CleveRoids.GetAction(slot)) + CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot) +end + +function CleveRoids.IndexActionBars() + for i = 1, 120 do + CleveRoids.IndexActionSlot(i) + end +end + +function CleveRoids.GetSpell(text) + text = CleveRoids.Trim(text) + local rs, _, rank = string.find(text, "[^%s]%((Rank %d+)%)$") + local name = rank and string.sub(text, 1, rs) or text + + for book, spells in CleveRoids.Spells do + if spells and spells[name] then + return spells[name][rank or "highest"] + end + end +end + +function CleveRoids.GetTalent(text) + text = CleveRoids.Trim(text) + return CleveRoids.Talents[text] +end + +function CleveRoids.GetItem(text) + local item = CleveRoids.Items[text] or CleveRoids.Items[tostring(text)] + + if item then + -- Items are stored as [name]->{item} and [id]->name + if type(item) == "table" then return item end + return CleveRoids.Items[tostring(item)] + else + local name, link, _, _, itemType, _, _, _, texture = GetItemInfo(text) + if not name then + return + end + + local item = { + id = text, + name = name, + link = link, + texture = texture, + } + CleveRoids.Items[name] = item + + return item + end +end + +function CleveRoids.GetNextBagSlotForUse(item, text) + if not item then return end + + if CleveRoids.lastGetItem == CleveRoids.GetItem(text) then + if table.getn(item.bagSlots) > item.slotsIndex then + item.slotsIndex = item.slotsIndex + 1 + item.bagID, item.slot = unpack(item.bagSlots[item.slotsIndex]) + end + end + + CleveRoids.lastGetItem = item + return item +end + +local Extension = CleveRoids.RegisterExtension("Generic_show") +Extension.RegisterEvent("SPELLS_CHANGED", "SPELLS_CHANGED") + +function Extension.OnLoad() +end + +function Extension.SPELLS_CHANGED() +end diff --git a/ExtensionsManager.lua b/ExtensionsManager.lua new file mode 100644 index 0000000..c2d984b --- /dev/null +++ b/ExtensionsManager.lua @@ -0,0 +1,220 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} + +-- A list of all registered extensions +CleveRoids.Extensions = CleveRoids.Extensions or {} + +-- Calls the "OnLoad" function of every extension +function CleveRoids.InitializeExtensions() + for k, v in pairs(CleveRoids.Extensions) do + local func = v["OnLoad"] + if not func then + CleveRoids.Print("CleveRoids.InitializeExtensions - Couldn't find 'OnLoad' function for extension "..k) + else + func() + end + end +end + +-- Removes the given hook +-- hook: The hook to remove +function CleveRoids.RemoveHook(hook) + _G[hook.name] = hook.original +end + +-- Removes the given hook from the given object +-- object: The object to remove the hook from +-- hook: The hook to remove +function CleveRoids.RemoveMethodHook(object, hook) + object[hook.name] = hook.original +end + +-- Clears all previously declared hooks +function CleveRoids.ClearHooks() + for k, v in pairs(CleveRoids.Extensions) do + for k2, v2 in pairs(v.internal.memberHooks) do + for k3, v3 in pairs(v2) do + CleveRoids.RemoveMethodHook(k2, v3) + end + end + + for k2, v2 in pairs(v.internal.hooks) do + CleveRoids.RemoveHook(v2) + end + end +end + +-- Creates a new extension with the given name +-- name: the name of the extension +function CleveRoids.RegisterExtension(name) + local extension = { + internal = + { + frame = CreateFrame("FRAME"), + eventHandlers = {}, + hooks = {}, + memberHooks = {}, + }, + } + + extension.RegisterEvent = function(eventName, callbackName) + CleveRoids.RegisterEvent(name, eventName, callbackName) + end + + extension.Hook = function(functionName, callbackName, dontCallOriginal) + CleveRoids.RegisterHook(name, functionName, callbackName, dontCallOriginal) + end + + extension.HookMethod = function(object, functionName, callbackName, dontCallOriginal) + CleveRoids.RegisterMethodHook(name, object, functionName, callbackName, dontCallOriginal) + end + + + extension.UnregisterEvent = function(eventName, callbackName) + CleveRoids.UnregisterEvent(name, eventName, callbackName) + end + + extension.internal.OnEvent = function() + local callbackName = extension.internal.eventHandlers[event] + if callbackName then + extension[callbackName]() + end + end + + + -- This is a function wrapper that we swap with the function that we want to hook + -- @return Value of Callback() or Origininal() if dontCallOriginal is false + extension.internal.OnHook = function(object, functionName, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + local hook + + if object then + hook = extension.internal.memberHooks[object][functionName] + else + hook = extension.internal.hooks[functionName] + end + + local retval = hook.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + if not hook.dontCallOriginal then + retval = hook.original(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + end + return retval + end + + + extension.internal.frame:SetScript("OnEvent", extension.internal.OnEvent) + + CleveRoids.Extensions[name] = extension + + return extension +end + +-- Registers a callback function for a given event name +-- extensionName: The name of the extension trying to register the callback +-- eventName: The event's name we'd like to register a callback for +-- callbackName: The name of the callback that get's called when the event fires +function CleveRoids.RegisterEvent(extensionName, eventName, callbackName) + local extension = CleveRoids.Extensions[extensionName] + extension.internal.eventHandlers[eventName] = callbackName + extension.internal.frame:RegisterEvent(eventName) +end + +-- Hooks the given function by it's name +-- extensionName: The name of the extension trying to register the callback +-- functionName: The name of the function that'll be hooked +-- callbackName: The name of the callback that get's called when the hooked function is called +-- dontCallOriginal: Set to true when the original function should not be called +function CleveRoids.RegisterHook(extensionName, functionName, callbackName, dontCallOriginal) + local orig = _G[functionName] + local extension = CleveRoids.Extensions[extensionName] + + if not orig then + CleveRoids.Print("CleveRoids.RegisterHook - Invalid function to hook: "..functionName) + end + + if not extension then + CleveRoids.Print("CleveRoids.RegisterHook - Invalid extension: "..extension) + return + end + + if not extension[callbackName] then + CleveRoids.Print("CleveRoids.RegisterHook - Couldn't find callback: "..callbackName) + return + end + + + extension.internal.hooks[functionName] = { + original = orig, + callback = extension[callbackName], + name = functionName, + dontCallOriginal = dontCallOriginal + } + + _G[functionName] = function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + return extension.internal.OnHook(nil, functionName, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + end +end + +-- Hooks the given object's function +-- object: The object you want to hook +-- extensionName: The name of the extension trying to register the callback +-- functionName: The name of the function that'll be hooked +-- callbackName: The name of the callback that get's called when the hooked function is called +function CleveRoids.RegisterMethodHook(extensionName, object, functionName, callbackName, dontCallOriginal) + if not object then + CleveRoids.Print("CleveRoids.RegisterMethodHook - The object could not be found!") + return + end + + if type(object) ~= "table" then + CleveRoids.Print("CleveRoids.RegisterMethodHook - The object needs to be a table!") + return + end + + local orig = object[functionName] + + if not orig then + CleveRoids.Print("CleveRoids.RegisterMethodHook - The object doesn't have a function named "..functionName) + return + end + + local extension = CleveRoids.Extensions[extensionName] + + if not extension then + CleveRoids.Print("CleveRoids.RegisterMethodHook - Invalid extension: "..extension) + return + end + + if not extension[callbackName] then + CleveRoids.Print("CleveRoids.RegisterMethodHook - Couldn't find callback: "..callbackName) + return + end + + if not extension.internal.memberHooks[object] then + extension.internal.memberHooks[object] = {} + end + + extension.internal.memberHooks[object][functionName] = { + original = orig, + callback = extension[callbackName], + name = functionName, + dontCallOriginal = dontCallOriginal + } + + object[functionName] = function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + return extension.internal.OnHook(object, functionName, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + end +end + +function CleveRoids.UnregisterEvent(extensionName, eventName, callbackName) + local extension = CleveRoids.Extensions[extensionName] + if extension.internal.eventHandlers[eventName] then + extension.internal.eventHandlers[eventName] = nil + extension.internal.frame:UnregisterEvent(eventName) + end +end + +_G["CleveRoids"] = CleveRoids diff --git a/Init.lua b/Init.lua new file mode 100644 index 0000000..5ca72e5 --- /dev/null +++ b/Init.lua @@ -0,0 +1,135 @@ +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +_G.CleveRoids = CleveRoids + +CleveRoids.ready = false + +CleveRoids.Hooks = CleveRoids.Hooks or {} +CleveRoids.Hooks.GameTooltip = {} + +CleveRoids.Extensions = CleveRoids.Extensions or {} +CleveRoids.actionEventHandlers = {} +CleveRoids.mouseOverResolvers = {} + +CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil +CleveRoids.mouseOverUnit = nil + +-- Environment flags +CleveRoids.hasSuperwow = SetAutoloot and true or false +CleveRoids.hasTurtle = (type(_G.TURTLE_WOW_VERSION) ~= "nil") +CleveRoids.supported = (CleveRoids.hasSuperwow or CleveRoids.hasTurtle) + +CleveRoids.ParsedMsg = {} +CleveRoids.Items = {} +CleveRoids.Spells = {} +CleveRoids.Talents = {} +CleveRoids.Cooldowns = {} +CleveRoids.Macros = {} +CleveRoids.Actions = {} +CleveRoids.Sequences = {} + +CleveRoids.lastUpdate = 0 +CleveRoids.lastGetItem = nil +CleveRoids.currentSequence = nil + +CleveRoids.bookTypes = {BOOKTYPE_SPELL, BOOKTYPE_PET} +CleveRoids.unknownTexture = "Interface\\Icons\\INV_Misc_QuestionMark" + +CleveRoids.spell_tracking = {} + +-- Holds information about the currently cast spell +CleveRoids.CurrentSpell = { + -- "channeled" or "cast" + type = "", + -- the name of the spell + spellName = "", + -- is the Attack ability enabled + autoAttack = false, + -- is the Auto Shot ability enabled + autoShot = false, + -- is the Shoot ability (wands) enabled + wand = false, +} + +CleveRoids.dynamicCmds = { + ["/cast"] = true, + ["/castsequence"] = true, + ["/use"] = true, + ["/equip"] = true, + ["/equipmh"] = true, + ["/equipoh"] = true, +} + +CleveRoids.ignoreKeywords = { + action = true, + ignoretooltip = true, + cancelaura = true, +} + +-- TODO: Localize? +CleveRoids.countedItemTypes = { + ["Consumable"] = true, + ["Reagent"] = true, + ["Projectile"] = true, + ["Trade Goods"] = true, +} + + +-- TODO: Localize? +CleveRoids.actionSlots = {} +CleveRoids.reactiveSlots = {} +CleveRoids.reactiveSpells = { + ["Revenge"] = true, + ["Overpower"] = true, + ["Riposte"] = true, + ["Surprise Attack"] = true, + ["Mongoose Bite"] = true, + ["Counterattack"] = true, + ["Arcane Surge"] = true, +} + +CleveRoids.spamConditions = { + [CleveRoids.Localized.Attack] = "checkchanneled", + [CleveRoids.Localized.AutoShot] = "checkchanneled", + [CleveRoids.Localized.Shoot] = "checkchanneled", +} + +CleveRoids.auraTextures = { + [CleveRoids.Localized.Spells["Stealth"]] = "Interface\\Icons\\Ability_Stealth", + [CleveRoids.Localized.Spells["Prowl"]] = "Interface\\Icons\\Ability_Ambush", + [CleveRoids.Localized.Spells["Shadowform"]] = "Interface\\Icons\\Spell_Shadow_Shadowform", + ["Seal of Wisdom"] = "Interface\\Icons\\Spell_Holy_RighteousnessAura", + ["Seal of the Crusader"] = "Interface\\Icons\\Spell_Holy_HolySmite", + ["Seal of Light"] = "Interface\\Icons\\Spell_Holy_HealingAura", + ["Seal of the Justice"] = "Interface\\Icons\\Spell_Holy_SealOfWrath", + ["Seal of Righteousness"] = "Interface\\Icons\\Ability_ThunderBolt", + ["Seal of Command"] = "Interface\\Icons\\Ability_Warrior_InnerRage", +} + + +-- I need to make a 2h modifier +-- Maps easy to use weapon type names (e.g. Axes, Shields) to their inventory slot name and their localized tooltip name +CleveRoids.WeaponTypeNames = { + Daggers = { slot = "MainHandSlot", name = CleveRoids.Localized.Dagger }, + Fists = { slot = "MainHandSlot", name = CleveRoids.Localized.FistWeapon }, + Axes = { slot = "MainHandSlot", name = CleveRoids.Localized.Axe }, + Swords = { slot = "MainHandSlot", name = CleveRoids.Localized.Sword }, + Staves = { slot = "MainHandSlot", name = CleveRoids.Localized.Staff }, + Maces = { slot = "MainHandSlot", name = CleveRoids.Localized.Mace }, + Polearms = { slot = "MainHandSlot", name = CleveRoids.Localized.Polearm }, + -- OH + Daggers2 = { slot = "SecondaryHandSlot", name = CleveRoids.Localized.Dagger }, + Fists2 = { slot = "SecondaryHandSlot", name = CleveRoids.Localized.FistWeapon }, + Axes2 = { slot = "SecondaryHandSlot", name = CleveRoids.Localized.Axe }, + Swords2 = { slot = "SecondaryHandSlot", name = CleveRoids.Localized.Sword }, + Maces2 = { slot = "SecondaryHandSlot", name = CleveRoids.Localized.Mace }, + Shields = { slot = "SecondaryHandSlot", name = CleveRoids.Localized.Shield }, + -- ranged + Guns = { slot = "RangedSlot", name = CleveRoids.Localized.Gun }, + Crossbows = { slot = "RangedSlot", name = CleveRoids.Localized.Crossbow }, + Bows = { slot = "RangedSlot", name = CleveRoids.Localized.Bow }, + Thrown = { slot = "RangedSlot", name = CleveRoids.Localized.Thrown }, + Wands = { slot = "RangedSlot", name = CleveRoids.Localized.Wand }, +} + +_G["CleveRoids"] = CleveRoids diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aed97d6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Dennis Werner Garske (DWG) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Localization.lua b/Localization.lua new file mode 100644 index 0000000..bc961ec --- /dev/null +++ b/Localization.lua @@ -0,0 +1,444 @@ +--[[ + Author : Dennis Werner Garske (DWG) / brian / Mewtiny + License : MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} +CleveRoids.Locale = GetLocale() +CleveRoids.Localized = {} + +if CleveRoids.Locale == "enUS" or CleveRoids.Locale == "enGB" then + -- place item in backpack slot 1 and run: + -- /script local l=GetContainerItemLink(0,1);local _,_,id=string.find(l,"item:(%d+)");local n,_,_,_,t,st=GetItemInfo(id);DEFAULT_CHAT_FRAME:AddMessage("\n\nID: ["..id.."]\nName: ["..n.."]\nType: ["..t.."]\nSub Type: ["..st.."]\n\n"); + CleveRoids.Localized.Shield = "Shields" + CleveRoids.Localized.Bow = "Bows" + CleveRoids.Localized.Crossbow = "Crossbows" + CleveRoids.Localized.Gun = "Guns" + CleveRoids.Localized.Thrown = "Thrown" + CleveRoids.Localized.Wand = "Wands" + CleveRoids.Localized.Sword = "Swords" + CleveRoids.Localized.Staff = "Staves" + CleveRoids.Localized.Polearm = "Polearms" + CleveRoids.Localized.Mace = "Maces" + CleveRoids.Localized.FistWeapon = "Fist Weapons" + CleveRoids.Localized.Dagger = "Daggers" + CleveRoids.Localized.Axe = "Axes" + + CleveRoids.Localized.Attack = "Attack" + CleveRoids.Localized.AutoShot = "Auto Shot" + CleveRoids.Localized.Shoot = "Shoot" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + -- target creature and run: + -- /script local ct, uc = UnitCreatureType("target"),UnitClassification("target"); DEFAULT_CHAT_FRAME:AddMessage("\n\nUnitCreatureType: ["..ct.."]\nUnitClassificationType: ["..uc.."]\n\n"); + CleveRoids.Localized.CreatureTypes = { + ["Beast"] = "Beast", + ["Critter"] = "Critter", + ["Demon"] = "Demon", + ["Dragonkin"] = "Dragonkin", + ["Elemental"] = "Elemental", + ["Giant"] = "Giant", + ["Humanoid"] = "Humanoid", + ["Mechanical"] = "Mechanical", + ["Not specified"] = "Not Specified", + ["Totem"] = "Totem", + ["Undead"] = "Undead", + } + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "Shadowform", + ["Stealth"] = "Stealth", + ["Prowl"] = "Prowl", + ["Revenge"] = "Revenge", + ["Overpower"] = "Overpower", + ["Riposte"] = "Riposte", + ["Surprise Attack"] = "Surprise Attack", + ["Mongoose Bite"] = "Mongoose Bite", + ["Counterattack"] = "Counterattack", + ["Arcane Surge"] = "Arcane Surge", + } + + -- place item in backpack slot 1 and run: + -- /script local l=GetContainerItemLink(0,1);local _,_,id=string.find(l,"item:(%d+)");local n,_,_,_,t,st=GetItemInfo(id);DEFAULT_CHAT_FRAME:AddMessage("\n\nID: ["..id.."]\nName: ["..n.."]\nType: ["..t.."]\nSub Type: ["..st.."]\n\n"); + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "Consumable", + ["Reagent"] = "Reagent", + ["Projectile"] = "Projectile", + ["Trade Goods"] = "Trade Goods", + } +elseif CleveRoids.Locale == "deDE" then + CleveRoids.Localized.Shield = "Schilde" + CleveRoids.Localized.Bow = "Bögen" + CleveRoids.Localized.Crossbow = "Armbrüste" + CleveRoids.Localized.Gun = "Waffen" + CleveRoids.Localized.Thrown = "Geworfen" + CleveRoids.Localized.Wand = "Zauberstäbe" + CleveRoids.Localized.Sword = "Schwerter" + CleveRoids.Localized.Staff = "Dauben" + CleveRoids.Localized.Polearm = "Stangenwaffen" + CleveRoids.Localized.Mace = "Streitkolben" + CleveRoids.Localized.FistWeapon = "Faustwaffen" + CleveRoids.Localized.Dagger = "Dolche" + + CleveRoids.Localized.Axe = "Äxte" + CleveRoids.Localized.Attack = "Angriff" + CleveRoids.Localized.AutoShot = "Automatischer Schuss" + CleveRoids.Localized.Shoot = "Schießen" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + CleveRoids.Localized.CreatureTypes = { + ["Wildtier"] = "Beast", + ["Kleintier"] = "Critter", + ["Dämon"] = "Demon", + ["Drachkin"] = "Dragonkin", + ["Elementar"] = "Elemental", + ["Riese"] = "Giant", + ["Humanoid"] = "Humanoid", + ["Mechanisch"] = "Mechanical", + ["Nicht spezifiziert"] = "Not Specified", + ["Totem"] = "Totem", + ["Untoter"] = "Undead", + } + + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "Schattengestalt", + ["Stealth"] = "Verstohlenheit", + ["Prowl"] = "Schleichen", + ["Revenge"] = "Rache", + ["Overpower"] = "Überwältigen", + ["Riposte"] = "Riposte", + ["Surprise Attack"] = "Überraschungsangriff", + ["Mongoose Bite"] = "Mungobiss", + ["Counterattack"] = "Gegenangriff", + ["Arcane Surge"] = "Arkane Woge", + } + + + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "Verbrauchsmaterial", + ["Reagent"] = "Reagens", + ["Projectile"] = "Projektil", + ["Trade Goods"] = "Handwerkswaren", + } +elseif CleveRoids.Locale == "frFR" then + CleveRoids.Localized.Shield = "Boucliers" + CleveRoids.Localized.Bow = "Arcs" + CleveRoids.Localized.Crossbow = "Arbalètes" + CleveRoids.Localized.Gun = "Armes à feu" + CleveRoids.Localized.Thrown = "Thrown" + CleveRoids.Localized.Wand = "Wands" + CleveRoids.Localized.Sword = "Swords" + CleveRoids.Localized.Staff = "Staves" + CleveRoids.Localized.Polearm = "Polearms" + CleveRoids.Localized.Mace = "Maces" + CleveRoids.Localized.FistWeapon = "Fist Weapons" + CleveRoids.Localized.Dagger = "Daggers" + CleveRoids.Localized.Axe = "Axes" + + CleveRoids.Localized.Attack = "Attack" + CleveRoids.Localized.AutoShot = "Auto Shot" + CleveRoids.Localized.Shoot = "Shoot" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + CleveRoids.Localized.CreatureTypes = { + ["Bête"] = "Beast", + ["Bestiole"] = "Critter", + ["Démon"] = "Demon", + ["Draconien"] = "Dragonkin", + ["Elémentaire"] = "Elemental", + ["Géant"] = "Giant", + ["Humanoïde"] = "Humanoid", + ["Machine"] = "Mechanical", + ["Non spécifié"] = "Not Specified", + ["Totem"] = "Totem", + ["Mort-vivant"] = "Undead", + } + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "Forme d'Ombre", + ["Stealth"] = "Camouflage", + ["Prowl"] = "Rôder", + ["Revenge"] = "Vengeance", + ["Overpower"] = "Fulgurance", + ["Riposte"] = "Riposte", + ["Surprise Attack"] = "Attaque surprise", + ["Mongoose Bite"] = "Morsure de la mangouste", + ["Counterattack"] = "Contre-attaque", + ["Arcane Surge"] = "Éruption d’arcanes", + } + + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "Consommable", + ["Reagent"] = "Reagent", + ["Projectile"] = "Projectile", + ["Trade Goods"] = "Artisanat", + } +elseif CleveRoids.Locale == "koKR" then + CleveRoids.Localized.Shield = "Shields" + CleveRoids.Localized.Bow = "Bows" + CleveRoids.Localized.Crossbow = "Crossbows" + CleveRoids.Localized.Gun = "Guns" + CleveRoids.Localized.Thrown = "Thrown" + CleveRoids.Localized.Wand = "Wands" + CleveRoids.Localized.Sword = "Swords" + CleveRoids.Localized.Staff = "Staves" + CleveRoids.Localized.Polearm = "Polearms" + CleveRoids.Localized.Mace = "Maces" + CleveRoids.Localized.FistWeapon = "Fist Weapons" + CleveRoids.Localized.Dagger = "Daggers" + CleveRoids.Localized.Axe = "Axes" + + CleveRoids.Localized.Attack = "Attack" + CleveRoids.Localized.AutoShot = "Auto Shot" + CleveRoids.Localized.Shoot = "Shoot" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + CleveRoids.Localized.CreatureTypes = { + ["야수"] = "Beast", + ["동물"] = "Critter", + ["악마"] = "Demon", + ["용족"] = "Dragonkin", + ["정령"] = "Elemental", + ["거인"] = "Giant", + ["인간형"] = "Humanoid", + ["기계"] = "Mechanical", + ["기타"] = "Not Specified", + ["토템"] = "Totem", + ["언데드"] = "Undead", + } + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "어둠의 형상", + ["Stealth"] = "은신", + ["Prowl"] = "숨기", + ["Revenge"] = "복수", + ["Overpower"] = "제압", + ["Riposte"] = "반격", + ["Surprise Attack"] = "기습", + ["Mongoose Bite"] = "살쾡이의 이빨", + ["Counterattack"] = "역습", + ["Arcane Surge"] = "비전 쇄도", + } + + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "소모품", + ["Reagent"] = "Reagent", + ["Projectile"] = "Projectile", + ["Trade Goods"] = "거래 용품", + } +elseif CleveRoids.Locale == "zhCN" then + CleveRoids.Localized.Shield = "Shields" + CleveRoids.Localized.Bow = "Bows" + CleveRoids.Localized.Crossbow = "Crossbows" + CleveRoids.Localized.Gun = "Guns" + CleveRoids.Localized.Thrown = "Thrown" + CleveRoids.Localized.Wand = "Wands" + CleveRoids.Localized.Sword = "Swords" + CleveRoids.Localized.Staff = "Staves" + CleveRoids.Localized.Polearm = "Polearms" + CleveRoids.Localized.Mace = "Maces" + CleveRoids.Localized.FistWeapon = "Fist Weapons" + CleveRoids.Localized.Dagger = "Daggers" + CleveRoids.Localized.Axe = "Axes" + + CleveRoids.Localized.Attack = "Attack" + CleveRoids.Localized.AutoShot = "Auto Shot" + CleveRoids.Localized.Shoot = "Shoot" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + CleveRoids.Localized.CreatureTypes = { + ["野兽"] = "Beast", + ["小动物"] = "Critter", + ["恶魔"] = "Demon", + ["龙类"] = "Dragonkin", + ["元素生物"] = "Elemental", + ["巨人"] = "Giant", + ["人型生物"] = "Humanoid", + ["机械"] = "Mechanical", + ["未指定"] = "Not Specified", + ["图腾"] = "Totem", + ["亡灵"] = "Undead", + } + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "暗影形态", + ["Stealth"] = "潜行", + ["Prowl"] = "潜行", + ["Revenge"] = "复仇", + ["Overpower"] = "压制", + ["Riposte"] = "还击", + ["Surprise Attack"] = "偷袭", + ["Mongoose Bite"] = "猫鼬撕咬", + ["Counterattack"] = "反击", + ["Arcane Surge"] = "奥术涌动", + } + + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "消耗品", + ["Reagent"] = "Reagent", + ["Projectile"] = "Projectile", + ["Trade Goods"] = "贸易品", + } +elseif CleveRoids.Locale == "zhTW" then + CleveRoids.Localized.Shield = "Shields" + CleveRoids.Localized.Bow = "Bows" + CleveRoids.Localized.Crossbow = "Crossbows" + CleveRoids.Localized.Gun = "Guns" + CleveRoids.Localized.Thrown = "Thrown" + CleveRoids.Localized.Wand = "Wands" + CleveRoids.Localized.Sword = "Swords" + CleveRoids.Localized.Staff = "Staves" + CleveRoids.Localized.Polearm = "Polearms" + CleveRoids.Localized.Mace = "Maces" + CleveRoids.Localized.FistWeapon = "Fist Weapons" + CleveRoids.Localized.Dagger = "Daggers" + CleveRoids.Localized.Axe = "Axes" + + CleveRoids.Localized.Attack = "Attack" + CleveRoids.Localized.AutoShot = "Auto Shot" + CleveRoids.Localized.Shoot = "Shoot" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + CleveRoids.Localized.CreatureTypes = { + ["野獸"] = "Beast", + ["小動物"] = "Critter", + ["惡魔"] = "Demon", + ["龍類"] = "Dragonkin", + ["元素生物"] = "Elemental", + ["巨人"] = "Giant", + ["人型生物"] = "Humanoid", + ["機械"] = "Mechanical", + ["不明"] = "Not Specified", + ["圖騰"] = "Totem", + ["不死族"] = "Undead", + } + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "暗影形態", + ["Stealth"] = "隱形", + ["Prowl"] = "徘徊", + ["Revenge"] = "復仇", + ["Overpower"] = "壓倒", + ["Riposte"] = "還擊", + ["Surprise Attack"] = "偷襲", + ["Mongoose Bite"] = "貓鼬咬傷", + ["Counterattack"] = "反擊", + ["Arcane Surge"] = "奧術湧動", + } + + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "消耗品", + ["Reagent"] = "Reagent", + ["Projectile"] = "Projectile", + ["Trade Goods"] = "貿易貨物", + } +elseif CleveRoids.Locale == "ruRU" then + CleveRoids.Localized.Shield = "Shields" + CleveRoids.Localized.Bow = "Bows" + CleveRoids.Localized.Crossbow = "Crossbows" + CleveRoids.Localized.Gun = "Guns" + CleveRoids.Localized.Thrown = "Thrown" + CleveRoids.Localized.Wand = "Wands" + CleveRoids.Localized.Sword = "Swords" + CleveRoids.Localized.Staff = "Staves" + CleveRoids.Localized.Polearm = "Polearms" + CleveRoids.Localized.Mace = "Maces" + CleveRoids.Localized.FistWeapon = "Fist Weapons" + CleveRoids.Localized.Dagger = "Daggers" + CleveRoids.Localized.Axe = "Axes" + + CleveRoids.Localized.Attack = "Attack" + CleveRoids.Localized.AutoShot = "Auto Shot" + CleveRoids.Localized.Shoot = "Shoot" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + CleveRoids.Localized.CreatureTypes = { + ["Животное"] = "Beast", + ["Существо"] = "Critter", + ["Демон"] = "Demon", + ["Дракон"] = "Dragonkin", + ["Элементаль"] = "Elemental", + ["Великан"] = "Giant", + ["Гуманоид"] = "Humanoid", + ["Механизм"] = "Mechanical", + ["Не указано"] = "Not Specified", + ["Тотем"] = "Totem", + ["Нежить"] = "Undead", + } + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "Облик Тени", + ["Stealth"] = "Незаметность", + ["Prowl"] = "Крадущийся зверь", + ["Revenge"] = "Реванш", + ["Overpower"] = "Превосходство", + ["Riposte"] = "Ответный удар", + ["Surprise Attack"] = "Внезапная атака", + ["Mongoose Bite"] = "Укус мангуста", + ["Counterattack"] = "Контратака", + ["Arcane Surge"] = "Чародейский выброс", + } + + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "Расходный материал", + ["Reagent"] = "Reagent", + ["Projectile"] = "Projectile", + ["Trade Goods"] = "Хозяйственные товары", + } +elseif CleveRoids.Locale == "esES" then + CleveRoids.Localized.Shield = "Shields" + CleveRoids.Localized.Bow = "Bows" + CleveRoids.Localized.Crossbow = "Crossbows" + CleveRoids.Localized.Gun = "Guns" + CleveRoids.Localized.Thrown = "Thrown" + CleveRoids.Localized.Wand = "Wands" + CleveRoids.Localized.Sword = "Swords" + CleveRoids.Localized.Staff = "Staves" + CleveRoids.Localized.Polearm = "Polearms" + CleveRoids.Localized.Mace = "Maces" + CleveRoids.Localized.FistWeapon = "Fist Weapons" + CleveRoids.Localized.Dagger = "Daggers" + CleveRoids.Localized.Axe = "Axes" + + CleveRoids.Localized.Attack = "Attack" + CleveRoids.Localized.AutoShot = "Auto Shot" + CleveRoids.Localized.Shoot = "Shoot" + CleveRoids.Localized.SpellRank = "%(Rank %d+%)" + + CleveRoids.Localized.CreatureTypes = { + ["Bestia"] = "Beast", + ["Alma"] = "Critter", + ["Demonio"] = "Demon", + ["Dragon"] = "Dragonkin", + ["Elemental"] = "Elemental", + ["Gigante"] = "Giant", + ["Humanoide"] = "Humanoid", + ["Mecánico"] = "Mechanical", + ["No especificado"] = "Not Specified", + ["Tótem"] = "Totem", + ["No-muerto"] = "Undead", + } + + CleveRoids.Localized.Spells = { + ["Shadowform"] = "Forma de las Sombras", + ["Stealth"] = "Sigilo", + ["Prowl"] = "Acechar", + ["Revenge"] = "Revancha", + ["Overpower"] = "Abrumar", + ["Riposte"] = "Estocada", + ["Surprise Attack"] = "Ataque sorpresa", + ["Mongoose Bite"] = "Mordedura de mangosta", + ["Counterattack"] = "Contraataque", + ["Arcane Surge"] = "Oleada Arcana", + } + + CleveRoids.Localized.ItemTypes = { + ["Consumable"] = "Consumible", + ["Reagent"] = "Reagent", + ["Projectile"] = "Projectile", + ["Trade Goods"] = "Objetos comerciables", + } +end + +_G["CleveRoids"] = CleveRoids \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c74223 --- /dev/null +++ b/README.md @@ -0,0 +1,465 @@ +# SuperCleveRoid Macros +This was originally an effort to bring the dynamic tooltip and cast sequence functionality of [CleverMacro](https://github.com/DanielAdolfsson/CleverMacro) into [Roid-Macros](https://github.com/MarcelineVQ/Roid-Macros). It has since expanded after some additional changes I wanted along with feedback from others. The majority of credit goes to the [original addon authors](#original-addons--authors). Still a work in progress. + +Both [SuperWoW](https://github.com/balakethelock/SuperWoW) and [Nampower](https://github.com/pepopo978/nampower) are REQUIRED. +Check slash command and all conditional lists for new usages! + +--- + +## Installation +### Manual +1. Download a zip from the green Code button or by clicking [here](https://github.com/jrc13245/SuperCleveRoidMacros/archive/refs/heads/main.zip) +2. Unzip the file and place the folder into your `Interface/Addons` folder. +3. Rename the `SuperCleveRoidMacros-main` folder to `SuperCleveRoidMacros` +4. Check that it is enabled in your addon list in-game. +5. Make sure you don't have other macro addons that may interfere. SuperMacro should be compatible. +6. SUPERWOW and NAMPOWER are REQUIRED! + + +* [SuperWoW](https://github.com/balakethelock/SuperWoW) dll mod is required +* [Nampower](https://github.com/pepopo978/nampower) dll mod is required + + +### Turtle WoW Launcher / GitAddonsManager +1. Open either application +2. Click the Add button +3. Paste the url `https://github.com/jrc13245/SuperCleveRoidMacros` +4. Download and keep up to date +### SuperCleveRoidMacros Settings +* /cleveroid - view settings +* /cleveroid realtime 0 or 1 - Force realtime updates rather than event based updates (Default: 0. 1 = on, increases CPU load.) +* /cleveroid refresh X - Set refresh rate. (1 to 10 updates per second. Default: 5) +--- + +## Known Issues +* By default, conditional checks to update icons are event based with realtime set to 0. This means icons using some conditionals that arent triggered by events are slow to change. example: @unitid mod etc. Enable realtime setting, but beware increase in cpu usage and lag. +* ALL macros must be given unique names, no blank names or muliple of the same name or using spell names. +* If any of your macros have syntax errors, it will affect all macros when it comes to errors. +* Spells with parenthesis ie: Faerie Fire (Feral) or Barkskin (Feral) MUST be written using ranks, becomming `Faerie Fire (Feral)(Rank X)` +* Minor: If you improperly create a macro, in some certain cases it can cause strange UI issues including not displaying any icons, other macros not working or lua errors. Fix the macro or remove it from your actionbar and it should go back to normal automatically. Depending on the cause, you may need to reload your UI (/rl). If you find one of these and can reproduce the issue, let me know. +* Minor: HealComm support. See [below](#healcomm-support) +* I have not tested all possible combinations of conditionals or ways to break things. Find me on Discord or open an issue if you find any bugs or have feedback. + +--- + +# What's Different + +## Commands and Conditionals +### ***Important! Spells, Items, conditionals, etc are case sensitive. Barring a bug, if there is an issue, it's almost always because something is typed incorrectly.*** +* New Alias for stopcasting 'unqueue' +* New update settings to swap between realtime or event based. Event based is the default and saves cpu and memory usage. +* New Conditional class/noclass. +* Conditional exists is now noable. +* Added alive/dead alias' nodead/noalive. +* Conditional combat can now take unitids. +* Renamed `attacking`/`noattacking` contitionals to `targeting`/`notargeting` for better clarity of what they do. +* All conditionals that should have an implied target of @target now do +* Conditionals can be separated by comma or space +* Conditional arguments with spaces that would need to be replaced with underscores (`_`) can instead be enclosed in quotes. +* Updated syntax for conditionals with stack/time checking + * You can use `>, <, =, ~=, >=, <=` as valid operators. Include a `#` sign to designate you're checking the stack's, not time + * Operators should be placed after the colon, either by itself (if applicable) or after the argument + `[power:>20]`, `[debuff:"Sunder Armor<#5"]`, `[buff:Thorns<30]` + * Omitting an operator or amount will just check that the buff/debuff exists + * When stack checking, omitting an operator assumes equals (=) + `[debuff:"Sunder Armor#3]` is the same as `[debuff:"Sunder Armor=#3]` + * Omitting the argument entirely will assume you want to use the action in the condition check + `[nobuff] Power Word: Fortitude` is the same as `[nobuff:"Power Word: Fortitude"] Power Word: Fortitude` +* Updated most conditionals (that make sense) to allow for multiple arguments. Use a / to separate them. +* Added `plain` [mod] and [nomod] to conditionals +* Added `alive` conditional +* Added `member` conditional +* Added `form` conditional (an alias of stance) +* Added Priest Shadowform to `stance`/`form` conditionals. Use `[stance:1]` or `[form:1]` +* Added Rogue Stealth to `stance`/`form` conditionals. Use `[stance:1]` or `[form:1]` +* Added `combo` conditional +* Added `known` conditional +* Added `resting` conditional +* Changed `cooldown` conditional to ignore the GCD (will show usable if the cooldown is exactly 1.5 seconds) -- It felt better but not 100% on keeping this, send feedback. +* Added `cdgcd` conditional that works the same as `cooldown` that will trigger for any length of cooldown +* Added `inrange` conditional ([Nampower](https://github.com/pepopo978/nampower) required) +* Added changes to make usable/mana/range updating show properly in pfUI. +* Added Bongos action bars compatibility support +* Added /runmacro command. Same as /cast {macroname} but always ignores icon/tooltip +* Added /retarget command. Clears your target if it doesn't exist, has 0 hp or if you can't attack it and then targets the nearest enemy +* Added /equipmh command. Equips the weapon into your mainhand slot. +* Added ? flag to prevent an action from affecting the icon/tooltips. It must be the first character. + ```lua + #showtooltip + /use ?Some Item + /cast [reactive] Overpower; Heroic Strike + ``` +* Updated ! flag as a shortcut easily make spammable spells. +* Added ~ flag to either cast or cancel the buff/aura if possible. (Toggles the spell on/off) +* Added /stopmacro command. +* Added conditional use to /startattack /stopattack /stopcasting. +* Added stat conditional. ex: '/cast [hp:<=20 stat:AP>=2000]Bloodthirst' +## Dynamic Icons and Tooltips +* The icon and tooltip for a macro will automatically update to the first true condition's action. Left to right, top to bottom. +* Consumables and certain other item types will now show a count on the action bar. +* Spells with reagent costs will now show a count of how many uses you have. +* Full `#showtooltip` support with dynamic icons and tooltips + * A macro must start with `#showtooltip` or `#showtooltip spell/item/itemid`. The icons and tooltips will update on your bars as the conditions are met. + + ```lua + #showtooltip + /cast [mod:alt] Frostbolt; [mod:ctrl] Fire Blast; [mod:shift] {MyMacro}; Blink + ``` + ```lua + #showtooltip + /cast [stance:1/3, nocooldown, notargeting:player] Mocking Blow + /cast [stance:2/3, nocooldown, notargeting:player] Taunt + /cast Shield Slam + ``` + + One line or separate lines are valid, sometimes one is better than the other + + ``` + #showtooltip + /cast [zone:Ahn'Qiraj] Yellow Qiraji Battle Tank;[nozone:Orgrimmar/Undercity/"Thunder Bluff" nomybuff:"Water Walking"] Red Goblin Shredder;Plainsrunning + ``` + Is the same as + ``` + #showtooltip + /cast [zone:Ahn'Qiraj] Yellow Qiraji Battle Tank + /cast [nozone:Orgrimmar/Undercity/"Thunder Bluff" nomybuff:"Water Walking"] Red Goblin Shredder + /cast Plainsrunning + ``` + + +* Updated `/castsequence` support. See [below](#cast-sequence) +* Added support for updating to the icon/tooltip of the {macro} named in a another macro + * Macro 1 calls Macro 2 and will update the icon when out of combat with Arcane Missiles from Macro2's `#showtooltip` + + ***Note: This will only use the icon/tooltip of either the referenced `#showtooltip ` or the icon you set for the macro.*** + + Macro 1: + ```lua + #showtooltip + /cast [nocombat] {Macro2}; Fireball + ``` + Macro 2 + ```lua + #showtooltip Arcane Missiles + /cast [mod] Conjure Food + /use 2288 + ``` +* Added support for itemid lookup instead of the item's name. Using this will show the icon/tooltip even if you don't have the item cached or in your inventory so you don't see ? icons. e.g. No Mage food/water on login + + ```lua + #showtooltip + /use [nomod] 5350 + /cast [mod] Conjure Water + ``` +*Added support of using equipped items by slot number + + ```lua + #showtooltip + /use 16 + ``` +--- + +# Usage +## Slash Commands +| Command | Conditionals Supported | Purpose | +|-----------------------| :-: |---------| +| /cleveroid | | show update settings. /cleveroid realtime 0/1 : /cleveroid refresh 1-10 | +| /target | | takes limited conditionals. will target by name first and then if conditionals are not valid will target your last target or clear target. help/harm check in a 40 yd cone in front of you. this is due to client limitations. available conditionals: (help,harm,alive,dead,exists,isplayer,isnpc,type,(no)inrange,(no(de))buff,(raw)hp,(raw)power,(no)casting) | +| /retarget | | Clears your target if it doesn't exist, has 0 hp or if you can't attack it and then targets the nearest enemy. | +| /startattack | * | Starts auto-attacking. | +| /stopattack | * | Stops auto-attacking. | +| /stopcasting | * | Stops casting. | +| /unqueue | * | Stops casting. | +| /petattack | * | Command pet to Attack. | +| /petfollow | * | Command pet to Follow. | +| /petwait | * | Command pet to Wait (Stay). | +| /petaggressive | * | Set pet mode to Aggressive. | +| /petdefensive | * | Set pet mode to Defensive. | +| /petpassive | * | Set pet mode to Passive. | +| /castsequence | * | Performs a cast sequence. See [below](#cast-sequence) for more infomation. | +| /equip | * | Equips an item by name or itemid. | +| /equipmh | * | Equips an item by name or itemid into your mainhand slot. | +| /equipoh | * | Equips an item by name or itemid into your offhand slot. | +| /unshift | * | Cancels your current shapeshift form. | +| /cancelaura, /unbuff | | Cancels a valid buff/aura. | +| /runmacro | | Runs a macro. Use /runmacro {macroname} | +| /use | * | Uses an item by name or id | +| /cast | * | Casts a spell by name | +| /stopmacro | * | prevent any lines under /stopmacro from being run unless conditionals are met | + +## Cast Sequence + ```lua + #showtooltip + /castsequence reset=3 Fireball, Frostbolt, Arcane Explosion, Fire Blast + ``` +* Dynamic icons and tooltips on each sequence with range/mana/usable checks +* Supports reset=#/mod/target/combat +* Supports conditionals for the entire sequence, not individual actions within the sequence. + ```lua + #showtooltip + /castsequence [group:party/raid] reset=3/target Sweaty Spell 1, Sweaty Spell 2, Sweaty Spell 3 + /castsequence reset=target/combat Casual Spell 1, Casual Spell 2, Casual Spell 3 + ``` + +## Conditionals +* Conditionals are basically if/then/else statements which are evaluated left to right, top to bottom. +* The first set of conditions that are all true will be the action that happens so the order you put things in is very important. +* Multiple conditionals can be used, separated with a space or comma. +* Spells and items with spaces in the name need to use underscores (`_`) instead of spaces or be enclosed in quotations. +* Each action can be written on one line or on separate. Both are effectively the same however WoW executes every line of a macro so it is technically better to use one line where possible. I usually prefer separate lines because it's easier to read and troubleshoot at a quick glance. You do you. + + + **startattack, stopattack, stopacasting:** + ``` + #showtooltip + /startattack [harm alive] + + #showtooltip + /stopattack [form:0/1/2/3] + + #showtooltip + /stopcasting [hp:<=20] + ``` + **Item Slots 1-19 supports /use only right now:** + ``` + #showtooltip + /use [combat,hp:<=20]13 + ``` + **Separate Lines:** + ``` + #showtooltip + /use [@mouseover alive help hp:<70 nodebuff:"Recently Bandaged"] Runecloth Bandage + /use [@target, alive, help, hp:<70, nodebuff:Recently_Bandaged] Runecloth Bandage + /use [@player] Runecloth Bandage + ``` + **One Line:** + ``` + #showtooltip + /use [@mouseover alive help hp:<70 nodebuff:"Recently Bandaged"] Runecloth Bandage; [@target, alive, help, hp:<70, nodebuff:Recently_Bandaged] Runecloth Bandage; [@player] Runecloth Bandage + ``` + **Note: Classic/Retail macros allow for cascading conditional blocks like below. This is NOT supported.** + ``` + #showtooltip + /use [@mouseover alive help hp:<70][@target alive help hp:<70][@player][] Runecloth Bandage + ``` + +* If a conditional on the table is marked as **Multi** you can provide multiple values in the same condition. *Only one needs to be true.* + `[targeting:party1/party2/party3/party4]` -- targeting one of your party members + `[zone:Stormwind/Ironforge]` -- in Stormwind OR Ironforge +* If a conditional is marked as **Noable** you can prefix the condition with "no" to test the opposite condition. *All need to be true* + `[notargeting:player]` -- not targeting the player + `[nozone:Stormwind/Ironforge]` -- not in Stormwind AND not in Ironforge +* You can specifiy a target unit for the macro by adding in a valid @unitid + `/cast [@party1 combat] Intervene` -- casts Intervene on party member 1 if you (player) are in combat +* @focus / focus can be used as an @unitid / conditional unitid however Vanilla does not support focus targets, a compatible addon is required to make this function. +* If the conditional allows for a numerical comparison, the format is `condition:>X` or `condition:"Some Value">X`. If you want to compare stacks/rank (where applicable) and not remaining time, use `>#X`. + * **Only the player's buffs/debufs can be checked for remaining time** + * Valid operators are =, ~=, >, >=, <, <= +* You can omit the value of a conditional if you want to check the same spell/item that you are using in the action. + `[debuff:"Sunder Armor"<#5] Sunder Armor` == `[debuff:<#5] Sunder Armor` + `[nobuff:"Mark of the Wild"] Mark of the Wild` == `[nobuff] Mark of the Wild` +* [SuperWoW](https://github.com/balakethelock/SuperWoW) dll mod is required for some conditionals +* [Nampower](https://github.com/pepopo978/nampower) dll mod required for some conditionals + +### Special Characters +| Character | Syntax Examples | Description | +| :-: |---------------|-------------| +| ! | !Attack
!Cat Form
!Spell Name | Prefix on spell name.
Will only use the spell if it's not already active.
Can also be used with other spells as shorthand for `nomybuff` | +| ? | /use ?[equipped:Swords]
/cast ?Presence of Mind | Prevents the icon and tooltip from showing.
Must be the first character. | +| ~ | ~Slow Fall | Prefix on spell name. Acts as a toggle ability.
Will cast or cancel the buff/aura if possible. | + +### Key Modifiers +| Conditional | Syntax Examples | Multi | Noable | Tests For | +|----------------|---------------| :-: | :-: |-----------| +| mod | [mod]
[mod:ctrl/alt/shift] | * | * | If any mod key is pressed.
If one of the listed modifier keys are pressed. | + + +### Player Only +| Conditional | Syntax Examples | Multi | Noable | Tests For | +|----------------|---------------| :-: | :-: |-----------| +| cdgcd | [cdgcd]
[cdgcd:"Name"]
[cdgcd:"Name">X] | * | * | If the Spell or Item is on cooldown and optionally if the amount of time left is >= or <= than X seconds. **GCD NOT IGNORED** | +| channeled | [channeled] | | | If the player is currently channeling a spell. | +| combo | [combo:>#3]
[combo:#2]
[combo:<#5] | | * | If the player has the specified number of combo points. | +| cooldown | [cooldown]
[cooldown:"Name"]
[cooldown:"Name"= or <= than X seconds. **GCD (if exatly 1.5 sec) IGNORED** | +| equipped | [equipped:"Name"]
[equipped:Shields]
[equipped:Daggers2] | * | * | If the player has an item name/id or item type equipped. See [below](#weapon-types) for a list of valid Weapon Types. | +| form | [form:0/1/2/3/4/5] | * | | Alias of `stance` | +| group | [group]
[group:party/raid] | * | * | If the player is in any group or specific group type. | +| known | [known]
[known:"Name"]
[known:"Name">#2] | * | * | If the player knows a spell or talent. Can optionally check the rank. | +| mybuff | [mybuff]
[mybuff:"Name"]
[mybuff:"Name">#X]
[mybuff:Optionally compared to X number of stacks.
Optionally compared to X time remaining. | +| mydebuff | [mydebuff]
[mydebuff:"Name"]
[mydebuff:"Name">#X]
[mydebuff:Optionally compared to X number of stacks.
Optionally compared to X time remaining. | +| myhp | [myhp:<=X]
[myhp:>=X/<=Y] | * | | The player's health **PERCENT** compared to X. | +| myhplost | [myhplost:>=X]
[myhplost:>=X/<=Y] | * | | The player's lost health compared to X. | +| mypower | [mypower:>=X]
[mypower:>=X/<=Y] | * | | The player's power (mana/rage/energy) **PERCENT** compared to X. | +| mypowerlost | [mypowerlost:>=X]
[mypowerlost:>=X/<=Y] | * | | The player's lost power (mana/rage/energy) compared to X. | +| myrawhp | [myrawhp:>=X]
[myrawhp:>=X/<=Y] | * | | The player's health compared to X. | +| myrawpower | [myrawpower:>=X]
[myrawpower:>=X/<=Y] | * | | The player's power (mana/rage/energy) compared to X. | +| reactive | [reactive]
[reactive:Overpower] | * | | If the player has the reactive ability (Revenge, Overpower, Riposte, etc.) available to use.

**NOTE: Currently requires the reactive ability to be somewhere on your actionbars in addition to any macros you're using it in. A planned future update will remove this requirement if using [Nampower](https://github.com/pepopo978/nampower).** | +| resting | [resting] | | * | If the player is resting (in an inn/capital city/etc.) | +| stance | [stance:0/1/2/3/4/5] | * | | If the player is in stance #.
Supports Shadowform and Stealth as stance 1.| +| stealth | [stealth] | | * | If the player is in Stealth or Prowl. | +| zone | [zone:"Zone"]
[zone:"Zone"/"Another Zone"] | * | * | If the player is in one or more zones of the given name. | +| checkchanneled | [checkchanneled] | * | | Prevents a spell from being cast if you are already channeling it. | +| stat | [stat:stat>=x] | | | Check if one of the players statistics is greater or less than a specific number. Available Stats: str/strength, agi/agility, stam/stamina, int/intellect, spi/spirit, ap/attackpower, rap/rangedattackpower, healing/healingpower, arcane_power, fire_power, frost_power, nature_power, shadow_power, armor, defense, arcane_res, fire_res, frost_res, nature_res, shadow_res. | + +### Unit Based +### The default @unitid is usually @target if you don't specify one +### The only conditionals that take conditional:unitid are combat/nocombat and targeting/notargeting +| Conditional | Syntax | Multi | Noable | Tests For | +|----------------|---------------| :-: | :-: |-----------| +| alive | [alive] | | * | If the @unitid is NOT dead or a ghost. | +| buff | [buff]
[buff:"Name"]
[buff:"Name">#X]
[buff:"Name"= or <= than X number of stacks. | +| casting | [casting]
[casting:"Spell Name"] | * | * | If the @unitid is casting any or one or more specific spells. | +| combat | [combat]
[combat:target] | | * | If the unitid (default is player) is in combat. | +| dead | [dead] | | * | If the @unitid is dead or a ghost. | +| debuff | [debuff]
[debuff:"Name"]
[debuff:"Name">#X]
[debuff:= or <= than X number of stacks. | +| harm | [harm] | | | If the @unitid is an enemy. | +| help | [help] | | | If the @unitid is friendly. | +| hp | [hp:>=X]
[hp:>=X/<=Y] | | | The @unitid health **PERCENT** compared to X. | +| hplost | [hplost:>=X]
[hplost:>=X/<=Y] | | | The @unitid health lost compared to X. | +| inrange | [inrange]
[inrange:"Name"] | * | * | If the specified @unitid is in range of the spell. | +| isnpc | [isnpc] | * | | If the @unitid is an npc.
See this [article](https://wowpedia.fandom.com/wiki/UnitId) for a list of unitids.
Not all units are valid in vanilla. | +| isplayer | [isplayer] | * | | If the @unitid is a player.
See this [article](https://wowpedia.fandom.com/wiki/UnitId) for a list of unitids.
Not all units are valid in vanilla. | +| member | [member] | | * | If the @unitid is in your party OR raid. | +| party | [party] | | * | If the @unitid is in your party. | +| power | [power:>=X]
[power:>=X/<=Y] | | | The @unitid power (mana/rage/energy) **PERCENT** compared to X. | +| powerlost | [powerlost:>=X]
[powerlost:>=X/<=Y] | | | The @unitid power (mana/rage/energy) lost compared to X. | +| raid | [raid] | | * | If the @unitid is in your raid. | +| rawphp | [rawhp:>=X]
[rawhp:>=X/<=Y] | | | The @unitid health compared to X. | +| rawpower | [rawpower:>=X]
[rawpower:>=X/<=Y] | | | The @unitid power (mana/rage/energy) compared to X. | +| type | [type:"Creature Type"] | * | * | If the @unitid is the specified creature type. See [below](#creature-types) for a list of valid Creature Types. | +| targeting | [targeting:unitid] | * | * | If the @unitid is targeting the specified unitid.
See this [article](https://wowpedia.fandom.com/wiki/UnitId) for a list of unitids.
Not all units are valid in vanilla. | +| exists | [exists] | | * | If the @unitid exists. | +| @unitid | [@mouseover] | | | The @unitid is a valid target. | +| class | [class:classname1/classname2]
[class:Warrior/Priest] | * | * | The target is a player of the specified class/classes. | + +### Unitids +| Name (N=party/raid slot number) | +|------| +| player | +| target | +| pet | +| mouseover | +| partyN | +| partypetN | +| raidN | +| raidpetN | +| targettarget | +| playertarget | +| pettarget | +| partyNtarget | +| raidNtarget | +| targettargettarget | +| focus (requires pfui or another addon) | + +### Classes +| Class | +|------| +| Warrior | +| Paladin | +| Hunter | +| Shaman | +| Druid | +| Rogue | +| Mage | +| Warlock | +| Priest | + +### Weapon Types +| Name | Slot | +|------|------| +| Axes, Axes2 | Main Hand, Off Hand | +| Bows | Ranged | +| Daggers, Daggers2 | Main Hand, Off Hand | +| Crossbows | Ranged | +| Fists, Fists2 | Main Hand, Off Hand | +| Guns | Ranged | +| Maces, Maces2 | Main Hand, Off Hand | +| Polearms | Main Hand | +| Shields | Off Hand | +| Staves | Main Hand | +| Swords, Swords2 | Main Hand, Off Hand | +| Thrown | Ranged | +| Wands | Ranged | + + +### Creature Types +| Name | +|------| +| Boss | +| Worldboss | +| Beast | +| Dragonkin | +| Demon | +| Elemental | +| Giant | +| Undead | +| Humanoid | +| Critter | +| Mechanical | +| Not specified | +| Totem | +| Non-combat Pet | +| Gas Cloud | + +### Reactive Spells (twow) +| Name | +|---------------| +| Revenge | +| Overpower | +| Riposte | +| Mongoose Bite | +| Counterattack | +| Arcane Surge | + +### UnitFrames +* agUnitFrames +* Blizzard's UnitFrames +* CT_RaidAssist +* CT_UnitFrames +* DiscordUnitFrames +* FocusFrame +* Grid +* LunaUnitFrames +* NotGrid +* PerfectRaid +* pfUI +* sRaidFrames +* UnitFramesImproved_Vanilla +* XPerl + +### Action Bars +* Blizzard's Action Bars +* Bongos +* Discord Action Bars +* pfUI + + +### Supported Addons +* ClassicFocus / FocusFrame +* SuperMacro +* ShaguTweaks + + +### HealComm Support +This addon uses [SuperWoW](https://github.com/balakethelock/SuperWoW)'s CastSpellByName which is not compatible with the standard HealComm-1.0 library. MarcelineVQ has an updated version of [LunaUnitFrames](https://github.com/MarcelineVQ/LunaUnitFrames) where they added [SuperWoW](https://github.com/balakethelock/SuperWoW) support to the HealComm-1.0 library. If you use [SuperWoW](https://github.com/balakethelock/SuperWoW) and want proper HealComm support with your macros, you need to do one or both of the following: + +* Contact the author of whichever addon and ask that they update their HealComm to support [SuperWoW](https://github.com/balakethelock/SuperWoW) and link them to MarcelineVQ's updated [LunaUnitFrames](https://github.com/MarcelineVQ/LunaUnitFrames). + +* Manually update any addon that is using the `HealComm-1.0` library by deleting the `HealComm-1.0` folder and replacing it with the `libs/HealComm-1.0` folder from MarcelineVQ's version of LunaUnitFrames. + +Example for the standalone HealComm addon: +1. Exit WoW completely. +2. Download a copy of the updated [LunaUnitFrames](https://github.com/MarcelineVQ/LunaUnitFrames). +3. Delete the `/Interface/AddOns/HealComm/libs/HealComm-1.0` folder. +4. From the [LunaUnitFrames](https://github.com/MarcelineVQ/LunaUnitFrames) download, copy the `/libs/HealComm-1.0` folder into your `/Interface/AddOns/HealComm/libs/` folder. + + +---- +### Original Addons & Authors +* [Roid-Macros by DennisWG (DWG)](https://github.com/DennisWG/Roid-Macros) +* [CleverMacro by DanielAdolfsson (_brain)](https://github.com/DanielAdolfsson/CleverMacro) +---- +License +---- + +MIT diff --git a/Utility.lua b/Utility.lua new file mode 100644 index 0000000..f413ba5 --- /dev/null +++ b/Utility.lua @@ -0,0 +1,265 @@ +--[[ + Author: Dennis Werner Garske (DWG) / brian / Mewtiny + License: MIT License +]] +local _G = _G or getfenv(0) +local CleveRoids = _G.CleveRoids or {} -- redundant since we're loading first but peace of mind if another file is added top of chain + + +function CleveRoids.Seq(_, i) + return (i or 0) + 1 +end + +-- Trims any leading or trailing white space characters from the given string +-- str: The string to trim +-- returns: The trimmed string +function CleveRoids.Trim(str) + if not str then + return nil + end + return string.gsub(str,"^%s*(.-)%s*$", "%1") +end + +do + local _G = _G or getfenv(0) + local CleveRoids = _G.CleveRoids or {} + _G.CleveRoids = CleveRoids + + CleveRoids.__mo = CleveRoids.__mo or { sources = {}, current = nil } + + local PRIORITY = { + pfui = 3, -- unitframe hovers should win + blizz = 3, -- same level as pfui + tooltip = 1, -- lowest; don’t stomp frames + } + + local function getBest() + local bestSource, bestUnit, bestP = nil, nil, -1 + for source, unit in CleveRoids.__mo.sources do + if unit and unit ~= "" then + local p = PRIORITY[source] or 0 + if p > bestP then + bestP, bestSource, bestUnit = p, source, unit + end + end + end + return bestSource, bestUnit + end + + local function apply(unit) + -- SuperWoW path if available; otherwise internal fallback + if CleveRoids.hasSuperwow and _G.SetMouseoverUnit then + _G.SetMouseoverUnit(unit) -- nil clears + else + CleveRoids.mouseoverUnit = unit + end + if CleveRoids.QueueActionUpdate then CleveRoids.QueueActionUpdate() end + end + + function CleveRoids.SetMouseoverFrom(source, unit) + -- set/refresh a source’s unit + if not source then return end + CleveRoids.__mo.sources[source] = unit + local _, bestUnit = getBest() + if bestUnit ~= CleveRoids.__mo.current then + CleveRoids.__mo.current = bestUnit + apply(bestUnit) + end + end + + function CleveRoids.ClearMouseoverFrom(source, unitIfMatch) + if not source then return end + if unitIfMatch and CleveRoids.__mo.sources[source] ~= unitIfMatch then + return -- don’t clear if it was replaced meanwhile + end + CleveRoids.__mo.sources[source] = nil + local _, bestUnit = getBest() + if bestUnit ~= CleveRoids.__mo.current then + CleveRoids.__mo.current = bestUnit + apply(bestUnit) + end + end +end + +-- TODO: Get rid of one Split function. CleveRoids.splitString is ~10% slower +function CleveRoids.Split(s, p, trim) + local r, o = {}, 1 + + if not p or p == "" then + if trim then + s = CleveRoids.Trim(s) + end + for i = 1, string.len(s) do + table.insert(r, string.sub(s, i, 1)) + end + return r + end + + repeat + local b, e = string.find(s, p, o) + if b == nil then + local sub = string.sub(s, o) + table.insert(r, trim and CleveRoids.Trim(sub) or sub) + return r + end + if b > 1 then + local sub = string.sub(s, o, b - 1) + table.insert(r, trim and CleveRoids.Trim(sub) or sub) + else + table.insert(r, "") + end + o = e + 1 + until false +end + +-- Splits the given string into a list of sub-strings +-- str: The string to split +-- seperatorPattern: The seperator between sub-string. May contain patterns +-- returns: A list of sub-strings +function CleveRoids.splitString(str, seperatorPattern) + local tbl = {} + if not str then + return tbl + end + local pattern = "(.-)" .. seperatorPattern + local lastEnd = 1 + local s, e, cap = string.find(str, pattern, 1) + + while s do + if s ~= 1 or cap ~= "" then + table.insert(tbl,cap) + end + lastEnd = e + 1 + s, e, cap = string.find(str, pattern, lastEnd) + end + + if lastEnd <= string.len(str) then + cap = string.sub(str, lastEnd) + table.insert(tbl, cap) + end + + return tbl +end + +function CleveRoids.splitStringIgnoringQuotes(str, separator) + local result = {} + local temp = "" + local insideQuotes = false + local separators = {} + + if type(separator) == "table" then + for _, s in separator do + separators[s] = s + end + else + separators[separator or ";"] = separator or ";" + end + + for i = 1, string.len(str) do + local char = string.sub(str, i, i) + + if char == "\"" then + insideQuotes = not insideQuotes + temp = temp .. char + elseif char == separators[char] and not insideQuotes then + temp = CleveRoids.Trim(temp) + if temp ~= "" then table.insert(result, temp) end + temp = "" + else + temp = temp .. char + end + end + + -- Add the last segment if it exists + if temp ~= "" then + temp = CleveRoids.Trim(temp) + table.insert(result, temp) + end + + -- if nothing was found, return the empty string + return (next(result) and result or {""}) +end + +-- Prints all the given arguments into WoW's default chat frame +function CleveRoids.Print(...) + local c = "|cFF4477FFCleveR|r|cFFFFFFFFoid :: |r" + local out = "" + + for i=1, arg.n, 1 do + out = out..tostring(arg[i]).." " + end + if WowLuaFrameOutput then + WowLuaFrameOutput:AddMessage(out) + else + if not DEFAULT_CHAT_FRAME:IsVisible() then + FCF_SelectDockFrame(DEFAULT_CHAT_FRAME) + end + DEFAULT_CHAT_FRAME:AddMessage(c..out) + end +end + +function CleveRoids.PrintI(msg, depth) + depth = depth or 0 + msg = string.rep(" ", depth) .. tostring(msg) + CleveRoids.Print(msg) +end + +function CleveRoids.PrintT(t, depth) + depth = depth or 0 + local cs = "|cffc8c864" + local ce = "|r" + + if t == nil or type(t) ~= "table" then + CleveRoids.PrintI(t, depth) + else + for k, v in pairs(t) do + if type(v) == "table" then + CleveRoids.PrintI(cs..tostring(k)..":"..ce.." ", depth) + CleveRoids.PrintT(v, depth + 1) + else + CleveRoids.PrintI(cs..tostring(k)..ce..": "..tostring(v), depth) + end + end + end +end + +CleveRoids.kmods = { + ctrl = IsControlKeyDown, + alt = IsAltKeyDown, + shift = IsShiftKeyDown, + mod = function() return (IsControlKeyDown() or IsAltKeyDown() or IsShiftKeyDown()) end, + nomod = function() return (not IsControlKeyDown() and not IsAltKeyDown() and not IsShiftKeyDown()) end, +} + +CleveRoids.operators = { + ["<"] = "lt", + ["lt"] = "<", + [">"] = "gt", + ["gt"] = ">", + ["="] = "eq", + ["eq"] = "=", + ["<="] = "lte", + ["lte"] = "<=", + [">="] = "gte", + ["gte"] = ">=", + ["~="] = "ne", + ["ne"] = "~=", +} + +CleveRoids.comparators = { + lt = function(a, b) return (a < b) end, + gt = function(a, b) return (a > b) end, + eq = function(a, b) return (a == b) end, + lte = function(a, b) return (a <= b) end, + gte = function(a, b) return (a >= b) end, + ne = function(a, b) return (a ~= b) end, +} +CleveRoids.comparators["<"] = CleveRoids.comparators.lt +CleveRoids.comparators[">"] = CleveRoids.comparators.gt +CleveRoids.comparators["="] = CleveRoids.comparators.eq +CleveRoids.comparators["<="] = CleveRoids.comparators.lte +CleveRoids.comparators[">="] = CleveRoids.comparators.gte +CleveRoids.comparators["~="] = CleveRoids.comparators.ne + + +_G["CleveRoids"] = CleveRoids