diff --git a/modules/superwow.lua b/modules/superwow.lua new file mode 100644 index 0000000..bafbb29 --- /dev/null +++ b/modules/superwow.lua @@ -0,0 +1,352 @@ +-- Compatibility layer to use castbars provided by SuperWoW: +-- https://github.com/balakethelock/SuperWoW + +-- DLL Status Check Command (always available) +SLASH_PFDLLSTATUS1 = "/pfdll" +SlashCmdList["PFDLLSTATUS"] = function() + local chat = DEFAULT_CHAT_FRAME + chat:AddMessage("|cff33ffccpfUI|r: DLL Status Check") + + -- SuperWoW + if SUPERWOW_VERSION then + chat:AddMessage(" |cff00ff00SuperWoW|r: v" .. tostring(SUPERWOW_VERSION)) + elseif SpellInfo or SetAutoloot then + chat:AddMessage(" |cffffff00SuperWoW|r: Detected (old version)") + else + chat:AddMessage(" |cffff0000SuperWoW|r: Not detected") + end + + -- Nampower + if GetNampowerVersion then + chat:AddMessage(" |cff00ff00Nampower|r: v" .. tostring(GetNampowerVersion())) + else + chat:AddMessage(" |cffff0000Nampower|r: Not detected") + end + + -- Check if castbar exists for indicator positioning + if pfUI.castbar and pfUI.castbar.player then + chat:AddMessage(" |cff00ff00Castbar|r: Available for indicator anchoring") + else + chat:AddMessage(" |cffffff00Castbar|r: Not available (indicators use fallback position)") + end + + -- Check indicator frames + if pfUI.uf and pfUI.uf.target then + chat:AddMessage(" |cff00ff00Target frame|r: exists") + else + chat:AddMessage(" |cffff0000Target frame|r: NOT found") + end +end + +pfUI:RegisterModule("superwow", "vanilla", function () + if SetAutoloot and SpellInfo and not SUPERWOW_VERSION then + -- Turn every enchanting link that we create in the enchanting frame, + -- from "spell:" back into "enchant:". The enchant-version is what is + -- used by all unmodified game clients. This is required to generate + -- usable links for everyone from the enchant frame while having SuperWoW. + local HookGetCraftItemLink = GetCraftItemLink + _G.GetCraftItemLink = function(index) + local link = HookGetCraftItemLink(index) + return string.gsub(link, "spell:", "enchant:") + end + + -- Convert every enchanting link that we receive into a + -- spell link, as for some reason SuperWoW can't handle + -- enchanting links at all and requires it to be a spell. + local HookSetItemRef = SetItemRef + _G.SetItemRef = function(link, text, button) + link = string.gsub(link, "enchant:", "spell:") + HookSetItemRef(link, text, button) + end + + local HookGameTooltipSetHyperlink = GameTooltip.SetHyperlink + _G.GameTooltip.SetHyperlink = function(self, link) + link = string.gsub(link, "enchant:", "spell:") + HookGameTooltipSetHyperlink(self, link) + end + + DEFAULT_CHAT_FRAME:AddMessage("|cffffffaaAn old version of SuperWoW was detected. Please consider updating:") + DEFAULT_CHAT_FRAME:AddMessage("-> https://github.com/balakethelock/SuperWoW/releases/") + end + + if SUPERWOW_VERSION == "1.5" then + QueueFunction(function() + local pfCombatText_AddMessage = _G.CombatText_AddMessage + _G.CombatText_AddMessage = function(message, a, b, c, d, e, f) + local match, _, hex = string.find(message, ".+ %[(0x.+)%]") + if hex and UnitName(hex) then + message = string.gsub(message, hex, UnitName(hex)) + end + + pfCombatText_AddMessage(message, a, b, c, d, e, f) + end + end) + end + + -- TrackUnit API for adding group members to minimap + -- Tracks friendly units on the minimap for easier group coordination + if TrackUnit and C.unitframes.track_group == "1" then + local trackFrame = CreateFrame("Frame") + trackFrame:RegisterEvent("PARTY_MEMBERS_CHANGED") + trackFrame:RegisterEvent("RAID_ROSTER_UPDATE") + trackFrame:RegisterEvent("PLAYER_ENTERING_WORLD") + trackFrame:RegisterEvent("PLAYER_LOGOUT") + + trackFrame:SetScript("OnEvent", function() + -- Handle shutdown to prevent crash 132 + if event == "PLAYER_LOGOUT" then + this:UnregisterAllEvents() + this:SetScript("OnEvent", nil) + return + end + + -- Track party members + for i = 1, 4 do + local unit = "party" .. i + if UnitExists(unit) and UnitIsConnected(unit) then + pcall(TrackUnit, unit) + end + end + + -- Track raid members + for i = 1, 40 do + local unit = "raid" .. i + if UnitExists(unit) and UnitIsConnected(unit) and not UnitIsUnit(unit, "player") then + pcall(TrackUnit, unit) + end + end + end) + end + + -- Raid Marker Targeting API + -- Allows targeting units by raid marker ("mark1" to "mark8") + if SUPERWOW_VERSION then + pfUI.api.GetMarkedUnit = function(markIndex) + local markUnit = "mark" .. markIndex + if UnitExists(markUnit) then + return markUnit + end + return nil + end + + pfUI.api.TargetMark = function(markIndex) + local markUnit = "mark" .. markIndex + if UnitExists(markUnit) then + TargetUnit(markUnit) + return true + end + return false + end + + -- Get owner of pet/totem using "owner" suffix + pfUI.api.GetUnitOwner = function(unit) + local ownerUnit = unit .. "owner" + if UnitExists(ownerUnit) then + return UnitName(ownerUnit), ownerUnit + end + return nil + end + end + + -- Clickthrough Mode API + -- Allows clicking through corpses to loot underneath + if Clickthrough then + pfUI.api.SetClickthrough = function(enabled) + Clickthrough(enabled and 1 or 0) + end + + pfUI.api.GetClickthrough = function() + return Clickthrough() == 1 + end + + pfUI.api.ToggleClickthrough = function() + local current = Clickthrough() + Clickthrough(current == 1 and 0 or 1) + return Clickthrough() == 1 + end + + -- Add slash command for clickthrough toggle + SLASH_PFCLICKTHROUGH1 = "/clickthrough" + SLASH_PFCLICKTHROUGH2 = "/ct" + SlashCmdList["PFCLICKTHROUGH"] = function() + local enabled = pfUI.api.ToggleClickthrough() + DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccpfUI|r: Clickthrough mode " .. (enabled and "|cff00ff00enabled|r" or "|cffff0000disabled|r")) + end + end + + -- Autoloot Control API + if SetAutoloot then + pfUI.api.SetAutoloot = function(enabled) + SetAutoloot(enabled and 1 or 0) + end + + pfUI.api.GetAutoloot = function() + return SetAutoloot() == 1 + end + + pfUI.api.ToggleAutoloot = function() + local current = SetAutoloot() + SetAutoloot(current == 1 and 0 or 1) + return SetAutoloot() == 1 + end + end + + -- GetPlayerBuffID wrapper + if GetPlayerBuffID then + pfUI.api.GetPlayerBuffSpellId = function(buffIndex) + return GetPlayerBuffID(buffIndex) + end + end + + -- CombatLogAdd wrapper for logging + if CombatLogAdd then + pfUI.api.LogToCombatLog = function(text, raw) + CombatLogAdd(text, raw and 1 or nil) + end + end + + -- Local Raid Markers (marks only visible to self) + if SetRaidTarget then + local origSetRaidTarget = SetRaidTarget + pfUI.api.SetLocalRaidTarget = function(unit, index) + origSetRaidTarget(unit, index, "local") + end + end + + -- Weapon Enchant Info on other players + if GetWeaponEnchantInfo then + local origGetWeaponEnchantInfo = GetWeaponEnchantInfo + pfUI.api.GetUnitWeaponEnchants = function(unit) + if unit and unit ~= "player" then + local mhName, ohName = GetWeaponEnchantInfo(unit) + return { + mainHand = mhName, + offHand = ohName, + } + else + local hasMainHandEnchant, mainHandExpiration, mainHandCharges, hasOffHandEnchant, offHandExpiration, offHandCharges = origGetWeaponEnchantInfo() + return { + mainHand = hasMainHandEnchant and true or false, + mainHandExpiration = mainHandExpiration, + mainHandCharges = mainHandCharges, + offHand = hasOffHandEnchant and true or false, + offHandExpiration = offHandExpiration, + offHandCharges = offHandCharges, + } + end + end + end + + -- Enhance libcast with SuperWoW data for NPCs and other players + -- Player casts use SPELLCAST_* events for proper pushback handling + local supercast = CreateFrame("Frame") + local playerGuid = nil + + supercast:RegisterEvent("PLAYER_ENTERING_WORLD") + supercast:RegisterEvent("UNIT_CASTEVENT") + supercast:RegisterEvent("PLAYER_LOGOUT") + supercast:SetScript("OnEvent", function() + -- Handle shutdown to prevent crash 132 + if event == "PLAYER_LOGOUT" then + this:UnregisterAllEvents() + this:SetScript("OnEvent", nil) + return + end + + if event == "PLAYER_ENTERING_WORLD" then + -- Cache player GUID + if UnitExists then + local guid = GetUnitGUID("player") + playerGuid = guid + end + return + end + + local guid = arg1 + local isPlayer = guid == playerGuid + + -- For non-player units: disable combat parsing events (one-time init) + if not isPlayer and not supercast.init then + -- disable combat parsing events in superwow mode (for non-player units) + libcast:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF") + libcast:UnregisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PARTY_BUFF") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS") + libcast:UnregisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE") + libcast:UnregisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF") + supercast.init = true + end + + if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then + local target = arg2 + local event_type = arg3 + local spell_id = arg4 + local timer = arg5 + + -- get spell info from spell id + local spell, icon, _ + if GetSpellRec then + local rec = GetSpellRec(spell_id) + if rec then + spell = rec.name + local iconID = rec.spellIconID + icon = iconID and GetSpellIconTexture(iconID) or nil + end + elseif SpellInfo and SpellInfo(spell_id) then + spell, _, icon = SpellInfo(spell_id) + end + + -- set fallback values + spell = spell or UNKNOWN + icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark" + + -- skip on buff procs during cast + if event_type == "CAST" then + if not libcast.db[guid] or libcast.db[guid].cast ~= spell then + -- ignore casts without 'START' event, while there is already another cast. + -- those events can be for example a frost shield proc while casting frostbolt. + -- we want to keep the cast itself, so we simply skip those. + return + end + end + + -- For player: store in libcast.db[playerName] so pushback tracking works + -- For others: store by GUID + local dbKey = isPlayer and UnitName("player") or guid + + -- add cast action to the database + if not libcast.db[dbKey] then libcast.db[dbKey] = {} end + libcast.db[dbKey].cast = spell + libcast.db[dbKey].rank = nil + libcast.db[dbKey].start = GetTime() + libcast.db[dbKey].casttime = timer or 0 + libcast.db[dbKey].icon = icon + libcast.db[dbKey].channel = event_type == "CHANNEL" or false + elseif arg3 == "FAIL" then + -- For player: use playerName, for others: use GUID + local dbKey = isPlayer and UnitName("player") or guid + + -- delete all cast entries + if libcast.db[dbKey] then + libcast.db[dbKey].cast = nil + libcast.db[dbKey].rank = nil + libcast.db[dbKey].start = nil + libcast.db[dbKey].casttime = nil + libcast.db[dbKey].icon = nil + libcast.db[dbKey].channel = nil + end + end + end) +end) \ No newline at end of file