local ADDON_NAME, Addon = ... local ThreatPlates = Addon.ThreatPlates --------------------------------------------------------------------------------------------------- -- Imported functions and constants --------------------------------------------------------------------------------------------------- -- WoW APIs local InCombatLockdown, IsInInstance = InCombatLockdown, IsInInstance local UnitIsDead, UnitPlayerControlled, UnitIsUnit = UnitIsDead, UnitPlayerControlled, UnitIsUnit local UnitIsOtherPlayersPet = UnitIsOtherPlayersPet local UnitIsBattlePet, UnitCreatureType = UnitIsBattlePet, UnitCreatureType local UnitCanAttack = UnitCanAttack -- ThreatPlates APIs local TOTEMS = Addon.TOTEMS local GetUnitVisibility = ThreatPlates.GetUnitVisibility local NameTriggers, AuraTriggers, CastTriggers = Addon.Cache.CustomPlateTriggers.Name, Addon.Cache.CustomPlateTriggers.Aura, Addon.Cache.CustomPlateTriggers.Cast local NameWildcardTriggers, TriggerWildcardTests = Addon.Cache.CustomPlateTriggers.NameWildcard, Addon.Cache.TriggerWildcardTests local CustomStylesForAllInstances, CustomStylesForCurrentInstance = Addon.Cache.Styles.ForAllInstances, Addon.Cache.Styles.ForCurrentInstance local UpdateCustomStyleAfterAuraTrigger = Addon.UpdateCustomStyleAfterAuraTrigger local _G =_G -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded -- List them here for Mikk's FindGlobals script -- GLOBALS: GetSpellInfo, UnitIsTapDenied --------------------------------------------------------------------------------------------------- -- Wrapper functions for WoW Classic --------------------------------------------------------------------------------------------------- -- UnitIsBattlePet: Mists - Patch 5.1.0 (2012-11-27): Added. if not Addon.ExpansionIsAtLeast() then -- Mists UnitIsBattlePet = function(...) return false end end --------------------------------------------------------------------------------------------------- -- Helper functions for styles and functions --------------------------------------------------------------------------------------------------- local REACTION_MAPPING = { FRIENDLY = "Friendly", HOSTILE = "Enemy", NEUTRAL = "Neutral", } local INSTANCE_TYPES = { none = false, pvp = false, arena = false, party = true, raid = true, scenario = true, } -- Mapping necessary - to removed it, config settings must be changed/migrated -- Visibility Scale / Alpha Threat System local MAP_UNIT_TYPE_TO_TP_TYPE = { FriendlyPlayer = "FriendlyPlayer", FriendlyNPC = "FriendlyNPC", FriendlyTotem = "Totem", FriendlyGuardian = "Guardian", FriendlyPet = "Pet", FriendlyMinus = "Minus", -- Not sure if they exist ... but to be sure and avoid Lua errors EnemyPlayer = "EnemyPlayer", EnemyNPC = "EnemyNPC", -- / Boss / Elite = Normal / Boss / Elite EnemyTotem = "Totem", EnemyGuardian = "Guardian", EnemyPet = "Pet", EnemyMinus = "Minus", NeutralNPC = "Neutral", NeutralMinus = "Minus", } -- TODO: Move these into MAP_UNIT_TYPE_TO_TP_TYPE, that should work as well (and change right side to target type) local REMAP_UNSUPPORTED_UNIT_TYPES = { NeutralTotem = "FriendlyTotem", -- When players are mind-controled, their totems turn neutral it seems (at least in Classic): https://www.curseforge.com/wow/addons/tidy-plates-threat-plates/issues/506 NeutralGuardian = "FriendlyGuardian", NeutralPet = "FriendlyPet", -- Sometimes, friendly pets turn into neutral pets when you lose control over them (e.g., in quests). } local function GetUnitType(unit) -- Minions are: -- - totems: unit.TotemSettings -- - guardians: unit is a player's pet -- - pets: all other player controlled units -- Not sure if there are other types of minions local unit_class -- not all combinations are possible in the game: Friendly Minus, Neutral Player/Totem/Pet if unit.type == "PLAYER" then unit_class = "Player" elseif unit.classification == "minus" then unit_class = "Minus" elseif unit.TotemSettings then unit_class = "Totem" elseif UnitIsOtherPlayersPet(unit.unitid) or UnitIsUnit(unit.unitid, "pet") then -- player pets are also considered guardians, so this check has priority -- ? Better to use UnitIsOwnerOrControllerOfUnit("player", unit.unitid) here? unit_class = "Pet" elseif UnitPlayerControlled(unit.unitid) then unit_class = "Guardian" else unit_class = "NPC" end -- Sometimes, friendly pets or totems turn into neutral when you lose control over them (e.g., in quests or -- when players are mind-controled). So map unknown neutral types (totems, pets, guardians) to friendly ones local unit_type = REACTION_MAPPING[unit.reaction] .. unit_class unit_type = REMAP_UNSUPPORTED_UNIT_TYPES[unit_type] or unit_type unit.TP_DetailedUnitType = MAP_UNIT_TYPE_TO_TP_TYPE[unit_type] if unit.TP_DetailedUnitType == "EnemyNPC" then unit.TP_DetailedUnitType = (unit.isBoss and "Boss") or (unit.isElite and "Elite") or unit.TP_DetailedUnitType end if _G.UnitIsTapDenied(unit.unitid) then unit.TP_DetailedUnitType = "Tapped" end -- If nameplate visibility is controlled by Wow itself (configured via CVars), this function is never used as -- nameplates aren't created in the first place (e.g. friendly NPCs, totems, guardians, pets, ...) return GetUnitVisibility(unit_type) end local function ShowUnit(unit) local show, headline_view = GetUnitType(unit) -- If a unit is targeted, show the nameplate if possible. show = show or unit.IsSoftTarget if not show then return false, false, headline_view end local e, b = (unit.isElite or unit.isRare), unit.isBoss local db_base = Addon.db.profile local db = db_base.Visibility local hide_unit_type = false if (e and db.HideElite) or (b and db.HideBoss) or (unit.TP_DetailedUnitType == "Tapped" and db.HideTapped) or (unit.TP_DetailedUnitType == "Guardian" and db.HideGuardian) then hide_unit_type = true elseif db.HideNormal and not (e or b) then hide_unit_type = true elseif UnitIsBattlePet(unit.unitid) then -- TODO: add configuration option for enable/disable hide_unit_type = true elseif db.HideFriendlyInCombat and unit.reaction == "FRIENDLY" and InCombatLockdown() then hide_unit_type = true end if hide_unit_type and not unit.IsSoftTarget then return show, hide_unit_type, headline_view end -- if full_unit_type == "EnemyNPC" then -- if b then -- unit.TP_DetailedUnitType = "Boss" -- elseif e then -- unit.TP_DetailedUnitType = "Elite" -- end -- end -- if t then -- --unit.TP_DetailedUnitType = "Tapped" -- show = not db.HideTapped -- end db = db_base.HeadlineView if db.ForceHealthbarOnTarget and unit.IsSoftTarget then headline_view = false elseif db.ForceOutOfCombat and not InCombatLockdown() then headline_view = true elseif db.ForceNonAttackableUnits and unit.reaction ~= "FRIENDLY" and not UnitCanAttack("player", unit.unitid) then headline_view = true elseif unit.reaction == "FRIENDLY" and InCombatLockdown() then if db.ForceFriendlyInCombat == "NAME" then headline_view = true elseif db.ForceFriendlyInCombat == "HEALTHBAR" then headline_view = false end end return show, false, headline_view end -- Returns style based on threat (currently checks for in combat, should not do hat) function Addon:GetThreatStyle(unit) -- style tank/dps only used for NPCs/non-player units if Addon:ShowThreatFeedback(unit) then return (Addon:PlayerRoleIsTank() and "tank") or "dps" end return "normal" end local function GetStyleForPlate(custom_style) -- If a style is found with useStyle == false, that means that it's active, but only the icon will be shown. if custom_style.useStyle then return (custom_style.showNameplate and "unique") or (custom_style.ShowHeadlineView and "NameOnly-Unique") or "etotem" end --return nil end -- Check if a unit is a totem or a custom nameplates (e.g., after UNIT_NAME_UPDATE) -- Depends on: -- * unit.name function Addon.UnitStyle_NameDependent(unit) local db = Addon.db.profile local plate_style, custom_style, totem_settings local name_custom_style = NameTriggers[unit.name] or NameTriggers[unit.NPCID] if name_custom_style and name_custom_style.Enable.UnitReaction[unit.reaction] then custom_style = name_custom_style plate_style = GetStyleForPlate(custom_style) elseif Addon.ActiveWildcardTriggers and unit.type == "NPC" then local cached_custom_style = TriggerWildcardTests[unit.name] if cached_custom_style == nil then cached_custom_style = false local trigger for i = 1, #NameWildcardTriggers do trigger = NameWildcardTriggers[i] --print ("Name Wildcard: ", unit.name, "=>", trigger[1], unit.name:find(trigger[1])) if unit.name:find(trigger[1]) then -- Static checks (based on not changing criterien (like unit type). if trigger[2].Enable.UnitReaction[unit.reaction] then custom_style = trigger[2] plate_style = GetStyleForPlate(custom_style) cached_custom_style = { Style = plate_style, CustomStyle = custom_style } -- Breaking here without plate_style being set means that only the icon part will be used from the custom style break end end end -- Add custom style, if one was found, or false if there is no custom style for this unit TriggerWildcardTests[unit.name] = cached_custom_style elseif cached_custom_style ~= false then custom_style = cached_custom_style.CustomStyle plate_style = cached_custom_style.Style end end if not plate_style and UnitCreatureType(unit.unitid) == Addon.TotemCreatureType and UnitPlayerControlled(unit.unitid) then -- Check for player totems and ignore NPC totems local totem_id = TOTEMS[unit.name] if totem_id then totem_settings = db.totemSettings[totem_id] if totem_settings.ShowNameplate then plate_style = (db.totemSettings.hideHealthbar and "etotem") or "totem" else plate_style = "empty" end end end -- Conditions: -- * custom_style == nil: No custom style found -- * custom_style ~= nil: Custom style found, active -- * plate_style == nil: Appearance part of style will not be used, only icon will be shown (if custom_style ~= nil) -- Set these values to nil if not custom nameplate or totem unit.CustomPlateSettings = custom_style unit.TotemSettings = totem_settings return plate_style end local UnitStyle_NameDependent = Addon.UnitStyle_NameDependent function Addon.UnitStyle_AuraTrigger_Initialize(unit) if Addon.ActiveAuraTriggers then unit.PreviousCustomStyleAura = unit.CustomStyleAura unit.CustomStyleAura = nil end end function Addon.UnitStyle_AuraTrigger_UpdateStyle(unit) -- Set the style if a aura trigger for a custom nameplate was found or the aura trigger -- is no longer there if Addon.ActiveAuraTriggers and (unit.CustomStyleAura or unit.PreviousCustomStyleAura) then UpdateCustomStyleAfterAuraTrigger(unit) end end -- TODO Return value not used here function Addon.UnitStyle_AuraTrigger_CheckIfActive(unit, aura_id, aura_name, aura_cast_by_player) -- Do this to prevent overwrite the first aura trigger custom style found (which is the one being used) if not Addon.ActiveAuraTriggers or unit.CustomStyleAura then return end local unique_settings = AuraTriggers[aura_id] or AuraTriggers[aura_name] -- Check if enabled for unit's faction and check for show only my auras if unique_settings and unique_settings.useStyle and unique_settings.Enable.UnitReaction[unit.reaction] and (not unique_settings.Trigger.Aura.ShowOnlyMine or aura_cast_by_player) then -- As this is called for every aura on a unit, never set it to false (overwriting a previous true value) unit.CustomStyleAura = (unique_settings.showNameplate and "unique") or (unique_settings.ShowHeadlineView and "NameOnly-Unique") or "etotem" unit.CustomPlateSettingsAura = unique_settings local _, _, icon = _G.GetSpellInfo(aura_id) unique_settings.AutomaticIcon = icon end end function Addon.UnitStyle_CastTrigger_CheckIfActive(unit, spell_id, spell_name) -- Cast triggers cannot be overwritten (like aura triggers) as there can only be one cast active at a time if not Addon.ActiveCastTriggers then return end unit.PreviousCustomStyleCast = unit.CustomStyleCast unit.CustomStyleCast = nil local unique_settings = CastTriggers[spell_id] or CastTriggers[spell_name] if unique_settings and unique_settings.useStyle and unique_settings.Enable.UnitReaction[unit.reaction] then unit.CustomStyleCast = (unique_settings.showNameplate and "unique") or (unique_settings.ShowHeadlineView and "NameOnly-Unique") or "etotem" unit.CustomPlateSettingsCast = unique_settings local _, _, icon = _G.GetSpellInfo(spell_id) unique_settings.AutomaticIcon = icon end end function Addon.UnitStyle_CastTrigger_UpdateStyle(unit) -- Set the style if a cast trigger for a custom nameplate was found or the cast trigger -- is no longer there return Addon.ActiveCastTriggers and (unit.CustomStyleCast or unit.PreviousCustomStyleCast) end function Addon.UnitStyle_CastTrigger_Reset(unit) if Addon.ActiveCastTriggers then local cast_trigger_was_active = unit.CustomStyleCast ~= nil unit.PreviousCustomStyleCast = nil unit.CustomStyleCast = nil return cast_trigger_was_active else return false end end function Addon:SetStyle(unit) local show, hide_unit_type, headline_view = ShowUnit(unit) -- Nameplate is disabled in General - Visibility if not show then return "empty" end -- Check if custom nameplate should be used for the unit: local style if unit.CustomStyleCast then style = unit.CustomStyleCast unit.CustomPlateSettings = unit.CustomPlateSettingsCast elseif unit.CustomStyleAura then style = unit.CustomStyleAura unit.CustomPlateSettings = unit.CustomPlateSettingsAura else style = UnitStyle_NameDependent(unit) or (headline_view and "NameOnly") end -- Dynamic enable checks for custom styles -- Only check it once if custom style is used for multiple mobs -- only check it once when entering a dungeon, not on every style change -- Array by instance ID and all enabled styles? local custom_style = unit.CustomPlateSettings if custom_style then local _, instance_type = IsInInstance() if INSTANCE_TYPES[instance_type] then if not CustomStylesForAllInstances[custom_style] and not CustomStylesForCurrentInstance[custom_style] then -- Without cache: if not custom_style.Enable.Instances or (custom_style.Enable.InstanceIDs.Enable and not CustomStylesForCurrentInstance[custom_style]) then style = nil unit.CustomPlateSettings = nil end elseif not custom_style.Enable.OutOfInstances then style = nil unit.CustomPlateSettings = nil end end -- TODO: Probably could merge these three if-s into one if-elseif-statement -- Hidden nameplates might be shown if a custom style is defined for them (Visibility - Hide Nameplates) if hide_unit_type and style ~= "unique" and style ~= "NameOnly-Unique" then return "empty" end if (UnitIsDead(unit.unitid) and UnitIsUnit("softinteract", unit.unitid)) or unit.SoftInteractTargetIsDead then -- We use IsDead to prevent the nameplate switching to healthbar view again shortly before disapearing unit.SoftInteractTargetIsDead = true return (unit.CustomPlateSettings and "NameOnly-Unique") or "NameOnly" end if not style then if not UnitExists(unit.unitid) then style = "etotem" elseif Addon:ShowThreatFeedback(unit) then -- could call GetThreatStyle here, but that would at a tiny overhead -- style tank/dps only used for hostile (enemy, neutral) NPCs style = (Addon:PlayerRoleIsTank() and "tank") or "dps" end end return style or "normal" end