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.
341 lines
11 KiB
341 lines
11 KiB
--========================================================--
|
|
-- Scorpio UnitFrame Cast API --
|
|
-- --
|
|
-- Author : kurapica125@outlook.com --
|
|
-- Create Date : 2021/03/06 --
|
|
--========================================================--
|
|
|
|
--========================================================--
|
|
Scorpio "Scorpio.Secure.UnitFrame.CastAPI" "1.0.0"
|
|
--========================================================--
|
|
|
|
namespace "Scorpio.Secure.UnitFrame"
|
|
|
|
import "System.Reactive"
|
|
import "System.Toolset"
|
|
|
|
------------------------------------------------------------
|
|
-- Unit Cast API --
|
|
------------------------------------------------------------
|
|
local _CurrentCastID = {}
|
|
local _CurrentCastEndTime = {}
|
|
|
|
local _UnitCastSubject = Subject()
|
|
local _UnitCastDelay = Subject()
|
|
local _UnitCastInterruptible = Subject()
|
|
local _UnitCastChannel = Subject()
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_START(unit, castID)
|
|
local n, _, t, s, e, _, _, i= UnitCastingInfo(unit)
|
|
if not n then return end
|
|
s, e = s / 1000, e / 1000
|
|
|
|
_CurrentCastID[unit] = castID
|
|
_CurrentCastEndTime[unit] = e
|
|
|
|
_UnitCastChannel:OnNext(unit, false)
|
|
_UnitCastSubject:OnNext(unit, n, t, s, e - s)
|
|
_UnitCastDelay:OnNext(unit, 0)
|
|
_UnitCastInterruptible:OnNext(unit, not i)
|
|
end
|
|
|
|
__SystemEvent__ "UNIT_SPELLCAST_FAILED" "UNIT_SPELLCAST_STOP" "UNIT_SPELLCAST_INTERRUPTED"
|
|
function UNIT_SPELLCAST_FAILED(unit, castID)
|
|
if _CurrentCastID[unit] and (not castID or castID == _CurrentCastID[unit]) then
|
|
_UnitCastSubject:OnNext(unit, nil, nil, 0, 0)
|
|
_UnitCastDelay:OnNext(unit, 0)
|
|
end
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_INTERRUPTIBLE(unit)
|
|
if not _CurrentCastID[unit] then return end
|
|
_UnitCastInterruptible:OnNext(unit, true)
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_NOT_INTERRUPTIBLE(unit)
|
|
if not _CurrentCastID[unit] then return end
|
|
_UnitCastInterruptible:OnNext(unit, false)
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_DELAYED(unit, castID)
|
|
if _CurrentCastID[unit] and (not castID or castID == _CurrentCastID[unit]) then
|
|
local n, _, t, s, e, _, _, i= UnitCastingInfo(unit)
|
|
if not n then return end
|
|
s, e = s / 1000, e / 1000
|
|
|
|
_UnitCastSubject:OnNext(unit, n, t, s, e - s)
|
|
_UnitCastDelay:OnNext(unit, e - _CurrentCastEndTime[unit])
|
|
end
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_CHANNEL_START(unit)
|
|
local n, _, t, s, e, _, i = UnitChannelInfo(unit)
|
|
if not n then return end
|
|
s, e = s / 1000, e / 1000
|
|
|
|
_CurrentCastID[unit] = nil
|
|
_CurrentCastEndTime[unit] = e
|
|
|
|
_UnitCastChannel:OnNext(unit, true)
|
|
_UnitCastSubject:OnNext(unit, n, t, s, e - s)
|
|
_UnitCastDelay:OnNext(unit, 0)
|
|
_UnitCastInterruptible:OnNext(unit, i)
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_CHANNEL_UPDATE(unit)
|
|
local n, _, t, s, e = UnitChannelInfo(unit)
|
|
if not n then return end
|
|
s, e = s / 1000, e / 1000
|
|
|
|
_UnitCastSubject:OnNext(unit, n, t, s, e - s)
|
|
_UnitCastDelay:OnNext(unit, e - _CurrentCastEndTime[unit])
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_CHANNEL_STOP(unit)
|
|
_UnitCastSubject:OnNext(unit, nil, nil, 0, 0)
|
|
_UnitCastDelay:OnNext(unit, 0)
|
|
end
|
|
|
|
__Static__() __AutoCache__()
|
|
function Wow.UnitCastCooldown()
|
|
local status = { start = 0, duration = 0 }
|
|
return Wow.FromUnitEvent(_UnitCastSubject):Map(function(unit, name, icon, start, duration)
|
|
if name then
|
|
status.start = start
|
|
status.duration = duration
|
|
else
|
|
-- Register the Unit Here
|
|
_CurrentCastID[unit]= 0
|
|
status.start = 0
|
|
status.duration = 0
|
|
end
|
|
return status
|
|
end)
|
|
end
|
|
|
|
__Static__() __AutoCache__()
|
|
function Wow.UnitCastChannel()
|
|
return Wow.FromUnitEvent(_UnitCastChannel):Map(function(unit, val) return val or false end)
|
|
end
|
|
|
|
__Static__() __AutoCache__()
|
|
function Wow.UnitCastInterruptible()
|
|
return Wow.FromUnitEvent(_UnitCastInterruptible):Map(function(unit, val) return val or false end)
|
|
end
|
|
|
|
__Static__() __AutoCache__()
|
|
function Wow.UnitCastName()
|
|
return Wow.FromUnitEvent(_UnitCastSubject):Map(function(unit, name) return name end)
|
|
end
|
|
|
|
__Static__() __AutoCache__()
|
|
function Wow.UnitCastIcon()
|
|
return Wow.FromUnitEvent(_UnitCastSubject):Map(function(unit, name, icon) return icon end)
|
|
end
|
|
|
|
__Static__() __AutoCache__()
|
|
function Wow.UnitCastDelay()
|
|
return Wow.FromUnitEvent(_UnitCastDelay):Map(function(unit, delay) return delay end)
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Classic Support --
|
|
------------------------------------------------------------
|
|
if Scorpio.IsRetail then return end
|
|
|
|
local HUNTER_AUTO = 75
|
|
local HUNTER_AIM = 19434
|
|
|
|
local _Cache = {}
|
|
local recycle = function(tbl) if tbl then tinsert(_Cache, wipe(tbl)) else return tremove(_Cache) or {} end end
|
|
local _CastingInfo = {}
|
|
local _PlayerGUID
|
|
local _CASTLINE = 0
|
|
local average = function(prev, now) if prev then return (prev + now) / 2 else return now end end
|
|
local scanTime = 0
|
|
|
|
local _IsAutoShot = false
|
|
local _IsCastAutoShot = false
|
|
local _AutoStart
|
|
local _AutoEnd
|
|
local _AutoLine
|
|
|
|
local function getUnmodifiedSpeed()
|
|
for _, left, right in GetGameTooltipLines("InventoryItem", "player", 18) do
|
|
if right then
|
|
local _, _, spd = strfind(right, "([%,%.%d]+)")
|
|
if spd then
|
|
spd = strgsub(spd, "%,", "%.")
|
|
return tonumber(spd)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function OnLoad()
|
|
_SVData:SetDefault {
|
|
CAST_INFO_DB = {
|
|
PLAYER = {},
|
|
NPC = {},
|
|
}
|
|
}
|
|
|
|
CAST_INFO_DB = _SVData.CAST_INFO_DB
|
|
|
|
_PlayerGUID = UnitGUID("player")
|
|
end
|
|
|
|
__Service__(true)
|
|
function RecycleCastInfo()
|
|
while true do
|
|
local limit = (GetTime() - 60) * 1000
|
|
|
|
for k, v in pairs(_CastingInfo) do
|
|
if v.start < limit then
|
|
_CastingInfo[k] = nil
|
|
recycle(v)
|
|
end
|
|
end
|
|
|
|
Delay(10)
|
|
end
|
|
end
|
|
|
|
function UpdatePlayerCondition()
|
|
local name, _, _, _, _, _, castID, notInterruptible = UnitCastingInfo("player")
|
|
|
|
if name then return UNIT_SPELLCAST_START("player", castID) end
|
|
if ChannelInfo() then return UNIT_SPELLCAST_CHANNEL_START("player") end
|
|
|
|
UNIT_SPELLCAST_CHANNEL_STOP("player")
|
|
end
|
|
|
|
__CombatEvent__"SPELL_CAST_START" "SPELL_CAST_SUCCESS" "SPELL_MISSED" "SPELL_CAST_FAILED" "SPELL_INTERRUPT"
|
|
function COMBAT_LOG_CAST_EVENT(timestamp, eventType, hideCaster, srcGUID, srcName, srcFlags, srcFlags2, dstGUID, dstName, dstFlags, dstFlags2, spellID, spellName, spellSchool, auraType, amount)
|
|
if not srcGUID then return end
|
|
|
|
if eventType == "SPELL_CAST_START" then
|
|
_CASTLINE = _CASTLINE + 1
|
|
|
|
local info = recycle()
|
|
info.spellName = spellName
|
|
info.timestamp = timestamp
|
|
info.isplayer = band(srcFlags, COMBATLOG_OBJECT_TYPE_PLAYER) > 0
|
|
info.LineID = _CASTLINE
|
|
info.start = GetTime() * 1000
|
|
|
|
local duration = CAST_INFO_DB[info.isplayer and "PLAYER" or "NPC"][spellName]
|
|
if duration then
|
|
info.endtime = info.start + duration
|
|
end
|
|
|
|
_CastingInfo[srcGUID] = info
|
|
|
|
if not srcGUID == _PlayerGUID then
|
|
for unit in GetUnitsFromGUID(srcGUID) do
|
|
UNIT_SPELLCAST_START(unit, info.LineID)
|
|
end
|
|
end
|
|
elseif eventType == "SPELL_CAST_SUCCESS" or eventType == "SPELL_MISSED" then
|
|
local info = _CastingInfo[srcGUID]
|
|
if info and info.spellName == spellName then
|
|
_CastingInfo[srcGUID] = nil
|
|
|
|
local duration = (timestamp - info.timestamp) * 1000
|
|
|
|
if info.isplayer then
|
|
CAST_INFO_DB.PLAYER[spellName] = average(CAST_INFO_DB.PLAYER[spellName], duration)
|
|
else
|
|
CAST_INFO_DB.NPC[spellName] = average(CAST_INFO_DB.NPC[spellName], duration)
|
|
end
|
|
|
|
if not srcGUID == _PlayerGUID then
|
|
for unit in GetUnitsFromGUID(srcGUID) do
|
|
UNIT_SPELLCAST_FAILED(unit, info.LineID)
|
|
end
|
|
end
|
|
|
|
recycle(info)
|
|
end
|
|
elseif eventType == "SPELL_CAST_FAILED" or eventType == "SPELL_INTERRUPT" then
|
|
local info = _CastingInfo[srcGUID]
|
|
if info and info.spellName == spellName then
|
|
_CastingInfo[srcGUID] = nil
|
|
|
|
if not srcGUID == _PlayerGUID then
|
|
for unit in GetUnitsFromGUID(srcGUID) do
|
|
UNIT_SPELLCAST_FAILED(unit, info.LineID)
|
|
end
|
|
end
|
|
|
|
recycle(info)
|
|
end
|
|
end
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function START_AUTOREPEAT_SPELL()
|
|
_IsAutoShot = true
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function STOP_AUTOREPEAT_SPELL()
|
|
_IsAutoShot = false
|
|
if _IsCastAutoShot then
|
|
_IsCastAutoShot = false
|
|
UpdatePlayerCondition()
|
|
end
|
|
end
|
|
|
|
__SystemEvent__()
|
|
function UNIT_SPELLCAST_SUCCEEDED(unit, line, spell)
|
|
if unit == "player" then
|
|
if spell == HUNTER_AUTO or spell == HUNTER_AIM then
|
|
if _IsAutoShot then
|
|
local spd, min, max = UnitRangedDamage("player")
|
|
if spell == HUNTER_AIM then
|
|
spd = getUnmodifiedSpeed() or spd
|
|
end
|
|
_CASTLINE = _CASTLINE + 1
|
|
|
|
_AutoStart = GetTime() * 1000
|
|
_AutoEnd = _AutoStart + spd * 1000
|
|
_AutoLine = _CASTLINE
|
|
|
|
_IsCastAutoShot = true
|
|
|
|
UpdatePlayerCondition()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function UnitCastingInfo(unit)
|
|
local guid = UnitGUID(unit)
|
|
|
|
if guid == _PlayerGUID then
|
|
if _IsCastAutoShot then
|
|
if CastingInfo() then return CastingInfo() end
|
|
local name, _, texture = GetSpellInfo(HUNTER_AUTO)
|
|
return name, _, texture, _AutoStart, _AutoEnd, nil, _AutoLine
|
|
end
|
|
return CastingInfo()
|
|
end
|
|
|
|
local info = _CastingInfo[guid]
|
|
if info and info.endtime then
|
|
return info.spellName, "", select(3, GetSpellInfo(info.spellName)), info.start, info.endtime, nil, info.LineID
|
|
end
|
|
end
|
|
|
|
function UnitChannelInfo(unit)
|
|
local guid = UnitGUID(unit)
|
|
if guid == _PlayerGUID then return ChannelInfo() end
|
|
end
|
|
|