1399 lines
65 KiB
Lua
1399 lines
65 KiB
Lua
local ADDON_NAME = "OSHS"
|
|
|
|
OSHS = CreateFrame("Frame", "OSHSFrame", UIParent)
|
|
local HS = OSHS
|
|
|
|
local defaults = {
|
|
enabled = true,
|
|
showDamage = true,
|
|
showDamageTaken = true,
|
|
showInterrupts = true,
|
|
showHealing = true,
|
|
showMisses = true,
|
|
colorBySchool = true,
|
|
critColor = true,
|
|
disableFloatingCombatText = true,
|
|
useTargetFrame = true,
|
|
scale = .9,
|
|
textSize = 22,
|
|
duration = 1.25,
|
|
offsetX = 0,
|
|
offsetY = -14,
|
|
horizontalSpacing = 23,
|
|
verticalSpacing = 19,
|
|
showStatusEffects = true,
|
|
statusShowIcons = true,
|
|
statusTextSize = 17,
|
|
statusDuration = 1.5,
|
|
statusRiseDistance = 45,
|
|
statusAnchorLocked = true,
|
|
statusAnchorPoint = "CENTER",
|
|
statusAnchorX = -120,
|
|
statusAnchorY = -10,
|
|
optionsPoint = "CENTER",
|
|
optionsX = 0,
|
|
optionsY = 0,
|
|
}
|
|
|
|
local damageEvents = {
|
|
"CHAT_MSG_COMBAT_SELF_HITS",
|
|
"CHAT_MSG_COMBAT_SELF_MISSES",
|
|
"CHAT_MSG_COMBAT_PET_HITS",
|
|
"CHAT_MSG_COMBAT_PET_MISSES",
|
|
"CHAT_MSG_SPELL_SELF_DAMAGE",
|
|
"CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE",
|
|
"CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE",
|
|
"CHAT_MSG_SPELL_PET_DAMAGE",
|
|
}
|
|
|
|
local incomingEvents = {
|
|
CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS = true,
|
|
CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES = true,
|
|
CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS = true,
|
|
CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES = true,
|
|
CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE = true,
|
|
CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE = true,
|
|
CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE = true,
|
|
}
|
|
|
|
local interruptOnlyEvents = {
|
|
CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE = true,
|
|
CHAT_MSG_SPELL_PARTY_DAMAGE = true,
|
|
CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE = true,
|
|
}
|
|
|
|
local periodicDamageEvents = {
|
|
CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE = true,
|
|
CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE = true,
|
|
}
|
|
|
|
local petDamageEvents = {
|
|
CHAT_MSG_COMBAT_PET_HITS = true,
|
|
CHAT_MSG_COMBAT_PET_MISSES = true,
|
|
CHAT_MSG_SPELL_PET_DAMAGE = true,
|
|
}
|
|
|
|
local healEvents = {
|
|
"CHAT_MSG_SPELL_SELF_BUFF",
|
|
"CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS",
|
|
"CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS",
|
|
"CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS",
|
|
"CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS",
|
|
}
|
|
|
|
local schoolTextures = {
|
|
physical = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Physical.tga",
|
|
holy = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Holy.tga",
|
|
fire = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Fire.tga",
|
|
nature = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Nature.tga",
|
|
frost = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Frost.tga",
|
|
shadow = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Shadow.tga",
|
|
arcane = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Arcane.tga",
|
|
}
|
|
|
|
local plates, active, pool = {}, {}, {}
|
|
local interruptActive, interruptPool = {}, {}
|
|
local statusActive, statusPool, statusByKey = {}, {}, {}
|
|
local spellIcons = {}
|
|
local recentTargetGUIDs = {}
|
|
local pendingInterrupt
|
|
local lastWorldChildren, scanElapsed = 0, 0
|
|
local knownBossNames = {}
|
|
local nampowerHealsActive = GetNampowerVersion and true or false
|
|
local nampowerAurasActive = GetNampowerVersion and true or false
|
|
local nampowerSpellDamageActive = false
|
|
local nampowerAutoAttackActive = false
|
|
local nampowerControlledSpellActive = false
|
|
local nampowerControlledAutoActive = false
|
|
local nampowerIncomingSpellActive = false
|
|
local nampowerIncomingAutoActive = false
|
|
HS.hasClassicAPI = C_NamePlate and C_NamePlate.GetNamePlateForUnit and UnitGUID
|
|
HS.hasNampower = GetNampowerVersion and true or false
|
|
|
|
local function applyDefaults()
|
|
if not OSHSDB then OSHSDB = {} end
|
|
local key, value
|
|
for key, value in pairs(defaults) do
|
|
if OSHSDB[key] == nil then OSHSDB[key] = value end
|
|
end
|
|
HS.db = OSHSDB
|
|
end
|
|
|
|
local function readCVar(name)
|
|
if not GetCVar then return nil end
|
|
local ok, value = pcall(GetCVar, name)
|
|
if ok and value ~= nil and value ~= "" then return tonumber(value) or value end
|
|
end
|
|
|
|
local function writeCVar(name, value)
|
|
if SetCVar then pcall(SetCVar, name, value) end
|
|
end
|
|
|
|
function HS:ResetSettings()
|
|
self:RestoreNativeTextSetting()
|
|
OSHSDB = {}
|
|
applyDefaults()
|
|
self:ApplyNativeTextSetting()
|
|
if self.PositionStatusAnchor then self:PositionStatusAnchor() end
|
|
if self.SetStatusAnchorLocked then self:SetStatusAnchorLocked(self.db.statusAnchorLocked) end
|
|
end
|
|
|
|
function HS:RestoreNativeTextSetting()
|
|
if not self.db then return end
|
|
if self.db.nativeDamageBeforeOSHS ~= nil then
|
|
writeCVar("CombatDamage", self.db.nativeDamageBeforeOSHS)
|
|
self.db.nativeDamageBeforeOSHS = nil
|
|
end
|
|
if self.db.nativeHealingBeforeOSHS ~= nil then
|
|
writeCVar("CombatHealing", self.db.nativeHealingBeforeOSHS)
|
|
self.db.nativeHealingBeforeOSHS = nil
|
|
end
|
|
end
|
|
|
|
function HS:ApplyNativeTextSetting()
|
|
if not self.db or not SetCVar then return end
|
|
if self.db.disableFloatingCombatText then
|
|
if self.db.nativeDamageBeforeOSHS == nil then
|
|
self.db.nativeDamageBeforeOSHS = readCVar("CombatDamage") or 1
|
|
end
|
|
if self.db.nativeHealingBeforeOSHS == nil then
|
|
self.db.nativeHealingBeforeOSHS = readCVar("CombatHealing")
|
|
end
|
|
writeCVar("CombatDamage", 0)
|
|
if self.db.nativeHealingBeforeOSHS ~= nil then writeCVar("CombatHealing", 0) end
|
|
else self:RestoreNativeTextSetting() end
|
|
end
|
|
|
|
local function schoolKey(school)
|
|
local value = string.lower(tostring(school or ""))
|
|
if string.find(value, "holy") then return "holy" end
|
|
if string.find(value, "fire") then return "fire" end
|
|
if string.find(value, "nature") or string.find(value, "poison") then return "nature" end
|
|
if string.find(value, "frost") then return "frost" end
|
|
if string.find(value, "shadow") then return "shadow" end
|
|
if string.find(value, "arcane") then return "arcane" end
|
|
return "physical"
|
|
end
|
|
|
|
local function trimName(name)
|
|
if not name then return nil end
|
|
name = string.gsub(name, "^%s+", "")
|
|
name = string.gsub(name, "%s+$", "")
|
|
return name
|
|
end
|
|
|
|
local function findNameRegion(frame)
|
|
local regions = { frame:GetRegions() }
|
|
local i, region, label
|
|
for i = 1, table.getn(regions) do
|
|
region = regions[i]
|
|
if region and region.GetObjectType and region:GetObjectType() == "FontString" then
|
|
label = region:GetText()
|
|
if label and label ~= "" and not tonumber(label) then return region end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function considerPlate(frame)
|
|
if not frame or plates[frame] then return end
|
|
local bar = frame:GetChildren()
|
|
if not bar or not bar.GetObjectType or bar:GetObjectType() ~= "StatusBar" then return end
|
|
local nameRegion = findNameRegion(frame)
|
|
if nameRegion then plates[frame] = { frame = frame, nameRegion = nameRegion } end
|
|
end
|
|
|
|
local function visualPlateAnchor(frame)
|
|
if not frame then return nil end
|
|
if frame.nameplate then return frame.nameplate.health or frame.nameplate end
|
|
if frame.parent and frame.health and frame.platename then return frame.health end
|
|
return frame
|
|
end
|
|
|
|
function HS:ScanNameplates()
|
|
local count = WorldFrame:GetNumChildren()
|
|
if count == lastWorldChildren then return end
|
|
local children = { WorldFrame:GetChildren() }
|
|
local i
|
|
for i = 1, table.getn(children) do considerPlate(children[i]) end
|
|
lastWorldChildren = count
|
|
end
|
|
|
|
local function plateForName(name)
|
|
name = trimName(name)
|
|
if not name then return nil end
|
|
if HS.hasClassicAPI then
|
|
if UnitExists("target") and UnitName("target") == name then
|
|
local targetPlate = C_NamePlate.GetNamePlateForUnit("target")
|
|
if targetPlate then return visualPlateAnchor(targetPlate) end
|
|
end
|
|
local i, token, frame
|
|
for i = 1, 40 do
|
|
token = "nameplate" .. i
|
|
if UnitExists(token) and UnitName(token) == name then
|
|
frame = C_NamePlate.GetNamePlateForUnit(token)
|
|
if frame then return visualPlateAnchor(frame) end
|
|
end
|
|
end
|
|
end
|
|
local data
|
|
for _, data in pairs(plates) do
|
|
if data.frame:IsShown() and trimName(data.nameRegion:GetText()) == name then
|
|
return visualPlateAnchor(data.frame)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function plateForGUID(guid)
|
|
if not guid then return nil end
|
|
if C_NamePlate and C_NamePlate.GetNamePlateForGUID then
|
|
local ok, frame = pcall(C_NamePlate.GetNamePlateForGUID, guid)
|
|
if ok and frame then return visualPlateAnchor(frame) end
|
|
end
|
|
if UnitTokenFromGUID and C_NamePlate and C_NamePlate.GetNamePlateForUnit then
|
|
local ok, token = pcall(UnitTokenFromGUID, guid)
|
|
if ok and token then
|
|
local frameOK, frame = pcall(C_NamePlate.GetNamePlateForUnit, token)
|
|
if frameOK and frame then return visualPlateAnchor(frame) end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function rememberCurrentBoss()
|
|
if not UnitExists("target") then return end
|
|
local name = UnitName("target")
|
|
local classification = UnitClassification and UnitClassification("target")
|
|
local level = UnitLevel and UnitLevel("target")
|
|
if name and (classification == "worldboss" or level == -1) then knownBossNames[name] = true end
|
|
end
|
|
|
|
local function targetFrameForName(name)
|
|
if not HS.db.useTargetFrame or not name or not UnitExists("target") or UnitName("target") ~= name then return nil end
|
|
rememberCurrentBoss()
|
|
if pfUI and pfUI.uf and pfUI.uf.target and pfUI.uf.target:IsShown() then
|
|
return pfUI.uf.target.health or pfUI.uf.target
|
|
end
|
|
if TargetFrameHealthBar and TargetFrameHealthBar:IsShown() then return TargetFrameHealthBar end
|
|
if TargetFrame and TargetFrame:IsShown() then return TargetFrame end
|
|
end
|
|
|
|
local function anchorIsVisible(anchor)
|
|
if not anchor then return false end
|
|
if anchor.IsVisible then return anchor:IsVisible() end
|
|
return anchor:IsShown()
|
|
end
|
|
|
|
local function acquireSplat()
|
|
local item = table.remove(pool)
|
|
if item then item:Show(); return item end
|
|
item = CreateFrame("Frame", nil, UIParent)
|
|
item:SetWidth(180); item:SetHeight(52); item:SetFrameStrata("HIGH")
|
|
item.splat = item:CreateTexture(nil, "ARTWORK")
|
|
item.splat:SetPoint("CENTER", item, "CENTER", 0, 0)
|
|
item.text = item:CreateFontString(nil, "OVERLAY")
|
|
item.text:SetPoint("CENTER", item, "CENTER", 0, 0)
|
|
return item
|
|
end
|
|
|
|
local function releaseSplat(item)
|
|
item:Hide(); item:ClearAllPoints()
|
|
item.anchor, item.slot, item.usesNameplate = nil, nil, nil
|
|
table.insert(pool, item)
|
|
end
|
|
|
|
local function removeActive(index)
|
|
local item = table.remove(active, index)
|
|
if item then releaseSplat(item) end
|
|
end
|
|
|
|
local function acquireInterrupt()
|
|
local item = table.remove(interruptPool)
|
|
if item then item:Show(); return item end
|
|
item = CreateFrame("Frame", nil, UIParent)
|
|
item:SetWidth(240); item:SetHeight(34); item:SetFrameStrata("HIGH")
|
|
item.text = item:CreateFontString(nil, "OVERLAY")
|
|
item.text:SetPoint("CENTER", item, "CENTER", 0, 0)
|
|
return item
|
|
end
|
|
|
|
local function releaseInterrupt(item)
|
|
item:Hide(); item:ClearAllPoints()
|
|
item.anchor = nil
|
|
table.insert(interruptPool, item)
|
|
end
|
|
|
|
function HS:ShowPlateMessage(targetName, targetGUID, message, red, green, blue)
|
|
if not self.db.enabled then return end
|
|
local anchor
|
|
if targetGUID then anchor = plateForGUID(targetGUID)
|
|
end
|
|
if not anchorIsVisible(anchor) and targetName and UnitExists("target") and trimName(UnitName("target")) == trimName(targetName) then
|
|
if C_NamePlate and C_NamePlate.GetNamePlateForUnit then
|
|
local ok, frame = pcall(C_NamePlate.GetNamePlateForUnit, "target")
|
|
if ok and frame then anchor = visualPlateAnchor(frame) end
|
|
end
|
|
if not anchorIsVisible(anchor) and UnitNameplate then
|
|
local ok, frame = pcall(UnitNameplate, "target")
|
|
if ok and frame then anchor = visualPlateAnchor(frame) end
|
|
end
|
|
end
|
|
if not anchorIsVisible(anchor) then anchor = plateForName(targetName) end
|
|
if not anchorIsVisible(anchor) then return end
|
|
|
|
local i, item
|
|
for i = table.getn(interruptActive), 1, -1 do
|
|
if interruptActive[i].anchor == anchor then
|
|
releaseInterrupt(table.remove(interruptActive, i))
|
|
end
|
|
end
|
|
|
|
item = acquireInterrupt()
|
|
local scale = tonumber(self.db.scale) or .9
|
|
local textSize = math.max(12, (tonumber(self.db.textSize) or 22) * scale)
|
|
item.text:SetFont("Interface\\AddOns\\OSHS\\Assets\\runescape.ttf", math.max(1, textSize - 1), "OUTLINE")
|
|
if item.text.SetTextHeight then item.text:SetTextHeight(textSize) end
|
|
item.text:SetText(message)
|
|
item.text:SetTextColor(red or 1, green or 1, blue or 1)
|
|
item:SetAlpha(1)
|
|
item.anchor = anchor
|
|
item.started = GetTime()
|
|
item.duration = math.max(.8, tonumber(self.db.duration) or 1.25)
|
|
item:SetPoint("BOTTOM", anchor, "TOP", tonumber(self.db.offsetX) or 0, 8)
|
|
table.insert(interruptActive, item)
|
|
return true
|
|
end
|
|
|
|
function HS:ShowInterrupt(targetName, targetGUID)
|
|
if not self.db.showInterrupts then return end
|
|
return self:ShowPlateMessage(targetName, targetGUID, "Interrupted!", 1, .86, .12)
|
|
end
|
|
|
|
function HS:ShowEnemyImmune(targetName, targetGUID)
|
|
if not self.db.showMisses then return end
|
|
return self:ShowPlateMessage(targetName, targetGUID, "Immune!", .7, .65, 1)
|
|
end
|
|
|
|
HS.DamageFallback = CreateFrame("Frame", "OSHSDamageAnchor", UIParent)
|
|
HS.DamageFallback:SetWidth(180); HS.DamageFallback:SetHeight(20)
|
|
HS.DamageFallback:SetPoint("CENTER", UIParent, "CENTER", 0, 105)
|
|
|
|
HS.HealFallback = CreateFrame("Frame", "OSHSHealAnchor", UIParent)
|
|
HS.HealFallback:SetWidth(180); HS.HealFallback:SetHeight(20)
|
|
HS.HealFallback:SetPoint("CENTER", UIParent, "CENTER", 0, -55)
|
|
|
|
HS.TakenFallback = CreateFrame("Frame", "OSHSTakenAnchor", UIParent)
|
|
HS.TakenFallback:SetWidth(180); HS.TakenFallback:SetHeight(20)
|
|
HS.TakenFallback:SetPoint("CENTER", UIParent, "CENTER", 0, -35)
|
|
|
|
HS.BossFallback = CreateFrame("Frame", "OSHSBossAnchor", UIParent)
|
|
HS.BossFallback:SetWidth(220); HS.BossFallback:SetHeight(20)
|
|
HS.BossFallback:SetPoint("CENTER", UIParent, "CENTER", 0, 175)
|
|
|
|
local statusAnchorBackdrop = {
|
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 12,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
}
|
|
|
|
HS.StatusAnchor = CreateFrame("Frame", "OSHSStatusAnchor", UIParent)
|
|
HS.StatusAnchor:SetWidth(230); HS.StatusAnchor:SetHeight(28)
|
|
HS.StatusAnchor:SetPoint("CENTER", UIParent, "CENTER", -120, -10)
|
|
HS.StatusAnchor:SetFrameStrata("DIALOG")
|
|
HS.StatusAnchor:SetMovable(true); HS.StatusAnchor:RegisterForDrag("LeftButton")
|
|
HS.StatusAnchor:SetBackdrop(statusAnchorBackdrop)
|
|
HS.StatusAnchor.label = HS.StatusAnchor:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
|
HS.StatusAnchor.label:SetPoint("CENTER", HS.StatusAnchor, "CENTER", 0, 0)
|
|
HS.StatusAnchor.label:SetText("OSHS status effects - drag to move")
|
|
HS.StatusAnchor:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
HS.StatusAnchor:SetScript("OnDragStop", function()
|
|
this:StopMovingOrSizing()
|
|
local point, _, _, x, y = this:GetPoint(1)
|
|
HS.db.statusAnchorPoint = point or "CENTER"
|
|
HS.db.statusAnchorX = x or 0
|
|
HS.db.statusAnchorY = y or 0
|
|
end)
|
|
|
|
function HS:PositionStatusAnchor()
|
|
if not self.db then return end
|
|
self.StatusAnchor:ClearAllPoints()
|
|
local point = self.db.statusAnchorPoint or "CENTER"
|
|
self.StatusAnchor:SetPoint(point, UIParent, point, self.db.statusAnchorX or -120, self.db.statusAnchorY or -10)
|
|
end
|
|
|
|
function HS:SetStatusAnchorLocked(locked)
|
|
self.db.statusAnchorLocked = locked and true or false
|
|
self.StatusAnchor:EnableMouse(not self.db.statusAnchorLocked)
|
|
if self.db.statusAnchorLocked then
|
|
self.StatusAnchor:SetBackdropColor(0, 0, 0, 0)
|
|
self.StatusAnchor:SetBackdropBorderColor(0, 0, 0, 0)
|
|
self.StatusAnchor.label:Hide()
|
|
else
|
|
self.StatusAnchor:SetBackdropColor(.35, .08, .45, .75)
|
|
self.StatusAnchor:SetBackdropBorderColor(.85, .45, 1, 1)
|
|
self.StatusAnchor.label:Show()
|
|
end
|
|
if self.UpdateStatusAnchorButton then self:UpdateStatusAnchorButton() end
|
|
end
|
|
|
|
local function acquireStatusItem()
|
|
local item = table.remove(statusPool)
|
|
if item then item:Show(); return item end
|
|
item = CreateFrame("Frame", nil, UIParent)
|
|
item:SetWidth(300); item:SetHeight(28); item:SetFrameStrata("HIGH")
|
|
item.icon = item:CreateTexture(nil, "ARTWORK")
|
|
item.icon:SetPoint("RIGHT", item, "CENTER", -3, 0)
|
|
item.icon:SetTexCoord(.08, .92, .08, .92)
|
|
item.text = item:CreateFontString(nil, "OVERLAY")
|
|
item.text:SetPoint("LEFT", item, "CENTER", 3, 0)
|
|
item.text:SetJustifyH("LEFT")
|
|
return item
|
|
end
|
|
|
|
|
|
local function releaseStatusItem(item)
|
|
if item.key and statusByKey[item.key] == item then statusByKey[item.key] = nil end
|
|
item:Hide(); item:ClearAllPoints(); item.key = nil
|
|
table.insert(statusPool, item)
|
|
end
|
|
|
|
function HS:ShowStatusEffect(message, r, g, b, texture, dedupeKey)
|
|
if not self.db.enabled or not self.db.showStatusEffects or not message or message == "" then return end
|
|
local now = GetTime()
|
|
local existing = dedupeKey and statusByKey[dedupeKey]
|
|
if existing and now - existing.started <= .3 then
|
|
existing.text:SetText(message); existing.text:SetTextColor(r or 1, g or 1, b or 1)
|
|
if self.db.statusShowIcons and texture then existing.icon:SetTexture(texture); existing.icon:Show() end
|
|
existing.started = now; existing.duration = self.db.statusDuration
|
|
return
|
|
end
|
|
|
|
if table.getn(statusActive) >= 4 then
|
|
local oldest = table.remove(statusActive, 1)
|
|
releaseStatusItem(oldest)
|
|
end
|
|
local item = acquireStatusItem()
|
|
local size = tonumber(self.db.statusTextSize) or 17
|
|
item.text:SetFont("Fonts\\FRIZQT__.TTF", math.max(1, size - 1), "OUTLINE")
|
|
if item.text.SetTextHeight then item.text:SetTextHeight(size) end
|
|
item.text:SetText(message); item.text:SetTextColor(r or 1, g or 1, b or 1)
|
|
if self.db.statusShowIcons and texture then
|
|
item.icon:SetTexture(texture); item.icon:SetWidth(size); item.icon:SetHeight(size); item.icon:Show()
|
|
else
|
|
item.icon:Hide()
|
|
end
|
|
item:SetAlpha(1); item.started = now; item.duration = tonumber(self.db.statusDuration) or 1.5
|
|
item.key = dedupeKey
|
|
if dedupeKey then statusByKey[dedupeKey] = item end
|
|
table.insert(statusActive, item)
|
|
end
|
|
|
|
function HS:ShowSplat(targetName, amount, isCrit, isHeal, school, anchorHint, allowFallback, targetGUID, isTaken)
|
|
if not self.db.enabled then return end
|
|
if isHeal and not self.db.showHealing then return end
|
|
if isTaken and not self.db.showDamageTaken then return end
|
|
if not isHeal and tonumber(amount) and not self.db.showDamage then return end
|
|
if (not tonumber(amount) or tonumber(amount) == 0) and not self.db.showMisses then return end
|
|
|
|
local plateAnchor
|
|
if not isTaken then
|
|
if anchorHint then
|
|
plateAnchor = anchorHint
|
|
elseif targetGUID then
|
|
plateAnchor = plateForGUID(targetGUID)
|
|
else
|
|
plateAnchor = plateForName(targetName)
|
|
end
|
|
end
|
|
if not anchorIsVisible(plateAnchor) then plateAnchor = nil end
|
|
local targetAnchor = not isTaken and not isHeal and not plateAnchor and targetFrameForName(targetName)
|
|
local bossAnchor = not isTaken and not isHeal and not plateAnchor and not targetAnchor and knownBossNames[targetName] and self.BossFallback
|
|
local fallback = isTaken and self.TakenFallback or (isHeal and self.HealFallback or self.DamageFallback)
|
|
local anchor = plateAnchor or targetAnchor or bossAnchor or (allowFallback ~= false and fallback)
|
|
if not anchor then return end
|
|
|
|
local numeric = tonumber(amount)
|
|
local amountText = tostring(numeric or 0)
|
|
local scale = tonumber(self.db.scale) or .9
|
|
local textSize = math.max(10, (tonumber(self.db.textSize) or 22) * .82 * scale)
|
|
local item = acquireSplat()
|
|
item.text:SetFont("Interface\\AddOns\\OSHS\\Assets\\runescape.ttf", math.max(1, textSize - 1), "OUTLINE")
|
|
if item.text.SetTextHeight then item.text:SetTextHeight(textSize) end
|
|
item.text:SetText(amountText)
|
|
if isHeal then
|
|
item.text:SetTextColor(.35, 1, .35)
|
|
elseif isCrit and self.db.critColor then
|
|
item.text:SetTextColor(1, .88, .3)
|
|
else
|
|
item.text:SetTextColor(1, 1, 1)
|
|
end
|
|
|
|
local texture = schoolTextures[self.db.colorBySchool and schoolKey(school) or "physical"]
|
|
if isHeal then texture = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Heal.tga"
|
|
elseif not numeric or numeric == 0 then texture = "Interface\\AddOns\\OSHS\\Assets\\Hitsplat-Miss.tga" end
|
|
item.splat:SetTexture(texture)
|
|
if isHeal then item.splat:SetVertexColor(.15, 1, .15)
|
|
else item.splat:SetVertexColor(1, 1, 1) end
|
|
item.splat:SetWidth(math.max(42, 26 + string.len(amountText) * 7) * scale)
|
|
item.splat:SetHeight(42 * scale)
|
|
item:SetAlpha(1)
|
|
item.anchor = anchor
|
|
item.usesNameplate = plateAnchor and true or false
|
|
|
|
local occupied = {}
|
|
local oldestIndex, oldestTime
|
|
local now = GetTime()
|
|
local occupiedCount = 0
|
|
local i, other
|
|
for i = 1, table.getn(active) do
|
|
other = active[i]
|
|
if other.anchor == anchor and other.slot then
|
|
if not occupied[other.slot] then
|
|
occupied[other.slot] = true
|
|
occupiedCount = occupiedCount + 1
|
|
end
|
|
if not oldestTime or other.started < oldestTime then oldestTime, oldestIndex = other.started, i end
|
|
end
|
|
end
|
|
|
|
local slot
|
|
if occupiedCount < 4 then
|
|
for i = 1, 4 do if not occupied[i] then slot = i; break end end
|
|
elseif oldestIndex then
|
|
slot = active[oldestIndex].slot
|
|
removeActive(oldestIndex)
|
|
end
|
|
slot = slot or 1
|
|
item.slot = slot
|
|
item.started = now
|
|
item.duration = tonumber(self.db.duration) or 1.25
|
|
|
|
local x = tonumber(self.db.offsetX) or 0
|
|
local y = tonumber(self.db.offsetY) or -14
|
|
local h = (tonumber(self.db.horizontalSpacing) or 23) * scale
|
|
local v = (tonumber(self.db.verticalSpacing) or 19) * scale
|
|
if slot == 2 then x, y = x - h, y - v
|
|
elseif slot == 3 then x, y = x + h, y - v
|
|
elseif slot == 4 then y = y - (2 * v) end
|
|
item.offsetX, item.offsetY = x, y
|
|
if plateAnchor then item:SetPoint("TOP", anchor, "BOTTOM", x, y)
|
|
else item:SetPoint("CENTER", anchor, "CENTER", x, y) end
|
|
table.insert(active, item)
|
|
end
|
|
|
|
function HS:Preview()
|
|
local anchor = UnitName("target") or "Target"
|
|
self:ShowSplat(anchor, 42, false, false, "Physical")
|
|
self:ShowSplat(anchor, 73, true, false, "Fire")
|
|
self:ShowSplat(anchor, 18, false, false, "Frost")
|
|
self:ShowSplat(anchor, MISS or "Miss", false, false, nil)
|
|
end
|
|
|
|
local function parsePetDamage(message, eventName)
|
|
local source, spell, target, amount, school
|
|
local meleeEvent = eventName == "CHAT_MSG_COMBAT_PET_HITS"
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
local patterns = {
|
|
{ SPELLLOGCRITSCHOOLOTHEROTHER, true, false, true }, { SPELLLOGSCHOOLOTHEROTHER, false, false, true },
|
|
{ SPELLLOGCRITOTHEROTHER, true, false, false }, { SPELLLOGOTHEROTHER, false, false, false },
|
|
}
|
|
local i, p
|
|
for i = 1, table.getn(patterns) do
|
|
p = patterns[i]
|
|
if p[1] then
|
|
if p[4] then source, spell, target, amount, school = cmatch(message, p[1])
|
|
else source, spell, target, amount = cmatch(message, p[1]); school = nil end
|
|
if source then return target, amount, p[2], school end
|
|
end
|
|
end
|
|
local meleePatterns = {
|
|
{ COMBATHITCRITSCHOOLOTHEROTHER, true, true }, { COMBATHITSCHOOLOTHEROTHER, false, true },
|
|
{ COMBATHITCRITOTHEROTHER, true, false }, { COMBATHITOTHEROTHER, false, false },
|
|
}
|
|
for i = 1, table.getn(meleePatterns) do
|
|
p = meleePatterns[i]
|
|
if p[1] then
|
|
if p[3] then source, target, amount, school = cmatch(message, p[1])
|
|
else source, target, amount = cmatch(message, p[1]); school = "Physical" end
|
|
if source then return target, amount, p[2], school end
|
|
end
|
|
end
|
|
end
|
|
_, _, source, spell, target, amount, school = string.find(message, "^(.+)'s (.+) crits (.+) for (%d+) (%a+) damage")
|
|
if source then return target, amount, true, school end
|
|
_, _, source, spell, target, amount, school = string.find(message, "^(.+)'s (.+) hits (.+) for (%d+) (%a+) damage")
|
|
if source then return target, amount, false, school end
|
|
_, _, source, spell, target, amount = string.find(message, "^(.+)'s (.+) crits (.+) for (%d+)")
|
|
if source then return target, amount, true end
|
|
_, _, source, spell, target, amount = string.find(message, "^(.+)'s (.+) hits (.+) for (%d+)")
|
|
if source then return target, amount, false end
|
|
_, _, source, target, amount, school = string.find(message, "^(.+) crits (.+) for (%d+) (%a+) damage")
|
|
if source then return target, amount, true, school end
|
|
_, _, source, target, amount, school = string.find(message, "^(.+) hits (.+) for (%d+) (%a+) damage")
|
|
if source then return target, amount, false, school end
|
|
_, _, source, target, amount = string.find(message, "^(.+) crits (.+) for (%d+)")
|
|
if source then return target, amount, true, meleeEvent and "Physical" or nil end
|
|
_, _, source, target, amount = string.find(message, "^(.+) hits (.+) for (%d+)")
|
|
if source then return target, amount, false, meleeEvent and "Physical" or nil end
|
|
end
|
|
|
|
local function parseDamage(message)
|
|
local spell, target, amount, school, source
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
if SPELLLOGCRITSCHOOLSELFOTHER then spell, target, amount, school = cmatch(message, SPELLLOGCRITSCHOOLSELFOTHER); if spell then return target, amount, true, school end end
|
|
if SPELLLOGSCHOOLSELFOTHER then spell, target, amount, school = cmatch(message, SPELLLOGSCHOOLSELFOTHER); if spell then return target, amount, false, school end end
|
|
if SPELLLOGCRITSELFOTHER then spell, target, amount = cmatch(message, SPELLLOGCRITSELFOTHER); if spell then return target, amount, true end end
|
|
if SPELLLOGSELFOTHER then spell, target, amount = cmatch(message, SPELLLOGSELFOTHER); if spell then return target, amount, false end end
|
|
if COMBATHITCRITSCHOOLSELFOTHER then target, amount, school = cmatch(message, COMBATHITCRITSCHOOLSELFOTHER); if target then return target, amount, true, school end end
|
|
if COMBATHITSCHOOLSELFOTHER then target, amount, school = cmatch(message, COMBATHITSCHOOLSELFOTHER); if target then return target, amount, false, school end end
|
|
if COMBATHITCRITSELFOTHER then target, amount = cmatch(message, COMBATHITCRITSELFOTHER); if target then return target, amount, true, "Physical" end end
|
|
if COMBATHITSELFOTHER then target, amount = cmatch(message, COMBATHITSELFOTHER); if target then return target, amount, false, "Physical" end end
|
|
if PERIODICAURADAMAGESELFOTHER then target, amount, school, spell = cmatch(message, PERIODICAURADAMAGESELFOTHER); if target then return target, amount, false, school end end
|
|
end
|
|
_, _, spell, target, amount, school = string.find(message, "^Your (.+) crits (.+) for (%d+) (%a+) damage")
|
|
if spell then return target, amount, true, school end
|
|
_, _, spell, target, amount, school = string.find(message, "^Your (.+) hits (.+) for (%d+) (%a+) damage")
|
|
if spell then return target, amount, false, school end
|
|
_, _, spell, target, amount = string.find(message, "^Your (.+) crits (.+) for (%d+)")
|
|
if spell then return target, amount, true end
|
|
_, _, spell, target, amount = string.find(message, "^Your (.+) hits (.+) for (%d+)")
|
|
if spell then return target, amount, false end
|
|
_, _, target, amount, spell = string.find(message, "^(.+) suffers (%d+) .- damage from your (.+)%.")
|
|
if target then return target, amount, false end
|
|
_, _, target, amount = string.find(message, "^You crit (.+) for (%d+)")
|
|
if target then return target, amount, true, "Physical" end
|
|
_, _, target, amount = string.find(message, "^You hit (.+) for (%d+)")
|
|
if target then return target, amount, false, "Physical" end
|
|
_, _, target, amount = string.find(message, "^Your pet crits (.+) for (%d+)")
|
|
if target then return target, amount, true, "Physical" end
|
|
_, _, target, amount = string.find(message, "^Your pet hits (.+) for (%d+)")
|
|
if target then return target, amount, false, "Physical" end
|
|
_, _, source, spell, target, amount, school = string.find(message, "^(.+)'s (.+) crits (.+) for (%d+) (%a+) damage")
|
|
if source and (source == UnitName("pet") or string.find(string.lower(source), "totem")) then return target, amount, true, school end
|
|
_, _, source, spell, target, amount, school = string.find(message, "^(.+)'s (.+) hits (.+) for (%d+) (%a+) damage")
|
|
if source and (source == UnitName("pet") or string.find(string.lower(source), "totem")) then return target, amount, false, school end
|
|
_, _, source, spell, target, amount = string.find(message, "^(.+)'s (.+) crits (.+) for (%d+)")
|
|
if source and (source == UnitName("pet") or string.find(string.lower(source), "totem")) then return target, amount, true end
|
|
_, _, source, spell, target, amount = string.find(message, "^(.+)'s (.+) hits (.+) for (%d+)")
|
|
if source and (source == UnitName("pet") or string.find(string.lower(source), "totem")) then return target, amount, false end
|
|
end
|
|
|
|
local function parsePeriodicDamage(message)
|
|
local target, amount, school, spell
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch and PERIODICAURADAMAGESELFOTHER then
|
|
target, amount, school, spell = cmatch(message, PERIODICAURADAMAGESELFOTHER)
|
|
if target then return target, amount, school end
|
|
end
|
|
_, _, target, amount, school, spell = string.find(message, "^(.+) suffers (%d+) (%a+) damage from your (.+)%.")
|
|
if target then return target, amount, school end
|
|
_, _, target, amount, spell = string.find(message, "^(.+) suffers (%d+) damage from your (.+)%.")
|
|
if target then return target, amount end
|
|
end
|
|
|
|
local function parseHeal(message)
|
|
local spell, target, amount
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
if HEALEDCRITSELFSELF then spell, amount = cmatch(message, HEALEDCRITSELFSELF); if spell then return UnitName("player"), amount, true end end
|
|
if HEALEDSELFSELF then spell, amount = cmatch(message, HEALEDSELFSELF); if spell then return UnitName("player"), amount, false end end
|
|
if HEALEDCRITSELFOTHER then spell, target, amount = cmatch(message, HEALEDCRITSELFOTHER); if spell then return target, amount, true end end
|
|
if HEALEDSELFOTHER then spell, target, amount = cmatch(message, HEALEDSELFOTHER); if spell then return target, amount, false end end
|
|
if PERIODICAURAHEALSELFSELF then amount, spell = cmatch(message, PERIODICAURAHEALSELFSELF); if amount then return UnitName("player"), amount, false end end
|
|
if PERIODICAURAHEALSELFOTHER then target, amount, spell = cmatch(message, PERIODICAURAHEALSELFOTHER); if target then return target, amount, false end end
|
|
end
|
|
_, _, spell, amount = string.find(message, "^Your (.+) critically heals you for (%d+)")
|
|
if spell then return UnitName("player"), amount, true end
|
|
_, _, spell, amount = string.find(message, "^Your (.+) heals you for (%d+)")
|
|
if spell then return UnitName("player"), amount, false end
|
|
_, _, spell, target, amount = string.find(message, "^Your (.+) critically heals (.+) for (%d+)")
|
|
if spell then return target, amount, true end
|
|
_, _, spell, target, amount = string.find(message, "^Your (.+) heals (.+) for (%d+)")
|
|
if spell then return target, amount, false end
|
|
end
|
|
|
|
local function parseMiss(message)
|
|
local spell, target
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
local patterns = {
|
|
{ SPELLMISSSELFOTHER, true, MISS or "Miss" }, { SPELLDODGEDSELFOTHER, true, DODGE or "Dodge" },
|
|
{ SPELLPARRIEDSELFOTHER, true, PARRY or "Parry" }, { SPELLBLOCKEDSELFOTHER, true, BLOCK or "Block" },
|
|
{ SPELLRESISTSELFOTHER, true, RESIST or "Resist" }, { SPELLLOGABSORBSELFOTHER, true, ABSORB or "Absorb" },
|
|
{ SPELLIMMUNESELFOTHER, true, IMMUNE or "Immune" }, { SPELLREFLECTSELFOTHER, true, REFLECT or "Reflect" },
|
|
{ SPELLEVADEDSELFOTHER, true, EVADE or "Evade" }, { MISSEDSELFOTHER, false, MISS or "Miss" },
|
|
{ VSDODGESELFOTHER, false, DODGE or "Dodge" }, { VSPARRYSELFOTHER, false, PARRY or "Parry" },
|
|
{ VSBLOCKSELFOTHER, false, BLOCK or "Block" }, { VSIMMUNESELFOTHER, false, IMMUNE or "Immune" },
|
|
{ VSABSORBSELFOTHER, false, ABSORB or "Absorb" }, { VSEVADESELFOTHER, false, EVADE or "Evade" },
|
|
}
|
|
local i, p
|
|
for i = 1, table.getn(patterns) do
|
|
p = patterns[i]
|
|
if p[1] then
|
|
if p[2] then spell, target = cmatch(message, p[1]) else target = cmatch(message, p[1]) end
|
|
if target then return target, p[3] end
|
|
end
|
|
end
|
|
end
|
|
_, _, spell, target = string.find(message, "^Your (.+) missed (.+)%.")
|
|
if spell then return target, MISS or "Miss" end
|
|
_, _, spell, target = string.find(message, "^Your (.+) was resisted by (.+)%.")
|
|
if spell then return target, RESIST or "Resist" end
|
|
_, _, spell, target = string.find(message, "^Your (.+) failed%. (.+) is immune%.")
|
|
if spell then return target, IMMUNE or "Immune" end
|
|
_, _, target, spell = string.find(message, "^(.+) is immune to your (.+)%.")
|
|
if target then return target, IMMUNE or "Immune" end
|
|
_, _, target = string.find(message, "^You miss (.+)%.")
|
|
if target then return target, MISS or "Miss" end
|
|
_, _, target = string.find(message, "^(.+) dodges")
|
|
if target then return target, DODGE or "Dodge" end
|
|
_, _, target = string.find(message, "^(.+) parries")
|
|
return target, target and (PARRY or "Parry")
|
|
end
|
|
|
|
local function parsePetMiss(message)
|
|
local source, spell, target
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
local patterns = {
|
|
{ SPELLMISSOTHEROTHER, true, MISS or "Miss" }, { SPELLDODGEDOTHEROTHER, true, DODGE or "Dodge" },
|
|
{ SPELLPARRIEDOTHEROTHER, true, PARRY or "Parry" }, { SPELLBLOCKEDOTHEROTHER, true, BLOCK or "Block" },
|
|
{ SPELLRESISTOTHEROTHER, true, RESIST or "Resist" }, { SPELLLOGABSORBOTHEROTHER, true, ABSORB or "Absorb" },
|
|
{ SPELLIMMUNEOTHEROTHER, true, IMMUNE or "Immune" }, { SPELLEVADEDOTHEROTHER, true, EVADE or "Evade" },
|
|
{ MISSEDOTHEROTHER, false, MISS or "Miss" }, { VSDODGEOTHEROTHER, false, DODGE or "Dodge" },
|
|
{ VSPARRYOTHEROTHER, false, PARRY or "Parry" }, { VSBLOCKOTHEROTHER, false, BLOCK or "Block" },
|
|
{ VSABSORBOTHEROTHER, false, ABSORB or "Absorb" }, { VSIMMUNEOTHEROTHER, false, IMMUNE or "Immune" },
|
|
{ VSEVADEOTHEROTHER, false, EVADE or "Evade" },
|
|
}
|
|
local i, p
|
|
for i = 1, table.getn(patterns) do
|
|
p = patterns[i]
|
|
if p[1] then
|
|
if p[2] then source, spell, target = cmatch(message, p[1]) else source, target = cmatch(message, p[1]) end
|
|
if source then return target, p[3] end
|
|
end
|
|
end
|
|
end
|
|
_, _, source, spell, target = string.find(message, "^(.+)'s (.+) missed (.+)%.")
|
|
if source then return target, MISS or "Miss" end
|
|
_, _, source, spell, target = string.find(message, "^(.+)'s (.+) failed%. (.+) is immune%.")
|
|
if source then return target, IMMUNE or "Immune" end
|
|
_, _, source, target = string.find(message, "^(.+) misses (.+)%.")
|
|
if source then return target end
|
|
_, _, source, target = string.find(message, "^(.+) attacks%. (.+) dodges%.")
|
|
if source then return target end
|
|
_, _, source, target = string.find(message, "^(.+) attacks%. (.+) parries%.")
|
|
if source then return target end
|
|
_, _, source, target = string.find(message, "^(.+) attacks%. (.+) blocks%.")
|
|
if source then return target end
|
|
_, _, target, source = string.find(message, "^(.+) dodges (.+)%.")
|
|
if target then return target end
|
|
_, _, target, source = string.find(message, "^(.+) parries (.+)%.")
|
|
if target then return target end
|
|
_, _, target, source = string.find(message, "^(.+) blocks (.+)%.")
|
|
return target, target and (BLOCK or "Block")
|
|
end
|
|
|
|
local function isImmuneResult(result)
|
|
if not result then return false end
|
|
return string.lower(tostring(result)) == string.lower(tostring(IMMUNE or "Immune"))
|
|
end
|
|
|
|
local function parseInterrupt(message, allowControlled)
|
|
local source, target, spell
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch and SPELLINTERRUPTSELFOTHER then
|
|
target, spell = cmatch(message, SPELLINTERRUPTSELFOTHER)
|
|
if target then return target end
|
|
end
|
|
_, _, target, spell = string.find(message, "^You interrupt (.+)'s (.-)[%.!]$")
|
|
if target then return target end
|
|
local ability
|
|
_, _, ability, target, spell = string.find(message, "^Your (.+) interrupts (.+)'s (.-)[%.!]$")
|
|
if target then return target end
|
|
if string.find(string.lower(message), "interrupt", 1, true) and UnitExists("target") then
|
|
return UnitName("target")
|
|
end
|
|
if not allowControlled then return end
|
|
if cmatch and SPELLINTERRUPTOTHEROTHER then
|
|
source, target, spell = cmatch(message, SPELLINTERRUPTOTHEROTHER)
|
|
else
|
|
_, _, source, target, spell = string.find(message, "^(.+) interrupts (.+)'s (.-)[%.!]$")
|
|
end
|
|
if source and (source == UnitName("pet") or string.find(string.lower(source), "totem")) then return target end
|
|
end
|
|
|
|
local function interruptGUIDForName(name)
|
|
name = trimName(name)
|
|
if name and UnitExists("target") and trimName(UnitName("target")) == name and UnitGUID then
|
|
return UnitGUID("target")
|
|
end
|
|
local recent = name and recentTargetGUIDs[name]
|
|
if recent and GetTime() - recent.time <= 1 then return recent.guid end
|
|
end
|
|
|
|
local function parseIncomingAvoidance(message)
|
|
local source, spell
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
local patterns = {
|
|
{ SPELLMISSOTHERSELF, true }, { SPELLDODGEDOTHERSELF, true }, { SPELLPARRIEDOTHERSELF, true },
|
|
{ SPELLBLOCKEDOTHERSELF, true }, { SPELLRESISTOTHERSELF, true }, { SPELLLOGABSORBOTHERSELF, true },
|
|
{ SPELLIMMUNEOTHERSELF, true }, { SPELLEVADEDOTHERSELF, true }, { SPELLREFLECTOTHERSELF, true },
|
|
{ MISSEDOTHERSELF, false }, { VSDODGEOTHERSELF, false }, { VSPARRYOTHERSELF, false },
|
|
{ VSBLOCKOTHERSELF, false }, { VSABSORBOTHERSELF, false }, { VSIMMUNEOTHERSELF, false }, { VSEVADEOTHERSELF, false },
|
|
}
|
|
local i, p
|
|
for i = 1, table.getn(patterns) do
|
|
p = patterns[i]
|
|
if p[1] then
|
|
if p[2] then source, spell = cmatch(message, p[1]) else source = cmatch(message, p[1]) end
|
|
if source then return source end
|
|
end
|
|
end
|
|
end
|
|
_, _, source = string.find(message, "^(.+)'s .+ was blocked%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^(.+)'s .+ missed you%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^(.+) attacks%. You block%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^(.+) attacks%. You dodge%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^(.+) attacks%. You parry%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^(.+) misses you%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^You block (.+)'s attack%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^You dodge (.+)'s attack%.")
|
|
if source then return source end
|
|
_, _, source = string.find(message, "^You parry (.+)'s attack%.")
|
|
return source
|
|
end
|
|
|
|
local function parseIncoming(message)
|
|
local source, spell, amount, school
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
if SPELLLOGCRITSCHOOLOTHERSELF then source, spell, amount, school = cmatch(message, SPELLLOGCRITSCHOOLOTHERSELF); if source then return source, amount, true, school end end
|
|
if SPELLLOGSCHOOLOTHERSELF then source, spell, amount, school = cmatch(message, SPELLLOGSCHOOLOTHERSELF); if source then return source, amount, false, school end end
|
|
if SPELLLOGCRITOTHERSELF then source, spell, amount = cmatch(message, SPELLLOGCRITOTHERSELF); if source then return source, amount, true end end
|
|
if SPELLLOGOTHERSELF then source, spell, amount = cmatch(message, SPELLLOGOTHERSELF); if source then return source, amount, false end end
|
|
if COMBATHITCRITSCHOOLOTHERSELF then source, amount, school = cmatch(message, COMBATHITCRITSCHOOLOTHERSELF); if source then return source, amount, true, school end end
|
|
if COMBATHITSCHOOLOTHERSELF then source, amount, school = cmatch(message, COMBATHITSCHOOLOTHERSELF); if source then return source, amount, false, school end end
|
|
if COMBATHITCRITOTHERSELF then source, amount = cmatch(message, COMBATHITCRITOTHERSELF); if source then return source, amount, true, "Physical" end end
|
|
if COMBATHITOTHERSELF then source, amount = cmatch(message, COMBATHITOTHERSELF); if source then return source, amount, false, "Physical" end end
|
|
if PERIODICAURADAMAGEOTHERSELF then amount, school, source, spell = cmatch(message, PERIODICAURADAMAGEOTHERSELF); if amount then return source, amount, false, school end end
|
|
if PERIODICAURADAMAGESELFSELF then amount, school, spell = cmatch(message, PERIODICAURADAMAGESELFSELF); if amount then return UnitName("player"), amount, false, school end end
|
|
end
|
|
_, _, source, spell, amount, school = string.find(message, "^(.+)'s (.+) hits you for (%d+) (%a+) damage")
|
|
if source then return source, amount, false, school end
|
|
_, _, source, spell, amount, school = string.find(message, "^(.+)'s (.+) crits you for (%d+) (%a+) damage")
|
|
if source then return source, amount, true, school end
|
|
_, _, source, spell, amount = string.find(message, "^(.+)'s (.+) hits you for (%d+)")
|
|
if source then return source, amount, false end
|
|
_, _, source, spell, amount = string.find(message, "^(.+)'s (.+) crits you for (%d+)")
|
|
if source then return source, amount, true end
|
|
_, _, source, amount = string.find(message, "^(.+) hits you for (%d+)")
|
|
if source then return source, amount, false, "Physical" end
|
|
_, _, source, amount = string.find(message, "^(.+) crits you for (%d+)")
|
|
if source then return source, amount, true, "Physical" end
|
|
_, _, amount, school, source, spell = string.find(message, "^You suffer (%d+) (%a+) damage from (.+)'s (.+)%.")
|
|
if amount then return source, amount, false, school end
|
|
_, _, amount, source, spell = string.find(message, "^You suffer (%d+) damage from (.+)'s (.+)%.")
|
|
if amount then return source, amount, false end
|
|
_, _, amount, school, spell = string.find(message, "^You suffer (%d+) (%a+) damage from your (.+)%.")
|
|
if amount then return UnitName("player"), amount, false, school end
|
|
end
|
|
|
|
function HS:HandleCombatMessage(eventName, message)
|
|
if not self.db or not message then return end
|
|
local target, amount, crit, school, result
|
|
if eventName == "CHAT_MSG_SPELL_SELF_DAMAGE" or eventName == "CHAT_MSG_SPELL_PET_DAMAGE" or interruptOnlyEvents[eventName] then
|
|
target = parseInterrupt(message, eventName == "CHAT_MSG_SPELL_PET_DAMAGE")
|
|
if target then self:ShowInterrupt(target, interruptGUIDForName(target)); return end
|
|
if interruptOnlyEvents[eventName] then return end
|
|
end
|
|
if incomingEvents[eventName] then
|
|
local isMelee = string.find(eventName, "COMBAT_") and true or false
|
|
if (isMelee and nampowerIncomingAutoActive) or (not isMelee and nampowerIncomingSpellActive) then return end
|
|
target = parseIncomingAvoidance(message)
|
|
if target then self:ShowSplat(target, 0, false, false, nil, nil, true, nil, true); return end
|
|
target, amount, crit, school = parseIncoming(message)
|
|
if target or amount then self:ShowSplat(target, amount, crit, false, school, nil, true, nil, true) end
|
|
elseif periodicDamageEvents[eventName] then
|
|
if nampowerSpellDamageActive then return end
|
|
target, amount, school = parsePeriodicDamage(message)
|
|
if target then self:ShowSplat(target, amount, false, false, school) end
|
|
elseif petDamageEvents[eventName] then
|
|
if (eventName == "CHAT_MSG_SPELL_PET_DAMAGE" and nampowerControlledSpellActive) or
|
|
(eventName ~= "CHAT_MSG_SPELL_PET_DAMAGE" and nampowerControlledAutoActive) then return end
|
|
target, amount, crit, school = parsePetDamage(message, eventName)
|
|
if target then self:ShowSplat(target, amount, crit, false, school)
|
|
else
|
|
target, result = parsePetMiss(message)
|
|
if target and (not isImmuneResult(result) or not self:ShowEnemyImmune(target)) then
|
|
self:ShowSplat(target, 0, false, false)
|
|
end
|
|
end
|
|
elseif string.find(eventName, "BUFF") then
|
|
if not nampowerHealsActive then target, amount, crit = parseHeal(message) end
|
|
if target then self:ShowSplat(target, amount, crit, true) end
|
|
else
|
|
if (eventName == "CHAT_MSG_SPELL_SELF_DAMAGE" and nampowerSpellDamageActive) or
|
|
(string.find(eventName, "COMBAT_SELF") and nampowerAutoAttackActive) then return end
|
|
target, amount, crit, school = parseDamage(message)
|
|
if target then self:ShowSplat(target, amount, crit, false, school)
|
|
else
|
|
target, result = parseMiss(message)
|
|
if target and (not isImmuneResult(result) or not self:ShowEnemyImmune(target)) then
|
|
self:ShowSplat(target, 0, false, false)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function unitNameForGUID(guid)
|
|
if not guid or not UnitGUID then return nil end
|
|
local units = { "player", "target", "pet", "mouseover" }
|
|
local i, unit
|
|
for i = 1, 4 do table.insert(units, "party" .. i) end
|
|
for i = 1, 40 do table.insert(units, "raid" .. i); table.insert(units, "nameplate" .. i) end
|
|
for i = 1, table.getn(units) do
|
|
unit = units[i]
|
|
if UnitExists(unit) and UnitGUID(unit) == guid then return UnitName(unit) end
|
|
end
|
|
if GetUnitField then
|
|
local ok, name = pcall(GetUnitField, guid, "name")
|
|
if ok and name then return name end
|
|
end
|
|
end
|
|
|
|
local function isPlayerOwnedGUID(guid)
|
|
if not guid or not UnitGUID then return false end
|
|
local playerGUID = UnitGUID("player")
|
|
if not playerGUID then return false end
|
|
if guid == playerGUID then return true end
|
|
if UnitExists("pet") and UnitGUID("pet") == guid then return true end
|
|
if UnitOwnerGUID then
|
|
local ok, ownerGUID = pcall(UnitOwnerGUID, guid)
|
|
if ok and ownerGUID == playerGUID then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local nampowerSchools = {
|
|
[0] = "Physical", [1] = "Holy", [2] = "Fire", [3] = "Nature",
|
|
[4] = "Frost", [5] = "Shadow", [6] = "Arcane",
|
|
}
|
|
|
|
local function autoAttackCrit(hitInfo)
|
|
hitInfo = tonumber(hitInfo) or 0
|
|
return bit and bit.band and bit.band(hitInfo, 128) ~= 0
|
|
end
|
|
|
|
local function spellNameFromID(spellID)
|
|
if not spellID then return nil end
|
|
if GetSpellRecField then
|
|
local ok, name = pcall(GetSpellRecField, spellID, "name")
|
|
if ok and name then return name end
|
|
end
|
|
if GetSpellInfo then
|
|
local ok, name = pcall(GetSpellInfo, spellID)
|
|
if ok and name then return name end
|
|
end
|
|
end
|
|
|
|
local interruptSpellNames = {
|
|
["kick"] = true, ["pummel"] = true, ["shield bash"] = true,
|
|
["counterspell"] = true, ["earth shock"] = true,
|
|
["feral charge"] = true, ["spell lock"] = true,
|
|
}
|
|
|
|
local function spellHasInterruptEffect(spellID)
|
|
spellID = tonumber(spellID)
|
|
if not spellID then return false end
|
|
if GetSpellRecField then
|
|
local fields = { "effect1", "effect2", "effect3", "Effect1", "Effect2", "Effect3" }
|
|
local i, ok, value
|
|
for i = 1, table.getn(fields) do
|
|
ok, value = pcall(GetSpellRecField, spellID, fields[i])
|
|
if ok and tonumber(value) == 68 then return true end
|
|
end
|
|
end
|
|
local name = spellNameFromID(spellID)
|
|
return name and interruptSpellNames[string.lower(name)] and true or false
|
|
end
|
|
|
|
local function spellTextureFromID(spellID)
|
|
if not spellID then return nil end
|
|
if pfUI and pfUI.libdebuff_GetSpellIcon then
|
|
local ok, texture = pcall(pfUI.libdebuff_GetSpellIcon, spellID)
|
|
if ok and texture then return texture end
|
|
end
|
|
if GetSpellRecField and GetSpellIconTexture then
|
|
local ok, iconID = pcall(GetSpellRecField, spellID, "spellIconID")
|
|
if ok and iconID then
|
|
local textureOK, texture = pcall(GetSpellIconTexture, iconID)
|
|
if textureOK and texture then
|
|
if not string.find(texture, "\\") then texture = "Interface\\Icons\\" .. texture end
|
|
return texture
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function buildSpellIcons()
|
|
spellIcons = {}
|
|
if not GetSpellName then return end
|
|
local i, name, texture
|
|
i = 1
|
|
while true do
|
|
name = GetSpellName(i, BOOKTYPE_SPELL or "spell")
|
|
if not name then break end
|
|
texture = GetSpellTexture(i, BOOKTYPE_SPELL or "spell")
|
|
if texture and not spellIcons[name] then spellIcons[name] = texture end
|
|
i = i + 1
|
|
end
|
|
if HasPetSpells and HasPetSpells() then
|
|
i = 1
|
|
while true do
|
|
name = GetSpellName(i, BOOKTYPE_PET or "pet")
|
|
if not name then break end
|
|
texture = GetSpellTexture(i, BOOKTYPE_PET or "pet")
|
|
if texture and not spellIcons[name] then spellIcons[name] = texture end
|
|
i = i + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
local function parseStatus(message, faded)
|
|
local effect
|
|
local cmatch = pfUI and pfUI.api and pfUI.api.cmatch
|
|
if cmatch then
|
|
if faded and AURAREMOVEDSELF then
|
|
effect = cmatch(message, AURAREMOVEDSELF)
|
|
if effect then return effect, "fade" end
|
|
elseif not faded and AURAADDEDSELFHARMFUL then
|
|
effect = cmatch(message, AURAADDEDSELFHARMFUL)
|
|
if effect then return effect, "debuff" end
|
|
end
|
|
if not faded and AURAADDEDSELFHELPFUL then
|
|
effect = cmatch(message, AURAADDEDSELFHELPFUL)
|
|
if effect then return effect, "buff" end
|
|
end
|
|
end
|
|
if faded then
|
|
_, _, effect = string.find(message, "^(.+) fades from you%.")
|
|
if effect then return effect, "fade" end
|
|
else
|
|
if string.find(message, "^You gain %d+ ") then return end
|
|
_, _, effect = string.find(message, "^You gain (.+)%.")
|
|
if effect then return effect, "buff" end
|
|
_, _, effect = string.find(message, "^You are afflicted by (.+)%.")
|
|
if effect then return effect, "debuff" end
|
|
end
|
|
end
|
|
|
|
function HS:HandleStatusMessage(message, faded)
|
|
if not message or nampowerAurasActive then return end
|
|
local effect, statusType = parseStatus(message, faded)
|
|
if not effect then return end
|
|
local key = "aura:" .. string.lower(effect)
|
|
if statusType == "debuff" then
|
|
self:ShowStatusEffect("- " .. effect, 1, .3, .45, spellIcons[effect] or "Interface\\Icons\\Spell_Shadow_CurseOfTounges", key)
|
|
elseif statusType == "fade" then
|
|
self:ShowStatusEffect("-" .. effect, .65, .65, .65, spellIcons[effect], key)
|
|
else
|
|
self:ShowStatusEffect("+ " .. effect, .45, 1, .55, spellIcons[effect] or "Interface\\Icons\\Spell_Holy_MagicalSentry", key)
|
|
end
|
|
end
|
|
|
|
HS:RegisterEvent("VARIABLES_LOADED")
|
|
HS:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
HS:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
HS:RegisterEvent("PLAYER_LOGOUT")
|
|
HS:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
|
HS:RegisterEvent("SPELLS_CHANGED")
|
|
HS:RegisterEvent("CHAT_MSG_SPELL_AURA_GONE_SELF")
|
|
HS:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
|
|
pcall(function() HS:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") end)
|
|
local _, eventName
|
|
for _, eventName in ipairs(damageEvents) do HS:RegisterEvent(eventName) end
|
|
for _, eventName in ipairs(healEvents) do HS:RegisterEvent(eventName) end
|
|
for eventName in pairs(incomingEvents) do HS:RegisterEvent(eventName) end
|
|
for eventName in pairs(interruptOnlyEvents) do HS:RegisterEvent(eventName) end
|
|
if HS.hasNampower then
|
|
HS:RegisterEvent("SPELL_CAST_EVENT")
|
|
HS:RegisterEvent("SPELL_FAILED_OTHER")
|
|
HS:RegisterEvent("SPELL_DAMAGE_EVENT_SELF")
|
|
HS:RegisterEvent("SPELL_DAMAGE_EVENT_OTHER")
|
|
HS:RegisterEvent("AUTO_ATTACK_SELF")
|
|
HS:RegisterEvent("AUTO_ATTACK_OTHER")
|
|
HS:RegisterEvent("SPELL_MISS_SELF")
|
|
HS:RegisterEvent("SPELL_MISS_OTHER")
|
|
HS:RegisterEvent("SPELL_HEAL_BY_SELF")
|
|
HS:RegisterEvent("BUFF_ADDED_SELF")
|
|
HS:RegisterEvent("BUFF_REMOVED_SELF")
|
|
HS:RegisterEvent("DEBUFF_ADDED_SELF")
|
|
HS:RegisterEvent("DEBUFF_REMOVED_SELF")
|
|
end
|
|
|
|
HS:SetScript("OnEvent", function()
|
|
if event == "VARIABLES_LOADED" then
|
|
applyDefaults(); HS:PositionStatusAnchor(); HS:SetStatusAnchorLocked(HS.db.statusAnchorLocked)
|
|
elseif event == "PLAYER_ENTERING_WORLD" then
|
|
applyDefaults(); buildSpellIcons(); HS:ScanNameplates(); HS:ApplyNativeTextSetting(); HS:PositionStatusAnchor(); HS:SetStatusAnchorLocked(HS.db.statusAnchorLocked)
|
|
if HS.hasNampower and SetCVar then
|
|
SetCVar("NP_EnableAutoAttackEvents", "1")
|
|
SetCVar("NP_EnableSpellHealEvents", "1")
|
|
end
|
|
if DEFAULT_CHAT_FRAME then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffd58cffOSHS:|r loaded. Type |cffffffff/oshs|r for settings.")
|
|
end
|
|
elseif event == "PLAYER_LOGOUT" then
|
|
HS:RestoreNativeTextSetting()
|
|
elseif event == "PLAYER_TARGET_CHANGED" then
|
|
rememberCurrentBoss()
|
|
elseif event == "LEARNED_SPELL_IN_TAB" or event == "SPELLS_CHANGED" then
|
|
buildSpellIcons()
|
|
elseif event == "SPELL_CAST_EVENT" then
|
|
if tonumber(arg1) == 1 and spellHasInterruptEffect(arg2) then
|
|
local targetGUID = arg4
|
|
if not targetGUID or targetGUID == "0x0000000000000000" or targetGUID == "0x000000000" then
|
|
targetGUID = UnitExists("target") and UnitGUID and UnitGUID("target")
|
|
end
|
|
pendingInterrupt = { guid = targetGUID, time = GetTime() }
|
|
end
|
|
elseif event == "SPELL_FAILED_OTHER" then
|
|
if pendingInterrupt and GetTime() - pendingInterrupt.time <= 1.25 and
|
|
(not pendingInterrupt.guid or pendingInterrupt.guid == arg1) then
|
|
local targetGUID = pendingInterrupt.guid or arg1
|
|
HS:ShowInterrupt(unitNameForGUID(targetGUID), targetGUID)
|
|
pendingInterrupt = nil
|
|
end
|
|
elseif event == "COMBAT_LOG_EVENT_UNFILTERED" then
|
|
if arg2 == "SPELL_INTERRUPT" or arg3 == "SPELL_INTERRUPT" then
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
local sourceGUID, targetGUID, targetName = arg4, arg7, arg8
|
|
if sourceGUID and (sourceGUID == playerGUID or isPlayerOwnedGUID(sourceGUID)) then
|
|
HS:ShowInterrupt(targetName or unitNameForGUID(targetGUID), targetGUID)
|
|
end
|
|
end
|
|
elseif event == "SPELL_DAMAGE_EVENT_SELF" then
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
local targetGUID, casterGUID = arg1, arg2
|
|
local targetName = unitNameForGUID(targetGUID)
|
|
if targetGUID and targetGUID == playerGUID then
|
|
nampowerIncomingSpellActive = true
|
|
HS:ShowSplat(UnitName("player"), arg4, (tonumber(arg6) or 0) ~= 0, false, nampowerSchools[tonumber(arg7)], nil, true, nil, true)
|
|
else
|
|
if casterGUID == playerGUID then nampowerSpellDamageActive = true
|
|
else nampowerControlledSpellActive = true end
|
|
if targetName and targetGUID then
|
|
recentTargetGUIDs[trimName(targetName)] = { guid = targetGUID, time = GetTime() }
|
|
end
|
|
HS:ShowSplat(targetName, arg4, (tonumber(arg6) or 0) ~= 0, false, nampowerSchools[tonumber(arg7)], nil, false, targetGUID)
|
|
end
|
|
elseif event == "SPELL_DAMAGE_EVENT_OTHER" then
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
if arg1 and arg1 == playerGUID then
|
|
nampowerIncomingSpellActive = true
|
|
HS:ShowSplat(UnitName("player"), arg4, (tonumber(arg6) or 0) ~= 0, false, nampowerSchools[tonumber(arg7)], nil, true, nil, true)
|
|
elseif isPlayerOwnedGUID(arg2) then
|
|
local targetGUID = arg1
|
|
local targetName = unitNameForGUID(targetGUID)
|
|
nampowerControlledSpellActive = true
|
|
if targetName and targetGUID then
|
|
recentTargetGUIDs[trimName(targetName)] = { guid = targetGUID, time = GetTime() }
|
|
end
|
|
HS:ShowSplat(targetName, arg4, (tonumber(arg6) or 0) ~= 0, false, nampowerSchools[tonumber(arg7)], nil, false, targetGUID)
|
|
end
|
|
elseif event == "AUTO_ATTACK_SELF" then
|
|
local casterGUID, targetGUID = arg1, arg2
|
|
local targetName = unitNameForGUID(targetGUID)
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
if casterGUID == playerGUID then nampowerAutoAttackActive = true
|
|
else nampowerControlledAutoActive = true end
|
|
if targetName and targetGUID then
|
|
recentTargetGUIDs[trimName(targetName)] = { guid = targetGUID, time = GetTime() }
|
|
end
|
|
local victimState = tonumber(arg5)
|
|
if victimState == 4 then HS:ShowInterrupt(targetName, targetGUID) end
|
|
if victimState == 7 then
|
|
if not HS:ShowEnemyImmune(targetName, targetGUID) then
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
elseif victimState == 1 or victimState == 4 then
|
|
HS:ShowSplat(targetName, arg3, autoAttackCrit(arg4), false, "Physical", nil, false, targetGUID)
|
|
else
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
elseif event == "AUTO_ATTACK_OTHER" then
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
if arg2 and arg2 == playerGUID then
|
|
nampowerIncomingAutoActive = true
|
|
if tonumber(arg5) == 1 or tonumber(arg5) == 4 then
|
|
HS:ShowSplat(UnitName("player"), arg3, autoAttackCrit(arg4), false, "Physical", nil, true, nil, true)
|
|
else
|
|
HS:ShowSplat(UnitName("player"), 0, false, false, nil, nil, true, nil, true)
|
|
end
|
|
elseif isPlayerOwnedGUID(arg1) then
|
|
local targetGUID = arg2
|
|
local targetName = unitNameForGUID(targetGUID)
|
|
nampowerControlledAutoActive = true
|
|
if targetName and targetGUID then
|
|
recentTargetGUIDs[trimName(targetName)] = { guid = targetGUID, time = GetTime() }
|
|
end
|
|
local victimState = tonumber(arg5)
|
|
if victimState == 4 then HS:ShowInterrupt(targetName, targetGUID) end
|
|
if victimState == 7 then
|
|
if not HS:ShowEnemyImmune(targetName, targetGUID) then
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
elseif victimState == 1 or victimState == 4 then
|
|
HS:ShowSplat(targetName, arg3, autoAttackCrit(arg4), false, "Physical", nil, false, targetGUID)
|
|
else
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
end
|
|
elseif event == "SPELL_MISS_SELF" then
|
|
local casterGUID, targetGUID = arg1, arg2
|
|
local targetName = unitNameForGUID(targetGUID)
|
|
local missInfo = tonumber(arg4)
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
if casterGUID == playerGUID then nampowerSpellDamageActive = true
|
|
else nampowerControlledSpellActive = true end
|
|
if missInfo == 7 or missInfo == 8 then
|
|
if not HS:ShowEnemyImmune(targetName, targetGUID) then
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
else
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
elseif event == "SPELL_MISS_OTHER" then
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
if arg2 and arg2 == playerGUID then
|
|
nampowerIncomingSpellActive = true
|
|
HS:ShowSplat(UnitName("player"), 0, false, false, nil, nil, true, nil, true)
|
|
elseif isPlayerOwnedGUID(arg1) then
|
|
local targetGUID = arg2
|
|
local targetName = unitNameForGUID(targetGUID)
|
|
local missInfo = tonumber(arg4)
|
|
nampowerControlledSpellActive = true
|
|
if missInfo == 7 or missInfo == 8 then
|
|
if not HS:ShowEnemyImmune(targetName, targetGUID) then
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
else
|
|
HS:ShowSplat(targetName, 0, false, false, nil, nil, false, targetGUID)
|
|
end
|
|
end
|
|
elseif event == "SPELL_HEAL_BY_SELF" then
|
|
nampowerHealsActive = true
|
|
local playerGUID = UnitGUID and UnitGUID("player")
|
|
local targetName = unitNameForGUID(arg1) or (arg1 == playerGUID and UnitName("player")) or "Friendly Player"
|
|
HS:ShowSplat(targetName, arg4, arg5 == 1, true, nil, nil, true, arg1)
|
|
elseif event == "BUFF_ADDED_SELF" or event == "BUFF_REMOVED_SELF" or event == "DEBUFF_ADDED_SELF" or event == "DEBUFF_REMOVED_SELF" then
|
|
nampowerAurasActive = true
|
|
if HS.db.showStatusEffects then
|
|
local spellName = spellNameFromID(arg3) or "Status Effect"
|
|
local texture = spellTextureFromID(arg3)
|
|
local stacks = tonumber(arg4) or 0
|
|
local state = tonumber(arg7) or 0
|
|
local stackText = stacks > 1 and " x" .. stacks or ""
|
|
local auraKey = "aura:" .. string.lower(spellName)
|
|
if state == 2 then return
|
|
elseif string.find(event, "REMOVED") then
|
|
HS:ShowStatusEffect("-" .. spellName, .65, .65, .65, texture, auraKey)
|
|
elseif string.find(event, "DEBUFF") then
|
|
HS:ShowStatusEffect("- " .. spellName .. stackText, 1, .3, .45, texture or "Interface\\Icons\\Spell_Shadow_CurseOfTounges", auraKey)
|
|
else
|
|
HS:ShowStatusEffect("+ " .. spellName .. stackText, .45, 1, .55, texture or "Interface\\Icons\\Spell_Holy_MagicalSentry", auraKey)
|
|
end
|
|
end
|
|
elseif event == "CHAT_MSG_SPELL_AURA_GONE_SELF" then
|
|
HS:HandleStatusMessage(arg1, true)
|
|
else
|
|
HS:HandleCombatMessage(event, arg1)
|
|
if event == "CHAT_MSG_SPELL_SELF_BUFF" or event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" or event == "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE" then
|
|
HS:HandleStatusMessage(arg1, false)
|
|
end
|
|
end
|
|
end)
|
|
|
|
HS:SetScript("OnUpdate", function()
|
|
scanElapsed = scanElapsed + (arg1 or 0)
|
|
if scanElapsed > .25 then HS:ScanNameplates(); scanElapsed = 0 end
|
|
local now = GetTime()
|
|
local i, item
|
|
for i = table.getn(active), 1, -1 do
|
|
item = active[i]
|
|
if (item.usesNameplate and not anchorIsVisible(item.anchor)) or now - item.started >= item.duration then
|
|
removeActive(i)
|
|
else
|
|
item:SetAlpha(1)
|
|
end
|
|
end
|
|
for i = table.getn(interruptActive), 1, -1 do
|
|
item = interruptActive[i]
|
|
local progress = (now - item.started) / item.duration
|
|
if not anchorIsVisible(item.anchor) or progress >= 1 then
|
|
table.remove(interruptActive, i); releaseInterrupt(item)
|
|
elseif progress > .65 then
|
|
item:SetAlpha(1 - ((progress - .65) / .35))
|
|
else
|
|
item:SetAlpha(1)
|
|
end
|
|
end
|
|
for i = table.getn(statusActive), 1, -1 do
|
|
item = statusActive[i]
|
|
local progress = (now - item.started) / item.duration
|
|
if progress >= 1 then
|
|
table.remove(statusActive, i); releaseStatusItem(item)
|
|
else
|
|
item:SetAlpha(1 - progress)
|
|
end
|
|
end
|
|
local spacing = (tonumber(HS.db.statusTextSize) or 17) + 4
|
|
local rise = tonumber(HS.db.statusRiseDistance) or 45
|
|
for i = 1, table.getn(statusActive) do
|
|
item = statusActive[i]
|
|
local progress = (now - item.started) / item.duration
|
|
item:ClearAllPoints()
|
|
item:SetPoint("BOTTOM", HS.StatusAnchor, "TOP", 0, 4 + (i - 1) * spacing + progress * rise)
|
|
end
|
|
end)
|
|
|
|
SLASH_OSHS1 = "/oshs"
|
|
SLASH_OSHS2 = "/hitsplats"
|
|
SlashCmdList.OSHS = function(msg)
|
|
msg = string.lower(msg or "")
|
|
if msg == "test" then HS:Preview()
|
|
elseif msg == "heal" then HS:ShowSplat(UnitName("player"), 125, false, true, nil, nil, true)
|
|
elseif msg == "interrupt" then
|
|
local targetName = UnitExists("target") and UnitName("target")
|
|
local targetGUID = targetName and UnitGUID and UnitGUID("target")
|
|
if not targetName then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffff5555OSHS:|r target an enemy with a visible nameplate first.")
|
|
elseif not HS:ShowInterrupt(targetName, targetGUID) then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffff5555OSHS:|r no visible nameplate found for " .. targetName .. ".")
|
|
end
|
|
elseif msg == "immune" then
|
|
local targetName = UnitExists("target") and UnitName("target")
|
|
local targetGUID = targetName and UnitGUID and UnitGUID("target")
|
|
if not targetName then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffff5555OSHS:|r target an enemy with a visible nameplate first.")
|
|
elseif not HS:ShowEnemyImmune(targetName, targetGUID) then
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffff5555OSHS:|r no visible nameplate found for " .. targetName .. ".")
|
|
end
|
|
elseif msg == "reset" then
|
|
HS:ResetSettings()
|
|
if HS.RefreshOptions then HS:RefreshOptions() end
|
|
DEFAULT_CHAT_FRAME:AddMessage("|cffd58cffOSHS:|r settings reset.")
|
|
elseif HS.ToggleOptions then HS:ToggleOptions()
|
|
else DEFAULT_CHAT_FRAME:AddMessage("|cffff5555OSHS:|r options failed to load.") end
|
|
end
|