You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

336 lines
13 KiB

--========================================================--
-- Scorpio UnitFrame Cast API --
-- --
-- Author : kurapica125@outlook.com --
-- Create Date : 2021/03/06 --
--========================================================--
--========================================================--
Scorpio "Scorpio.Secure.UnitFrame.HealthAPI" "1.0.0"
--========================================================--
namespace "Scorpio.Secure.UnitFrame"
import "System.Reactive"
import "System.Toolset"
------------------------------------------------------------
-- UNIT HEALTH --
------------------------------------------------------------
local _PlayerClass = select(2, UnitClass("player"))
local _DISPELLABLE = ({
["MAGE"] = { Curse = Color.CURSE, },
["DRUID"] = { Poison = Color.POISON, Curse = Color.CURSE, Magic = Color.MAGIC },
["PALADIN"] = { Poison = Color.POISON, Disease = Color.DISEASE, Magic = Color.MAGIC },
["PRIEST"] = { Disease = Color.DISEASE,Magic = Color.MAGIC },
["SHAMAN"] = { Curse = Color.CURSE, Magic = Color.MAGIC },
["WARLOCK"] = { Magic = Color.MAGIC, },
["MONK"] = { Poison = Color.POISON, Disease = Color.DISEASE, Magic = Color.MAGIC },
})[_PlayerClass] or false
local _UnitGUIDMap = {}
local _UnitHealthMap = {}
local _UnitHealthSubject = Subject()
local _UnitMaxHealthSubject = Subject()
function RegisterFrequentHealthUnit(unit, guid, health)
local oguid = _UnitGUIDMap[unit]
if oguid == guid then return end
_UnitGUIDMap[unit] = guid
if guid then
_UnitHealthMap[guid] = health
_UnitGUIDMap[guid] = (_UnitGUIDMap[guid] or 0) + 1
end
if oguid and _UnitGUIDMap[oguid] then
_UnitGUIDMap[oguid] = _UnitGUIDMap[oguid] - 1
if _UnitGUIDMap[oguid] <= 0 then
_UnitGUIDMap[oguid] = nil
_UnitHealthMap[oguid] = nil
end
end
end
__CombatEvent__ "SWING_DAMAGE" "RANGE_DAMAGE" "SPELL_DAMAGE" "SPELL_PERIODIC_DAMAGE" "DAMAGE_SPLIT" "DAMAGE_SHIELD" "ENVIRONMENTAL_DAMAGE" "SPELL_HEAL" "SPELL_PERIODIC_HEAL"
function COMBAT_HEALTH_CHANGE(_, event, _, _, _, _, _, destGUID, _, _, _, arg12, arg13, arg14, arg15, arg16)
local health = _UnitHealthMap[destGUID]
if not health then return end
local change = 0
if event == "SWING_DAMAGE" then
-- amount : arg12
-- overkill : arg13
change = (arg13 > 0 and arg13 or 0) - arg12
elseif event == "RANGE_DAMAGE" or event == "SPELL_DAMAGE" or event == "SPELL_PERIODIC_DAMAGE" or event == "DAMAGE_SPLIT" or event == "DAMAGE_SHIELD" then
-- amount : arg15
-- overkill : arg16
change = (arg16 > 0 and arg16 or 0) - arg15
elseif event == "ENVIRONMENTAL_DAMAGE" then
-- amount : arg13
-- overkill : arg14
change = (arg14 > 0 and arg14 or 0) - arg13
elseif event == "SPELL_HEAL" or event == "SPELL_PERIODIC_HEAL" then
-- amount : arg15
-- overhealing : arg16
change = arg15 - arg16
end
if change == 0 then return end
health = health + change
if change < 0 then
if health < 0 then health = 0 end
elseif change > 0 then
local unit = GetUnitFromGUID(destGUID)
if not unit then
_UnitGUIDMap[destGUID] = nil
_UnitHealthMap[destGUID]= nil
return
end
local max = UnitHealthMax(unit)
if health > max then health = max end
end
_UnitHealthMap[destGUID] = health
-- Distribute the new health
for unit in GetUnitsFromGUID(destGUID) do
_UnitHealthSubject:OnNext(unit)
end
end
__SystemEvent__()
function PLAYER_ENTERING_WORLD()
-- Clear the Registered Units
wipe(_UnitGUIDMap)
wipe(_UnitHealthMap)
end
__SystemEvent__(Scorpio.IsRetail and "UNIT_HEALTH" or "UNIT_HEALTH_FREQUENT")
function UNIT_HEALTH(unit)
local guid = UnitGUID(unit)
if _UnitHealthMap[guid] then
_UnitHealthMap[guid] = UnitHealth(unit)
end
_UnitHealthSubject:OnNext(unit)
end
__SystemEvent__()
function UNIT_MAXHEALTH(unit)
_UnitMaxHealthSubject:OnNext(unit)
_UnitHealthSubject:OnNext(unit)
end
__Static__() __AutoCache__()
function Wow.UnitHealth()
-- Use the Next for a tiny delay after the UnitHealthMax
return Wow.FromUnitEvent(_UnitHealthSubject):Next():Map(UnitHealth)
end
__Static__() __AutoCache__()
function Wow.UnitHealthFrequent()
-- Based on the CLEU
return Wow.FromUnitEvent(_UnitHealthSubject):Next():Map(function(unit)
local guid = UnitGUID(unit)
local health = _UnitHealthMap[guid]
if health and _UnitGUIDMap[unit] == guid then return health end
-- Register the unit
health = health or UnitHealth(unit)
RegisterFrequentHealthUnit(unit, guid, health)
return health
end)
end
__Static__() __AutoCache__()
function Wow.UnitHealthPercent()
return Wow.FromUnitEvent(_UnitHealthSubject):Next():Map(function(unit)
local health = UnitHealth(unit)
local max = UnitHealthMax(unit)
return floor(0.5 + (health and max and health / max * 100) or 0)
end)
end
__Static__() __AutoCache__()
function Wow.UnitHealthPercentFrequent()
-- Based on the CLEU
return Wow.FromUnitEvent(_UnitHealthSubject):Next():Map(function(unit)
local guid = UnitGUID(unit)
local health = _UnitHealthMap[guid]
if not (health and _UnitGUIDMap[unit] == guid) then
-- Register the unit
health = health or UnitHealth(unit)
RegisterFrequentHealthUnit(unit, guid, health)
end
local max = UnitHealthMax(unit)
return floor(0.5 + (health and max and health / max * 100) or 0)
end)
end
__Static__() __AutoCache__()
function Wow.UnitHealthMax()
local minMax = { min = 0 }
return Wow.FromUnitEvent(_UnitMaxHealthSubject):Map(function(unit)
minMax.max = UnitHealthMax(unit) or 100
return minMax
end)
end
__Static__() __AutoCache__() -- Too complex to do it here, leave it to the indicators or map chains
function Wow.UnitHealPrediction()
return Wow.FromUnitEvent("UNIT_HEALTH", "UNIT_MAXHEALTH", "UNIT_HEAL_PREDICTION", "UNIT_ABSORB_AMOUNT_CHANGED", "UNIT_HEAL_ABSORB_AMOUNT_CHANGED"):Next()
end
__Arguments__{ (ColorType + Boolean)/nil, ColorType/nil }
__Static__()
function Wow.UnitConditionColor(useClassColor, smoothEndColor)
local defaultColor = type(useClassColor) == "table" and useClassColor or Color.GREEN
useClassColor = useClassColor == true
if smoothEndColor then
local cache = Color{ r = 1, g = 1, b = 1 }
local br, bg, bb = smoothEndColor.r, smoothEndColor.g, smoothEndColor.b
if _DISPELLABLE then
return Wow.FromUnitEvent("UNIT_HEALTH", "UNIT_MAXHEALTH", "UNIT_AURA"):Next():Map(function(unit)
local index = 1
repeat
local n, _, _, d = UnitAura(unit, index, "HARMFUL")
local color = _DISPELLABLE[d]
if color then return color end
index = index + 1
until not n
local health = UnitHealth(unit)
local maxHealth = UnitHealthMax(unit)
local pct = health / maxHealth
local dcolor = defaultColor
if useClassColor then
local _, cls= UnitClass(unit)
if cls then dcolor = Color[cls] end
end
cache.r = br + (dcolor.r - br) * pct
cache.g = bg + (dcolor.g - bg) * pct
cache.b = bb + (dcolor.b - bb) * pct
return cache
end)
else
return Wow.FromUnitEvent(_UnitHealthSubject):Next():Map(function(unit)
local health = _UnitHealthMap[unit] or UnitHealth(unit)
local maxHealth = UnitHealthMax(unit)
local pct = health / maxHealth
local dcolor = defaultColor
if useClassColor then
local _, cls= UnitClass(unit)
if cls then dcolor = Color[cls] end
end
cache.r = br + (dcolor.r - br) * pct
cache.g = bg + (dcolor.g - bg) * pct
cache.b = bb + (dcolor.b - bb) * pct
return cache
end)
end
else
if _DISPELLABLE then
return Wow.FromUnitEvent("UNIT_AURA"):Next():Map(function(unit)
local index = 1
repeat
local n, _, _, d = UnitAura(unit, index, "HARMFUL")
local color = _DISPELLABLE[d]
if color then return color end
index = index + 1
until not n
local dcolor = defaultColor
if useClassColor then
local _, cls= UnitClass(unit)
if cls then dcolor = Color[cls] end
end
return dcolor
end)
else
return Wow.FromUnitEvent():Map(function(unit)
local dcolor = defaultColor
if useClassColor then
local _, cls= UnitClass(unit)
if cls then dcolor = Color[cls] end
end
return dcolor
end)
end
end
end
------------------------------------------------------------
-- Wow Classic --
------------------------------------------------------------
if Scorpio.IsRetail then return end
_Parent.UnitGetIncomingHeals = _G.UnitGetIncomingHeals or Toolset.fakefunc
_Parent.UnitGetTotalAbsorbs = _G.UnitGetTotalAbsorbs or Toolset.fakefunc
_Parent.UnitGetTotalHealAbsorbs = _G.UnitGetTotalHealAbsorbs or Toolset.fakefunc
--- Try Get LibHealComm
pcall(LoadAddOn, "LibHealComm-4.0")
local ok, LibHealComm = pcall(_G.LibStub, "LibHealComm-4.0")
if not (ok and LibHealComm) then return end
local function HealComm_HealUpdated(event, _, _, _, _, ...)
for i = 1, select("#", ...) do
local guid = select(i, ...)
if guid then
for unit in GetUnitsFromGUID(guid) do
FireSystemEvent("UNIT_HEAL_PREDICTION", unit)
end
end
end
end
local function HealComm_HealModified(event, guid)
for unit in GetUnitsFromGUID(guid) do
FireSystemEvent("UNIT_HEAL_PREDICTION", unit)
end
end
LibHealComm.RegisterCallback("Scorpio", "HealComm_HealStarted", HealComm_HealUpdated)
LibHealComm.RegisterCallback("Scorpio", "HealComm_HealStopped", HealComm_HealUpdated)
LibHealComm.RegisterCallback("Scorpio", "HealComm_HealDelayed", HealComm_HealUpdated)
LibHealComm.RegisterCallback("Scorpio", "HealComm_HealUpdated", HealComm_HealUpdated)
LibHealComm.RegisterCallback("Scorpio", "HealComm_ModifierChanged", HealComm_HealModified)
LibHealComm.RegisterCallback("Scorpio", "HealComm_GUIDDisappeared", HealComm_HealModified)
INCOMING_HEAL_WINDOW = 4
_Parent.UnitGetIncomingHeals = _G.UnitGetIncomingHeals or function(unit, casterUnit)
local guid = UnitGUID(unit)
local incoming = 0
if casterUnit then
local casterGUID = UnitGUID(casterUnit)
incoming = (LibHealComm:GetHealAmount(guid, LibHealComm.ALL_HEALS, GetTime() + INCOMING_HEAL_WINDOW, casterGUID) or 0) * (LibHealComm:GetHealModifier(guid) or 1)
else
incoming = (LibHealComm:GetHealAmount(guid, LibHealComm.ALL_HEALS, GetTime() + INCOMING_HEAL_WINDOW) or 0) * (LibHealComm:GetHealModifier(guid) or 1)
end
return incoming
end