diff --git a/RelationshipsThreatPlates.toc b/RelationshipsThreatPlates.toc index 4ff0ed7..b6442b3 100644 --- a/RelationshipsThreatPlates.toc +++ b/RelationshipsThreatPlates.toc @@ -2,9 +2,9 @@ ## Title: RelationshipsThreatPlates - OctoWoW ## Notes: OctoWoW-compatible RelationshipsThreatPlates ## Author: Packaged By Relationship -## Version: 1.1 +## Version: 1.2 ## SavedVariables: RelationshipsThreatPlatesDB ## X-Website: https://github.com/Relationship-OctoWoW/RelationshipsThreatPlates ## X-GitHub: https://github.com/Relationship-OctoWoW/RelationshipsThreatPlates -RelationshipsThreatPlates.xml +RelationshipsThreatPlates.xml \ No newline at end of file diff --git a/RelationshipsThreatPlates_Core.lua b/RelationshipsThreatPlates_Core.lua index b6457a8..b734315 100644 --- a/RelationshipsThreatPlates_Core.lua +++ b/RelationshipsThreatPlates_Core.lua @@ -12,7 +12,7 @@ addon.MAJOR = 1 addon.MINOR = 1 -- Version string -TP_VERSION = "2.3.1" +TP_VERSION = "2.4.0" TP_ADDON_NAME = "RelationshipsThreatPlates" -- ================================================================== @@ -21,9 +21,11 @@ TP_ADDON_NAME = "RelationshipsThreatPlates" local DEFAULT_CONFIG = { enabled = true, showText = true, + tankMode = false, + targetSnap100 = true, barHeight = 6, barAlpha = 0.5, - barOffsetY = 20, + barOffsetY = 24, barOffsetX = -17.8, barWidthScale = 0.8, fontSize = 9, diff --git a/RelationshipsThreatPlates_GUI.lua b/RelationshipsThreatPlates_GUI.lua index fc52efe..e0a2930 100644 --- a/RelationshipsThreatPlates_GUI.lua +++ b/RelationshipsThreatPlates_GUI.lua @@ -341,7 +341,7 @@ function addon.GUI:BuildSettings(parent) local slider = CreateFrame("Slider", "RelationshipsThreatPlatesSlider_" .. configKey, parent, "OptionsSliderTemplate") slider:SetWidth(W - 60) - slider:SetHeight(16) + slider:SetHeight(10) slider:SetPoint("TOPLEFT", parent, "TOPLEFT", MARGIN, -y) slider:SetOrientation("HORIZONTAL") slider:SetMinMaxValues(minVal, maxVal) @@ -364,6 +364,8 @@ function addon.GUI:BuildSettings(parent) if thumbTexture then thumbTexture:SetTexture(C.SOLID) thumbTexture:SetVertexColor(unpack(C.ACCENT)) + thumbTexture:SetWidth(8) + thumbTexture:SetHeight(14) end local lowText = getglobal(sliderName .. "Low") @@ -403,7 +405,9 @@ function addon.GUI:BuildSettings(parent) -- ============================================================== SectionLabel("General") CheckBox("Enable Threat Plates", "enabled", "Show threat bars above nameplates") - CheckBox("Show Threat Text", "showText", "Display percentage in the bar") + CheckBox("Show Threat Text", "showText", "Display value in the bar") + CheckBox("Tank Mode", "tankMode", "Show raw threat lead over 2nd place instead of %") + CheckBox("Snap to 100% when Targeted", "targetSnap100", "Force 100% while a mob is focused on you") CheckBox("Smooth Colours", "smoothColors", "Gradient colour transition") -- ============================================================== @@ -412,7 +416,7 @@ function addon.GUI:BuildSettings(parent) SectionLabel("Appearance") Slider("Bar Height", "barHeight", 2, 20, 1, "px") Slider("Bar Width Scale", "barWidthScale", 0.3, 1.0, 0.05, "") - Slider("Bar Offset Y", "barOffsetY", 0, 20, 1, "px") + Slider("Bar Offset Y", "barOffsetY", 0, 50, 1, "px") Slider("Bar Offset X", "barOffsetX", -50, 50, 0.1, "px") Slider("Bar Alpha", "barAlpha", 0.1, 1.0, 0.1, "") Slider("Font Size", "fontSize", 6, 16, 1, "pt") @@ -437,6 +441,44 @@ function addon.GUI:BuildSettings(parent) CheckBox("Show on Neutral", "showOnNeutral", "Show threat bars on neutral units") CheckBox("Show on Friendly", "showOnFriendly", "Show threat bars on friendly units") + -- ============================================================== + -- Reset button at bottom + -- ============================================================== + y = y + 10 + local resetBtn = CreateFrame("Button", nil, parent) + resetBtn:SetWidth(W - MARGIN * 2) + resetBtn:SetHeight(22) + resetBtn:SetPoint("TOPLEFT", parent, "TOPLEFT", MARGIN, -y) + SafeSetBackdrop(resetBtn, { + bgFile = C.SOLID, + edgeFile = C.SOLID, + tile = false, tileSize = 0, + edgeSize = 1, + }) + resetBtn:SetBackdropColor(unpack(C.BUTTON_BG)) + resetBtn:SetBackdropBorderColor(unpack(C.BORDER)) + + local resetText = resetBtn:CreateFontString(nil, "OVERLAY") + resetText:SetFont(FONT_NORMAL, FONT_SIZE, "OUTLINE") + resetText:SetPoint("CENTER", resetBtn, "CENTER", 0, 0) + resetText:SetTextColor(unpack(C.BUTTON_TEXT)) + resetText:SetText("Reset to Defaults") + resetBtn:SetFontString(resetText) + + resetBtn:SetScript("OnEnter", function() + this:SetBackdropColor(unpack(C.BUTTON_HOVER)) + end) + resetBtn:SetScript("OnLeave", function() + this:SetBackdropColor(unpack(C.BUTTON_BG)) + end) + resetBtn:SetScript("OnClick", function() + SlashCmdList["THREATPLATES"]("reset") + if addon.Nameplates and addon.Nameplates.RecreateAll then + addon.Nameplates:RecreateAll() + end + end) + y = y + 22 + 8 + -- Return total content height for scroll setup return y end diff --git a/RelationshipsThreatPlates_Nameplates.lua b/RelationshipsThreatPlates_Nameplates.lua index d96dc80..e05e446 100644 --- a/RelationshipsThreatPlates_Nameplates.lua +++ b/RelationshipsThreatPlates_Nameplates.lua @@ -897,10 +897,12 @@ function addon.Nameplates:ApplyThreatToPlate(data, pct, situation) -- Smooth the percentage local oldPct = data.lastPct or 0 local smoothPct - if pct == 0 then - smoothPct = 0 + -- Snap instantly when jumping to full aggro (100%) or dropping to 0 + -- (mob switched target). Smooth intermediate transitions moderately. + if pct == 0 or pct >= 99.5 or math.abs(pct - oldPct) > 40 then + smoothPct = pct else - smoothPct = oldPct + (pct - oldPct) * 0.3 + smoothPct = oldPct + (pct - oldPct) * 0.6 end data.lastPct = smoothPct @@ -948,8 +950,20 @@ function addon.Nameplates:ApplyThreatToPlate(data, pct, situation) -- Update text if data.text then - local displayPct = math.floor(pct + 0.5) - data.text:SetText(displayPct .. "%") + local textStr + if RelationshipsThreatPlatesDB.tankMode and data.plateName then + -- Tank mode: show raw threat lead over the 2nd-highest player + -- (positive = you have that much lead as top-threat holder; + -- negative = you're that much behind the current top). + textStr = addon.Threat:GetTankDisplayForMob(data.plateName) + if not textStr or textStr == "--" then + -- Fall back to % when no absolute values are available. + textStr = math.floor(pct + 0.5) .. "%" + end + else + textStr = math.floor(pct + 0.5) .. "%" + end + data.text:SetText(textStr) if pct >= 90 then data.text:SetTextColor(1, 0.2, 0.2, 1) diff --git a/RelationshipsThreatPlates_Threat.lua b/RelationshipsThreatPlates_Threat.lua index 18bd06d..7575bc5 100644 --- a/RelationshipsThreatPlates_Threat.lua +++ b/RelationshipsThreatPlates_Threat.lua @@ -57,7 +57,11 @@ local partyNames = {} local partyUnits = {} -- Update throttle (seconds) -local THROTTLE = 0.2 +local THROTTLE = 0.1 +-- Cache of per-mob tank-mode details, keyed by mob UID (name in vanilla). +-- Populated by UpdateUnitThreat via ThreatEngine, and read by the Nameplates +-- module when rendering the tank-mode text (raw threat lead). +local tankDetailsCache = {} -- ================================================================== -- Safe API call helpers @@ -647,12 +651,54 @@ function addon.Threat:UpdateUnitThreat(unit) mobTargetName = mt end + -- ------------------------------------------------------------ + -- TARGET-SNAP override: if the mob is currently targeting the + -- player, force display to 100% (aggro). Some mobs pick focused + -- / random targets regardless of threat, so the plate should + -- immediately snap to 100% while targeted, and fall back to the + -- real threat % when the mob switches targets. + -- ------------------------------------------------------------ + local targetSnapped = false + if RelationshipsThreatPlatesDB and RelationshipsThreatPlatesDB.targetSnap100 + and mobTargetName and playerName and mobTargetName == playerName then + local ok_pc, pc = pcall(UnitAffectingCombat, "player") + if ok_pc and pc then + pct = 100 + situation = 3 + targetSnapped = true + end + end + + -- ------------------------------------------------------------ + -- Compute raw tank-mode details (player threat vs 2nd highest). + -- Uses ThreatEngine's combat-log-parsed absolute values when + -- available. This is what the nameplate renders in Tank Mode. + -- ------------------------------------------------------------ + local ok_mn, mobName = pcall(UnitName, unit) + if ok_mn and mobName then + local ePct, eSit, ePlayerThreat, eTopThreat, eHasGroup, eTopPlayer, eSecondThreat = + addon.ThreatEngine:GetThreatOnTarget(mobName) + + tankDetailsCache[uid] = { + playerThreat = ePlayerThreat or 0, + topThreat = eTopThreat or 0, + secondThreat = eSecondThreat or 0, + topPlayer = eTopPlayer, + hasAggro = (eTopPlayer == playerName) or targetSnapped, + hasGroupData = eHasGroup or false, + targetSnapped = targetSnapped, + mobTargetName = mobTargetName, + lastUpdate = GetTime(), + } + end + threatCache[uid] = { pct = pct, situation = situation, unit = unit, lastUpdate = GetTime(), mobTargetName = mobTargetName, + targetSnapped = targetSnapped, } -- Also store in group threat cache for cross-reference @@ -681,6 +727,76 @@ function addon.Threat:GetCachedThreat(uid) return 0, 0 end +-- ================================================================== +-- Tank-mode details for a mob (by UID / name in vanilla). +-- Returns a table (or nil) with: +-- playerThreat, topThreat, secondThreat, topPlayer, +-- hasAggro, hasGroupData, targetSnapped, mobTargetName +-- ================================================================== +function addon.Threat:GetTankDetails(uid) + if not uid then return nil end + local d = tankDetailsCache[uid] + if not d then return nil end + if GetTime() - d.lastUpdate > 5 then return nil end + return d +end + +-- ================================================================== +-- Format a raw threat value as a compact signed string. +-- Examples: 12345 -> "+12.3K", -845 -> "-845", 0 -> "0" +-- ================================================================== +function addon.Threat:FormatThreatLead(v) + if not v then return "--" end + local a = math.abs(v) + local sign = "" + if v > 0 then sign = "+" elseif v < 0 then sign = "-" end + if a >= 100000 then + return sign .. string.format("%.0fK", a / 1000) + elseif a >= 10000 then + return sign .. string.format("%.1fK", a / 1000) + elseif a >= 1000 then + return sign .. string.format("%.2fK", a / 1000) + else + return sign .. string.format("%d", a) + end +end + +-- ================================================================== +-- Return the display string for tank mode given a plate's mob name. +-- Uses the ThreatEngine directly (works even without a unit token, +-- e.g. for unique-name plates). +-- Returns: string like "+1.20K" or "-450", or "--" if unknown. +-- ================================================================== +function addon.Threat:GetTankDisplayForMob(mobName) + if not mobName then return "--" end + local d = tankDetailsCache[mobName] + if not d or (GetTime() - d.lastUpdate > 5) then + -- Query ThreatEngine on demand (no cache hit). + local _, _, playerThreat, topThreat, _, topPlayer, secondThreat = + addon.ThreatEngine:GetThreatOnTarget(mobName) + if not playerThreat or playerThreat <= 0 then + return "--" + end + local lead + if topPlayer == playerName then + lead = playerThreat - (secondThreat or 0) + else + lead = playerThreat - (topThreat or 0) + end + return self:FormatThreatLead(lead) + end + + if d.playerThreat <= 0 then return "--" end + local lead + if d.hasAggro then + lead = d.playerThreat - (d.secondThreat or 0) + else + lead = d.playerThreat - (d.topThreat or 0) + end + return self:FormatThreatLead(lead) +end + + -- ================================================================== -- Get threat percentage by mob NAME (no unit token required) -- This is the fallback for when FindUnitByName couldn't resolve diff --git a/RelationshipsThreatPlates_ThreatEngine.lua b/RelationshipsThreatPlates_ThreatEngine.lua index d16fcb6..591f836 100644 --- a/RelationshipsThreatPlates_ThreatEngine.lua +++ b/RelationshipsThreatPlates_ThreatEngine.lua @@ -1268,30 +1268,34 @@ end -- ============================================================ function addon.ThreatEngine:GetThreatOnTarget(mobName) - if not mobName then return 0, 0, 0, 0, false, nil end + if not mobName then return 0, 0, 0, 0, false, nil, 0 end -- Check if we have threat data for this mob local mobThreat = threatData[mobName] if not mobThreat then - return 0, 0, 0, 0, false, nil + return 0, 0, 0, 0, false, nil, 0 end - -- Find player's threat and top threat + -- Find player's threat, top threat and second-highest threat local playerThreat = mobThreat[playerName] or 0 local topThreat = 0 local topPlayer = nil + local secondThreat = 0 local sourceCount = 0 for source, value in pairs(mobThreat) do sourceCount = sourceCount + 1 if value > topThreat then + secondThreat = topThreat topThreat = value topPlayer = source + elseif value > secondThreat then + secondThreat = value end end if topThreat <= 0 then - return 0, 0, 0, 0, false, nil + return 0, 0, 0, 0, false, nil, 0 end -- Calculate the player's percentage relative to top threat @@ -1314,7 +1318,7 @@ function addon.ThreatEngine:GetThreatOnTarget(mobName) -- (via addon communication or party combat log parsing). local hasGroupData = (sourceCount > 1) - return pct, sit, playerThreat, topThreat, hasGroupData, topPlayer + return pct, sit, playerThreat, topThreat, hasGroupData, topPlayer, secondThreat end -- ============================================================