Initial commit
This commit is contained in:
200
src/types/auralist.lua
Normal file
200
src/types/auralist.lua
Normal file
@@ -0,0 +1,200 @@
|
||||
-- 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 omi = select(2, ...)
|
||||
local types = omi.GetModule("types")
|
||||
local AuraTrigger = types.AuraTrigger
|
||||
|
||||
local AuraList = types.CreateClass("AuraList")
|
||||
types.AuraList = AuraList
|
||||
|
||||
local statusLists = {
|
||||
Immune = {
|
||||
[642] = true, -- Divine Shield, Paladin
|
||||
[186265] = true -- Aspect of the Turtle, Hunter
|
||||
},
|
||||
Bomb = {
|
||||
[642] = true, -- Divine Shield, Paladin
|
||||
[186265] = true, -- Aspect of the Turtle, Hunter
|
||||
[381615] = true, -- Raszageth, Static Charge
|
||||
[377467] = true -- Raszageth, Fulminating Charge
|
||||
},
|
||||
Burn = {
|
||||
[642] = true, -- Divine Shield, Paladin
|
||||
[186265] = true -- Aspect of the Turtle, Hunter
|
||||
}
|
||||
}
|
||||
|
||||
local function ForEachAuraSlots(unit, fn, continuationToken, ...)
|
||||
local GetAuraDataBySlot = C_UnitAuras.GetAuraDataBySlot
|
||||
local n = select('#', ...)
|
||||
for i=1, n do
|
||||
local slot = select(i, ...)
|
||||
fn(GetAuraDataBySlot(unit, slot))
|
||||
end
|
||||
return continuationToken
|
||||
end
|
||||
|
||||
local function ForEachAuraFiltered(unit, filter, fn)
|
||||
local UnitAuraSlots = UnitAuraSlots
|
||||
local continuationToken = nil
|
||||
repeat
|
||||
continuationToken = ForEachAuraSlots(unit, fn, UnitAuraSlots(unit, filter, nil, continuationToken))
|
||||
until (continuationToken == nil)
|
||||
end
|
||||
|
||||
-- Similar to what AuraUtils.ForEachAura does except the callback fn takes a
|
||||
-- UnitAuraInfo structure rather than individual arguments
|
||||
local function ForEachAura(unit, fn)
|
||||
ForEachAuraFiltered(unit, "HELPFUL", fn)
|
||||
ForEachAuraFiltered(unit, "HARMFUL", fn)
|
||||
end
|
||||
|
||||
function AuraList:Init(unitframe)
|
||||
self.unitframe = unitframe
|
||||
self.unit = unitframe.unit
|
||||
|
||||
self.statusCount = {}
|
||||
self.auras = {} -- map AuraInstanceID to UnitAuraInfo table
|
||||
|
||||
|
||||
self.triggers = {}
|
||||
self.triggersBySpell = {}
|
||||
end
|
||||
|
||||
|
||||
function AuraList:AddTrigger(trigger)
|
||||
table.insert(self.triggers, trigger)
|
||||
if trigger:IsInstanceOf(AuraTrigger) then
|
||||
local spellId = trigger.spellId
|
||||
if self.triggersBySpell[spellId] == nil then
|
||||
self.triggersBySpell[spellId] = {}
|
||||
end
|
||||
table.insert(self.triggersBySpell[spellId], trigger)
|
||||
end
|
||||
end
|
||||
|
||||
-- Scan all the auras of the owning unit and build the aura list from scratch
|
||||
-- This should only ever happen when the unit is first assigned to the frame
|
||||
function AuraList:Reset()
|
||||
self.auras = {}
|
||||
|
||||
self.statusCount = {}
|
||||
|
||||
for _, trigger in ipairs(self.triggers) do
|
||||
trigger:Reset()
|
||||
end
|
||||
|
||||
ForEachAura(self.unit, function(aura)
|
||||
self:AddAura(aura)
|
||||
end)
|
||||
end
|
||||
|
||||
function AuraList:AddAura(aura)
|
||||
local statusChanged = false
|
||||
self.auras[aura.auraInstanceID] = aura
|
||||
|
||||
-- Update status counts associated with this aura
|
||||
local count = self.statusCount
|
||||
for _, status in ipairs(self:GetStatusForAura(aura)) do
|
||||
if not count[status] then
|
||||
count[status] = 1
|
||||
statusChanged = true
|
||||
else
|
||||
count[status] = count[status] + 1
|
||||
end
|
||||
end
|
||||
|
||||
for _, trigger in ipairs(self.triggersBySpell[aura.spellId] or {}) do
|
||||
trigger:AddAura(aura)
|
||||
end
|
||||
|
||||
-- Update triggers associated with this aura
|
||||
return statusChanged
|
||||
end
|
||||
|
||||
-- Returns a table with every status that applies to the given aura
|
||||
function AuraList:GetStatusForAura(aura)
|
||||
local status = {}
|
||||
if aura.dispelName and aura.isHarmful then
|
||||
table.insert(status, aura.dispelName)
|
||||
end
|
||||
for statusName, auraIds in pairs(statusLists) do
|
||||
if auraIds[aura.spellId] then
|
||||
table.insert(status, statusName)
|
||||
end
|
||||
end
|
||||
return status
|
||||
end
|
||||
|
||||
function AuraList:UpdateAura(aura)
|
||||
for _, trigger in ipairs(self.triggersBySpell[aura.spellId] or {}) do
|
||||
trigger:UpdateAura(self.auras[aura.auraInstanceId], aura)
|
||||
end
|
||||
self.auras[aura.auraInstanceID] = aura
|
||||
end
|
||||
|
||||
function AuraList:RemoveAura(iid)
|
||||
local aura = self.auras[iid]
|
||||
if aura == nil then
|
||||
-- FIXME: Why does this happen? Are we actually missing important auras?
|
||||
return false
|
||||
end
|
||||
self.auras[iid] = nil
|
||||
|
||||
local statusChanged = false
|
||||
local count = self.statusCount
|
||||
for _, status in ipairs(self:GetStatusForAura(aura)) do
|
||||
if not count[status] then
|
||||
print("Removed a status that we didn't already have...?", status, aura.spellId)
|
||||
elseif count[status] == 1 then
|
||||
count[status] = nil
|
||||
statusChanged = true
|
||||
else
|
||||
count[status] = count[status] - 1
|
||||
end
|
||||
end
|
||||
|
||||
for _, trigger in ipairs(self.triggersBySpell[aura.spellId] or {}) do
|
||||
trigger:RemoveAura(aura)
|
||||
end
|
||||
return statusChanged
|
||||
end
|
||||
|
||||
function AuraList:Update(info)
|
||||
local GetAuraDataByAuraInstanceID = C_UnitAuras.GetAuraDataByAuraInstanceID
|
||||
local statusChanged = false
|
||||
for _, aura in ipairs(info.addedAuras or {}) do
|
||||
if self:AddAura(aura) then
|
||||
statusChanged = true
|
||||
end
|
||||
end
|
||||
|
||||
for _, iid in ipairs(info.updatedAuraInstanceIDs or {}) do
|
||||
local aura = GetAuraDataByAuraInstanceID(self.unit, iid)
|
||||
-- FIXME: Why can aura be nil? Are we actually missing important auras?
|
||||
if aura ~= nil then
|
||||
self:UpdateAura(aura)
|
||||
end
|
||||
end
|
||||
|
||||
for _, iid in ipairs(info.removedAuraInstanceIDs or {}) do
|
||||
if self:RemoveAura(iid) then
|
||||
statusChanged = true
|
||||
end
|
||||
end
|
||||
return statusChanged
|
||||
end
|
61
src/types/indicator.lua
Normal file
61
src/types/indicator.lua
Normal file
@@ -0,0 +1,61 @@
|
||||
-- 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 omi = select(2, ...)
|
||||
local types = omi.GetModule("types")
|
||||
local AuraTrigger = types.AuraTrigger
|
||||
|
||||
local Indicator = types.CreateClass("Indicator")
|
||||
types.Indicator = Indicator
|
||||
|
||||
-- TODO: make different indicators, such as texture, text, border
|
||||
-- TODO: spellid feels out of place but I wanna quickly get something for now.
|
||||
-- Should rethink this when Trigger is its own class
|
||||
function Indicator:Init(unitframe, auralist, size, point, x, y, color, spellId)
|
||||
local frame = unitframe.overlays
|
||||
|
||||
local texture = frame:CreateTexture(nil, "ARTWORK")
|
||||
self.texture = texture
|
||||
texture:Hide()
|
||||
texture:SetTexture("Interface\\Addons\\OmicronFrames\\media\\textures\\square_b")
|
||||
texture:SetSize(size, size)
|
||||
texture:SetPoint(point, x, y)
|
||||
self:SetColor(color)
|
||||
texture:SetVertexColor(unpack(color))
|
||||
texture:SetDrawLayer("ARTWORK")
|
||||
|
||||
local fn = function(activate)
|
||||
if activate then
|
||||
self:Show()
|
||||
else
|
||||
self:Hide()
|
||||
end
|
||||
end
|
||||
local trigger = AuraTrigger:new(fn, spellId, true)
|
||||
auralist:AddTrigger(trigger)
|
||||
end
|
||||
|
||||
function Indicator:SetColor(color)
|
||||
self.texture:SetVertexColor(unpack(color))
|
||||
end
|
||||
|
||||
function Indicator:Show()
|
||||
self.texture:Show()
|
||||
end
|
||||
|
||||
function Indicator:Hide()
|
||||
self.texture:Hide()
|
||||
end
|
53
src/types/object.lua
Normal file
53
src/types/object.lua
Normal file
@@ -0,0 +1,53 @@
|
||||
-- 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 omi = select(2, ...)
|
||||
local Object = omi.GetModule("types").Object
|
||||
local IsDerivedFrom = omi.GetModule("types").IsDerivedFrom
|
||||
|
||||
-- Create a new instance of Object or (more likely) any inheriting class
|
||||
function Object.new(class, ...)
|
||||
local o = {}
|
||||
setmetatable(o, class)
|
||||
|
||||
if o.Init ~= nil then
|
||||
o:Init(...)
|
||||
end
|
||||
return o
|
||||
end
|
||||
|
||||
-- Returns true if the Object is a class, false if it is an instance object
|
||||
function Object.IsClassObject(self)
|
||||
return self.__typeInfo.class == self
|
||||
end
|
||||
|
||||
-- Returns true if the Object is an instance, false if it is a class
|
||||
function Object.IsInstanceObject(self)
|
||||
return self.__typeInfo.class ~= self
|
||||
end
|
||||
|
||||
-- return true if this object is an instance of the given class
|
||||
function Object:IsInstanceOf(class)
|
||||
if not self:IsInstanceObject() or not class:IsClassObject() then
|
||||
return false
|
||||
end
|
||||
return IsDerivedFrom(self:GetClass(), class)
|
||||
end
|
||||
|
||||
-- Return the class of any object
|
||||
function Object.GetClass(self)
|
||||
return self.__typeInfo.class
|
||||
end
|
71
src/types/statusbar.lua
Normal file
71
src/types/statusbar.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
-- 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 omi = select(2, ...)
|
||||
local types = omi.GetModule("types")
|
||||
|
||||
types.StatusBar = types.CreateClass("StatusBar")
|
||||
local StatusBar = types.StatusBar
|
||||
|
||||
function StatusBar:Init(parent, width, height)
|
||||
-- parent: the parent frame
|
||||
-- pct: nil if no percent value is shown, otherwise "left", "right" or "center"
|
||||
-- value: nil if no value is shown, otherwise "left", "right" or "center"
|
||||
-- text: nil if no text is shown, otherwise "left", "right" or "center"
|
||||
-- stack: another StatusBar we will stack under
|
||||
self.parent = parent
|
||||
|
||||
local parentFrame = parent.secureFrame
|
||||
local bar = CreateFrame("StatusBar", nil, parentFrame)
|
||||
bar:SetFrameStrata("MEDIUM")
|
||||
bar:SetFrameLevel(10)
|
||||
bar:SetPoint("TOPLEFT", parentFrame, "TOPLEFT", 0, 0)
|
||||
|
||||
bar:SetSize(width, height)
|
||||
bar:SetStatusBarTexture("Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal")
|
||||
bar:GetStatusBarTexture():SetHorizTile(false)
|
||||
bar:GetStatusBarTexture():SetVertTile(false)
|
||||
self.bar = bar
|
||||
|
||||
local bg = bar:CreateTexture(nil, "BACKGROUND")
|
||||
bg:SetTexture("Interface\\Addons\\OmicronFrames\\media\\textures\\bar_subtle_diagonal")
|
||||
bg:SetAllPoints(true)
|
||||
self.bg = bg
|
||||
|
||||
self:SetRange(0, 1)
|
||||
self:SetValue(1)
|
||||
bar:Show()
|
||||
end
|
||||
|
||||
function StatusBar:SetRange(min, max)
|
||||
self.bar:SetMinMaxValues(min, max)
|
||||
end
|
||||
|
||||
function StatusBar:SetValue(value)
|
||||
self.bar:SetValue(value)
|
||||
end
|
||||
|
||||
function StatusBar:SetInterpolatedColor(start, stop, progress)
|
||||
local r = start[1] + (stop[1] - start[1])*progress
|
||||
local g = start[2] + (stop[2] - start[2])*progress
|
||||
local b = start[3] + (stop[3] - start[3])*progress
|
||||
self:SetColor(r, g, b)
|
||||
end
|
||||
|
||||
function StatusBar:SetColor(r, g, b)
|
||||
self.bar:SetStatusBarColor(r, g, b)
|
||||
self.bg:SetVertexColor(0.2*r, 0.2*g, 0.2*b)
|
||||
end
|
136
src/types/trigger.lua
Normal file
136
src/types/trigger.lua
Normal file
@@ -0,0 +1,136 @@
|
||||
-- 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 omi = select(2, ...)
|
||||
local types = omi.GetModule("types")
|
||||
|
||||
types.Trigger = types.CreateClass("Trigger")
|
||||
local Trigger = types.Trigger
|
||||
|
||||
|
||||
-- Constructor for Trigger.
|
||||
-- fn:
|
||||
-- The callback function that gets called every time this trigger changes
|
||||
-- states between active and inactive. Function takes one boolean argument.
|
||||
function Trigger:Init(fn)
|
||||
self.fn = fn
|
||||
end
|
||||
|
||||
-- Returns whether the trigger is active
|
||||
function Trigger:IsActive()
|
||||
return false
|
||||
end
|
||||
|
||||
-- Reset the trigger to the default state. Does run the untrigger callback if
|
||||
-- the trigger is active when Reset is called.
|
||||
function Trigger:Reset()
|
||||
end
|
||||
|
||||
|
||||
types.AuraTrigger = types.CreateClass("AuraTrigger", Trigger)
|
||||
local AuraTrigger = types.AuraTrigger
|
||||
|
||||
-- Constructor for AuraTrigger
|
||||
-- fn:
|
||||
-- The callback function that gets called every time this trigger changes
|
||||
-- states between active and inactive. Function takes one boolean argument.
|
||||
--
|
||||
-- requiredCount = 1:
|
||||
-- The minimum number of conditions that must be met before the trigger
|
||||
-- activates. What exactly this means depends on the trigger, examples are
|
||||
-- # of matching auras, # of stacks.
|
||||
--
|
||||
-- invert = false:
|
||||
-- If the trigger is inverted it will activate when count < requiredCount.
|
||||
-- If the trigger is not inverted it will activate when count >= requiredCount.
|
||||
function AuraTrigger:Init(fn, spellId, own, requiredCount, invert)
|
||||
Trigger.Init(self, fn)
|
||||
self.spellId = spellId
|
||||
self.requiredCount = requiredCount or 1
|
||||
self.own = own
|
||||
self.count = 0
|
||||
self.invert = invert or false
|
||||
end
|
||||
|
||||
-- Reset the trigger to the default state. Does run the untrigger callback if
|
||||
-- the trigger is active when it is called
|
||||
function AuraTrigger:Reset()
|
||||
local before = self:IsActive()
|
||||
self.count = 0
|
||||
local after = self:IsActive()
|
||||
if before ~= after then
|
||||
self.fn(after)
|
||||
end
|
||||
end
|
||||
|
||||
-- Return true if the aura matches the trigger
|
||||
-- aura:
|
||||
-- Must be a valid UnitAuraInfo structure.
|
||||
function AuraTrigger:IsMatching(aura)
|
||||
if aura.spellId ~= aura.spellId then
|
||||
return false
|
||||
end
|
||||
if self.own and aura.sourceUnit ~= "player" then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Inform the trigger about an added aura
|
||||
-- aura:
|
||||
-- Must be a valid UnitAuraInfo
|
||||
function AuraTrigger:AddAura(aura)
|
||||
if not self:IsMatching(aura) then
|
||||
return
|
||||
end
|
||||
self.count = self.count + 1
|
||||
|
||||
-- Be mindful, this works only if count always changes by 1.
|
||||
if self.count == self.requiredCount then
|
||||
self.fn(not self.invert)
|
||||
end
|
||||
end
|
||||
|
||||
-- Inform the trigger about an updated aura
|
||||
-- before:
|
||||
-- Must be a valid UnitAuraInfo for the aura before the update
|
||||
-- after:
|
||||
-- Must be a valid UnitAuraInfo for the aura after the update
|
||||
function AuraTrigger:UpdateAura(before, after)
|
||||
--
|
||||
end
|
||||
|
||||
-- Inform the trigger about an aura that got removed
|
||||
-- Must be a valid UnitAuraInfo for the aura before it got removed
|
||||
function AuraTrigger:RemoveAura(aura)
|
||||
if not self:IsMatching(aura) then
|
||||
return
|
||||
end
|
||||
self.count = self.count - 1
|
||||
-- Be mindful, this works only if count always changes by 1.
|
||||
if self.count == self.requiredCount - 1 then
|
||||
self.fn(self.invert)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns true if the trigger is active, false otherwise
|
||||
function AuraTrigger:IsActive()
|
||||
if self.invert then
|
||||
return self.count < self.requiredCount
|
||||
else
|
||||
return self.count >= self.requiredCount
|
||||
end
|
||||
end
|
53
src/types/types.lua
Normal file
53
src/types/types.lua
Normal file
@@ -0,0 +1,53 @@
|
||||
-- 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")
|
||||
|
||||
-- Create any class, possibly top level
|
||||
local function CreateClassRaw(name, parent)
|
||||
local c = {}
|
||||
c.__typeInfo = {
|
||||
name = name,
|
||||
class = c,
|
||||
parent = parent
|
||||
}
|
||||
c.__index = c
|
||||
if parent ~= nil then
|
||||
setmetatable(c, parent)
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
types.Object = CreateClassRaw("Object")
|
||||
|
||||
-- Create a new class, eventually rooted at Object
|
||||
function types.CreateClass(name, parent)
|
||||
return CreateClassRaw(name, parent or types.Object)
|
||||
end
|
||||
|
||||
-- Return true if a is derived from b or if a is b
|
||||
function types.IsDerivedFrom(a, b)
|
||||
local current = a
|
||||
repeat
|
||||
if current == b then
|
||||
return true
|
||||
end
|
||||
current = current.__typeInfo.parent
|
||||
until current == nil;
|
||||
return false
|
||||
end
|
396
src/types/unitframe.lua
Normal file
396
src/types/unitframe.lua
Normal file
@@ -0,0 +1,396 @@
|
||||
-- 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)
|
||||
self.unit = unit
|
||||
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.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
|
||||
|
||||
function UnitFrame:StopRangeTicker()
|
||||
if self.rangeTicker then
|
||||
self.rangeTicker:Cancel()
|
||||
self.rangeTicker = nil
|
||||
end
|
||||
end
|
||||
|
||||
function UnitFrame:UpdateRange()
|
||||
local unit = self.unit
|
||||
if 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", "menu")
|
||||
|
||||
|
||||
if self.unit == "player" then
|
||||
secure.menu = function(self)
|
||||
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor", 0 ,0)
|
||||
end
|
||||
elseif self.unit == "target" then
|
||||
secure.menu = function(self)
|
||||
ToggleDropDownMenu(1, nil, TargetFrameDropDown, "cursor", 0 ,0)
|
||||
end
|
||||
elseif self.unit == "focus" then
|
||||
secure.menu = function(self)
|
||||
ToggleDropDownMenu(1, nil, FocusFrameDropDown, "cursor", 0 ,0)
|
||||
end
|
||||
elseif self.unit:match("party%d") == self.unit then
|
||||
--secure.menu = function(self)
|
||||
-- ToggleDropDownMenu(1, nil, FriendsFrameDropDown, "cursor", 0 ,0)
|
||||
--end
|
||||
end
|
||||
return secure
|
||||
end
|
||||
|
||||
function UnitFrame:Enable()
|
||||
if self.unit ~= "player" then
|
||||
RegisterUnitWatch(self.secureFrame)
|
||||
else
|
||||
self.secureFrame:Show()
|
||||
end
|
||||
if UnitExists(self.unit) then
|
||||
self:UpdateAll(self:HasUnitChanged())
|
||||
end
|
||||
end
|
||||
|
||||
function UnitFrame:Disable()
|
||||
if self.unit ~= "player" then
|
||||
UnregisterUnitWatch(self.secure_frame)
|
||||
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)
|
||||
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: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()
|
||||
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
|
||||
|
||||
--[[
|
||||
-- UNIT_AURA
|
||||
-- UNIT_CLASSIFICATION_CHANGED
|
||||
-- UNIT_COMBAT
|
||||
-- UNIT_CONNECTION
|
||||
-- UNIT_DISPLAYPOWER
|
||||
-- UNIT_FACTION
|
||||
-- UNIT_FLAGS
|
||||
-- UNIT_LEVEL
|
||||
-- UNIT_MANA
|
||||
-- UNIT_HEALTH_PREDICTION
|
||||
-- UNIT_PHASE
|
||||
]]--
|
||||
|
Reference in New Issue
Block a user