Author:ptrace
Comitter:ptrace
Date:2026-06-18 14:23:29 UTC
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..634bae6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
# NSWA
This addon collection solves some problems I've had with the WoW UI:
- hide talking head
- hide error messages
- temporarily display which M+ dungeon I joined
- alert me, when the group is considered full
- alert group when the dungeon difficulty is not set to mythic
It's entirely tailored to my needs, so it might not suit your needs.
But you're welcome to use this code however you like.
Note: You call this addon ingame via `/nswa` or `/wa`. The latter will
conflict with Weakauras.
diff --git a/assets/circle.png b/assets/circle.png
new file mode 100644
index 0000000..1ae7063
Binary files /dev/null and b/assets/circle.png differ
diff --git a/assets/okletsgo.ogg b/assets/okletsgo.ogg
new file mode 100755
index 0000000..c7c563f
Binary files /dev/null and b/assets/okletsgo.ogg differ
diff --git a/assets/triangle.png b/assets/triangle.png
new file mode 100644
index 0000000..2c05485
Binary files /dev/null and b/assets/triangle.png differ
diff --git a/desc.gt b/desc.gt
new file mode 100644
index 0000000..babf269
--- /dev/null
+++ b/desc.gt
@@ -0,0 +1 @@
WoW addon which removes annoyances and adds QoL (for me)
diff --git a/nswa.toc b/nswa.toc
new file mode 100644
index 0000000..c5dfdf2
--- /dev/null
+++ b/nswa.toc
@@ -0,0 +1,25 @@
## Interface: 120000, 120001
## Title: NSWA
## Author: https://github.com/worstprgr
## Version: 1337.0
## Notes: Non Sucking WoW Addon
## DefaultState: Enabled
## LoadOnDemand: 0
## SavedVariables: nswa_db_global
## SavedVariablesPerCharacter: nswa_db
nswa_load.lua
nswa_hide_cringe.lua
nswa_portals.lua
nswa_which_dungeon.lua
nswa_dungeon_difficulty.lua
nswa_group_is_full.lua
nswa_hello.lua
nswa_repair.lua
nswa_cursor.lua
nswa_gossip.lua
nswa_main.lua
diff --git a/nswa_cursor.lua b/nswa_cursor.lua
new file mode 100644
index 0000000..1169e1f
--- /dev/null
+++ b/nswa_cursor.lua
@@ -0,0 +1,47 @@
local SIZE = 95
local THROTTLE = 0.01
local OFFSET_X = 0
local OFFSET_Y = 0
local COLOR = { r = 0, g = 1, b = 0.3, a = 0.8 }
local circle = CreateFrame("Frame", "MouseCombatCircle", UIParent)
circle:SetSize(SIZE, SIZE)
circle:SetFrameStrata("TOOLTIP")
circle:Hide()
local tex = circle:CreateTexture(nil, "OVERLAY")
tex:SetAllPoints()
tex:SetTexture("Interface\\AddOns\\nswa\\assets\\circle.png")
tex:SetVertexColor(COLOR.r, COLOR.g, COLOR.b, COLOR.a)
local elapsed = 0
circle:SetScript("OnUpdate", function(self, delta)
elapsed = elapsed + delta
if elapsed < THROTTLE then return end
elapsed = 0
local x, y = GetCursorPosition()
local scale = UIParent:GetScale()
x = (x / scale) + OFFSET_X
y = (y / scale) + OFFSET_Y
self:ClearAllPoints()
self:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y)
end)
local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_REGEN_DISABLED")
f:RegisterEvent("PLAYER_REGEN_ENABLED")
f:SetScript("OnEvent", function(_, event)
if event == "PLAYER_REGEN_DISABLED" then
circle:Show()
elseif event == "PLAYER_REGEN_ENABLED" then
circle:Hide()
end
end)
diff --git a/nswa_dungeon_difficulty.lua b/nswa_dungeon_difficulty.lua
new file mode 100644
index 0000000..ba44a5b
--- /dev/null
+++ b/nswa_dungeon_difficulty.lua
@@ -0,0 +1,48 @@
local already_warned = false
local function check_dungeon_difficulty()
if IsInRaid() then return end
if not IsInGroup() then return end
if already_warned then return end
local difficulty_id = GetDungeonDifficultyID()
-- 23 = Mythic
if difficulty_id ~= 23 then
local text = "[Alert]: Dungeon difficulty is currently not set to Mythic."
print("NSWA: " .. text)
SendChatMessage(text, "PARTY")
already_warned = true
end
end
local function delayed_check()
C_Timer.After(1, function()
check_dungeon_difficulty()
end)
end
local function reset_state()
already_warned = false
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("GROUP_ROSTER_UPDATE")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function(self, event)
if event == "GROUP_ROSTER_UPDATE" then
if not IsInGroup() then
reset_state()
else
delayed_check()
end
elseif event == "PLAYER_ENTERING_WORLD" then
delayed_check()
end
end)
diff --git a/nswa_gossip.lua b/nswa_gossip.lua
new file mode 100644
index 0000000..42a6f31
--- /dev/null
+++ b/nswa_gossip.lua
@@ -0,0 +1,38 @@
local frame = CreateFrame("Frame")
frame:RegisterEvent("GOSSIP_SHOW")
-- Note: We need instanceIDs not mapIDs. Because an instance can have multiple mapIDs!
-- And whatever those IDs below are, they're wrong ...
-- Keeping it in case I have to change it back.
-- local allowed_zones = {
-- 14032, -- Algethar Academy
-- 15808, -- Windrunner Spire
-- 15829, -- Magisters Terrace
-- 16395, -- Maisara Caverns
-- 16573, -- Nexus Point Xenas
-- 4813, -- Pit of Saron
-- 6988, -- Skyreach
-- 8910, -- The Seat of the Triumvirate
-- }
local function select_first_gossip_option(self, event)
if not IsInInstance() then return end
local options = C_GossipInfo.GetOptions()
if options and #options == 1 then
C_Timer.After(0.1, function()
if not IsShiftKeyDown() then
C_GossipInfo.SelectOption(options[1].gossipOptionID)
end
end)
print("NSWA - Info: Chosen the first gossip option.")
end
end
frame:SetScript("OnEvent", select_first_gossip_option)
diff --git a/nswa_group_is_full.lua b/nswa_group_is_full.lua
new file mode 100644
index 0000000..e713512
--- /dev/null
+++ b/nswa_group_is_full.lua
@@ -0,0 +1,35 @@
local was_full = false
local last_play = 0
local sound_fp = "Interface\\AddOns\\nswa\\assets\\okletsgo.ogg"
local frame = CreateFrame("Frame")
frame:RegisterEvent("GROUP_ROSTER_UPDATE")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
local function play_sound()
local now = GetTime()
if now - last_play > 3 then
PlaySoundFile(sound_fp, "Master")
last_play = now
end
end
frame:SetScript("OnEvent", function()
if IsInGroup() and not IsInRaid() then
local num = GetNumGroupMembers()
local is_full = (num == 5)
if is_full and not was_full then
print("Full House!")
play_sound()
end
was_full = is_full
else
was_full = false
end
end)
diff --git a/nswa_hello.lua b/nswa_hello.lua
new file mode 100644
index 0000000..a12678e
--- /dev/null
+++ b/nswa_hello.lua
@@ -0,0 +1,53 @@
local DELAY_MIN = 2
local DELAY_MAX = 5
local messages = {
"hi",
"o/",
"o7",
"yo",
"hello",
}
local is_in_group = false
local has_greeted = false
local frame = CreateFrame("Frame")
frame:RegisterEvent("GROUP_ROSTER_UPDATE")
local function send_message(msg)
if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
SendChatMessage(msg, "INSTANCE_CHAT")
elseif IsInGroup() then
SendChatMessage(msg, "PARTY")
end
end
local function greet_group()
local delay = math.random(DELAY_MIN, DELAY_MAX)
C_Timer.After(delay, function()
local msg = messages[ math.random(#messages) ]
send_message(msg)
end)
end
frame:SetScript("OnEvent", function()
if IsInGroup() and not IsInRaid() then
if not is_in_group then
if not has_greeted then
greet_group()
has_greeted = true
end
is_in_group = true
end
else
is_in_group = false
has_greeted = false
end
end)
diff --git a/nswa_hide_cringe.lua b/nswa_hide_cringe.lua
new file mode 100644
index 0000000..19967b5
--- /dev/null
+++ b/nswa_hide_cringe.lua
@@ -0,0 +1,68 @@
local N = nswa
function N.init_hide_cringe()
-- Error/event frames
EventToastManagerFrame:SetAlpha(0)
UIErrorsFrame:SetAlpha(0)
RaidBossEmoteFrame:SetAlpha(0)
-- Loot window after boss
GroupLootHistoryFrame:UnregisterAllEvents()
GroupLootHistoryFrame:Hide()
GroupLootHistoryFrame:SetScript("OnShow", GroupLootHistoryFrame.Hide)
-- Yap frame
hooksecurefunc(TalkingHeadFrame, "PlayCurrent", function(self)
self:Hide()
end)
-- Tooltips
N.hide_tooltips()
-- Spell overlays
-- Blizzard does a great job displaying overlays that are basically constant active.
local HIDDEN = {
[1270990] = true, -- Brewmaster talent: harmonic wave, or whatever
}
local original_ShowOverlay = SpellActivationOverlayFrame.ShowOverlay
SpellActivationOverlayFrame.ShowOverlay = function(self, spellID, ...)
if HIDDEN[spellID] then
return
end
return original_ShowOverlay(self, spellID, ...)
end
print("Cringe Removed!")
end
function N.hc_toggle_error()
if UIErrorsFrame:GetAlpha() == 0 then
print("Showing Error Frame")
UIErrorsFrame:SetAlpha(100)
else
print("Hiding Error Frame")
UIErrorsFrame:SetAlpha(0)
end
end
function N.hide_tooltips()
local tooltips = {
GameTooltip,
ItemRefTooltip,
ShoppingTooltip1,
ShoppingTooltip2,
}
for _, tip in ipairs(tooltips) do
tip:HookScript("OnShow", function(self)
if InCombatLockdown() then
self:Hide()
end
end)
end
end
diff --git a/nswa_load.lua b/nswa_load.lua
new file mode 100644
index 0000000..ecd7f42
--- /dev/null
+++ b/nswa_load.lua
@@ -0,0 +1,25 @@
nswa = nswa or {}
local N = nswa
local frame = CreateFrame("Frame")
frame:RegisterEvent("ADDON_LOADED")
frame:SetScript("OnEvent", function(self, event, arg1)
if event == "ADDON_LOADED" and arg1 == "nswa" then
nswa_db_global = nswa_db_global or {}
nswa_db = nswa_db or {}
nswa_db.activity_state = nswa_db.activity_state or {}
local activity_state = nswa_db.activity_state
activity_state.pos = activity_state.pos or {}
print("NSWA - Debug: Hii!")
N.main()
end
end)
--DevTools_Dump(thing)
diff --git a/nswa_main.lua b/nswa_main.lua
new file mode 100644
index 0000000..a5a9996
--- /dev/null
+++ b/nswa_main.lua
@@ -0,0 +1,30 @@
local N = nswa
SLASH_NSWA1 = "/nswa"
SLASH_NSWA2 = "/wa"
local function argparse(args)
if args == "" then
print("Beep bop")
elseif args == "move" then
N.move_activity_label()
elseif args == "err" then
N.hc_toggle_error()
elseif args == "d" then
N.destroy_activity_frame()
elseif args == "zone" then
local current_zone = C_Map.GetBestMapForUnit("player")
print("Current Zone: " .. current_zone)
end
end
function N.main()
N.init_portals()
N.init_activity_frame()
N.init_hide_cringe()
SlashCmdList["NSWA"] = argparse
end
diff --git a/nswa_portals.lua b/nswa_portals.lua
new file mode 100644
index 0000000..f261b63
--- /dev/null
+++ b/nswa_portals.lua
@@ -0,0 +1,131 @@
local N = nswa
local season_portals = {
{ id = 393273, label = "AA", map_id = 2526 }, -- Algeth'ar Academy
{ id = 1254400, label = "WS", map_id = 2805 }, -- Windrunner Spire
{ id = 1254572, label = "MT", map_id = 2811 }, -- Magisters' Terrace
{ id = 1254563, label = "NPX", map_id = 2915 }, -- Nexus-Point Xenas
{ id = 1254559, label = "MC", map_id = 2874 }, -- Maisara Caverns
{ id = 159898, label = "SKYR", map_id = 1209 }, -- Skyreach
{ id = 1254555, label = "POS", map_id = 658 }, -- Pit of Saron
{ id = 1254551, label = "SOT", map_id = 1753 }, -- Seat of Triumvirate
}
-- mapID from GetActivityInfoTable
-- -------------------------------
-- Triumvirat 1753
-- Xenas 2915
-- Mördergasse 2813
-- Windspire 2805
-- Blendendes Tal 2859
-- Nalorakks Bau 2825
-- Leerennarbe 2923
-- Magister 2811
-- Kavernen 2874
-- Skyreach 1209
-- Saron 658
-- Akademie 2526
local buttons = {}
local index = 0
local size = 36
local spacing = 4
local current_id = nil
local function button_create(spell_id, label, parent_frame)
local button = CreateFrame("Button", "Nswa_portals_button"..index, parent_frame, "SecureActionButtonTemplate")
button:SetSize(size, size)
if index == 0 then
button:SetPoint("BOTTOMLEFT", parent_frame, "BOTTOMLEFT", 30, -80)
else
button:SetPoint("LEFT", buttons[index-1], "RIGHT", spacing, 0)
end
button:RegisterForClicks("AnyUp", "AnyDown")
button:SetAttribute("type", "spell")
button:SetAttribute("spell", spell_id)
local icon = button:CreateTexture(nil, "BACKGROUND")
icon:SetAllPoints()
icon:SetTexture(C_Spell.GetSpellTexture(spell_id))
local text = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
text:SetPoint("TOP", button, "BOTTOM", 0, -2)
text:SetText(label)
button.icon = icon
button.label = text
buttons[index] = button
end
local function buttons_clear()
for _, button in ipairs(buttons) do
if button and button:IsShown() then
button:Hide()
button:SetParent(nil)
end
end
buttons = {}
index = 0
end
local function button_glow(id, index)
if not current_id then return end
if not id then
local _, id, _, _, name = GetLFGProposal()
print("INFO: Maybe unknown id: " .. id .. ", name: " .. name)
return
end
if id == current_id then
local button = buttons[index]
ActionButtonSpellAlertManager:ShowAlert(button)
end
end
local function buttons_build(parent_frame)
index = 0 -- maybe redundant
for _, data in ipairs(season_portals) do
local spell_id = data.id
local label = data.label
if IsSpellKnown(spell_id) then
button_create(spell_id, label, parent_frame)
button_glow(data.map_id, index)
index = index + 1
end
end
end
local function wait_for_pve_frame()
local frame = CreateFrame("Frame")
frame:RegisterEvent("LFG_LOCK_INFO_RECEIVED")
frame:SetScript("OnEvent", function(self, event, ...)
if event == "LFG_LOCK_INFO_RECEIVED" then
frame:UnregisterEvent("LFG_LOCK_INFO_RECEIVED")
PVEFrame:HookScript("OnShow", function()
buttons_clear()
buttons_build(PVEFrame)
end)
end
end)
end
function N.portals_info_create(map_id)
current_id = map_id
end
function N.portals_info_destroy()
current_id = nil
end
function N.init_portals()
wait_for_pve_frame()
end
diff --git a/nswa_repair.lua b/nswa_repair.lua
new file mode 100644
index 0000000..f9ea522
--- /dev/null
+++ b/nswa_repair.lua
@@ -0,0 +1,12 @@
local frame = CreateFrame("Frame")
frame:RegisterEvent("MERCHANT_SHOW")
frame:SetScript("OnEvent", function()
if CanMerchantRepair() then
print("Repaired items!")
RepairAllItems()
end
end)
diff --git a/nswa_which_dungeon.lua b/nswa_which_dungeon.lua
new file mode 100644
index 0000000..58eead9
--- /dev/null
+++ b/nswa_which_dungeon.lua
@@ -0,0 +1,255 @@
-- Code for reference
-- - When in a LFG group, get the ID first via `_, id = C_LFGList.GetActiveEntryInfo()`
-- - Use the same id in `info = C_LFGList.GetActivityInfoTable(id)`
-- - Then you have things like `info.name, info.mapID`
local N = nswa
-- TODO: Instead of having multiple frames, just put it in one or two frames and filter by event
local frame = CreateFrame("Frame", "FrameActivity", UIParent)
local frame_player = CreateFrame("Frame")
local frame_lfg = CreateFrame("Frame")
local frame_group = CreateFrame("Frame")
local text = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
local was_in_group = false
local activity_label_is_moving = true
local is_shown = false
local text_label = "init"
local INSTACE_TYPES_TO_CHECK = {
party = true,
raid = true,
pvp = true,
arena = true,
scenario = true,
}
--
-- Local functions
--
local function move_enable()
text:SetText("MOVING")
frame:Show()
frame:EnableMouse(true)
frame:SetMovable(true)
end
local function move_disable()
if not is_shown then
frame:Hide()
end
text:SetText(text_label)
frame:EnableMouse(false)
frame:SetMovable(false)
end
function N.move_activity_label()
if activity_label_is_moving then
move_enable()
else
move_disable()
end
activity_label_is_moving = not activity_label_is_moving
end
local function activity_get_try()
local active_entry_info = C_LFGList.GetActiveEntryInfo()
if active_entry_info == nil then
return nil
end
local _, first_id = next(active_entry_info.activity_ids)
if first_id == nil then
return nil
end
local activity_info = C_LFGList.GetActivityInfoTable(first_id)
if activity_info == nil then
return nil
end
return activity_info.fullName, activity_info.mapID
end
local function activity_get()
local delay = 1
local max_tries = 3
local tries = 0
local function try_once()
tries = tries + 1
local name, map_id = activity_get_try()
if name ~= nil and map_id ~= nil then
return name, map_id
end
if tries < max_tries then
C_Timer.After(delay, try_once)
else
return "error", ""
end
end
return try_once()
end
local function activity_text_update(message)
if text == nil then
print("activity_text_update: nil")
return
end
text_label = message
is_shown = true
text:SetText(message)
frame:Show()
end
local function activity_text_destroy()
if text == nil then
print("activity_text_destroy: nil")
return
end
text_label = ""
is_shown = false
text:SetText("")
frame:Hide()
end
local function frames_unregister()
frame_lfg:UnregisterEvent("LFG_LIST_APPLICATION_STATUS_UPDATED")
end
local function lfg_info_get(callback)
frame_lfg:RegisterEvent("LFG_LIST_APPLICATION_STATUS_UPDATED")
frame_lfg:SetScript("OnEvent", function(self, event, search_result_id)
local search_result_data = C_LFGList.GetSearchResultInfo(search_result_id)
local activity_ids = search_result_data.activityIDs
if not activity_ids then return end
local activity_id = activity_ids[1]
assert(activity_id, "activity_id")
local activity_info = C_LFGList.GetActivityInfoTable(activity_id)
local ai = activity_info
assert(ai, "activity_info")
callback(ai.fullName, ai.mapID)
frames_unregister()
end)
end
local function dispatch_lfg_info(activity_name, map_id)
activity_text_update(activity_name)
N.portals_info_create(map_id)
end
--
-- Public functions
--
function N.destroy_activity_frame()
activity_text_destroy()
N.portals_info_destroy()
frames_unregister()
end
function N.init_activity_frame()
local DB = nswa_db
--
-- Init hook for activity info
--
LFGListInviteDialog:HookScript("OnShow", function(self)
lfg_info_get(function(activity_name, map_id)
dispatch_lfg_info(activity_name, map_id)
end)
end)
LFGListInviteDialog.DeclineButton:HookScript("OnClick", function(self)
N.destroy_activity_frame()
end)
--
-- Events
--
frame_player:RegisterEvent("PLAYER_ENTERING_WORLD")
frame_group:RegisterEvent("GROUP_ROSTER_UPDATE");
frame_player:SetScript("OnEvent", function(self, event, ...)
C_Timer.After(1, function()
local in_instance, instance_type = IsInInstance()
if in_instance and INSTACE_TYPES_TO_CHECK[instance_type] then
N.destroy_activity_frame()
end
end)
end)
frame_group:SetScript("OnEvent", function(self, event, ...)
C_Timer.After(0.5, function()
if not IsInGroup() and not IsInRaid() then
N.destroy_activity_frame()
end
end)
end)
--
-- Save / Restore position of info text
--
frame:SetSize(300, 50)
frame:RegisterForDrag("LeftButton")
frame:SetClampedToScreen(true)
is_shown = false
frame:Hide()
if DB.activity_state.pos.point then
frame:SetPoint(
DB.activity_state.pos.point,
UIParent,
DB.activity_state.pos.relPoint,
DB.activity_state.pos.x,
DB.activity_state.pos.y
)
else
frame:SetPoint("CENTER")
end
text:SetFont("Fonts\\FRIZQT__.TTF", 24)
text:SetTextColor(0, 1, 0)
text:SetPoint("CENTER")
text:SetText(text_label)
frame:SetScript("OnDragStop", function(self)
self:StopMovingOrSizing()
local point, _, relPoint, x, y = self:GetPoint()
DB.activity_state.pos.point = point
DB.activity_state.pos.relPoint = relPoint
DB.activity_state.pos.x = x
DB.activity_state.pos.y = y
-- DevTools_Dump(DB.activity_state)
end)
frame:SetScript("OnDragStart", function(self)
self:StartMoving()
end)
frame:SetScript("OnEvent", frame.OnEvent)
end