421 lines
12 KiB
Lua
421 lines
12 KiB
Lua
-- Copyright 2023 <omicron.me@protonmail.com>
|
|
--
|
|
-- This file is part of Omicron Frames
|
|
--
|
|
-- Omicron Frames is free software: you can redistribute it and/or modify it
|
|
-- under the terms of the GNU General Public License as published by the Free
|
|
-- Software Foundation, either version 3 of the License, or (at your option)
|
|
-- any later version.
|
|
--
|
|
-- Omicron Frames is distributed in the hope that it will be useful, but
|
|
-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
-- more details.
|
|
--
|
|
-- You should have received a copy of the GNU General Public License along with
|
|
-- Omicron Frames. If not, see <https://www.gnu.org/licenses/>.
|
|
local omif = select(2, ...)
|
|
local types = omif.GetModule("types")
|
|
local StatusBar = types.StatusBar
|
|
local AuraList = types.AuraList
|
|
local Indicator = types.Indicator
|
|
|
|
types.UnitFrame = types.CreateClass("UnitFrame")
|
|
local UnitFrame = types.UnitFrame
|
|
|
|
|
|
local colors = {
|
|
hostile = {0.5, 0.0, 0.0},
|
|
neutral = {0.7, 0.7, 0.0},
|
|
healthy = {0, 0.1, 0},
|
|
high = {0, 1.0, 0},
|
|
mid = {1.0, 1.0, 0},
|
|
low = {1.0, 0.0, 0},
|
|
offline = {0.7, 0.7, 0.7},
|
|
dead = {0.7, 0.7, 0.7},
|
|
magic = {0.4, 0.4, 1.0},
|
|
disease = {0.4, 0.2, 0.0},
|
|
poison = {0.0, 0.7, 0.7},
|
|
curse = {0.7, 0.0, 0.7},
|
|
immune = {0.0, 0.2, 0.4},
|
|
bomb = {1.0, 0.7, 0.7},
|
|
cyan = {0.0, 0.8, 0.8},
|
|
white = {1.0, 1.0, 1.0}
|
|
}
|
|
|
|
function UnitFrame:Init(unit, width, height, hideInRaid)
|
|
self.unit = unit
|
|
self.hideInRaid = hideInRaid or false
|
|
local secure = self:CreateSecureFrame(width, height)
|
|
secure:Hide()
|
|
self.hp = StatusBar:new(self, width, height)
|
|
self.auras = AuraList:new(self)
|
|
local overlays = CreateFrame("Frame", nil, secure)
|
|
overlays:SetFrameStrata("MEDIUM")
|
|
overlays:SetFrameLevel(100)
|
|
overlays:SetAllPoints(true)
|
|
overlays:Show()
|
|
self.overlays = overlays
|
|
self:CreateName()
|
|
self.indicators = {
|
|
Indicator:new(self, self.auras, 14, "TOPLEFT", 2, -2, colors.white, 383648),
|
|
Indicator:new(self, self.auras, 14, "TOPLEFT", 2, -2, colors.white, 974),
|
|
Indicator:new(self, self.auras, 14, "BOTTOMLEFT", 2, 2, colors.cyan, 61295)
|
|
}
|
|
self:RegisterEvents()
|
|
self:Enable()
|
|
end
|
|
|
|
function UnitFrame:StartRangeTicker()
|
|
if self.rangeTicker then
|
|
return
|
|
end
|
|
local delta = 0.45 + (fastrandom(0, 100)/1000)
|
|
self.rangeTicker = C_Timer.NewTicker(delta, function()
|
|
self:UpdateRange()
|
|
end)
|
|
end
|
|
|
|
-- TODO: maybe string indicators need to be a thing
|
|
function UnitFrame:CreateName()
|
|
local name = self.overlays:CreateFontString(nil, "BACKGROUND")
|
|
self.name = name
|
|
|
|
name:SetFont("Interface\\AddOns\\OmicronFrames\\media\\fonts\\roboto\\Roboto-Bold.ttf", 13, "")
|
|
name:SetTextColor(0.8, 0.6, 0.1)
|
|
name:SetShadowColor(0, 0, 0)
|
|
name:SetShadowOffset(1, -1)
|
|
name:SetPoint("CENTER")
|
|
name:SetText("")
|
|
name:Show()
|
|
end
|
|
|
|
function UnitFrame:StopRangeTicker()
|
|
if self.rangeTicker then
|
|
self.rangeTicker:Cancel()
|
|
self.rangeTicker = nil
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UpdateRange()
|
|
local unit = self.unit
|
|
if UnitIsDead(unit) then
|
|
self.secureFrame:SetAlpha(1.0)
|
|
elseif not UnitIsFriend("player", unit) and IsSpellInRange("Lightning Bolt", unit) ~= 1 then
|
|
self.secureFrame:SetAlpha(0.2)
|
|
elseif UnitIsFriend("player", unit) and IsSpellInRange("Healing Surge", unit) ~= 1 then
|
|
self.secureFrame:SetAlpha(0.2)
|
|
else
|
|
self.secureFrame:SetAlpha(1.0)
|
|
end
|
|
end
|
|
|
|
function UnitFrame:SetSpellAction(button, spell)
|
|
local secure = self.secureFrame
|
|
local attributeName = button:gsub("type", "spell")
|
|
|
|
secure:SetAttribute(button, "spell")
|
|
secure:SetAttribute(attributeName, spell)
|
|
end
|
|
|
|
function UnitFrame:SetMacroAction(button, macro)
|
|
local secure = self.secureFrame
|
|
local attributeName = button:gsub("type", "macrotext")
|
|
|
|
secure:SetAttribute(button, "macro")
|
|
secure:SetAttribute(attributeName, macro:gsub("@UNIT", "@" .. self.unit))
|
|
end
|
|
|
|
function UnitFrame:PrepareWheelBinds()
|
|
-- By default you can't use the SecureUnitButtonTemplate to handle scroll
|
|
-- wheel "clicks". We solve this by using SecureHandler*Template to bind
|
|
-- scroll wheel to the current unit frame button when the mouse enters and
|
|
-- to unbind it when the mouse leaves. When the keybind is triggered the
|
|
-- button will receive a click with a custom button name
|
|
|
|
local secure = self.secureFrame
|
|
local bindScript = ([[
|
|
self:ClearBindings()
|
|
self:SetBindingClick(false, "MOUSEWHEELUP", "BUTTONNAME", "wheel-up")
|
|
self:SetBindingClick(false, "MOUSEWHEELDOWN", "BUTTONNAME", "wheel-down")
|
|
self:SetBindingClick(false, "ALT-MOUSEWHEELUP", "BUTTONNAME", "alt-wheel-up")
|
|
self:SetBindingClick(false, "ALT-MOUSEWHEELDOWN", "BUTTONNAME", "alt-wheel-down")
|
|
]]):gsub("BUTTONNAME", "OmicronSecureFrame" .. self.unit)
|
|
|
|
local removeBindScript = [[
|
|
self:ClearBindings()
|
|
]]
|
|
secure:SetAttribute("_onenter", bindScript)
|
|
secure:SetAttribute("_onleave", removeBindScript)
|
|
secure:SetAttribute("_onshow", removeBindScript)
|
|
secure:SetAttribute("_onhide", removeBindScript)
|
|
end
|
|
|
|
function UnitFrame:CreateSecureFrame(width, height)
|
|
local name = "OmicronSecureFrame" .. self.unit
|
|
local templates = table.concat({
|
|
"SecureUnitButtonTemplate",
|
|
"SecureHandlerEnterLeaveTemplate",
|
|
"SecureHandlerShowHideTemplate",
|
|
}, ",")
|
|
local secure = CreateFrame("Button", name, UIParent, templates)
|
|
self.secureFrame = secure
|
|
secure:SetFrameStrata("MEDIUM")
|
|
secure:SetFrameLevel(0)
|
|
secure:SetAttribute("unit", self.unit)
|
|
|
|
secure:SetSize(width, height)
|
|
|
|
secure:RegisterForClicks("AnyDown")
|
|
self:PrepareWheelBinds()
|
|
|
|
-- No modifiers
|
|
secure:SetAttribute("type1", "target")
|
|
self:SetMacroAction("type2", "/use [@UNIT,dead,help]Ancestral Vision; [@UNIT]Chain Heal")
|
|
self:SetMacroAction("type3", "/use [@UNIT,dead,help]Ancestral Spirit; [@UNIT]Purify Spirit")
|
|
self:SetMacroAction("type-wheel-up", "/use [@UNIT]Healing Surge")
|
|
self:SetMacroAction("type-wheel-down", "/use [@UNIT,help]Riptide")
|
|
|
|
-- alt
|
|
self:SetSpellAction("type-alt-wheel-up", "Healing Wave")
|
|
self:SetSpellAction("type-alt-wheel-down", "Earth Shield")
|
|
|
|
-- Shift
|
|
secure:SetAttribute("SHIFT-type2", "togglemenu")
|
|
|
|
return secure
|
|
end
|
|
|
|
function UnitFrame:Enable()
|
|
local secure = self.secureFrame
|
|
if self.hideInRaid then
|
|
local condition = "[@UNIT,exists,nogroup:raid] show; hide"
|
|
condition = condition:gsub("@UNIT", "@" .. self.unit)
|
|
RegisterAttributeDriver(secure, "state-visibility", condition)
|
|
elseif self.unit ~= "player" then
|
|
RegisterUnitWatch(self.secureFrame, false)
|
|
else
|
|
self.secureFrame:Show()
|
|
end
|
|
if UnitExists(self.unit) then
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
end
|
|
|
|
function UnitFrame:Disable()
|
|
if self.hideInRaid then
|
|
UnregisterAttributeDriver(self.secureFrame, "state-visibility")
|
|
elseif self.unit ~= "player" then
|
|
UnregisterUnitWatch(self.secure_frame, self.condition)
|
|
else
|
|
self.secureFrame:Hide()
|
|
end
|
|
end
|
|
|
|
-- returns true if this unit is a target unit
|
|
function UnitFrame:IsTargetFrame()
|
|
return self.unit:match(".*target") == self.unit
|
|
end
|
|
|
|
-- returns the target owner
|
|
function UnitFrame:GetTargetOwner()
|
|
if self.unit == "target" then
|
|
return "player"
|
|
else
|
|
return self.unit:sub(1, -7)
|
|
end
|
|
end
|
|
|
|
-- Set position from the center of the UIParent
|
|
function UnitFrame:SetPosition(x, y)
|
|
self.secureFrame:SetPoint("CENTER", UIParent, x, y)
|
|
end
|
|
|
|
function UnitFrame:RegisterEvents()
|
|
local unit = self.unit
|
|
local secure = self.secureFrame
|
|
|
|
secure:SetScript("OnShow", function(frame, event, ...)
|
|
self:OnShow()
|
|
end)
|
|
|
|
secure:SetScript("OnHide", function(frame, event, ...)
|
|
self:OnHide()
|
|
end)
|
|
|
|
secure:SetScript("OnEvent", function(frame, event, ...)
|
|
self[event](self, ...)
|
|
end)
|
|
|
|
if self:IsTargetFrame() then
|
|
local owner = self:GetTargetOwner()
|
|
if owner == "player" then
|
|
secure:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
else
|
|
secure:RegisterUnitEvent("UNIT_TARGET", self.parent:GetTargetOwner())
|
|
end
|
|
end
|
|
|
|
secure:RegisterUnitEvent("UNIT_AURA", unit)
|
|
secure:RegisterUnitEvent("UNIT_HEALTH", unit)
|
|
secure:RegisterUnitEvent("UNIT_MAXHEALTH", unit)
|
|
secure:RegisterUnitEvent("UNIT_NAME_UPDATE", unit)
|
|
end
|
|
|
|
-- returns whether or not the unit guid has changed since the last call to this
|
|
-- function. Always returns false if the current unit does not exist.
|
|
function UnitFrame:HasUnitChanged()
|
|
local unit = self.unit
|
|
if not UnitExists(unit) then
|
|
self.guid = nil
|
|
return false
|
|
end
|
|
|
|
local guid = UnitGUID(unit)
|
|
if self.guid == guid then
|
|
return false
|
|
else
|
|
self.guid = guid
|
|
return true
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UNIT_AURA(unit, info)
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
if self.auras:Update(info) then
|
|
self:UpdateHealthColor()
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UNIT_HEALTH()
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
self:UpdateHealth()
|
|
self:UpdateHealthColor()
|
|
end
|
|
|
|
function UnitFrame:UNIT_MAXHEALTH()
|
|
if self:HasUnitChanged() then
|
|
self:UpdateAll(true)
|
|
return
|
|
end
|
|
self:UpdateHealth()
|
|
self:UpdateHealthColor()
|
|
end
|
|
|
|
function UnitFrame:UNIT_TARGET()
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
|
|
function UnitFrame:PLAYER_TARGET_CHANGED()
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
|
|
function UnitFrame:UNIT_NAME_UPDATE()
|
|
self:UpdateName()
|
|
end
|
|
|
|
function UnitFrame:OnShow()
|
|
self:StartRangeTicker()
|
|
self:UpdateAll(self:HasUnitChanged())
|
|
end
|
|
|
|
function UnitFrame:OnHide()
|
|
self.guid = nil
|
|
self:StopRangeTicker()
|
|
end
|
|
|
|
function UnitFrame:UpdateAll(unitChanged)
|
|
if not UnitExists(self.unit) then
|
|
return
|
|
end
|
|
if unitChanged then
|
|
self.auras:Reset()
|
|
end
|
|
self:UpdateHealth()
|
|
self:UpdateHealthColor()
|
|
self:UpdateRange()
|
|
self:UpdateName()
|
|
end
|
|
|
|
function UnitFrame:UpdateHealth()
|
|
local unit = self.unit
|
|
local val = UnitHealth(unit)
|
|
local max = UnitHealthMax(unit)
|
|
|
|
self.hp:SetRange(0, max)
|
|
self.hp:SetValue(val)
|
|
end
|
|
|
|
function UnitFrame:UpdateHealthColor()
|
|
local unit = self.unit
|
|
local val = UnitHealth(unit)
|
|
local max = UnitHealthMax(unit)
|
|
|
|
if max == 0 then
|
|
return
|
|
end
|
|
local pct = val / max
|
|
local isFriend = UnitIsFriend("player", unit)
|
|
|
|
if not isFriend then
|
|
self.hp:SetColor(unpack(colors.hostile))
|
|
elseif self.auras.statusCount["Immune"] then
|
|
self.hp:SetColor(unpack(colors.immune))
|
|
elseif self.auras.statusCount["Bomb"] then
|
|
self.hp:SetColor(unpack(colors.bomb))
|
|
elseif self.auras.statusCount["Magic"] then
|
|
self.hp:SetColor(unpack(colors.magic))
|
|
elseif self.auras.statusCount["Curse"] then
|
|
self.hp:SetColor(unpack(colors.curse))
|
|
elseif self.auras.statusCount["Poison"] then
|
|
self.hp:SetColor(unpack(colors.poison))
|
|
elseif self.auras.statusCount["Disease"] then
|
|
self.hp:SetColor(unpack(colors.disease))
|
|
elseif isFriend and UnitIsDead(unit) then
|
|
self.hp:SetColor(unpack(colors.dead))
|
|
elseif isFriend and pct >= 0.90 then
|
|
self.hp:SetColor(unpack(colors.healthy))
|
|
elseif isFriend and pct >= 0.75 then
|
|
local progress = (pct - 0.75) / (0.90-0.75)
|
|
self.hp:SetInterpolatedColor(colors.mid, colors.high, progress)
|
|
else
|
|
local progress = pct / 0.75
|
|
-- Quadratic progress so we get to the dangerous colors faster as hp
|
|
-- goes lower
|
|
progress = progress * progress
|
|
self.hp:SetInterpolatedColor(colors.low, colors.mid, progress)
|
|
end
|
|
end
|
|
|
|
function UnitFrame:UpdateName()
|
|
if UnitIsPlayer(self.unit) then
|
|
local _, class = UnitClass(self.unit)
|
|
local color = RAID_CLASS_COLORS[class]
|
|
self.name:SetTextColor(color.r, color.g, color.b)
|
|
else
|
|
self.name:SetTextColor(1, 1, 1)
|
|
end
|
|
self.name:SetText(UnitName(self.unit):sub(1, 5))
|
|
end
|
|
|
|
--[[
|
|
-- UNIT_AURA
|
|
-- UNIT_CLASSIFICATION_CHANGED
|
|
-- UNIT_COMBAT
|
|
-- UNIT_CONNECTION
|
|
-- UNIT_DISPLAYPOWER
|
|
-- UNIT_FACTION
|
|
-- UNIT_FLAGS
|
|
-- UNIT_LEVEL
|
|
-- UNIT_MANA
|
|
-- UNIT_HEALTH_PREDICTION
|
|
-- UNIT_PHASE
|
|
]]--
|
|
|