diff --git a/Conditionals.lua b/Conditionals.lua index 6d2aeab..81913d8 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -2198,6 +2198,110 @@ function CleveRoids.GetCountModeArgs(conditionalValue) return nil end +-- ============================================================================ +-- PFUI TANK INTEGRATION +-- ============================================================================ +-- Integrates with pfUI's tank systems for targeting loose mobs +-- Checks BOTH: +-- 1. pfUI.uf.raid.tankrole (raid frame right-click toggle) +-- 2. pfUI_config.nameplates.combatofftanks (nameplate off-tank names setting) + +-- Cache for parsed nameplate off-tank names (lowercase) +local pfUIOfftankCache = nil +local pfUIOfftankCacheTime = 0 + +--- Parse pfUI nameplate off-tank names setting into a lookup table +--- @return table Lowercase name -> true lookup +local function GetPfUIOfftanks() + -- Cache for 5 seconds to avoid repeated string parsing + local now = GetTime() + if pfUIOfftankCache and (now - pfUIOfftankCacheTime) < 5 then + return pfUIOfftankCache + end + + pfUIOfftankCache = {} + pfUIOfftankCacheTime = now + + -- Check pfUI_config.nameplates.combatofftanks (# separated list) + if type(pfUI_config) == "table" and + type(pfUI_config.nameplates) == "table" and + type(pfUI_config.nameplates.combatofftanks) == "string" then + local list = pfUI_config.nameplates.combatofftanks + -- pfUI uses # as separator: "#Name1#Name2#Name3" + for name in string.gfind(list, "[^#]+") do + name = string.gsub(name, "^%s*(.-)%s*$", "%1") -- trim whitespace + if name ~= "" then + pfUIOfftankCache[string.lower(name)] = true + end + end + end + + return pfUIOfftankCache +end + +--- Check if pfUI tank data is available (either source) +--- @return boolean True if any pfUI tank system is accessible +function CleveRoids.HasPfUITanks() + -- Check raid frame tankrole + local hasRaidTanks = type(pfUI) == "table" and + type(pfUI.uf) == "table" and + type(pfUI.uf.raid) == "table" and + type(pfUI.uf.raid.tankrole) == "table" + + -- Check nameplate off-tank config + local hasNameplateTanks = type(pfUI_config) == "table" and + type(pfUI_config.nameplates) == "table" and + type(pfUI_config.nameplates.combatofftanks) == "string" and + pfUI_config.nameplates.combatofftanks ~= "" + + return hasRaidTanks or hasNameplateTanks +end + +--- Check if a player is marked as a tank in pfUI (either source) +--- @param name string - Player name to check +--- @return boolean True if player is marked as tank +function CleveRoids.IsPlayerTank(name) + if not name then return false end + + -- Check raid frame tankrole (exact case) + if type(pfUI) == "table" and + type(pfUI.uf) == "table" and + type(pfUI.uf.raid) == "table" and + type(pfUI.uf.raid.tankrole) == "table" and + pfUI.uf.raid.tankrole[name] then + return true + end + + -- Check nameplate off-tank names (lowercase comparison) + local offtanks = GetPfUIOfftanks() + if offtanks[string.lower(name)] then + return true + end + + return false +end + +--- Check if a unit is targeting any player marked as tank +--- @param unit string - Unit token to check (e.g., "target") +--- @return boolean True if unit's target is a tank +function CleveRoids.IsTargetingAnyTank(unit) + -- Default to "target" if unit is nil + unit = unit or "target" + + -- Get the unit's target + local targetOfUnit = unit .. "target" + if not UnitExists(targetOfUnit) then + return false + end + + local targetName = UnitName(targetOfUnit) + if not targetName then + return false + end + + return CleveRoids.IsPlayerTank(targetName) +end + -- ============================================================================ -- CURSIVE ADDON INTEGRATION -- ============================================================================ @@ -5098,15 +5202,41 @@ CleveRoids.Keywords = { return true end, + -- [targeting:unit] - Target is targeting specified unit + -- [targeting:tank] - Target is targeting ANY player marked as tank in pfUI targeting = function(conditionals) - return Or(conditionals.targeting, function (unit) - return (UnitIsUnit("targettarget", unit) == 1) + local target = conditionals.target or "target" + + -- Handle single "tank" keyword directly (most common case) + local val = conditionals.targeting + if val == "tank" or (type(val) == "table" and val[1] == "tank" and not val[2]) then + return CleveRoids.IsTargetingAnyTank(target) + end + + return Or(val, function (unit) + if unit == "tank" then + return CleveRoids.IsTargetingAnyTank(target) + end + return (UnitIsUnit(target .. "target", unit) == 1) end) end, + -- [notargeting:unit] - Target is NOT targeting specified unit + -- [notargeting:tank] - Target is NOT targeting ANY tank (loose mob!) notargeting = function(conditionals) - return NegatedMulti(conditionals.notargeting, function (unit) - return UnitIsUnit("targettarget", unit) ~= 1 + local target = conditionals.target or "target" + + -- Handle single "tank" keyword directly (most common case) + local val = conditionals.notargeting + if val == "tank" or (type(val) == "table" and val[1] == "tank" and not val[2]) then + return not CleveRoids.IsTargetingAnyTank(target) + end + + return NegatedMulti(val, function (unit) + if unit == "tank" then + return not CleveRoids.IsTargetingAnyTank(target) + end + return UnitIsUnit(target .. "target", unit) ~= 1 end, conditionals, "notargeting") end, @@ -5128,6 +5258,36 @@ CleveRoids.Keywords = { return UnitIsPlayer(conditionals.target) end, + -- [istank] - Target unit is marked as tank in pfUI + -- [istank:unit] - Specified unit is marked as tank + istank = function(conditionals) + if conditionals.istank and type(conditionals.istank) == "table" then + -- Check specific unit: [istank:focus] + return Or(conditionals.istank, function(unit) + local name = UnitName(unit) + return CleveRoids.IsPlayerTank(name) + end) + end + -- Check target: [istank] + local target = conditionals.target or "target" + local name = UnitName(target) + return CleveRoids.IsPlayerTank(name) + end, + + -- [noistank] - Target unit is NOT marked as tank in pfUI + noistank = function(conditionals) + if conditionals.noistank and type(conditionals.noistank) == "table" then + -- Check specific unit: [noistank:focus] + return NegatedMulti(conditionals.noistank, function(unit) + return not CleveRoids.IsPlayerTank(UnitName(unit)) + end, conditionals, "noistank") + end + -- Check target: [noistank] + local target = conditionals.target or "target" + local name = UnitName(target) + return not CleveRoids.IsPlayerTank(name) + end, + -- [inrange] or [inrange:Spell] - Target is in range of spell -- [inrange:Spell>N] - More than N enemies are in range of spell (count mode) inrange = function(conditionals) diff --git a/Core.lua b/Core.lua index 4c326f9..6a96136 100644 --- a/Core.lua +++ b/Core.lua @@ -5312,6 +5312,8 @@ SlashCmdList["CLEVEROID"] = function(msg) DEFAULT_CHAT_FRAME:AddMessage('/cleveroid testtalent - Test talent modifier for a spell') DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Debuff Tracking Debug:|r") DEFAULT_CHAT_FRAME:AddMessage('/cleveroid debuffdebug [spell] - Debug debuff tracking on target') + DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00pfUI Tank Integration:|r") + DEFAULT_CHAT_FRAME:AddMessage('/cleveroid tankdebug - Debug tank targeting conditionals') return end @@ -5883,6 +5885,94 @@ SlashCmdList["CLEVEROID"] = function(msg) return end + -- tankdebug (debug pfUI tank integration) + if cmd == "tankdebug" then + CleveRoids.Print("=== Tank Debug ===") + + -- Check pfUI availability + if not CleveRoids.HasPfUITanks() then + CleveRoids.Print("|cffff0000pfUI tank system not available!|r") + CleveRoids.Print(" pfUI loaded: " .. tostring(type(pfUI) == "table")) + CleveRoids.Print(" pfUI_config: " .. tostring(type(pfUI_config) == "table")) + return + end + + CleveRoids.Print("|cff00ff00pfUI tank system available|r") + + -- List raid frame tanks + CleveRoids.Print(" ") + CleveRoids.Print("|cffffaa00Raid Frame Tanks (right-click toggle):|r") + local raidTankCount = 0 + if type(pfUI) == "table" and type(pfUI.uf) == "table" and + type(pfUI.uf.raid) == "table" and type(pfUI.uf.raid.tankrole) == "table" then + for name, isTank in pairs(pfUI.uf.raid.tankrole) do + if isTank then + CleveRoids.Print(" - " .. name) + raidTankCount = raidTankCount + 1 + end + end + end + if raidTankCount == 0 then + CleveRoids.Print(" (none)") + end + + -- List nameplate off-tank names + CleveRoids.Print(" ") + CleveRoids.Print("|cffffaa00Nameplate Off-Tank Names (config setting):|r") + local npTankCount = 0 + if type(pfUI_config) == "table" and type(pfUI_config.nameplates) == "table" then + local list = pfUI_config.nameplates.combatofftanks or "" + if list ~= "" then + CleveRoids.Print(" Raw setting: \"" .. list .. "\"") + -- pfUI uses # as separator + for name in string.gfind(list, "[^#]+") do + name = string.gsub(name, "^%s*(.-)%s*$", "%1") -- trim + if name ~= "" then + CleveRoids.Print(" - " .. name .. " (lowercase: " .. string.lower(name) .. ")") + npTankCount = npTankCount + 1 + end + end + end + end + if npTankCount == 0 then + CleveRoids.Print(" (none)") + end + + -- Check current target + CleveRoids.Print(" ") + CleveRoids.Print("|cffffaa00Current Target Check:|r") + if not UnitExists("target") then + CleveRoids.Print(" No target selected") + else + local targetName = UnitName("target") or "Unknown" + CleveRoids.Print(" Your target: " .. targetName) + + if UnitExists("targettarget") then + local ttName = UnitName("targettarget") or "Unknown" + CleveRoids.Print(" Target's target: " .. ttName) + local isTank = CleveRoids.IsPlayerTank(ttName) + if isTank then + CleveRoids.Print(" |cff00ff00" .. ttName .. " IS marked as tank|r") + else + CleveRoids.Print(" |cffff8800" .. ttName .. " is NOT marked as tank|r") + CleveRoids.Print(" (lowercase check: " .. string.lower(ttName) .. ")") + end + else + CleveRoids.Print(" Target's target: (none)") + end + + -- Test the conditional functions + CleveRoids.Print(" ") + CleveRoids.Print("|cffffaa00Conditional Results:|r") + local isTargetingTank = CleveRoids.IsTargetingAnyTank("target") + CleveRoids.Print(" IsTargetingAnyTank('target'): " .. tostring(isTargetingTank)) + CleveRoids.Print(" [targeting:tank] would be: " .. tostring(isTargetingTank)) + CleveRoids.Print(" [notargeting:tank] would be: " .. tostring(not isTargetingTank)) + end + + return + end + -- Unknown command fallback CleveRoids.Print("Usage:") DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings") diff --git a/README.md b/README.md index d50041b..d37dc04 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,8 @@ All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff | `type` | `[type:Undead/Beast]` | Creature type | | `isplayer` | `[isplayer]` | Target is a player | | `isnpc` | `[isnpc]` | Target is an NPC | -| `targeting` | `[targeting:player]` | Unit targeting you | +| `targeting` | `[targeting:player]` `[targeting:tank]` | Unit targeting you / any tank | +| `istank` | `[istank]` `[@focus,istank]` | Unit is marked as tank (pfUI) | | `casting` | `[casting:"Spell"]` | Unit casting spell | | `party` | `[party]` `[party:focus]` | Unit in your party | | `raid` | `[raid]` `[raid:mouseover]` | Unit in your raid | @@ -211,6 +212,50 @@ All conditionals support negation with `no` prefix (e.g., `[nocombat]`, `[nobuff | `tte` | TimeToKill | `[tte:<5]` | Time to execute (20% HP) | | `cursive` | Cursive | `[cursive:Rake>3]` | GUID debuff tracking | | `moving` | MonkeySpeed | `[moving:>100]` | Speed % (100=normal run) | +| `targeting:tank` | pfUI | `[targeting:tank]` | Target is attacking any tank | +| `istank` | pfUI | `[istank]` | Target is marked as tank | + +### pfUI Tank Integration + +Identify loose mobs in trash pulls by checking if enemies are targeting designated tanks. Works with pfUI's tank marking systems. + +**Tank Conditionals:** +| Conditional | Description | +|-------------|-------------| +| `[targeting:tank]` | Target IS attacking any player marked as tank | +| `[notargeting:tank]` | Target is NOT attacking any tank (loose mob!) | +| `[istank]` | Target unit IS marked as tank | +| `[noistank]` | Target unit is NOT marked as tank | + +**Setting Up Tanks in pfUI:** + +1. **Nameplate Off-Tank Names** (recommended): `/pfui` → Nameplates → Off-Tank Names + - Add tank names separated by `#`: `#TankName1#TankName2#TankName3` + - These names will show different colored nameplates AND work with `[targeting:tank]` + +2. **Raid Frame Toggle**: Right-click a player in raid frames → "Toggle as Tank" + - Only works when raid frames are visible + +**Example Macros:** +```lua +-- Pick up loose mobs (not targeting any tank) +/cast [multiscan:nearest,notargeting:tank,harm] Taunt + +-- Only taunt if mob is targeting you (tank) +/cast [targeting:player] Sunder Armor + +-- Assist tanks - attack what they're tanking +/cast [multiscan:nearest,targeting:tank,harm] Sunder Armor + +-- Emergency taunt on loose mob hitting a healer +/cast [notargeting:tank,notargeting:player] Taunt +``` + +**Debug Command:** +``` +/cleveroid tankdebug +``` +Shows all marked tanks, current target info, and conditional results. ### Warrior Slam Conditionals For optimizing Slam rotations without clipping auto-attacks: @@ -294,6 +339,7 @@ Scans enemies and soft-casts without changing your target. Requires UnitXP_SP3. /cast [multiscan:skull,harm] Eviscerate /cast [multiscan:markorder] Sinister Strike /cast [multiscan:highesthp,noimmune:stun] Cheap Shot +/cast [multiscan:nearest,notargeting:tank,harm] Taunt -- Pick up loose mobs (pfUI) ``` **Priorities:**