/Tukui/modules/unitframes/core/oUF/elements/tags.lua

http://github.com/Asphyxia/Tukui · Lua · 650 lines · 640 code · 7 blank · 3 comment · 78 complexity · ed7cde516f9dec74179ac0ee37aa1ade MD5 · raw file

  1. --[[
  2. -- Credits: Vika, Cladhaire, Tekkub
  3. ]]
  4. local parent, ns = ...
  5. local oUF = ns.oUF
  6. local _PATTERN = '%[..-%]+'
  7. local _ENV = {
  8. Hex = function(r, g, b)
  9. if type(r) == "table" then
  10. if r.r then r, g, b = r.r, r.g, r.b else r, g, b = unpack(r) end
  11. end
  12. return string.format("|cff%02x%02x%02x", r*255, g*255, b*255)
  13. end,
  14. ColorGradient = oUF.ColorGradient,
  15. }
  16. local _PROXY = setmetatable(_ENV, {__index = _G})
  17. local tagStrings = {
  18. ["creature"] = [[function(u)
  19. return UnitCreatureFamily(u) or UnitCreatureType(u)
  20. end]],
  21. ["dead"] = [[function(u)
  22. if(UnitIsDead(u)) then
  23. return 'Dead'
  24. elseif(UnitIsGhost(u)) then
  25. return 'Ghost'
  26. end
  27. end]],
  28. ["difficulty"] = [[function(u)
  29. if UnitCanAttack("player", u) then
  30. local l = UnitLevel(u)
  31. return Hex(GetQuestDifficultyColor((l > 0) and l or 99))
  32. end
  33. end]],
  34. ["leader"] = [[function(u)
  35. if(UnitIsPartyLeader(u)) then
  36. return 'L'
  37. end
  38. end]],
  39. ["leaderlong"] = [[function(u)
  40. if(UnitIsPartyLeader(u)) then
  41. return 'Leader'
  42. end
  43. end]],
  44. ["level"] = [[function(u)
  45. local l = UnitLevel(u)
  46. if(l > 0) then
  47. return l
  48. else
  49. return '??'
  50. end
  51. end]],
  52. ["missinghp"] = [[function(u)
  53. local current = UnitHealthMax(u) - UnitHealth(u)
  54. if(current > 0) then
  55. return current
  56. end
  57. end]],
  58. ["missingpp"] = [[function(u)
  59. local current = UnitPowerMax(u) - UnitPower(u)
  60. if(current > 0) then
  61. return current
  62. end
  63. end]],
  64. ["name"] = [[function(u, r)
  65. return UnitName(r or u)
  66. end]],
  67. ["offline"] = [[function(u)
  68. if(not UnitIsConnected(u)) then
  69. return 'Offline'
  70. end
  71. end]],
  72. ["perhp"] = [[function(u)
  73. local m = UnitHealthMax(u)
  74. if(m == 0) then
  75. return 0
  76. else
  77. return math.floor(UnitHealth(u)/m*100+.5)
  78. end
  79. end]],
  80. ["perpp"] = [[function(u)
  81. local m = UnitPowerMax(u)
  82. if(m == 0) then
  83. return 0
  84. else
  85. return math.floor(UnitPower(u)/m*100+.5)
  86. end
  87. end]],
  88. ["plus"] = [[function(u)
  89. local c = UnitClassification(u)
  90. if(c == 'elite' or c == 'rareelite') then
  91. return '+'
  92. end
  93. end]],
  94. ["pvp"] = [[function(u)
  95. if(UnitIsPVP(u)) then
  96. return 'PvP'
  97. end
  98. end]],
  99. ["raidcolor"] = [[function(u)
  100. local _, x = UnitClass(u)
  101. if(x) then
  102. return Hex(_COLORS.class[x])
  103. end
  104. end]],
  105. ["rare"] = [[function(u)
  106. local c = UnitClassification(u)
  107. if(c == 'rare' or c == 'rareelite') then
  108. return 'Rare'
  109. end
  110. end]],
  111. ["resting"] = [[function(u)
  112. if(u == 'player' and IsResting()) then
  113. return 'zzz'
  114. end
  115. end]],
  116. ["sex"] = [[function(u)
  117. local s = UnitSex(u)
  118. if(s == 2) then
  119. return 'Male'
  120. elseif(s == 3) then
  121. return 'Female'
  122. end
  123. end]],
  124. ["smartclass"] = [[function(u)
  125. if(UnitIsPlayer(u)) then
  126. return _TAGS['class'](u)
  127. end
  128. return _TAGS['creature'](u)
  129. end]],
  130. ["status"] = [[function(u)
  131. if(UnitIsDead(u)) then
  132. return 'Dead'
  133. elseif(UnitIsGhost(u)) then
  134. return 'Ghost'
  135. elseif(not UnitIsConnected(u)) then
  136. return 'Offline'
  137. else
  138. return _TAGS['resting'](u)
  139. end
  140. end]],
  141. ["threat"] = [[function(u)
  142. local s = UnitThreatSituation(u)
  143. if(s == 1) then
  144. return '++'
  145. elseif(s == 2) then
  146. return '--'
  147. elseif(s == 3) then
  148. return 'Aggro'
  149. end
  150. end]],
  151. ["threatcolor"] = [[function(u)
  152. return Hex(GetThreatStatusColor(UnitThreatSituation(u)))
  153. end]],
  154. ["cpoints"] = [[function(u)
  155. local cp
  156. if(UnitHasVehicleUI'player') then
  157. cp = GetComboPoints('vehicle', 'target')
  158. else
  159. cp = GetComboPoints('player', 'target')
  160. end
  161. if(cp > 0) then
  162. return cp
  163. end
  164. end]],
  165. ['smartlevel'] = [[function(u)
  166. local c = UnitClassification(u)
  167. if(c == 'worldboss') then
  168. return 'Boss'
  169. else
  170. local plus = _TAGS['plus'](u)
  171. local level = _TAGS['level'](u)
  172. if(plus) then
  173. return level .. plus
  174. else
  175. return level
  176. end
  177. end
  178. end]],
  179. ["classification"] = [[function(u)
  180. local c = UnitClassification(u)
  181. if(c == 'rare') then
  182. return 'Rare'
  183. elseif(c == 'eliterare') then
  184. return 'Rare Elite'
  185. elseif(c == 'elite') then
  186. return 'Elite'
  187. elseif(c == 'worldboss') then
  188. return 'Boss'
  189. end
  190. end]],
  191. ["shortclassification"] = [[function(u)
  192. local c = UnitClassification(u)
  193. if(c == 'rare') then
  194. return 'R'
  195. elseif(c == 'eliterare') then
  196. return 'R+'
  197. elseif(c == 'elite') then
  198. return '+'
  199. elseif(c == 'worldboss') then
  200. return 'B'
  201. end
  202. end]],
  203. ["group"] = [[function(unit)
  204. local name, server = UnitName(unit)
  205. if(server and server ~= "") then
  206. name = string.format("%s-%s", name, server)
  207. end
  208. for i=1, GetNumRaidMembers() do
  209. local raidName, _, group = GetRaidRosterInfo(i)
  210. if( raidName == name ) then
  211. return group
  212. end
  213. end
  214. end]],
  215. ["deficit:name"] = [[function(u)
  216. local missinghp = _TAGS['missinghp'](u)
  217. if(missinghp) then
  218. return '-' .. missinghp
  219. else
  220. return _TAGS['name'](u)
  221. end
  222. end]],
  223. ['pereclipse'] = [[function(u)
  224. local m = UnitPowerMax('player', SPELL_POWER_ECLIPSE)
  225. if(m == 0) then
  226. return 0
  227. else
  228. return math.abs(UnitPower('player', SPELL_POWER_ECLIPSE)/m*100)
  229. end
  230. end]],
  231. ['curmana'] = [[function(unit)
  232. return UnitPower(unit, SPELL_POWER_MANA)
  233. end]],
  234. ['maxmana'] = [[function(unit)
  235. return UnitPowerMax(unit, SPELL_POWER_MANA)
  236. end]],
  237. }
  238. local tags = setmetatable(
  239. {
  240. curhp = UnitHealth,
  241. curpp = UnitPower,
  242. maxhp = UnitHealthMax,
  243. maxpp = UnitPowerMax,
  244. class = UnitClass,
  245. faction = UnitFactionGroup,
  246. race = UnitRace,
  247. },
  248. {
  249. __index = function(self, key)
  250. local tagFunc = tagStrings[key]
  251. if(tagFunc) then
  252. local func, err = loadstring('return ' .. tagFunc)
  253. if(func) then
  254. func = func()
  255. -- Want to trigger __newindex, so no rawset.
  256. self[key] = func
  257. tagStrings[key] = nil
  258. return func
  259. else
  260. error(err, 3)
  261. end
  262. end
  263. end,
  264. __newindex = function(self, key, val)
  265. if(type(val) == 'string') then
  266. tagStrings[key] = val
  267. elseif(type(val) == 'function') then
  268. -- So we don't clash with any custom envs.
  269. if(getfenv(val) == _G) then
  270. setfenv(val, _PROXY)
  271. end
  272. rawset(self, key, val)
  273. end
  274. end,
  275. }
  276. )
  277. _ENV._TAGS = tags
  278. local tagEvents = {
  279. ["curhp"] = "UNIT_HEALTH",
  280. ["dead"] = "UNIT_HEALTH",
  281. ["leader"] = "PARTY_LEADER_CHANGED",
  282. ["leaderlong"] = "PARTY_LEADER_CHANGED",
  283. ["level"] = "UNIT_LEVEL PLAYER_LEVEL_UP",
  284. ["maxhp"] = "UNIT_MAXHEALTH",
  285. ["missinghp"] = "UNIT_HEALTH UNIT_MAXHEALTH",
  286. ["name"] = "UNIT_NAME_UPDATE",
  287. ["perhp"] = "UNIT_HEALTH UNIT_MAXHEALTH",
  288. ["pvp"] = "UNIT_FACTION",
  289. ["resting"] = "PLAYER_UPDATE_RESTING",
  290. ["smartlevel"] = "UNIT_LEVEL PLAYER_LEVEL_UP UNIT_CLASSIFICATION_CHANGED",
  291. ["threat"] = "UNIT_THREAT_SITUATION_UPDATE",
  292. ["threatcolor"] = "UNIT_THREAT_SITUATION_UPDATE",
  293. ['cpoints'] = 'UNIT_COMBO_POINTS PLAYER_TARGET_CHANGED',
  294. ['rare'] = 'UNIT_CLASSIFICATION_CHANGED',
  295. ['classification'] = 'UNIT_CLASSIFICATION_CHANGED',
  296. ['shortclassification'] = 'UNIT_CLASSIFICATION_CHANGED',
  297. ["group"] = "RAID_ROSTER_UPDATE",
  298. ["curpp"] = 'UNIT_POWER',
  299. ["maxpp"] = 'UNIT_MAXPOWER',
  300. ["missingpp"] = 'UNIT_MAXPOWER UNIT_POWER',
  301. ["perpp"] = 'UNIT_MAXPOWER UNIT_POWER',
  302. ["offline"] = "UNIT_HEALTH UNIT_CONNECTION",
  303. ["status"] = "UNIT_HEALTH PLAYER_UPDATE_RESTING UNIT_CONNECTION",
  304. ["pereclipse"] = 'UNIT_POWER',
  305. ['curmana'] = 'UNIT_POWER UNIT_MAXPOWER',
  306. ['maxmana'] = 'UNIT_POWER UNIT_MAXPOWER',
  307. }
  308. local unitlessEvents = {
  309. PLAYER_LEVEL_UP = true,
  310. PLAYER_UPDATE_RESTING = true,
  311. PLAYER_TARGET_CHANGED = true,
  312. PARTY_LEADER_CHANGED = true,
  313. RAID_ROSTER_UPDATE = true,
  314. UNIT_COMBO_POINTS = true
  315. }
  316. local events = {}
  317. local frame = CreateFrame"Frame"
  318. frame:SetScript('OnEvent', function(self, event, unit)
  319. local strings = events[event]
  320. if(strings) then
  321. for k, fontstring in next, strings do
  322. if(fontstring:IsVisible() and (unitlessEvents[event] or fontstring.parent.unit == unit)) then
  323. fontstring:UpdateTag()
  324. end
  325. end
  326. end
  327. end)
  328. local OnUpdates = {}
  329. local eventlessUnits = {}
  330. local createOnUpdate = function(timer)
  331. local OnUpdate = OnUpdates[timer]
  332. if(not OnUpdate) then
  333. local total = timer
  334. local frame = CreateFrame'Frame'
  335. local strings = eventlessUnits[timer]
  336. frame:SetScript('OnUpdate', function(self, elapsed)
  337. if(total >= timer) then
  338. for k, fs in next, strings do
  339. if(fs.parent:IsShown() and UnitExists(fs.parent.unit)) then
  340. fs:UpdateTag()
  341. end
  342. end
  343. total = 0
  344. end
  345. total = total + elapsed
  346. end)
  347. OnUpdates[timer] = frame
  348. end
  349. end
  350. local OnShow = function(self)
  351. for _, fs in next, self.__tags do
  352. fs:UpdateTag()
  353. end
  354. end
  355. local getTagName = function(tag)
  356. local s = (tag:match('>+()') or 2)
  357. local e = tag:match('.*()<+')
  358. e = (e and e - 1) or -2
  359. return tag:sub(s, e), s, e
  360. end
  361. local RegisterEvent = function(fontstr, event)
  362. if(not events[event]) then events[event] = {} end
  363. frame:RegisterEvent(event)
  364. table.insert(events[event], fontstr)
  365. end
  366. local RegisterEvents = function(fontstr, tagstr)
  367. for tag in tagstr:gmatch(_PATTERN) do
  368. tag = getTagName(tag)
  369. local tagevents = tagEvents[tag]
  370. if(tagevents) then
  371. for event in tagevents:gmatch'%S+' do
  372. RegisterEvent(fontstr, event)
  373. end
  374. end
  375. end
  376. end
  377. local UnregisterEvents = function(fontstr)
  378. for event, data in pairs(events) do
  379. for k, tagfsstr in pairs(data) do
  380. if(tagfsstr == fontstr) then
  381. if(#data == 1) then
  382. frame:UnregisterEvent(event)
  383. end
  384. table.remove(data, k)
  385. end
  386. end
  387. end
  388. end
  389. local tagPool = {}
  390. local funcPool = {}
  391. local tmp = {}
  392. local Tag = function(self, fs, tagstr)
  393. if(not fs or not tagstr) then return end
  394. if(not self.__tags) then
  395. self.__tags = {}
  396. table.insert(self.__elements, OnShow)
  397. else
  398. -- Since people ignore everything that's good practice - unregister the tag
  399. -- if it already exists.
  400. for _, tag in pairs(self.__tags) do
  401. if(fs == tag) then
  402. -- We don't need to remove it from the __tags table as Untag handles
  403. -- that for us.
  404. self:Untag(fs)
  405. end
  406. end
  407. end
  408. fs.parent = self
  409. local func = tagPool[tagstr]
  410. if(not func) then
  411. local format, numTags = tagstr:gsub('%%', '%%%%'):gsub(_PATTERN, '%%s')
  412. local args = {}
  413. for bracket in tagstr:gmatch(_PATTERN) do
  414. local tagFunc = funcPool[bracket] or tags[bracket:sub(2, -2)]
  415. if(not tagFunc) then
  416. local tagName, s, e = getTagName(bracket)
  417. local tag = tags[tagName]
  418. if(tag) then
  419. s = s - 2
  420. e = e + 2
  421. if(s ~= 0 and e ~= 0) then
  422. local pre = bracket:sub(2, s)
  423. local ap = bracket:sub(e, -2)
  424. tagFunc = function(u,r)
  425. local str = tag(u,r)
  426. if(str) then
  427. return pre..str..ap
  428. end
  429. end
  430. elseif(s ~= 0) then
  431. local pre = bracket:sub(2, s)
  432. tagFunc = function(u,r)
  433. local str = tag(u,r)
  434. if(str) then
  435. return pre..str
  436. end
  437. end
  438. elseif(e ~= 0) then
  439. local ap = bracket:sub(e, -2)
  440. tagFunc = function(u,r)
  441. local str = tag(u,r)
  442. if(str) then
  443. return str..ap
  444. end
  445. end
  446. end
  447. funcPool[bracket] = tagFunc
  448. end
  449. end
  450. if(tagFunc) then
  451. table.insert(args, tagFunc)
  452. else
  453. return error(('Attempted to use invalid tag %s.'):format(bracket), 3)
  454. end
  455. end
  456. if(numTags == 1) then
  457. func = function(self)
  458. local parent = self.parent
  459. local realUnit
  460. if(self.overrideUnit) then
  461. realUnit = parent.realUnit
  462. end
  463. _ENV._COLORS = parent.colors
  464. return self:SetFormattedText(
  465. format,
  466. args[1](parent.unit, realUnit) or ''
  467. )
  468. end
  469. elseif(numTags == 2) then
  470. func = function(self)
  471. local parent = self.parent
  472. local unit = parent.unit
  473. local realUnit
  474. if(self.overrideUnit) then
  475. realUnit = parent.realUnit
  476. end
  477. _ENV._COLORS = parent.colors
  478. return self:SetFormattedText(
  479. format,
  480. args[1](unit, realUnit) or '',
  481. args[2](unit, realUnit) or ''
  482. )
  483. end
  484. elseif(numTags == 3) then
  485. func = function(self)
  486. local parent = self.parent
  487. local unit = parent.unit
  488. local realUnit
  489. if(self.overrideUnit) then
  490. realUnit = parent.realUnit
  491. end
  492. _ENV._COLORS = parent.colors
  493. return self:SetFormattedText(
  494. format,
  495. args[1](unit, realUnit) or '',
  496. args[2](unit, realUnit) or '',
  497. args[3](unit, realUnit) or ''
  498. )
  499. end
  500. else
  501. func = function(self)
  502. local parent = self.parent
  503. local unit = parent.unit
  504. local realUnit
  505. if(self.overrideUnit) then
  506. realUnit = parent.realUnit
  507. end
  508. _ENV._COLORS = parent.colors
  509. for i, func in next, args do
  510. tmp[i] = func(unit, realUnit) or ''
  511. end
  512. -- We do 1, numTags because tmp can hold several unneeded variables.
  513. return self:SetFormattedText(format, unpack(tmp, 1, numTags))
  514. end
  515. end
  516. tagPool[tagstr] = func
  517. end
  518. fs.UpdateTag = func
  519. local unit = self.unit
  520. if((unit and unit:match'%w+target') or fs.frequentUpdates) then
  521. local timer
  522. if(type(fs.frequentUpdates) == 'number') then
  523. timer = fs.frequentUpdates
  524. else
  525. timer = .5
  526. end
  527. if(not eventlessUnits[timer]) then eventlessUnits[timer] = {} end
  528. table.insert(eventlessUnits[timer], fs)
  529. createOnUpdate(timer)
  530. else
  531. RegisterEvents(fs, tagstr)
  532. end
  533. table.insert(self.__tags, fs)
  534. end
  535. local Untag = function(self, fs)
  536. if(not fs) then return end
  537. UnregisterEvents(fs)
  538. for _, timers in next, eventlessUnits do
  539. for k, fontstr in next, timers do
  540. if(fs == fontstr) then
  541. table.remove(timers, k)
  542. end
  543. end
  544. end
  545. for k, fontstr in next, self.__tags do
  546. if(fontstr == fs) then
  547. table.remove(self.__tags, k)
  548. end
  549. end
  550. fs.UpdateTag = nil
  551. end
  552. oUF.Tags = tags
  553. oUF.TagEvents = tagEvents
  554. oUF.UnitlessTagEvents = unitlessEvents
  555. oUF:RegisterMetaFunction('Tag', Tag)
  556. oUF:RegisterMetaFunction('Untag', Untag)