Skip to content

Scripting Custom Styles

Backupiseasy edited this page May 14, 2022 · 12 revisions

Scripting is still under heavy development and therefore the API for it is not stable and will be changed frequently. Any scripts you write, must be updated regularly to stay functional.

Also, be aware that this will change your Threat Plates settings in a way that you cannot go back to the release version. So, be sure to make a backup of your settings.

Version 2: Working, but restricted in functionality as no changes to core code of Threat Plates were yet made.

Scripting is a new feature of Threat Plates that will be introduced in one of the upcoming versions of Threat Plates. It allows you to extend the functionality with custom Lua scripts. Once finished, you should be able to change all aspects of nameplates. Currently, functionality is still restricted to a high degree as internal functions of Threat Plates (like, e.g., scaling) are not easily accessible to scripts. So overwriting or changing them to, e.g., implement custom scaling is not that easy or may even be impossible right now.

Note that the Scripting API as is is not fully secure. Critical WoW functions should be blocked (like, e.g. GuildDisband, thanks to WeakAuras and Plater for this information), but there might be holes. So, be especially careful when you import scripted custom styles from other users.

Enable Scripting

Enabl (or disable) it with the following chat command:

/tptp toggle-scripting

Script Types

Currently, there are two types of scripted styles (or custom nameplates): scripts that work on all nameplates (trigger Script) or scripts that work only on nameplates where the trigger fired (like units with a certain name or units casting a particular spell).

Script Functions and Events

Every script must be assigned to either a Threat Plates function or a WoW event. The following Threat Plates functions are available:

  • IsEnabled
  • OnEnable
  • OnDisable
  • EnabledForStyle
  • Create
  • UpdateFrame
  • UpdateSettings
  • UpdateLayout
  • OnUnitAdded: Only available for standard (non-target/focus-based) scripts.
  • OnUnitRemoved: Only available for (non-target/focus-based) scripts.
  • OnUpdate
  • OnTargetUnitAdded: Only available for target-based scripts.
  • OnTargetUnitRemoved: Only available for target-based scripts.
  • OnFocusUnitAdded: Only available for focus-based scripts.
  • OnFocusUnitRemoved: Only available for focus-based scripts.
  • WoWEvent: Add script code for any WoW event. The name of the event must be entered in field "Event Name".

Global Variables

  • ThreatPlates.Environment: A table that currently contains the style (custom nameplate) that defined the script. You can also use this table to store variables that should be accessed across the different script functions for a style (e.g. to transfer information between OnEnable and OnUnitAdded, see examples below).
    • Style: a table containing all settings for the custom style.
    • Profile: a table with the settings of the current profile.
  • ThreatPlates.API
    • Data
      • CrowdControlAuras: a table
      • StealthDetectionAuras: a table
      • StealthDetectionUnits: a table
    • ThreatPlates.Logging
      • Info(...)
      • Warning(...)
      • Error(...)
      • Debug(...)
    • ThreatPlates.Widgets
      • CreateStatusBar
  • PlatesByUnit: Hashtable with all nameplates accessible by unitid.
  • PlatesByGUID: Hashtable with all nameplates accessible by GUID.

To access the Threat Plates nameplate, use, e.g.: PlatesByUnit["target"].TPFrame

Current Restrictions

  • Target- and focus-based scripts are not really tested - they might not work or not work efficiently
  • Some, mostly security-related functions and tables are blocked.

Example 1: Execute - Color the healthbar in a particular color once the unit's health is in execute range.

As internal functions (like coloring) are not accessible to scripts right now, this script uses hooksecurefunc to change the color after Threat Plates's calculation if health is below the execute range. Not very efficient as the Threat Plates color calculation is done unnecessary in these cases, but it works.

As color, the color from the custom style defined under Appearance is used.

Function: IsEnabled

function()
  -- Return false or nil, to disable the script, e.g., when it should be only active on a certain class
  local player_class = select(2, UnitClass("player"))
  return player_class == "WARRIOR"
end

Only enable this script for class Warrior.

Function: Create

function(widget_frame)
  local healthbar = widget_frame:GetParent().visual.healthbar
  hooksecurefunc(healthbar, "SetAllColors", function()
      local unitid = healthbar:GetParent().unit.unitid
      local health = _G.UnitHealth(unitid) or 0
      local healthmax = _G.UnitHealthMax(unitid) or 1
      local pct = health  / healthmax
      
      if pct < 0.20 then
        local color = ThreatPlates.Environment.Style.color
        healthbar:SetStatusBarColor(color.r, color.g, color.b)
      end
  end)
end

If you change this code, you need to reload the UI to take effect as the hooked script cannot be removed otherwise.

Import String

9vvBZPnmm4)l8j4U1mk0UUUB7UXy020JcDyA3TVeIjXH4vx7C2oq5l53(Ku412R74oq2s6rpsYseDA00i2JcRtA0rSVxyn)vK4pzz9nFpI1V05npZ8Rvchy7iYHbA(CLaLEql9te8ep6pCE0GhMoP3WOFeXUAs4Gr)C4Fq5BgZMgoCai6JyJl9JZc1opxNaGcQhjGagnpIfQp6(Thc)PBFytjdHByayaCUCZQWei8GdSeRSWtgFvPUMvS(wb3lIytxxaFZaet52uaULcn4pyGjLsMHIf8K14nKkeg)EGiudDBjblBZ9nBvDBvCv8jNufpr4lT6Q4mUYjQIn2QyTu9HQyVPkov6qxHd5WxoIQGkrWIa4Nv5cWrPh0KBkvPvXZre0Q1vXy9DjDcKRItewpxcYjkUZvhDLjHRQIlu81c7SAfXFdatOGgAZoqeWEvFurZg1M1O1gQB3q7x5n4EJF3BYKWXtAuDRqNUVwUl5xjtxi8ZYS8NfToIk5cUYNpNBjECODF5AH)EUfkXnBfSu6k5QG9wtyKBmp5ejLwbgPM70cPrdMW3tP6BugRRbCXRBehWHsiLLPeb2bXrrhniyJvV25ApiNNDDaw9UHUPzT9TQ7VTFh)EM)YBD9o(lh79PVX7Ie)b8fv8XdH829oiZ2y9xbwe0Pn9WsVx)bGMGflc2P5yh8Ef0fDbd0lLwJ(zOweqJ4b1gEee7lCqHhME8LUFWTu5VjzEa2vQLwStA(bnd4Lt9bqOv9djF9SBPtqaHZUsCgMfo6Xz3jDjZ(vPWHD1742NcMRkGDnorVsyze3lt2oWd3vNrxRmRWfdmyBauRtvsT4rPGUBQvUybSGbMF7ZDEAowxusZ(9C9Sw(AAu3VBhbnBc7LGhR)pR7vA5VREIkJH537aQa0WVd33hr)bzuDHbOpxvKZXnVSbzzWWmTiIYxmHiROfZJAhC55rJ6SvOlk0foEgQf3KHdFqwz072gEV8fHA7w(TrK4bxbral(WUoOqBGn0wzQOhrM5BQnSbVadP4cbOtsVGo0wg82dZCwYgsYMtPXcGyrmB0vNDENt78P2FUDNUxC5fx2jk7KZ7s70jprBXT7yOkWxS0)FGFI(3

Example 2: Silence Effect - Show the duration for which a player is unable to cast a spell after a successful interrupt.

The effect is currently shown on NPCs (which might be wrong) and no matter who interrupted the unit.

Function: OnEnable

function()
ThreatPlates.Environment.INTERRUPT_SPELLS = {
  [1766]   = 5, -- Kick (Rogue) 
  [1766]   = 5, -- Kick (Rogue)
  [2139]   = 6, -- Counterspell (Mage)
  [6552]   = 4, -- Pummel (Warrior)
  [13491]  = 5, -- Pummel (Iron Knuckles Item) 
  [19647]  = 6, -- Spell Lock (felhunter) (Warlock) 
  [29443]  = 10, -- Counterspell (Clutch of Foresight) 
  [47528]  = 3, -- Mind Freeze (Death Knight) 
  [57994]  = 3, -- Wind Shear (Shaman) 
  [91802]  = 2, -- Shambling Rush (Death Knight) 
  [96231]  = 4, -- Rebuke (Paladin) 
  [93985]  = 4, -- Skull Bash (Druid Feral) 
  [97547]  = 5, -- Solar Beam (Druid Balance) 
  [115781] = 6, -- Optical Blast (Warlock) 
  [116705] = 4, -- Spear Hand Strike (Monk) 
  [132409] = 6, -- Spell Lock (command demon) (Warlock) 
  [147362] = 3, -- Countershot (Hunter) 
  [183752] = 3, -- Consume Magic (Demon Hunter) 
  [187707] = 3, -- Muzzle (Hunter) 
  [212619] = 6, -- Call Felhunter (Warlock) 
  [217824] = 4, -- Shield of Virtue (Protec Paladin) 
  [231665] = 3, -- Avengers Shield (Paladin)
}
end

List of interrupts with silence duration, thanks to bambziqt for this list.

Function: Create

function(widget_frame)
  local frame  = _G.CreateFrame("Frame", nil, widget_frame)    
  frame:SetAllPoints(widget_frame)
  frame:Hide()
  widget_frame.InterruptFrame = frame
  
  local icon = frame:CreateTexture(nil, "OVERLAY")  
  icon:SetSize(32, 32) 
  icon:SetPoint("RIGHT", frame, "LEFT", -10, 0)
  icon:Show()
  frame.Icon = icon
  
  local time = frame:CreateFontString(nil, "OVERLAY") -- Duration Text
  time:SetJustifyH("CENTER")
  time:SetJustifyV("CENTER")
  time:SetShadowOffset(1, -1)
  time:SetShadowColor(0, 0, 0, 1)
  time:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE")
  time:SetTextColor(1, 0, 0)
  time:SetAllPoints(icon)
  time:Show()
  frame.Time = time
  
  frame:SetScript("OnUpdate", function(self, elapsed) 
      self.ElapsedTime = self.ElapsedTime - elapsed
      local cooldown = ceil(self.ElapsedTime * 10) / 10
      frame.Time:SetText(cooldown)
      if self.ElapsedTime < 0 then
        self:Hide()
      end
  end)
end

Code should be pretty straighforward. Once the silence effect expires, the frame is hidden and the OnUpdate function will stop to be executed.

Event: COMBAT_LOG_EVENT_UNFILTERED

function(...)  
  local timeStamp, event, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags, spellID, spellName, spellSchool = CombatLogGetCurrentEventInfo()
  local counterspell_duration =   ThreatPlates.Environment.INTERRUPT_SPELLS[spellID or 0]
  
  if event == "SPELL_INTERRUPT" and counterspell_duration then
    local frame = PlatesByGUID[destGUID]
    if frame then
      frame = frame.TPFrame.widgets.Script.InterruptFrame
      frame.ElapsedTime = counterspell_duration
      frame.Icon:SetTexture(GetSpellTexture(spellID))
      frame:Show()     
    end
  end
end

Import String

nE1EZPnoq8Vl8xMBA4W8m05UzkHysCkbOiNKPxpEySfGUySyKLBAYnhF2VDLSngWPthgSLL2h)2xsANzoZzg5rQiIXdNr(0ob)FOEYl(UEMpnJ0losY3sKVgqJaAhQyWk0Dzafh9qitoH66jr(HVhA9GZKUdMD1ms)j2wdVEWxXX3oI4ypWcgkNrgflhTYoms6g6bcfwEifu4SLZi2HhnF6h2xhDqT(kcHziGWaXfTH)IThOEGbINGTtQiUFCOgvKN4pz9DAiqQZR7OarGm9Df(Ga1t3B09x11z(Gr3m36rRHoZFyyF7bowtSUgwK7RS0b01UEVckvZLsh)ugxLaaJkvQuE)c43D4ZaUNBW(fs2wkaKT7(W(fuuGW7nmFAp3ijvaFeXJfE0BEW(6Spg6ULM9r)a31rzFnXL5NoJpnsMWhomHlCyEkYZr0oAqGwr4Ou9GJjEB4CaV)5(f94Bx6khWxFdv2lwiaqRCf2HR4gLpY684XHGzOKWC)yHl6iucz)cNncQRCCGRKgvXk87mbpCliMk2dbp3Khg7mNm2AWaY3sG1(fCX(fvNcAqPd2QexgiqqILuuppJ7s7xaX33fdYn0qTCYH3vcWKv4tJRREfDGFl1to9adOYtO(yjH)oig1OkoJ7RE)cZFnvgvrNEwXgXLiENuTAbIOIvG7UiQVdlrCfBlfWiwi8rcv6q)HmwqnGqfbzk97exA5YfW8hjqLKr5CZNrdfCO3LnaEGfEPvy6sbmOsZL1RT55kjFCYrIxszyZVPIMtLRWOK6vji5lKfappwihbReitLDdcgZzqfzrQut1TqHvAgAEIonsKf5sS88OM5LKbRfPg1PUvnAln6rRjd6(1s5k3zjbec7nQr9Aav1Rv(0fv43O0e7BU1bnELkqboWQVAIlmRcpRwopB6G1bRuf7viub1ZTajlVjMyb95HsIuWcxxGrCXf7xCDwLdASAbIscX9DWHdSvVERrPEwy5xPYfU(JV36KnU(8xgTAvevAyQSZcPOhpGlmuUGK)NshAgq2d8m6V)B4KN)6loZNxXXPp69mBOSRhCgyp06umGwLw(MPY)ekoKGHo2Jw8SyqwfRYzFxbPR69amknk8HD(qaqfVtlAIObRWde0L)z5jP)WLpBVHZN8ImjCm7zBoZdaNQoxXJYcmoxe)g41a)WIFhFx0gno5CFgPs8KDvW9kpx0)b4HlAZZud8OQ10F53aQSEhiYOWK7HCythfx)YhWOCa)RwUFZSDRwt1kdMT5h0z)FM598(fgt4RJPzHJFjA1uwZSENdu2kHYE52oh44E31zm0QzZAhyOrcdJJ3ULIK(KRqW4IuQnR3OJ50tWrgX2cSS9ZHXEpd3EB)cBjDBoJOtRgTNEcYiAinGRmLv0GnkKwwRAib65dcOwNgnQNka1gufyB9cILEBGJWHCH(CbnITEJ8GmA0UzTltLr9erCpdp)UVGsFdYxmUgIMBq74ywB2UtNgNY6tkwjBOUWvgmGDp26gEGLoMxwTwkl1snzGOLbWgG7xmjoAZ7RWoTQvpZzNgzMqxg)mIYXUbU(S8kRENlBEk5KNJr3YvUAfjIzOLsfUb5ySDZdbM0GkHhGM0vu3Thy8kqNW1JZftnB2(seJ5cPJ2jzQ6(RcG7wwuG00Sv7QnNEmq3PCH3QUkfEaHYiVNhMNV61AuTZ0FscKhF7wLe8PBXTols5nAxVvTPhfftZH2Wr8EBAkykhxwVDZZ4imkg3DbQLyEQy4wm5)CEB3UA7J59(43ElGwGIQzwRL5j2xpx086NwyuyDHz7lR14e35ggnWxxg8itiJvPmcUKcG9SuhipRvRMhJYUWnExd(KdY6qk3D)x6LYuDefhrvNOHDeHhzbTln8X53ZI8M)Ly4kTWoL37kEUYYGDqhCr0UXqlEWH8EPTrbZP3e9Ma(ly7w4bD3sD9HYe6JmQAohbBnai1L)Gel8TD4Uyj2Iu3OUcH7RWCkaL03L6KpOBp48JFg1DHBC8URRGYOWGxHnjOamKzY99LOmNfPDma8Dd2TXf7NLyTAf0ZR6wSk7fnifvQ2DhwTsNMZgwlDqDCqD4Zg4Q41GXZdbRId4jXshZ(bniT35unQWbK9WFbD(qBSGJMd99kGJ76QaZYeFdHWcOqD9(fAOPIOQwWZZdbkQrpajsnaTeVeyteQpxREUmbNyFYO43HhmQ6eh)n7))