Merge the ShaguTweaks Extras ClassicAPI modules into ShaguTweaks (3.7)

Bring in 27 module files from Dusk/ShaguTweaks-Extras-ClassicAPI
(8e14bb4): action bar, chat, loot, macro, merchant (Buy Em All),
minimap & world map, tooltip and unit frame modules, with their
textures. Titles are unchanged, so saved toggles carry over.

- Categories: Chat -> Chat & Social, World Map -> Minimap & World Map,
  Merchant & Auction -> Merchant; Macro is a new category.
- Texture paths point at ShaguTweaks\img.
- Not merged: Raid Frames, Bag Item Click and Bag Search Bar (Bagshui
  covers them), Auction Alt-Buy (aux covers it).
- Chat note when the old ShaguTweaks-extras addon is still enabled.
- English translation keys, notices, license copy, asset provenance,
  README, changelog and version 3.7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019W3rwusM4i1CYaHQAYixJ7
This commit is contained in:
2026-11-02 21:52:53 +00:00
co-authored by Claude Opus 5.5
parent b2c30df0e1
commit 2c58ce2aa3
40 changed files with 3537 additions and 4 deletions
+25
View File
@@ -4,6 +4,31 @@ Changes in this fork (OEVG/ShaguTweaks-ClassicAPI), newest first. Each entry is
This fork is based on [Dusk/ShaguTweaks-ClassicAPI](https://octowow.st/git/Dusk/ShaguTweaks-ClassicAPI) at commit `4391564` (2026-09-08). Earlier changes are in that repository's history.
## 3.7 — 2026-11-02 — Extras modules built in
### Added
- The modules of Dusk's ShaguTweaks Extras ClassicAPI are now part of this addon, so the separate addon is no longer needed:
- **Action Bar:** Dragonflight Gryphons, Floating Actionbar, Hide Macro Text, Mouseover Right, Mouseover Right 2, Reagent Counter, Center Vertical Actionbar, Key-Down Casting, Alt Self-Cast, Show Bags and Show Micro Menu.
- **Chat & Social:** Center Text Input Box, Block NPC Spam, Enable Text Shadow, Chat Timestamps and World Chat Hider.
- **Loot:** Loot Quality.
- **Macro** (new category): Macro Icons and Macro Tweaks.
- **Merchant** (new category): Buy Em All.
- **Minimap & World Map:** MiniMap Framerate & Latency and Reveal World Map.
- **Tooltip & Items:** Cursor Tooltip, Hide Combat Tooltip and Metric Range.
- **Unit Frames:** Unit Frame Abbreviated Names.
- They keep their names, so saved on/off choices carry over. If the old Extras addon is still enabled, a chat message asks you to disable it.
### Not merged
- **Raid Frames** (all its modules).
- **Bag Item Click** and **Bag Search Bar:** Bagshui already has a search box and sends items to trade, the auction house and mail on right-click.
- **Auction Alt-Buy:** aux already buys out or cancels with Alt+click.
### Fixed
- **XP Bar Text:** the numbers are drawn with a shadow instead of an outline, so neighbouring digits no longer run into each other.
## 3.6 — 2026-11-02 — Map window, Transports button and Loot History
### Added
+4
View File
@@ -33,6 +33,10 @@ hashes.
| `img/UI-TargetingFrame.tga` | exact-upstream-git-blob-match | `shagu/ShaguTweaks: img/UI-TargetingFrame.tga` | `c3c56c28ff81f99325a8aa5a8d8c5cd4a9c56a95` |
| `img/circleparty.tga` | exact-upstream-git-blob-match | `shagu/ShaguTweaks: img/circleparty.tga` | `9634f9ffb07ae4fb047223f83c299a8ba1906dff` |
| `img/circleraid.tga` | exact-upstream-git-blob-match | `shagu/ShaguTweaks: img/circleraid.tga` | `c8a5c3e4c572ebd4af426d878bdc8db21d758691` |
| `img/df-gryphon-beta.tga` | exact-upstream-git-blob-match | `paokkerkir/ShaguTweaks-extras: img/df-gryphon-beta.tga` (via Dusk/ShaguTweaks-Extras-ClassicAPI) | `bb4262fe7bf811fa7fff958f7f29269b69038280` |
| `img/df-gryphon.tga` | exact-upstream-git-blob-match | `paokkerkir/ShaguTweaks-extras: img/df-gryphon.tga` (via Dusk/ShaguTweaks-Extras-ClassicAPI) | `6c7e965613222b0313dc1d56d9aa81414a828eef` |
| `img/df-wyvern.tga` | exact-upstream-git-blob-match | `paokkerkir/ShaguTweaks-extras: img/df-wyvern.tga` (via Dusk/ShaguTweaks-Extras-ClassicAPI) | `4414fedcec732ea3096c30598ba7ada25a6af585` |
| `img/xpbar.tga` | exact-upstream-git-blob-match | `paokkerkir/ShaguTweaks-extras: img/xpbar.tga` (via Dusk/ShaguTweaks-Extras-ClassicAPI) | `de80c16875cd7d9fa96baf99bd6ae2e1514595f7` |
## Screenshot manifest
+3
View File
@@ -11,6 +11,9 @@ apply every listed license to the ShaguTweaks-ClassicAPI repository as a whole.
- `ShaguTweaks-mods-MIT.txt` — MIT license for the adapted
TokensWorth/ShaguTweaks-mods material documented in
`THIRD_PARTY_NOTICES.md`.
- `ShaguTweaks-extras-MIT.txt` — MIT license of ShaguTweaks-extras, for the
modules merged from ShaguTweaks-Extras-ClassicAPI documented in
`THIRD_PARTY_NOTICES.md`.
- `pfUI-MIT.txt` — MIT license for the Swing Timer and Wand Timer ported from
brues-code/pfUI, documented in `THIRD_PARTY_NOTICES.md`.
- `ClassicAPI-GPL-3.0.txt` — verbatim license document from the external
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2025 Eric Mauser (Shagu)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+53 -3
View File
@@ -9,13 +9,15 @@ This is an English-only customization of [Dusk/ShaguTweaks-ClassicAPI](https://o
> This fork requires ClassicAPI and targets **Turtle WoW-like server/client environments**.
**Version 3.6.** What changed in each update: see the [changelog](CHANGELOG.md).
**Version 3.7.** What changed in each update: see the [changelog](CHANGELOG.md).
> **Upgrading to 3.7:** the modules of the separate *ShaguTweaks Extras ClassicAPI* addon are now built into ShaguTweaks. **Disable or delete `ShaguTweaks-extras`** after updating, otherwise its older copies replace the built-in ones. Your on/off settings carry over. Raid Frames, Bag Item Click, Bag Search Bar and Auction Alt-Buy were not merged. See [Built-in Extras modules](#built-in-extras-modules).
## 🔌 Requirements
- **[ClassicAPI](https://github.com/brues-code/ClassicAPI) — required.** Install both the DLL and its bundled `!!!ClassicAPI` addon, and restart the client after a DLL update. v1.18.10 or newer is recommended (it includes a crash fix for addons that restyle nameplates). The integration was last reviewed against v1.18.13; see [Docs/CLASSICAPI_UPDATES.md](Docs/CLASSICAPI_UPDATES.md).
- **[SuperWoW](https://github.com/balakethelock/SuperWoW) — optional / recommended.** Only used as an additional fallback for some cast/GUID information.
- **[ShaguTweaks Extras ClassicAPI](https://octowow.st/git/Dusk/ShaguTweaks-Extras-ClassicAPI) — optional / recommended.** Dusk's companion addon with some extra modules and features. It loads alongside this one and adds its own option categories.
- **[ShaguTweaks Extras ClassicAPI](https://octowow.st/git/Dusk/ShaguTweaks-Extras-ClassicAPI) — no longer needed.** Its modules are built in since 3.7 (see **Built-in Extras modules** below). Disable or remove it; while it is enabled, its copies replace the built-in ones.
ClassicAPI is the main API layer.
@@ -59,6 +61,12 @@ Settings: **Esc → Advanced Options**.
- **WorldMap Window:** the map keeps the size you pick with Ctrl+Mousewheel (saved per character), and the title bar drags the window as well. With [OctoBus](https://github.com/Phridgey/OctoBus) installed, a **Transports** button left of the Continent dropdown shows or hides its transport timers.
- **Loot History:** `/loothistory` lists recent group-loot rolls with the winner of each item; click an item for everyone's need, greed or pass and their rolls. Ported from [brues-code/pfUI](https://github.com/brues-code/pfUI). Requires ClassicAPI.
**Built-in Extras modules**
- The modules of Dusk's [ShaguTweaks Extras ClassicAPI](https://octowow.st/git/Dusk/ShaguTweaks-Extras-ClassicAPI) are part of this addon, each under the matching category: Action Bar, Chat & Social, Loot, Minimap & World Map, Tooltip & Items and Unit Frames, plus two new categories, **Macro** and **Merchant**.
- They keep their names, so your saved on/off choices carry over.
- Left out: **Raid Frames**; **Bag Item Click** and **Bag Search Bar**, which [Bagshui](https://octowow.st/git/OEVG/Bagshui) already covers; and **Auction Alt-Buy**, which [aux](https://octowow.st/git/Dusk/aux-addon-ClassicAPI) already does (Alt+click a row to buy out or cancel).
**Performance and stability**
- Reduced per-frame work, tooltip scans and repeated UI updates.
@@ -97,6 +105,13 @@ Modules that use ClassicAPI directly:
- Enemy Castbars
- Tooltip Details
- WorldMap Window
- Buy Em All
- Key-Down Casting and Alt Self-Cast
- Macro Icons and Macro Tweaks
- MiniMap Framerate & Latency
- Reagent Counter
- Reveal World Map
- Show Bags and Show Micro Menu
Other modules can also benefit indirectly from ClassicAPI through shared ShaguTweaks libraries and caches. Per-version notes are in [Docs/CLASSICAPI_UPDATES.md](Docs/CLASSICAPI_UPDATES.md).
@@ -116,6 +131,17 @@ Names match the in-game options list. Modules marked *(off)* are disabled until
- Wand Timer *(off)*
- No Toggle Auto-Attack
- Reduced Actionbar Size *(off)*
- Show Bags *(off)*
- Show Micro Menu *(off)*
- Key-Down Casting
- Alt Self-Cast *(off)*
- Reagent Counter *(off)*
- Hide Macro Text *(off)*
- Mouseover Right *(off)*
- Mouseover Right 2 *(off)*
- Center Vertical Actionbar *(off)*
- Floating Actionbar *(off)*
- Dragonflight Gryphons *(off)*
### Unit Frames
@@ -125,6 +151,7 @@ Names match the in-game options list. Modules marked *(off)* are disabled until
- Unit Frame Class Portraits *(off)*
- Unit Frame Energy & Mana Tick *(off)*
- Hide Pet Unit Frame *(off)*
- Unit Frame Abbreviated Names *(off)*
- Movable Unit Frames
- Real Health Numbers *(off)*
- White Unit Frame Numbers *(off)*
@@ -146,6 +173,9 @@ Names match the in-game options list. Modules marked *(off)* are disabled until
- Sell Junk
- Tooltip Details
- Vendor Values
- Metric Range
- Cursor Tooltip *(off)*
- Hide Combat Tooltip *(off)*
### Chat & Social
@@ -154,6 +184,11 @@ Names match the in-game options list. Modules marked *(off)* are disabled until
- Chat Levels *(off)*
- Chat Spam Filter *(off)*
- Social Colors
- Chat Timestamps *(off)*
- Enable Text Shadow *(off)*
- Center Text Input Box *(off)*
- Block NPC Spam *(off)*
- World Chat Hider *(off)*
### Minimap & World Map
@@ -166,6 +201,8 @@ Names match the in-game options list. Modules marked *(off)* are disabled until
- WorldMap Class Colors
- WorldMap Coordinates
- WorldMap Window
- Reveal World Map
- MiniMap Framerate & Latency *(off)*
### Loot
@@ -173,6 +210,16 @@ Names match the in-game options list. Modules marked *(off)* are disabled until
- Improved Roll Frames *(off)*
- Loot Cursor
- Loot History (`/loothistory`)
- Loot Quality *(off)*
### Macro
- Macro Icons
- Macro Tweaks
### Merchant
- Buy Em All
### Interface
@@ -193,6 +240,7 @@ The following original ShaguTweaks modules are not included as standalone module
- **Chat Links** → integrated into **Chat Tweaks**
- **Movable Unit Frames Extended** → integrated into **Movable Unit Frames**
- **ShaguTweaks Extras ClassicAPI** → its modules are built in, except Raid Frames, Bag Item Click, Bag Search Bar and Auction Alt-Buy
## 📜 Project identity & licensing
@@ -225,8 +273,10 @@ Special thanks to **Jesper_92** for reporting issues that helped improve the add
Additional code and ideas from **pfUI** and **zUI**.
Extras modules from **Dusk-92**'s ShaguTweaks-Extras-ClassicAPI, a fork of **paokkerkir**'s ShaguTweaks-extras (MIT, Shagu).
Swing Timer, Wand Timer and Loot History ported from **brues-code/pfUI** (MIT, Shagu and contributors).
Extended Movable Unit Frames support adapted from **TokensWorth/ShaguTweaks-mods**, originally released under MIT by **GryllsAddons**. See `THIRD_PARTY_NOTICES.md`.
Extended Movable Unit Frames support and several of the Extras modules adapted from **TokensWorth/ShaguTweaks-mods**, originally released under MIT by **GryllsAddons**. See `THIRD_PARTY_NOTICES.md`.
Released under the original **MIT License**.
+38 -1
View File
@@ -2,7 +2,7 @@
## Title: |cffffcc00Shagu|cffffffffTweaks |cffcccccc[ClassicAPI]
## Author: Shagu
## Notes: A collection of tweaks and enhancements to the Default UI
## Version: 3.6
## Version: 3.7
## Dependencies: !!!ClassicAPI
## SavedVariables: ShaguTweaks_player_cache, ShaguTweaks_social_cache
## SavedVariablesPerCharacter: ShaguTweaks_config, ShaguTweaks_cache
@@ -86,6 +86,43 @@ mods\swing-timer.lua
mods\smaller-errors-frame.lua
mods\skip-gossip.lua
# modules merged from ShaguTweaks Extras ClassicAPI (Dusk)
# action bar
mods\actionbar-df-gryphon.lua
mods\actionbar-right-center.lua
mods\actionbar-reagents.lua
mods\actionbar-float.lua
mods\classic-snowfall.lua
mods\reduced-actionbar-bags.lua
mods\reduced-actionbar-micromenu.lua
mods\actionbar-mouseover-common.lua
mods\actionbar-mouseover-bar-right.lua
mods\actionbar-mouseover-bar-right2.lua
mods\actionbar-hide-macro.lua
# merchant
mods\buy-em-all.lua
# loot
mods\loot-quality.lua
# tooltip & items
mods\metric-range.lua
mods\cursor-tooltip.lua
mods\hide-combat-tooltip.lua
# unit frames
mods\unitframes-abbrev-names.lua
# minimap & world map
mods\minimap-framerate-latency.lua
mods\worldmap-reveal.lua
# macro
mods\macro-icons.lua
mods\macro-tweaks.lua
# chat & social
mods\chat-filter-common.lua
mods\chat-npc-spam.lua
mods\world-chat-hider.lua
mods\chat-input-center.lua
mods\chat-text-shadow.lua
mods\chat-timestamps.lua
# compatibility
compat-dragonflightui.lua
compat-guda.lua
+51
View File
@@ -33,9 +33,60 @@ from `TokensWorth/ShaguTweaks-mods`:
- Copyright (c) 2022 GryllsAddons
- License: MIT
The following modules, merged from ShaguTweaks-Extras-ClassicAPI, also
contain code adapted from `TokensWorth/ShaguTweaks-mods` (as recorded by that
project):
- `mods/actionbar-mouseover-common.lua`
- `mods/actionbar-mouseover-bar-right.lua`
- `mods/actionbar-mouseover-bar-right2.lua`
- `mods/actionbar-hide-macro.lua`
- `mods/unitframes-abbrev-names.lua`
- `mods/cursor-tooltip.lua`
- `mods/hide-combat-tooltip.lua`
- `mods/minimap-framerate-latency.lua`
- `mods/chat-npc-spam.lua`
- `mods/world-chat-hider.lua`
- `mods/loot-quality.lua`
The preserved license text is stored in
`LICENSES/ShaguTweaks-mods-MIT.txt`.
## ShaguTweaks-Extras-ClassicAPI
These modules were merged from Dusk-92's ShaguTweaks-Extras-ClassicAPI
(commit `8e14bb4`, 2026-09-18), itself a fork of paokkerkir's
ShaguTweaks-extras:
- Action bar: `actionbar-df-gryphon.lua`, `actionbar-right-center.lua`,
`actionbar-reagents.lua`, `actionbar-float.lua`, `classic-snowfall.lua`,
`reduced-actionbar-bags.lua`, `reduced-actionbar-micromenu.lua`,
`actionbar-mouseover-common.lua`, `actionbar-mouseover-bar-right.lua`,
`actionbar-mouseover-bar-right2.lua`, `actionbar-hide-macro.lua`
- Merchant: `buy-em-all.lua`
- Loot: `loot-quality.lua`
- Tooltip & items: `metric-range.lua`, `cursor-tooltip.lua`,
`hide-combat-tooltip.lua`
- Unit frames: `unitframes-abbrev-names.lua`
- Minimap & world map: `minimap-framerate-latency.lua`, `worldmap-reveal.lua`
- Macro: `macro-icons.lua`, `macro-tweaks.lua`
- Chat & social: `chat-filter-common.lua`, `chat-npc-spam.lua`,
`world-chat-hider.lua`, `chat-input-center.lua`, `chat-text-shadow.lua`,
`chat-timestamps.lua`
Their option categories, texture paths and one chat message were changed for
the merge; the rest of the code is unchanged. The textures
`img/df-gryphon.tga`, `img/df-gryphon-beta.tga`, `img/df-wyvern.tga` and
`img/xpbar.tga` come from the same tree (see `Docs/ASSET_PROVENANCE.md`).
- Sources: https://octowow.st/git/Dusk/ShaguTweaks-Extras-ClassicAPI and
https://github.com/paokkerkir/ShaguTweaks-extras
- Copyright (c) 2025 Eric Mauser (Shagu)
- License: MIT
The preserved license text is stored in
`LICENSES/ShaguTweaks-extras-MIT.txt`.
## brues-code/pfUI
The Swing Timer and Wand Timer in `mods/swing-timer.lua` are ported from
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Executable
BIN
View File
Binary file not shown.
+66
View File
@@ -101,4 +101,70 @@ ShaguTweaks_translation["enUS"] = {
["Clear"] = nil,
["No loot rolls yet."] = nil,
["Loot History requires ClassicAPI."] = nil,
["Dragonflight Gryphons"] = nil,
["Replaces actionbar gryphons with the dragonflight version."] = nil,
["Action Bar"] = nil,
["Floating Actionbar"] = nil,
["Removes all background textures and lets the actionbar float."] = nil,
["Hide Macro Text"] = nil,
["Hides macro names on action buttons."] = nil,
["Mouseover Right"] = nil,
["Hide the right actionbar and show it on mouseover."] = nil,
["Mouseover Right 2"] = nil,
["Hide the second right actionbar and show it on mouseover."] = nil,
["Reagent Counter"] = nil,
["Shows a reagent counter on action buttons."] = nil,
["Center Vertical Actionbar"] = nil,
["Center the vertical actionbar on the right side."] = nil,
["Buy Em All"] = nil,
["Shift-click vendor items to choose stack, maximum, or a custom purchase amount."] = nil,
["Merchant"] = nil,
["Center Text Input Box"] = nil,
["Move the chat input box to the center of the screen."] = nil,
["Chat & Social"] = nil,
["Block NPC Spam"] = nil,
["Blocks known repetitive NPC say/yell spam messages."] = nil,
["Enable Text Shadow"] = nil,
["Enable text shadow in all chat frames."] = nil,
["Chat Timestamps"] = nil,
["Add timestamps to chat messages."] = nil,
["Alt Self-Cast"] = nil,
["When Key-Down Casting is enabled, hold Alt to cast eligible actions on yourself."] = nil,
["Key-Down Casting"] = nil,
["Activates action-bar and pet actions when the key is pressed instead of when it is released."] = nil,
["Cursor Tooltip"] = nil,
["Attaches default tooltips to the cursor."] = nil,
["Hide Combat Tooltip"] = nil,
["Hides the tooltip in combat. Hold Shift to show it temporarily."] = nil,
["Loot Quality"] = nil,
["Colors the loot frame using the highest item quality in the current loot window."] = nil,
["Loot"] = nil,
["Macro Icons"] = nil,
["Detect showtooltip and spells in macros to use them on action buttons."] = nil,
["Macro"] = nil,
["Macro Tweaks"] = nil,
["Add /equip, /use and modern ClassicAPI macro commands, remove #showtooltip from chat and hide macro commands from history."] = nil,
["Metric Range"] = nil,
["Displays tooltip range units in metres instead of yards."] = nil,
["MiniMap Framerate & Latency"] = nil,
["Adds a small framerate & latency display to the mini map."] = nil,
["Minimap & World Map"] = nil,
["Framerate"] = nil,
["High"] = nil,
["fps"] = nil,
["Low"] = nil,
["Latency"] = nil,
["ms"] = nil,
["Show Bags"] = nil,
["Shows bag and keyring buttons when using the reduced actionbar layout. Hold Ctrl+Shift to move the bag bar."] = nil,
["Show Micro Menu"] = nil,
["Shows micro menu buttons when using the reduced actionbar layout. Hold Ctrl+Shift to move the micro menu."] = nil,
["Unit Frame Abbreviated Names"] = nil,
["Abbreviates long target and target-of-target names."] = nil,
["World Chat Hider"] = nil,
["Hides World channel messages while inside an instance without leaving the channel."] = nil,
["Reveal World Map"] = nil,
["Reveals unexplored world map areas and shows exploration hints."] = nil,
["Reveal Unexplored"] = nil,
["Exploration Point"] = nil,
}
+6
View File
@@ -43,6 +43,12 @@ ShaguTweaks:RegisterEvent("VARIABLES_LOADED")
ShaguTweaks:SetScript("OnEvent", function()
-- flag all external modules as unofficial
if event == "ADDON_LOADED" then
-- Most Extras ClassicAPI modules are built in now. With the old addon
-- still enabled, its copies replace the built-in ones (same titles).
if arg1 == "ShaguTweaks-extras" then
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00ShaguTweaks:|r the Extras modules are now built in. Disable the |cffffcc00ShaguTweaks Extras ClassicAPI|r addon so they only run once.")
end
ShaguTweaks.provider = nil
official = false
return
+40
View File
@@ -0,0 +1,40 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Dragonflight Gryphons"],
description = T["Replaces actionbar gryphons with the dragonflight version."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
maintainer = "@shagu (GitHub)",
category = T["Action Bar"],
config = {
["dragonfly.gryphon"] = "retail",
},
enabled = nil,
})
module.enable = function(self)
local gryphon = CreateFrame("Frame", nil, MainMenuBarArtFrame)
gryphon:SetFrameStrata("HIGH")
gryphon:RegisterEvent("PLAYER_ENTERING_WORLD")
gryphon:SetScript("OnEvent", function()
-- replace original gryphons by dragonflight versions
if module.config["dragonfly.gryphon"] == "retail" and UnitFactionGroup("player") == "Horde" then
-- retail horde
MainMenuBarLeftEndCap:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\df-wyvern")
MainMenuBarRightEndCap:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\df-wyvern")
elseif module.config["dragonfly.gryphon"] == "retail" and UnitFactionGroup("player") == "Alliance" then
-- retail alliance
MainMenuBarLeftEndCap:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\df-gryphon")
MainMenuBarRightEndCap:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\df-gryphon")
else
-- beta artwork
MainMenuBarLeftEndCap:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\df-gryphon-beta")
MainMenuBarRightEndCap:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\df-gryphon-beta")
end
-- move gryphons above action buttons
MainMenuBarLeftEndCap:SetParent(this)
MainMenuBarRightEndCap:SetParent(this)
end)
end
+107
View File
@@ -0,0 +1,107 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local module = ShaguTweaks:register({
title = T["Floating Actionbar"],
description = T["Removes all background textures and lets the actionbar float."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
maintainer = "@shagu (GitHub)",
category = T["Action Bar"],
enabled = nil,
})
local texture_removals = {
MainMenuXPBarTexture0, MainMenuXPBarTexture1, MainMenuXPBarTexture2, MainMenuXPBarTexture3,
ReputationXPBarTexture0, ReputationXPBarTexture1, ReputationXPBarTexture2, ReputationXPBarTexture3,
ReputationWatchBarTexture0, ReputationWatchBarTexture1, ReputationWatchBarTexture2, ReputationWatchBarTexture3,
MainMenuBarTexture0, MainMenuBarTexture1, MainMenuBarTexture2, MainMenuBarTexture3,
BonusActionBarTexture1, BonusActionBarTexture0, BonusActionBarTexture2
}
local actionbars = {
"Action", "BonusAction", "MultiBarBottomLeft", "MultiBarBottomRight",
"MultiBarLeft", "MultiBarRight", "Shapeshift"
}
module.enable = function(self)
MainMenuBar:ClearAllPoints()
MainMenuBar:SetPoint("BOTTOM", 0, 8)
-- align actionbutton textures and add border
for _, prefix in ipairs(actionbars) do
for i = 1, NUM_ACTIONBAR_BUTTONS do
local button = _G[prefix .. "Button" .. i]
local texture = _G[prefix.."Button"..i.."NormalTexture"]
if button and texture then
texture:SetWidth(60)
texture:SetHeight(60)
texture:SetPoint("CENTER", 0, 0)
ShaguTweaks.AddBorder(button, 3, { r=.7, g=.7, b=.7, a=1 })
end
end
end
ShaguTweaks.AddBorder(MainMenuBarPerformanceBarFrameButton, { -12, -0.5, -8, 4.5 }, { r=.7, g=.7, b=.7, a=1 })
-- replace reputation bar texture
ReputationWatchStatusBar:SetStatusBarTexture("Interface\\AddOns\\ShaguTweaks\\img\\xpbar")
ReputationWatchStatusBarBackground:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\xpbar")
ReputationWatchStatusBarBackground:SetVertexColor(0, 0, 0, .5)
-- replace experience bar texture
MainMenuExpBar:SetStatusBarTexture("Interface\\AddOns\\ShaguTweaks\\img\\xpbar")
local _, _, _, _, _, background = MainMenuExpBar:GetRegions()
if background then
background:SetTexture("Interface\\AddOns\\ShaguTweaks\\img\\xpbar")
background:SetVertexColor(0, 0, 0, .5)
end
-- center the experience bar when Floating Actionbar is used on its own.
-- Reduced Actionbar applies its own matching offset, so avoid shifting it twice.
local function UpdateExperiencePosition()
local reducedEnabled = ShaguTweaks_config
and ShaguTweaks_config[T["Reduced Actionbar Size"]] == 1
if reducedEnabled then return end
MainMenuExpBar:ClearAllPoints()
if ReputationWatchBar:IsShown() then
MainMenuExpBar:SetPoint("TOP", MainMenuBar, "TOP", 0, 0)
else
MainMenuExpBar:SetPoint("TOP", MainMenuBar, "TOP", 0, 3)
end
end
-- keep the custom reputation position without replacing Blizzard's updater
local function UpdateReputationPosition()
UpdateExperiencePosition()
ReputationWatchBar:ClearAllPoints()
if MainMenuExpBar:IsShown() then
ReputationWatchBar:SetPoint("BOTTOM", MainMenuBar, "TOP", 0, -7)
else
ReputationWatchBar:SetPoint("TOP", MainMenuBar, "TOP", 0, 2)
end
end
hooksecurefunc("ReputationWatchBar_Update", UpdateReputationPosition)
-- Blizzard can restore the default XP bar anchor after login/reloads and
-- actionbar visibility changes, so re-apply the fixed floating offset.
hooksecurefunc("UIParent_ManageFramePositions", UpdateExperiencePosition)
UpdateReputationPosition()
-- hide max level top frame
MainMenuBarMaxLevelBar:SetAlpha(0)
-- remove textures
for _, texture in ipairs(texture_removals) do
if texture then
texture:SetTexture()
texture:Hide()
end
end
end
+34
View File
@@ -0,0 +1,34 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Hide Macro Text"],
description = T["Hides macro names on action buttons."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
local function HideMacroText(button)
if not button or not button.GetName then return end
local name = button:GetName()
local text = name and _G[name .. "Name"]
if text then text:SetAlpha(0) end
end
for i = 1, 24 do
HideMacroText(_G["BonusActionButton" .. i])
end
for i = 1, 12 do
HideMacroText(_G["ActionButton" .. i])
HideMacroText(_G["MultiBarRightButton" .. i])
HideMacroText(_G["MultiBarLeftButton" .. i])
HideMacroText(_G["MultiBarBottomLeftButton" .. i])
HideMacroText(_G["MultiBarBottomRightButton" .. i])
end
end
+15
View File
@@ -0,0 +1,15 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Mouseover Right"],
description = T["Hide the right actionbar and show it on mouseover."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
ShaguTweaks.CreateMouseoverActionBar(MultiBarRight, "SHOW_MULTI_ACTIONBAR_3")
end
+15
View File
@@ -0,0 +1,15 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Mouseover Right 2"],
description = T["Hide the second right actionbar and show it on mouseover."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
ShaguTweaks.CreateMouseoverActionBar(MultiBarLeft, "SHOW_MULTI_ACTIONBAR_4")
end
+138
View File
@@ -0,0 +1,138 @@
-- Shared mouseover actionbar helper.
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
if not ShaguTweaks.CreateMouseoverActionBar then
ShaguTweaks.CreateMouseoverActionBar = function(bar, visibilityFlag)
if not bar or not visibilityFlag then return end
if bar.ShaguTweaksMouseoverController then return end
local controller = CreateFrame("Frame", nil, UIParent)
local hotspot = CreateFrame("Frame", nil, UIParent)
controller.bar = bar
controller.hotspot = hotspot
controller.visibilityFlag = visibilityFlag
controller.enabled = false
controller.elapsed = 0
controller.idle = 0
bar.ShaguTweaksMouseoverController = controller
-- One invisible reveal area replaces the original per-button overlay
-- frames. It only accepts mouse input while the actionbar is hidden.
hotspot:SetAllPoints(bar)
hotspot:SetFrameStrata("DIALOG")
hotspot:EnableMouse(false)
hotspot:Hide()
local function ReadEnabled()
return _G[visibilityFlag] and true or false
end
local function StopWatching()
controller:SetScript("OnUpdate", nil)
controller.elapsed = 0
controller.idle = 0
end
local function StartWatching()
if not controller.enabled or not bar:IsShown() then return end
hotspot:EnableMouse(false)
controller.elapsed = 0
controller.idle = 0
controller:SetScript("OnUpdate", function()
this.elapsed = this.elapsed + (arg1 or 0)
if this.elapsed < .10 then return end
local step = this.elapsed
this.elapsed = 0
if not this.enabled then
StopWatching()
hotspot:EnableMouse(false)
hotspot:Hide()
return
end
if not bar:IsShown() then
StopWatching()
hotspot:Show()
hotspot:EnableMouse(true)
return
end
if MouseIsOver(bar) then
this.idle = 0
return
end
this.idle = this.idle + step
if this.idle >= 2 then
StopWatching()
bar:Hide()
end
end)
end
local function Sync()
controller.enabled = ReadEnabled()
if not controller.enabled then
StopWatching()
hotspot:EnableMouse(false)
hotspot:Hide()
return
end
hotspot:Show()
if bar:IsShown() then
StartWatching()
else
hotspot:EnableMouse(true)
end
end
hotspot:SetScript("OnEnter", function()
if not controller.enabled then return end
hotspot:EnableMouse(false)
bar:Show()
-- The bar's OnShow script starts the watcher. Avoid a second redundant
-- StartWatching() call here.
end)
-- Keep existing scripts intact while observing native/UI-addon visibility
-- changes. These wrappers are installed once per bar.
local oldOnShow = bar:GetScript("OnShow")
bar:SetScript("OnShow", function()
if oldOnShow then oldOnShow() end
if controller.enabled then StartWatching() end
end)
local oldOnHide = bar:GetScript("OnHide")
bar:SetScript("OnHide", function()
if oldOnHide then oldOnHide() end
StopWatching()
if controller.enabled then
hotspot:Show()
hotspot:EnableMouse(true)
else
hotspot:EnableMouse(false)
hotspot:Hide()
end
end)
controller:RegisterEvent("PLAYER_ENTERING_WORLD")
controller:RegisterEvent("CVAR_UPDATE")
controller:SetScript("OnEvent", Sync)
Sync()
end
end
+127
View File
@@ -0,0 +1,127 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Reagent Counter"],
description = T["Shows a reagent counter on action buttons."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
maintainer = "@shagu (GitHub)",
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
local reagent_slots = { }
local reagent_counts = { }
local bars = { "Action", "BonusAction", "MultiBarBottomLeft", "MultiBarBottomRight", "MultiBarLeft", "MultiBarRight" }
local reagentcounter = CreateFrame("Frame", "ShaguTweaksReagentCount", UIParent)
reagentcounter:RegisterEvent("PLAYER_ENTERING_WORLD")
reagentcounter:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
reagentcounter:RegisterEvent("ACTIONBAR_PAGE_CHANGED")
reagentcounter:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
reagentcounter:RegisterEvent("UPDATE_MACROS")
reagentcounter:RegisterEvent("BAG_UPDATE")
-- Hidden frames still receive events, but do not execute OnUpdate.
-- Wake for one frame only when work is pending.
reagentcounter:Hide()
reagentcounter:SetScript("OnEvent", function()
this.pending = true
if event ~= "BAG_UPDATE" then
this.rescan = true
end
this:Show()
end)
reagentcounter:SetScript("OnUpdate", function()
if not this.pending then
this:Hide()
return
end
-- Rebuild the action-slot -> reagent mapping only when the action layout
-- changes. ClassicAPI resolves the spell/macro directly, so no tooltip
-- scan or localized reagent-name parsing is required.
if this.rescan then
reagent_counts = {}
for slot = 1, 120 do
reagentcounter.ScanSlot(slot)
end
end
-- Count only reagents referenced by active actions. ClassicAPI reads the
-- inventory directly, avoiding a Lua bag scan for every reagent.
for itemID in pairs(reagent_counts) do
reagent_counts[itemID] = API.GetItemCount(itemID, false, false) or 0
end
-- update all actionbar buttons
for _, prefix in ipairs(bars) do
for i = 1, NUM_ACTIONBAR_BUTTONS do
local button = _G[prefix .. "Button" .. i]
if button then
local text = _G[button:GetName().."Count"]
local slot = ActionButton_GetPagedID(button)
if text then
local itemID = reagent_slots[slot]
if itemID then
text:SetText(reagent_counts[itemID] or 0)
elseif not IsConsumableAction(slot) then
text:SetText()
end
end
end
end
end
this.pending = nil
this.rescan = nil
this:Hide()
end)
reagentcounter.ScanSlot = function(slot)
reagent_slots[slot] = nil
if not HasAction(slot) then return end
local actionType, actionID = API.GetActionInfo(slot)
local spellID
if actionType == "spell" then
spellID = actionID
elseif actionType == "macro" and actionID then
local _, _, macroSpellID = API.GetMacroSpell(actionID)
spellID = macroSpellID
end
if not spellID then return end
local reagents = API.GetSpellReagents(spellID)
local reagent = reagents and reagents[1]
if not reagent or not reagent.itemID then return end
-- The action button only has one count field. Preserve the historical
-- behavior by showing the first reagent's owned stack count.
reagent_slots[slot] = reagent.itemID
reagent_counts[reagent.itemID] = reagent_counts[reagent.itemID] or 0
end
-- The stock UI clears the Count fontstring on non-consumable actions whenever
-- ActionButton_UpdateCount() runs. Re-apply our reagent value afterwards so
-- login/reload refreshes cannot erase it a moment later.
ShaguTweaks.hooksecurefunc("ActionButton_UpdateCount", function()
local button = this
if not button or not button.GetName then return end
local slot = ActionButton_GetPagedID(button)
local itemID = slot and reagent_slots[slot]
if not itemID then return end
local text = _G[button:GetName().."Count"]
if text then
text:SetText(reagent_counts[itemID] or 0)
end
end)
end
+17
View File
@@ -0,0 +1,17 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local rgbhex = ShaguTweaks.rgbhex
local module = ShaguTweaks:register({
title = T["Center Vertical Actionbar"],
description = T["Center the vertical actionbar on the right side."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
maintainer = "@shagu (GitHub)",
category = T["Action Bar"],
enabled = nil,
})
module.enable = function(self)
MultiBarRight:ClearAllPoints()
MultiBarRight:SetPoint("RIGHT", 0, -16)
end
+662
View File
@@ -0,0 +1,662 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local HookScript = ShaguTweaks.HookScript
local floor = math.floor
local ceil = math.ceil
local min = math.min
local mod = math.mod or mod
STBuyEmAll = STBuyEmAll or {}
local Buy = STBuyEmAll
local strings = {
enUS = {
["Max"] = "Max",
["Stack"] = "Stack",
["Are you sure you want to buy\n %d × %s?"] = "Are you sure you want to buy\n %d × %s?",
["Stack purchase"] = "Stack purchase",
["Stack size"] = "Stack size",
["Partial stack"] = "Partial stack",
["Maximum purchase"] = "Maximum purchase",
["You can fit"] = "You can fit",
["You can afford"] = "You can afford",
["Vendor has"] = "Vendor has",
},
deDE = {
["Are you sure you want to buy\n %d × %s?"] = "Bist du sicher das du\n %d × %s kaufen willst?",
["Stack size"] = "Stack größe",
["Maximum purchase"] = "Größt möglicher Einkauf",
["You can fit"] = "Du hast Platz für",
["You can afford"] = "Du kannst dir leisten",
["Vendor has"] = "Der Verkäufer hat",
},
frFR = {
["Stack"] = "Pile",
["Are you sure you want to buy\n %d × %s?"] = "Voulez-vous vraiment acheter\n %d × %s?",
["Stack size"] = "Taille de pile",
["Maximum purchase"] = "Achat maximum",
["You can fit"] = "Vous pouvez transporter",
["You can afford"] = "Vous pouvez payer",
["Vendor has"] = "Le marchand a",
},
}
local locale = strings[GetLocale()] or strings.enUS
local function L(key)
return locale[key] or strings.enUS[key] or key
end
BUYEMALL_MAX = L("Max")
BUYEMALL_STACK = L("Stack")
local module = ShaguTweaks:register({
title = T["Buy Em All"],
description = T["Shift-click vendor items to choose stack, maximum, or a custom purchase amount."],
expansions = { ["vanilla"] = true },
category = T["Merchant"],
enabled = true,
})
local function FamilyMatches(itemFamily, bagFamily)
bagFamily = tonumber(bagFamily) or 0
itemFamily = tonumber(itemFamily) or 0
if bagFamily == 0 then return true end
if itemFamily == 0 then return false end
-- Bag families are power-of-two bit flags. Avoid requiring an external bit
-- library on 1.12 while still supporting Turtle custom specialty families.
return mod(floor(itemFamily / bagFamily), 2) == 1
end
function Buy:CountPurchaseItem()
if not self.purchaseItemID then return nil end
if API and API.GetItemCount then
return API.GetItemCount(self.purchaseItemID, false, false)
end
return nil
end
function Buy:StopPurchaseQueue()
if self.purchaseFrame then self.purchaseFrame:Hide() end
self.purchaseItemIndex = nil
self.purchaseItemID = nil
self.purchasePreset = 1
self.purchaseLoops = 0
self.purchaseAmount = 0
self.purchaseLeftover = 0
self.purchaseElapsed = 0
self.purchaseWaiting = nil
self.purchaseExpectedCount = nil
self.purchasePendingKind = nil
end
function Buy:AbortPurchaseQueue()
self:StopPurchaseQueue()
DEFAULT_CHAT_FRAME:AddMessage("Buy Em All: purchase queue stopped because the previous stack was not confirmed. No further stacks were sent.")
end
function Buy:ConfirmPendingPurchase()
if not self.purchaseWaiting or not self.purchaseExpectedCount then return false end
local count = self:CountPurchaseItem()
if count == nil or count < self.purchaseExpectedCount then return false end
if self.purchasePendingKind == "loop" then
self.purchaseLoops = self.purchaseLoops - 1
elseif self.purchasePendingKind == "leftover" then
self.purchaseLeftover = 0
end
self.purchaseWaiting = nil
self.purchaseExpectedCount = nil
self.purchasePendingKind = nil
self.purchaseElapsed = 0
return true
end
function Buy:Purchase_OnEvent()
self:ConfirmPendingPurchase()
end
function Buy:SendPurchase(quantity, kind)
local before = self:CountPurchaseItem()
if before == nil then
self:AbortPurchaseQueue()
return
end
self.purchaseWaiting = true
self.purchasePendingKind = kind
self.purchaseExpectedCount = before + quantity * (self.purchasePreset or 1)
self.purchaseElapsed = 0
BuyMerchantItem(self.purchaseItemIndex, quantity)
end
function Buy:Purchase_OnUpdate(elapsed)
self.purchaseElapsed = (self.purchaseElapsed or 0) + (elapsed or 0)
if self.purchaseWaiting then
if self.purchaseElapsed >= self.purchaseTimeout then
if not self:ConfirmPendingPurchase() then self:AbortPurchaseQueue() end
end
return
end
if self.purchaseElapsed < self.purchaseDelay then return end
self.purchaseElapsed = 0
if self.purchaseLoops and self.purchaseLoops > 0 then
self:SendPurchase(self.purchaseAmount, "loop")
return
end
if self.purchaseLeftover and self.purchaseLeftover > 0 then
self:SendPurchase(self.purchaseLeftover, "leftover")
return
end
self:StopPurchaseQueue()
end
function Buy:DoPurchase(amount)
if not amount or amount <= 0 or not self.itemIndex or not self.preset or not self.stack then return end
if STBuyEmAllFrame then STBuyEmAllFrame:Hide() end
local numLoops, purchaseAmount, leftover
if self.preset > 1 then
numLoops = floor(amount / self.preset)
purchaseAmount = 1
leftover = 0
else
numLoops = floor(amount / self.stack)
purchaseAmount = self.stack
leftover = mod(amount, self.stack)
end
self:StopPurchaseQueue()
self.purchaseItemIndex = self.itemIndex
self.purchaseItemID = self.itemID
self.purchasePreset = self.preset
self.purchaseLoops = numLoops
self.purchaseAmount = purchaseAmount
self.purchaseLeftover = leftover
self.purchaseElapsed = self.purchaseDelay
if self.purchaseLoops > 0 or self.purchaseLeftover > 0 then
self.purchaseFrame:Show()
end
end
function Buy:DoConfirmation(amount)
self.confirmAmount = amount
local dialog = StaticPopup_Show("STBUYEMALL_CONFIRM", amount, self.itemName)
if dialog then dialog.data = amount end
end
function Buy:VerifyPurchase(amount)
amount = amount or self.split
if not amount or amount <= 0 or not self.preset or self.preset <= 0 then return end
amount = ceil(amount / self.preset) * self.preset
if amount > self.max then amount = self.max end
if amount > self.stack and amount > self.defaultStack then
self:DoConfirmation(amount)
else
self:DoPurchase(amount)
end
end
function Buy:SetStackClick()
if not self.stack or self.stack <= 0 then
self.stackClick = self.max or self.split or 1
return
end
local targetRemainder = self.partialFit or 0
if targetRemainder == 0 then targetRemainder = self.stack end
local currentRemainder = mod(self.split, self.stack)
local increase = targetRemainder - currentRemainder
if increase <= 0 then increase = increase + self.stack end
self.stackClick = self.split + increase
end
function Buy:UpdateDisplay(amount)
if not self.preset or not self.price or not self.split then return end
local purchase = ceil((amount or self.split) / self.preset)
local cost = purchase * self.price
if STBuyEmAllMoneyFrame then
MoneyFrame_Update("STBuyEmAllMoneyFrame", cost)
end
if not amount and not self.isUpdating then
self.isUpdating = true
STBuyEmAllText:SetText(self.split)
STBuyEmAllLeftButton:Enable()
STBuyEmAllRightButton:Enable()
if self.split >= self.max then
STBuyEmAllRightButton:Disable()
elseif self.split <= self.preset then
STBuyEmAllLeftButton:Disable()
end
self:SetStackClick()
STBuyEmAllStackButton:Enable()
if not self.stackClick or self.max < self.stackClick then
STBuyEmAllStackButton:Disable()
end
self.isUpdating = nil
end
end
function Buy:Show(anchor)
self.typing = 0
STBuyEmAllLeftButton:Disable()
STBuyEmAllRightButton:Enable()
self:SetStackClick()
STBuyEmAllStackButton:Enable()
if not self.stackClick or self.max < self.stackClick then STBuyEmAllStackButton:Disable() end
STBuyEmAllFrame:ClearAllPoints()
STBuyEmAllFrame:SetPoint("BOTTOMLEFT", anchor, "TOPLEFT", 0, 4)
STBuyEmAllFrame:Show()
self:UpdateDisplay()
end
function Buy:OnClick()
if this == STBuyEmAllOkayButton then
self:VerifyPurchase()
elseif this == STBuyEmAllCancelButton then
STBuyEmAllFrame:Hide()
elseif this == STBuyEmAllStackButton then
if self.stackClick and self.stackClick <= self.max then
self.split = self.stackClick
self:UpdateDisplay()
if this:IsEnabled() == 1 then self:OnEnter() else GameTooltip:Hide() end
end
elseif this == STBuyEmAllMaxButton then
self.split = self.max
self:UpdateDisplay()
end
end
function Buy:OnChar()
local digit = tonumber(arg1)
if digit == nil then return end
if self.typing == 0 then
self.typing = 1
self.split = 0
end
local split = (self.split * 10) + digit
if split <= self.max then
self.split = split
end
if self.split <= 0 then self.split = 1 end
self:UpdateDisplay()
end
function Buy:OnKeyDown()
if arg1 == "BACKSPACE" or arg1 == "DELETE" then
if self.typing == 0 or self.split == 1 then return end
self.split = floor(self.split / 10)
if self.split <= 1 then
self.split = 1
self.typing = 0
end
self:UpdateDisplay()
elseif arg1 == "ENTER" then
self:VerifyPurchase()
elseif arg1 == "ESCAPE" then
STBuyEmAllFrame:Hide()
elseif arg1 == "LEFT" or arg1 == "DOWN" then
self:Left_Click()
elseif arg1 == "RIGHT" or arg1 == "UP" then
self:Right_Click()
end
end
function Buy:Left_Click()
if self.split <= self.preset then return end
local decrease = mod(self.split, self.preset)
decrease = decrease == 0 and self.preset or decrease
self.split = self.split - decrease
self:UpdateDisplay()
end
function Buy:Right_Click()
local increase = self.preset - mod(self.split, self.preset)
if self.split + increase > self.max then return end
self.split = self.split + increase
self:UpdateDisplay()
end
Buy.lines = {
stack = {
label = L("Stack purchase"),
field = "stackClick",
{ label = L("Stack size"), field = "stack" },
{ label = L("Partial stack"), field = "partialFit" },
},
max = {
label = L("Maximum purchase"),
field = "max",
{ label = L("You can fit"), field = "fit" },
{ label = L("You can afford"), field = "afford" },
{
label = L("Vendor has"),
field = "available",
Hide = function()
return not Buy.available or Buy.available <= 1
end,
},
},
}
function Buy:CreateTooltip(lines)
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetText(lines.label .. "|cFFFFFFFF - |r" .. GREEN_FONT_COLOR_CODE .. (lines.amount or 0) .. "|r")
for _, line in ipairs(lines) do
if not (line.Hide and line.Hide()) then
local color = line.amount == lines.amount and GREEN_FONT_COLOR or HIGHLIGHT_FONT_COLOR
GameTooltip:AddDoubleLine(line.label, line.amount or 0, 1, 1, 1, color.r, color.g, color.b)
end
end
GameTooltip:Show()
end
function Buy:OnEnter()
local lines = self.lines[this == STBuyEmAllMaxButton and "max" or "stack"]
lines.amount = self[lines.field]
for _, line in ipairs(lines) do line.amount = self[line.field] end
self:CreateTooltip(lines)
self:UpdateDisplay(lines.amount)
end
function Buy:OnLeave()
self:UpdateDisplay()
GameTooltip:Hide()
end
function Buy:OnHide()
StaticPopup_Hide("STBUYEMALL_CONFIRM")
end
function Buy:FreeBagSpace(itemID)
if not itemID then return 0, 1 end
local stackSize = API.GetItemMaxStackSizeByID(itemID) or 1
if stackSize < 1 then stackSize = 1 end
local itemFamily = API.GetItemFamily(itemID) or 0
local capacity = 0
for bag = 0, 4 do
local freeSlots, bagFamily = API.GetContainerNumFreeSlots(bag)
if FamilyMatches(itemFamily, bagFamily) then
capacity = capacity + (freeSlots or 0) * stackSize
end
local numSlots = GetContainerNumSlots(bag)
for slot = 1, numSlots do
local bagItemID = API.GetContainerItemID(bag, slot)
if bagItemID == itemID then
local itemCount = API.GetContainerItemStackCount(bag, slot) or 0
if itemCount < stackSize then
capacity = capacity + stackSize - itemCount
end
end
end
end
return capacity, stackSize
end
function Buy:GetMerchantData(index)
local info = API.GetMerchantItemInfo and API.GetMerchantItemInfo(index)
if info and info.itemID then
local name = API.GetItemNameByID(info.itemID)
if not name then
name = GetMerchantItemInfo(index)
end
return name, info.itemID, info.price or 0, info.stackCount or 1,
info.numAvailable or -1
end
local name, _, price, quantity, numAvailable = GetMerchantItemInfo(index)
local itemID = API.GetMerchantItemID and API.GetMerchantItemID(index)
return name, itemID, price, quantity, numAvailable
end
function Buy:HandleMerchantClick(anchor, button, ignoreModifiers)
if not anchor or not anchor.GetID then return false end
if MerchantFrame.selectedTab ~= 1
or not API.IsShiftKeyDown()
or API.IsControlKeyDown()
or ignoreModifiers
or (ChatFrameEditBox:IsVisible() and button == "LeftButton") then
return false
end
self.itemIndex = anchor:GetID()
local name, itemID, price, quantity, numAvailable = self:GetMerchantData(self.itemIndex)
if not name or not itemID or not price or not quantity or quantity < 1 then return false end
self.preset = quantity
self.price = price
self.itemName = name
self.itemID = itemID
self.available = numAvailable
local bagCapacity, stack = self:FreeBagSpace(itemID)
self.stack = stack
self.fit = floor(bagCapacity / quantity) * quantity
if price > 0 then
self.afford = floor(GetMoney() / price) * quantity
else
self.afford = self.fit
end
self.max = min(self.fit, self.afford)
if numAvailable and numAvailable > -1 then
self.max = min(self.max, numAvailable * quantity)
end
if self.max <= 0 then return true end
if self.max == 1 then
if self.originalMerchantItemButton_OnClick then
self.originalMerchantItemButton_OnClick("LeftButton", 1)
end
return true
end
self.defaultStack = quantity
if self.defaultStack > self.max then self.defaultStack = self.max end
self.split = self.defaultStack
self.partialFit = mod(self.fit, stack)
self:SetStackClick()
self:Show(anchor)
return true
end
function Buy:CreateUI()
if STBuyEmAllFrame then return end
local frame = CreateFrame("Frame", "STBuyEmAllFrame", MerchantFrame)
frame:SetWidth(210)
frame:SetHeight(110)
frame:SetFrameStrata("DIALOG")
frame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 16,
insets = { left = 4, right = 4, top = 4, bottom = 4 },
})
frame:SetBackdropColor(0, 0, 0, .9)
frame:EnableKeyboard(true)
frame:SetScript("OnChar", function() Buy:OnChar() end)
frame:SetScript("OnKeyDown", function() Buy:OnKeyDown() end)
frame:SetScript("OnHide", function() Buy:OnHide() end)
frame:Hide()
local title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
title:SetPoint("TOP", frame, "TOP", 0, -10)
title:SetText(T["Buy Em All"])
local amount = frame:CreateFontString("STBuyEmAllText", "ARTWORK", "GameFontHighlight")
amount:SetWidth(50)
amount:SetPoint("TOP", frame, "TOP", 0, -29)
amount:SetText("1")
local left = CreateFrame("Button", "STBuyEmAllLeftButton", frame, "UIPanelButtonTemplate")
left:SetWidth(28)
left:SetHeight(22)
left:SetPoint("RIGHT", amount, "LEFT", -4, 0)
left:SetText("-")
left:SetScript("OnClick", function() Buy:Left_Click() end)
local right = CreateFrame("Button", "STBuyEmAllRightButton", frame, "UIPanelButtonTemplate")
right:SetWidth(28)
right:SetHeight(22)
right:SetPoint("LEFT", amount, "RIGHT", 4, 0)
right:SetText("+")
right:SetScript("OnClick", function() Buy:Right_Click() end)
local stack = CreateFrame("Button", "STBuyEmAllStackButton", frame, "UIPanelButtonTemplate")
stack:SetWidth(58)
stack:SetHeight(22)
stack:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -50)
stack:SetText(BUYEMALL_STACK)
stack:SetScript("OnClick", function() Buy:OnClick() end)
stack:SetScript("OnEnter", function() Buy:OnEnter() end)
stack:SetScript("OnLeave", function() Buy:OnLeave() end)
local maxButton = CreateFrame("Button", "STBuyEmAllMaxButton", frame, "UIPanelButtonTemplate")
maxButton:SetWidth(58)
maxButton:SetHeight(22)
maxButton:SetPoint("LEFT", stack, "RIGHT", 4, 0)
maxButton:SetText(BUYEMALL_MAX)
maxButton:SetScript("OnClick", function() Buy:OnClick() end)
maxButton:SetScript("OnEnter", function() Buy:OnEnter() end)
maxButton:SetScript("OnLeave", function() Buy:OnLeave() end)
local okay = CreateFrame("Button", "STBuyEmAllOkayButton", frame, "UIPanelButtonTemplate")
okay:SetWidth(58)
okay:SetHeight(22)
okay:SetPoint("LEFT", maxButton, "RIGHT", 4, 0)
okay:SetText(OKAY)
okay:SetScript("OnClick", function() Buy:OnClick() end)
local cancel = CreateFrame("Button", "STBuyEmAllCancelButton", frame, "UIPanelButtonTemplate")
cancel:SetWidth(58)
cancel:SetHeight(22)
cancel:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, -78)
cancel:SetText(CANCEL)
cancel:SetScript("OnClick", function() Buy:OnClick() end)
local money = CreateFrame("Frame", "STBuyEmAllMoneyFrame", frame, "SmallMoneyFrameTemplate")
money:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 11)
MoneyTypeInfo["STBUYEMALL"] = {
UpdateFunc = function() return 0 end,
showSmallerCoins = "Backpack",
fixedWidth = 1,
collapse = 1,
}
money.info = MoneyTypeInfo["STBUYEMALL"]
money.moneyType = "STBUYEMALL"
money.small = 1
end
module.enable = function(self)
if IsAddOnLoaded("BuyEmAll") then return end
local required = {
"GetItemCount", "GetItemMaxStackSizeByID", "GetItemFamily",
"GetContainerNumFreeSlots", "GetContainerItemID",
"GetContainerItemStackCount", "GetMerchantItemInfo",
"GetMerchantItemID", "IsShiftKeyDown", "IsControlKeyDown",
}
for _, name in ipairs(required) do
if not API or type(API[name]) ~= "function" then
DEFAULT_CHAT_FRAME:AddMessage("|cffff5555ShaguTweaks:|r Buy Em All requires ClassicAPI.")
return
end
end
Buy:CreateUI()
Buy.purchaseDelay = 0.05
Buy.purchaseTimeout = 3.0
Buy.purchaseFrame = Buy.purchaseFrame or CreateFrame("Frame", "STBuyEmAllPurchaseQueue", UIParent)
Buy.purchaseFrame:Hide()
Buy.purchaseFrame:RegisterEvent("BAG_UPDATE")
Buy.purchaseFrame:RegisterEvent("MERCHANT_UPDATE")
Buy.purchaseFrame:SetScript("OnEvent", function()
Buy:Purchase_OnEvent()
end)
Buy.purchaseFrame:SetScript("OnUpdate", function()
Buy:Purchase_OnUpdate(arg1)
end)
StaticPopupDialogs["STBUYEMALL_CONFIRM"] = {
text = L("Are you sure you want to buy\n %d × %s?"),
button1 = YES,
button2 = NO,
OnAccept = function()
Buy:DoPurchase(Buy.confirmAmount)
end,
timeout = 0,
hideOnEscape = true,
}
HookScript(MerchantFrame, "OnHide", function()
STBuyEmAllFrame:Hide()
Buy:StopPurchaseQueue()
end)
local function MerchantItemButton_OnClick_Wrapper(button, ignoreModifiers)
local anchor = this
if Buy:HandleMerchantClick(anchor, button, ignoreModifiers) then return end
if Buy.originalMerchantItemButton_OnClick then
return Buy.originalMerchantItemButton_OnClick(button, ignoreModifiers)
end
end
local function InstallHook()
local current = _G.MerchantItemButton_OnClick
if type(current) ~= "function" then return end
if current == MerchantItemButton_OnClick_Wrapper then return end
Buy.originalMerchantItemButton_OnClick = current
_G.MerchantItemButton_OnClick = MerchantItemButton_OnClick_Wrapper
end
InstallHook()
local frame = CreateFrame("Frame", nil, UIParent)
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("MERCHANT_SHOW")
frame:SetScript("OnEvent", InstallHook)
end
+68
View File
@@ -0,0 +1,68 @@
-- Shared chat-filter dispatcher for the chat filter modules (formerly in Extras).
-- Installs one ChatFrame_OnEvent wrapper only when a filter is enabled.
local Filter = ShaguTweaks.ExtrasChatFilter
if not Filter then
Filter = {
handlers = {},
order = {},
known = {},
installed = false,
installer = nil,
}
ShaguTweaks.ExtrasChatFilter = Filter
end
function Filter:Install()
if self.installed then return end
self.installed = true
self.original = ChatFrame_OnEvent
ChatFrame_OnEvent = function(event)
for i = 1, table.getn(Filter.order) do
local current = Filter.handlers[Filter.order[i]]
if current and current(event) then
return
end
end
if Filter.original then
return Filter.original(event)
end
end
end
function Filter:ScheduleInstall()
if self.installed or self.installer then return end
-- ShaguTweaks enables modules during VARIABLES_LOADED in unspecified table
-- order. Install on PLAYER_ENTERING_WORLD so this dispatcher becomes the
-- outer wrapper after the core Chat Spam Filter regardless of module order.
-- That prevents locally hidden World/NPC messages from entering its cache.
self.installer = CreateFrame("Frame")
self.installer:RegisterEvent("PLAYER_ENTERING_WORLD")
self.installer:SetScript("OnEvent", function()
Filter:Install()
this:UnregisterAllEvents()
this:Hide()
Filter.installer = nil
end)
end
function Filter:Register(key, handler)
if type(key) ~= "string" or type(handler) ~= "function" then return end
if not self.known[key] then
self.known[key] = true
self.order[table.getn(self.order) + 1] = key
end
self.handlers[key] = handler
self:ScheduleInstall()
end
function Filter:Unregister(key)
self.handlers[key] = nil
end
+45
View File
@@ -0,0 +1,45 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local module = ShaguTweaks:register({
title = T["Center Text Input Box"],
description = T["Move the chat input box to the center of the screen."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
category = T["Chat & Social"],
maintainer = "@shagu (GitHub)",
enabled = nil,
})
local dodge_frames = {
MainMenuBarArtFrame, MultiBarBottomLeft, MultiBarBottomRight, PetActionBarFrame, ShapeshiftBarFrame
}
module.enable = function(self)
ChatFrameEditBox:SetWidth(300)
local function UpdateInputPosition()
local top = 0
for _, frame in ipairs(dodge_frames) do
if frame and frame:IsVisible() and frame:GetTop() then
top = math.max(top, frame:GetTop())
end
end
ChatFrameEditBox:ClearAllPoints()
ChatFrameEditBox:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, top)
end
-- Other ShaguTweaks modules also post-hook UIParent_ManageFramePositions.
-- Module enable order is intentionally unordered, so install this layout
-- hook at PLAYER_ENTERING_WORLD after the normal module pass. This makes
-- the edit box position use the final actionbar/pet/stance positions.
local installer = CreateFrame("Frame", nil, UIParent)
installer:RegisterEvent("PLAYER_ENTERING_WORLD")
installer:SetScript("OnEvent", function()
hooksecurefunc("UIParent_ManageFramePositions", UpdateInputPosition)
UpdateInputPosition()
this:UnregisterAllEvents()
this:Hide()
end)
end
+92
View File
@@ -0,0 +1,92 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local Filter = ShaguTweaks.ExtrasChatFilter
local module = ShaguTweaks:register({
title = T["Block NPC Spam"],
description = T["Blocks known repetitive NPC say/yell spam messages."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Chat & Social"],
enabled = nil,
})
local blockedEvents = {
["CHAT_MSG_MONSTER_SAY"] = true,
["CHAT_MSG_MONSTER_YELL"] = true,
}
local blockedNPCs = {}
local npcNames = {
-- enUS
"Tansy Sparkpen",
"Fara Boltbreaker",
"Shellcoin Promoter",
-- ruRU
"Зазывала ярмарки Новолуния",
"Томас Миллер",
"Уильям",
"Донна",
"Жюстина Демалье",
"Мерлис Малаган",
"Лиана Пирс",
"Сюзанна",
"Джейни Аншип",
}
for _, name in pairs(npcNames) do
blockedNPCs[name] = true
blockedNPCs[string.lower(name)] = true
end
local blockedPhrases = {
{ "shellcoin", "invest" },
{ "shells", "trade" },
{ "shells", "money" },
}
local function IsBlockedNPC(sender)
if not sender then return false end
return blockedNPCs[sender] or blockedNPCs[string.lower(sender)] or false
end
local function IsBlockedPhrase(message)
if not message then return false end
local normalized = string.lower(string.gsub(message, "[^A-Za-z0-9]", ""))
for _, phrase in pairs(blockedPhrases) do
local matched = true
for _, word in pairs(phrase) do
if not string.find(normalized, word, 1, true) then
matched = false
break
end
end
if matched then return true end
end
return false
end
local function FilterNPCSpam(event)
if not blockedEvents[event] then return false end
if not arg1 or not arg2 then return false end
return IsBlockedNPC(arg2) or IsBlockedPhrase(arg1)
end
module.enable = function(self)
if Filter then
Filter:Register("BlockNPCSpam", FilterNPCSpam)
end
end
module.disable = function(self)
if Filter then
Filter:Unregister("BlockNPCSpam")
end
end
+19
View File
@@ -0,0 +1,19 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local rgbhex = ShaguTweaks.rgbhex
local module = ShaguTweaks:register({
title = T["Enable Text Shadow"],
description = T["Enable text shadow in all chat frames."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
category = T["Chat & Social"],
maintainer = "@shagu (GitHub)",
enabled = nil,
})
module.enable = function(self)
for i=1,NUM_CHAT_WINDOWS do
local font, size = _G["ChatFrame"..i]:GetFont()
_G["ChatFrame"..i]:SetFont(font, size, "OUTLINE")
end
end
+57
View File
@@ -0,0 +1,57 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local rgbhex = ShaguTweaks.rgbhex
local module = ShaguTweaks:register({
title = T["Chat Timestamps"],
description = T["Add timestamps to chat messages."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
category = T["Chat & Social"],
maintainer = "@shagu (GitHub)",
enabled = false,
config = {
["chat.timestamp.bracket"] = "[]",
["chat.timestamp.format"] = 24,
["chat.timestamp.color"] = { r = .8, g = .8, b = .8, a = 1},
}
})
module.enable = function(self)
-- config shortcuts
local bracket = self.config["chat.timestamp.bracket"]
local clock = self.config["chat.timestamp.format"]
local rgb = self.config["chat.timestamp.color"]
-- parse config
local left = string.sub(bracket, 1, 1) or ""
local right = string.sub(bracket, 2, 2) or ""
local format = clock == 24 and "%H:%M:%S" or "%I:%M:%S %p"
local color = rgbhex({ rgb.r, rgb.g, rgb.b, rgb.a })
local function InstallTimestampHooks()
for i = 1, NUM_CHAT_WINDOWS do
local frame = _G["ChatFrame"..i]
if frame and not frame.ShaguTweaksExtrasTimestampAddMessage then
frame.ShaguTweaksExtrasTimestampAddMessage = frame.AddMessage
frame.AddMessage = function(self, msg, a1, a2, a3, a4, a5)
if not msg then return end
msg = color .. left .. date(format) .. right .. "|r " .. msg
self:ShaguTweaksExtrasTimestampAddMessage(msg, a1, a2, a3, a4, a5)
end
end
end
end
-- Install after the normal ShaguTweaks module pass. Chat Tweaks in the
-- main fork also wraps AddMessage, and its module order is intentionally
-- unordered. Waiting for PLAYER_ENTERING_WORLD makes timestamps the outer
-- wrapper every time, so history consistently stores the displayed time.
local installer = CreateFrame("Frame", nil, UIParent)
installer:RegisterEvent("PLAYER_ENTERING_WORLD")
installer:SetScript("OnEvent", function()
InstallTimestampHooks()
this:UnregisterAllEvents()
this:Hide()
end)
end
+299
View File
@@ -0,0 +1,299 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local selfcastTitle = T["Alt Self-Cast"]
local selfcast = ShaguTweaks:register({
title = selfcastTitle,
description = T["When Key-Down Casting is enabled, hold Alt to cast eligible actions on yourself."],
expansions = { ["vanilla"] = true },
category = T["Action Bar"],
enabled = nil,
})
selfcast.enable = function(self)
-- Runtime behavior is handled by Key-Down Casting below.
end
local module = ShaguTweaks:register({
title = T["Key-Down Casting"],
description = T["Activates action-bar and pet actions when the key is pressed instead of when it is released."],
expansions = { ["vanilla"] = true },
category = T["Action Bar"],
enabled = true,
})
module.enable = function(self)
-- Keep this feature independent from No Toggle Auto-Attack.
if IsAddOnLoaded("ClassicSnowFall") or IsAddOnLoaded("ClassicSnowfall") then return end
local function SelfCast()
if ShaguTweaks_config and ShaguTweaks_config[selfcastTitle] == 1 then
return API.IsAltKeyDown() and 1 or 0
end
return 0
end
-- Preserve the original slash command while using a unique registry key.
SLASH_STCLASSICSNOWFALLSELFCAST1 = "/csselfcast"
SlashCmdList["STCLASSICSNOWFALLSELFCAST"] = function()
ShaguTweaks_config = ShaguTweaks_config or {}
if ShaguTweaks_config[selfcastTitle] == 1 then
ShaguTweaks_config[selfcastTitle] = 0
DEFAULT_CHAT_FRAME:AddMessage("Classic Snowfall ALT SelfCast now disabled.")
else
ShaguTweaks_config[selfcastTitle] = 1
DEFAULT_CHAT_FRAME:AddMessage("Classic Snowfall ALT SelfCast now enabled.")
end
end
local function IsBongosReady()
return _G.BActionButton and type(_G.BActionButton.GetPagedID) == "function"
end
local function ST_ActionButtonDown(id)
local button, pagedID
if IsBongosReady() then
button = _G["BActionButton" .. id]
pagedID = _G.BActionButton.GetPagedID(id)
if button and button:GetButtonState() == "NORMAL" then
button:SetButtonState("PUSHED")
end
if pagedID then UseAction(pagedID, 0, SelfCast()) end
return
end
if BonusActionBarFrame and BonusActionBarFrame:IsShown() then
button = _G["BonusActionButton" .. id]
else
button = _G["ActionButton" .. id]
end
if button and button:GetButtonState() == "NORMAL" then
button:SetButtonState("PUSHED")
UseAction(ActionButton_GetPagedID(button), 0, SelfCast())
end
end
local function ST_ActionButtonUp(id)
local button, pagedID
if IsBongosReady() then
button = _G["BActionButton" .. id]
pagedID = _G.BActionButton.GetPagedID(id)
if button and button:GetButtonState() == "PUSHED" then
button:SetButtonState("NORMAL")
if MacroFrame_SaveMacro then MacroFrame_SaveMacro() end
if pagedID and IsCurrentAction(pagedID) then
button:SetChecked(1)
else
button:SetChecked(0)
end
end
return
end
if BonusActionBarFrame and BonusActionBarFrame:IsShown() then
button = _G["BonusActionButton" .. id]
else
button = _G["ActionButton" .. id]
end
if button and button:GetButtonState() == "PUSHED" then
button:SetButtonState("NORMAL")
if MacroFrame_SaveMacro then MacroFrame_SaveMacro() end
if IsCurrentAction(ActionButton_GetPagedID(button)) then
button:SetChecked(1)
else
button:SetChecked(0)
end
end
end
local function ST_MultiActionButtonDown(bar, id)
local button = _G[bar .. "Button" .. id]
if button and button:GetButtonState() == "NORMAL" then
button:SetButtonState("PUSHED")
UseAction(ActionButton_GetPagedID(button), 0, SelfCast())
end
end
local function ST_MultiActionButtonUp(bar, id)
local button = _G[bar .. "Button" .. id]
if button and button:GetButtonState() == "PUSHED" then
button:SetButtonState("NORMAL")
if MacroFrame_SaveMacro then MacroFrame_SaveMacro() end
if IsCurrentAction(ActionButton_GetPagedID(button)) then
button:SetChecked(1)
else
button:SetChecked(0)
end
end
end
local function ST_PetActionButtonDown(id)
local button = _G["PetActionButton" .. id]
if button and button:GetButtonState() == "NORMAL" then
button:SetButtonState("PUSHED")
CastPetAction(id)
end
end
local function ST_PetActionButtonUp(id)
local button = _G["PetActionButton" .. id]
if button and button:GetButtonState() == "PUSHED" then
button:SetButtonState("NORMAL")
end
end
-- Vanilla's BONUSACTIONBUTTON bindings are aliases for the pet-action
-- handlers. Keep the same relationship while moving the cast to key-down.
local function ST_BonusActionButtonDown(id)
ST_PetActionButtonDown(id)
end
local function ST_BonusActionButtonUp(id)
ST_PetActionButtonUp(id)
end
local function Install()
_G.ActionButtonDown = ST_ActionButtonDown
_G.ActionButtonUp = ST_ActionButtonUp
_G.MultiActionButtonDown = ST_MultiActionButtonDown
_G.MultiActionButtonUp = ST_MultiActionButtonUp
_G.PetActionButtonDown = ST_PetActionButtonDown
_G.PetActionButtonUp = ST_PetActionButtonUp
if type(_G.BonusActionButtonDown) == "function" then
_G.BonusActionButtonDown = ST_BonusActionButtonDown
end
if type(_G.BonusActionButtonUp) == "function" then
_G.BonusActionButtonUp = ST_BonusActionButtonUp
end
end
local function HandlersAreInstalled()
return _G.ActionButtonDown == ST_ActionButtonDown
and _G.ActionButtonUp == ST_ActionButtonUp
and _G.MultiActionButtonDown == ST_MultiActionButtonDown
and _G.MultiActionButtonUp == ST_MultiActionButtonUp
and _G.PetActionButtonDown == ST_PetActionButtonDown
and _G.PetActionButtonUp == ST_PetActionButtonUp
end
-- Modified bindings (SHIFT-*, CTRL-*, ALT-*) are routed through ClassicAPI's
-- normalized override-binding layer. Vanilla's runOnUp binding path is not
-- reliable for every modified-key combination on custom 1.12 clients.
-- Normal unmodified keys continue through the original ClassicSnowfall-style
-- global handlers above.
local bindingOwner = CreateFrame("Frame", "ShaguTweaksKeyDownBindingOwner", UIParent)
local proxies = {}
local specs = {}
local function AddSpec(command, kind, a, b)
table.insert(specs, { command = command, kind = kind, a = a, b = b })
end
for i = 1, 12 do
AddSpec("ACTIONBUTTON" .. i, "action", i)
AddSpec("MULTIACTIONBAR1BUTTON" .. i, "multi", "MultiBarBottomLeft", i)
AddSpec("MULTIACTIONBAR2BUTTON" .. i, "multi", "MultiBarBottomRight", i)
AddSpec("MULTIACTIONBAR3BUTTON" .. i, "multi", "MultiBarRight", i)
AddSpec("MULTIACTIONBAR4BUTTON" .. i, "multi", "MultiBarLeft", i)
AddSpec("BONUSACTIONBUTTON" .. i, "bonus", i)
end
local function IsModifiedKey(key)
if type(key) ~= "string" then return false end
local upper = string.upper(key)
return string.find(upper, "SHIFT-", 1, true)
or string.find(upper, "CTRL-", 1, true)
or string.find(upper, "ALT-", 1, true)
end
local function GetProxy(spec, index)
local name = "ShaguTweaksKeyDownProxy" .. index
if proxies[index] then return proxies[index] end
local button = CreateFrame("Button", name, bindingOwner)
button.spec = spec
button:SetScript("OnClick", function()
local s = this.spec
if s.kind == "action" then
ST_ActionButtonDown(s.a)
ST_ActionButtonUp(s.a)
elseif s.kind == "multi" then
ST_MultiActionButtonDown(s.a, s.b)
ST_MultiActionButtonUp(s.a, s.b)
elseif s.kind == "bonus" then
ST_BonusActionButtonDown(s.a)
ST_BonusActionButtonUp(s.a)
end
end)
proxies[index] = button
return button
end
local refreshingBindings
local function RefreshModifiedBindings()
if refreshingBindings then return end
if not API or not API.overridebindings then return end
refreshingBindings = true
API.ClearOverrideBindings(bindingOwner)
for index, spec in ipairs(specs) do
local key1, key2 = GetBindingKey(spec.command)
local proxy = GetProxy(spec, index)
if IsModifiedKey(key1) then
API.SetOverrideBindingClick(bindingOwner, true, key1, proxy:GetName())
end
if IsModifiedKey(key2) then
API.SetOverrideBindingClick(bindingOwner, true, key2, proxy:GetName())
end
end
refreshingBindings = nil
end
Install()
RefreshModifiedBindings()
local frame = CreateFrame("Frame", nil, UIParent)
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("UPDATE_BINDINGS")
frame.guardUntil = 0
frame.guardTicker = nil
local function GuardTick()
if not frame.guardUntil or frame.guardUntil <= GetTime() then
if frame.guardTicker then
frame.guardTicker:Cancel()
frame.guardTicker = nil
end
return
end
if not HandlersAreInstalled() then Install() end
end
local function StartGuard()
frame.guardUntil = GetTime() + 8
if not frame.guardTicker then
frame.guardTicker = C_Timer.NewTicker(0.25, GuardTick)
end
end
frame:SetScript("OnEvent", function()
Install()
RefreshModifiedBindings()
if event == "PLAYER_LOGIN" or event == "PLAYER_ENTERING_WORLD" then
StartGuard()
end
end)
end
+99
View File
@@ -0,0 +1,99 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local module = ShaguTweaks:register({
title = T["Cursor Tooltip"],
description = T["Attaches default tooltips to the cursor."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Tooltip & Items"],
enabled = nil,
})
module.enable = function(self)
if ShaguTweaks.CursorTooltipInstalled then return end
ShaguTweaks.CursorTooltipInstalled = true
-- Keep the native helper for non-GameTooltip callers. The feature only
-- needs to move the normal world/UI GameTooltip and should not unexpectedly
-- change other tooltip frames that may reuse the same helper.
local originalDefaultAnchor = ShaguTweaks.CursorTooltipOriginalDefaultAnchor
or _G.GameTooltip_SetDefaultAnchor
ShaguTweaks.CursorTooltipOriginalDefaultAnchor = originalDefaultAnchor
local cursor = CreateFrame("Frame", nil, UIParent)
cursor:SetWidth(1)
cursor:SetHeight(1)
cursor:Hide()
local following = false
local lastX, lastY, lastScale
local function UpdateCursor(force)
local scale = UIParent:GetEffectiveScale()
if not scale or scale == 0 then scale = UIParent:GetScale() end
if not scale or scale == 0 then scale = 1 end
local x, y = GetCursorPosition()
-- GetCursorPosition is cheap and required for following the pointer, but
-- avoid a frame-layout operation while the mouse is completely still.
if not force and x == lastX and y == lastY and scale == lastScale then
return
end
lastX, lastY, lastScale = x, y, scale
-- Reusing the same CENTER anchor updates it in place; ClearAllPoints every
-- rendered frame is unnecessary.
cursor:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x / scale, y / scale)
end
cursor:SetScript("OnUpdate", function()
if not following or not GameTooltip:IsShown() then
this:Hide()
return
end
UpdateCursor(false)
end)
function _G.GameTooltip_SetDefaultAnchor(tooltip, parent)
if tooltip ~= GameTooltip then
return originalDefaultAnchor(tooltip, parent)
end
-- Cursor Tooltip intentionally uses ANCHOR_CURSOR without marking the
-- tooltip as a default anchored tooltip. This matches the upstream mod's
-- lifecycle and makes world-unit tooltips disappear immediately when the
-- mouseover unit is lost, while keeping the custom cursor positioning.
tooltip:SetOwner(parent or UIParent, "ANCHOR_CURSOR")
tooltip:ClearAllPoints()
-- Position once before Show() so there is no first-frame jump from the
-- cursor frame's default origin.
UpdateCursor(true)
tooltip:SetPoint("BOTTOMLEFT", cursor, "TOPRIGHT", 12, 12)
tooltip:SetClampedToScreen(true)
following = true
end
-- Default anchoring normally happens before GameTooltip:Show(). Start the
-- per-frame cursor tracker only once the tooltip is actually visible.
hooksecurefunc(GameTooltip, "Show", function()
if not following then return end
UpdateCursor(true)
cursor:Show()
end)
hooksecurefunc(GameTooltip, "Hide", function()
following = false
cursor:Hide()
end)
end
+146
View File
@@ -0,0 +1,146 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Hide Combat Tooltip"],
description = T["Hides the tooltip in combat. Hold Shift to show it temporarily."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Tooltip & Items"],
enabled = nil,
})
module.enable = function(self)
if ShaguTweaks.HideCombatTooltipInstalled then return end
ShaguTweaks.HideCombatTooltipInstalled = true
local controller = CreateFrame("Frame", nil, UIParent)
local inCombat = UnitAffectingCombat("player") and true or false
local shiftDown = API.IsShiftKeyDown() and true or false
local guardActive = false
local elapsed = 0
local function Apply()
if not inCombat then
if GameTooltip:GetAlpha() ~= 1 then
GameTooltip:SetAlpha(1)
end
return
end
local alpha = shiftDown and 1 or 0
if GameTooltip:GetAlpha() ~= alpha then
GameTooltip:SetAlpha(alpha)
end
end
local function GuardTick()
elapsed = elapsed + (arg1 or 0)
if elapsed < .05 then return end
elapsed = 0
-- ClassicAPI provides modifier events, so normal clients don't need to
-- query Shift here. This fallback is only for old environments.
if not API.modifierstate then
shiftDown = API.IsShiftKeyDown() and true or false
end
Apply()
end
local function StartGuard()
if guardActive or not inCombat or not GameTooltip:IsShown() then return end
guardActive = true
elapsed = 0
controller:SetScript("OnUpdate", GuardTick)
end
local function StopGuard()
if not guardActive then return end
guardActive = false
elapsed = 0
controller:SetScript("OnUpdate", nil)
end
local function RefreshShift()
shiftDown = API.IsShiftKeyDown() and true or false
if inCombat and GameTooltip:IsShown() then
Apply()
StartGuard()
end
end
local function EnterCombat()
inCombat = true
shiftDown = API.IsShiftKeyDown() and true or false
if GameTooltip:IsShown() then
Apply()
StartGuard()
end
end
local function LeaveCombat()
inCombat = false
StopGuard()
Apply()
end
-- Vanilla/Turtle can restore GameTooltip alpha internally while updating a
-- visible unit tooltip. A purely event-driven implementation therefore does
-- not stay hidden reliably. The guard below exists only while BOTH combat
-- and an actual GameTooltip are active, and is throttled to 20 Hz instead of
-- running the full upstream logic every rendered frame.
local oldOnShow = GameTooltip:GetScript("OnShow")
GameTooltip:SetScript("OnShow", function()
if oldOnShow then oldOnShow() end
if inCombat then
shiftDown = API.IsShiftKeyDown() and true or false
Apply()
StartGuard()
else
Apply()
end
end)
local oldOnHide = GameTooltip:GetScript("OnHide")
GameTooltip:SetScript("OnHide", function()
if oldOnHide then oldOnHide() end
StopGuard()
end)
controller:RegisterEvent("PLAYER_ENTERING_WORLD")
controller:RegisterEvent("PLAYER_REGEN_DISABLED")
controller:RegisterEvent("PLAYER_REGEN_ENABLED")
if API.modifierstate then
controller:RegisterEvent("MODIFIER_STATE_CHANGED")
end
controller:SetScript("OnEvent", function()
if event == "PLAYER_REGEN_DISABLED" then
EnterCombat()
elseif event == "PLAYER_REGEN_ENABLED" then
LeaveCombat()
elseif event == "MODIFIER_STATE_CHANGED" then
RefreshShift()
elseif event == "PLAYER_ENTERING_WORLD" then
if UnitAffectingCombat("player") then
EnterCombat()
else
LeaveCombat()
end
end
end)
if inCombat then
EnterCombat()
else
LeaveCombat()
end
end
+128
View File
@@ -0,0 +1,128 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Loot Quality"],
description = T["Colors the loot frame using the highest item quality in the current loot window."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Loot"],
enabled = nil,
})
local MIN_QUALITY = 2 -- uncommon / green
local function FindLootTitle()
local regions = { LootFrame:GetRegions() }
for i = 1, table.getn(regions) do
local region = regions[i]
-- Some Vanilla/Turtle regions expose GetText without being FontStrings.
-- Only keep a region that can actually be recolored.
if region
and type(region.GetText) == "function"
and type(region.SetTextColor) == "function"
and region:GetText() == ITEMS then
return region
end
end
end
module.enable = function(self)
if not self.border then
self.border = LootFrame:CreateTexture(nil, "OVERLAY")
self.border:SetTexture("Interface\\LootFrame\\UI-LootPanel")
self.border:SetPoint("TOPLEFT", LootFrame, "TOPLEFT", -4, 3)
self.border:SetPoint("BOTTOMRIGHT", LootFrame, "BOTTOMRIGHT", 5, -3)
self.border:SetDrawLayer("ARTWORK")
self.highlight = LootFrame:CreateTexture(nil, "OVERLAY")
self.highlight:SetTexture("Interface\\LootFrame\\UI-LootPanel")
self.highlight:SetAllPoints(self.border)
self.highlight:SetDrawLayer("ARTWORK")
self.highlight:SetBlendMode("ADD")
end
self.title = self.title or FindLootTitle()
if self.title
and not self.titleColor
and type(self.title.GetTextColor) == "function" then
local r, g, b, a = self.title:GetTextColor()
self.titleColor = { r or 1, g or .82, b or 0, a or 1 }
end
local function RestoreTitle()
if not self.title or type(self.title.SetTextColor) ~= "function" then return end
local color = self.titleColor
if color then
self.title:SetTextColor(color[1], color[2], color[3], color[4])
else
self.title:SetTextColor(1, .82, 0)
end
end
local function ResetVisuals()
self.border:Hide()
self.highlight:Hide()
RestoreTitle()
end
local function UpdateQuality()
local highestQuality = 0
local count = GetNumLootItems() or 0
for i = 1, count do
if LootSlotIsItem(i) then
local _, _, _, quality = GetLootSlotInfo(i)
quality = tonumber(quality) or 0
if quality > highestQuality then
highestQuality = quality
end
end
end
local color = ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[highestQuality]
if highestQuality < MIN_QUALITY or not color then
ResetVisuals()
return
end
self.border:SetVertexColor(color.r, color.g, color.b)
self.highlight:SetVertexColor(color.r, color.g, color.b)
self.border:Show()
self.highlight:Show()
if self.title and type(self.title.SetTextColor) == "function" then
self.title:SetTextColor(color.r, color.g, color.b)
end
end
self.ResetVisuals = ResetVisuals
self.events = self.events or CreateFrame("Frame")
self.events:UnregisterAllEvents()
self.events:RegisterEvent("LOOT_OPENED")
self.events:RegisterEvent("LOOT_SLOT_CLEARED")
self.events:RegisterEvent("LOOT_CLOSED")
self.events:SetScript("OnEvent", function()
if event == "LOOT_CLOSED" then
ResetVisuals()
else
UpdateQuality()
end
end)
ResetVisuals()
end
module.disable = function(self)
if self.events then
self.events:UnregisterAllEvents()
end
if self.ResetVisuals then
self.ResetVisuals()
end
end
+144
View File
@@ -0,0 +1,144 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local libspell = ShaguTweaks.libspell
local module = ShaguTweaks:register({
title = T["Macro Icons"],
description = T["Detect showtooltip and spells in macros to use them on action buttons."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
maintainer = "@shagu (GitHub)",
category = T["Macro"],
enabled = true,
})
module.enable = function(self)
local gfind = string.gmatch or string.gfind
local function BuildMacroCache()
local cache = {}
for macroSlot = 1, 36 do
local name, _, body = GetMacroInfo(macroSlot)
if name and body then
cache[name] = { id = macroSlot, body = body }
end
end
return cache
end
local function GetMacroData(actionSlot, cache)
local actionType, id = API.GetActionInfo(actionSlot)
if actionType == "macro" and id then
local _, _, body = GetMacroInfo(id)
if body then return id, body end
end
local macroName = GetActionText(actionSlot)
local entry = macroName and cache[macroName]
if entry then return entry.id, entry.body end
end
local function ParseSpellText(match)
if not match then return end
local _, _, spell, rank = string.find(match, "(.+)%((.+)%)")
return spell or match, rank
end
local function ResolveSpell(body, macroSlot)
if not body then return end
local customTooltip
local fallback
for line in gfind(body, "[^%\n]+") do
-- Explicit #showtooltip always wins.
local _, _, match = string.find(line, "^#showtooltip%s+(.+)")
if match then
return ParseSpellText(match)
end
-- Preserve the historical ShaguTweaks custom override.
if not customTooltip then
_, _, customTooltip = string.find(line, "%-%-showtooltip%s+(.+)")
end
-- Keep text parsing as a fallback for old/stale macro caches and /pfcast.
if not fallback then
_, _, fallback = string.find(line, "^/cast%s+(.+)")
end
if not fallback then
_, _, fallback = string.find(line, "^/pfcast%s+(.+)")
end
if not fallback then
_, _, fallback = string.find(line, "CastSpellByName%(%\"(.+)%\"%)")
end
end
if customTooltip then
return ParseSpellText(customTooltip)
end
-- ClassicAPI already resolves and caches the macro's primary spell.
-- This is the correct modern behavior for a bare "#showtooltip" line and
-- also covers /castsequence, CastSpellByName and CastSpellNoToggle.
if macroSlot then
local spell, rank = API.GetMacroSpell(macroSlot)
if spell then
return spell, rank ~= "" and rank or nil
end
end
return ParseSpellText(fallback)
end
local function ButtonMacroScan(bar, macroCache)
if not bar:IsVisible() then return end
local prefix = bar:GetName()
prefix = bar == MainMenuBar and "Action" or prefix
prefix = bar == BonusActionBarFrame and "BonusAction" or prefix
for index = 1, 12 do
local button = _G[prefix.."Button"..index]
local icon = _G[prefix.."Button"..index.."Icon"]
if not button then break end
local actionSlot = ActionButton_GetPagedID(button)
local texture = GetActionTexture(actionSlot)
button.spellslot, button.booktype = nil, nil
local macroSlot, body = GetMacroData(actionSlot, macroCache)
local spell, rank = ResolveSpell(body, macroSlot)
if spell then
button.spellslot, button.booktype = libspell.GetSpellIndex(spell, rank)
if button.spellslot and button.booktype then
texture = GetSpellTexture(button.spellslot, button.booktype) or texture
end
end
if icon and texture and texture ~= icon:GetTexture() then
icon:SetTexture(texture)
end
end
end
local bars = {
MainMenuBar, BonusActionBarFrame, MultiBarRight, MultiBarLeft,
MultiBarBottomRight, MultiBarBottomLeft
}
local macroicons = CreateFrame("Frame", "ShaguTweaksMacroIcons", UIParent)
macroicons:RegisterEvent("PLAYER_ENTERING_WORLD")
macroicons:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
macroicons:RegisterEvent("ACTIONBAR_PAGE_CHANGED")
macroicons:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
macroicons:RegisterEvent("ACTIONBAR_SHOWGRID")
macroicons:RegisterEvent("UPDATE_MACROS")
macroicons:SetScript("OnEvent", function()
local macroCache = BuildMacroCache()
for _, bar in ipairs(bars) do
ButtonMacroScan(bar, macroCache)
end
end)
end
+160
View File
@@ -0,0 +1,160 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Macro Tweaks"],
description = T["Add /equip, /use and modern ClassicAPI macro commands, remove #showtooltip from chat and hide macro commands from history."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
maintainer = "@shagu (GitHub)",
category = T["Macro"],
enabled = true,
})
module.enable = function(self)
-- This interception is intentional: #showtooltip is macro metadata and must
-- not be sent as chat text by the Vanilla macro parser.
local hookSendChatMessage = _G.SendChatMessage
local function IsShowTooltipMetadata(msg)
if not msg then return false end
local line = string.gsub(msg, "^%s+", "")
line = string.gsub(line, "%s+$", "")
return line == "#showtooltip"
or string.find(line, "^#showtooltip%s+") ~= nil
end
function _G.SendChatMessage(msg, a1, a2, a3, a4, a5, a6, a7, a8)
if IsShowTooltipMetadata(msg) then return end
return hookSendChatMessage(msg, a1, a2, a3, a4, a5, a6, a7, a8)
end
-- do not write executed macro commands into chat input history
if not ChatFrameEditBox._AddHistoryLine then
local userinput
ChatFrameEditBox._AddHistoryLine = ChatFrameEditBox.AddHistoryLine
ChatFrameEditBox.AddHistoryLine = function(self, text)
if not userinput and text then
if string.find(text, "^/run%s*") then return end
if string.find(text, "^/script%s*") then return end
if string.find(text, "^/cast%s*") then return end
if string.find(text, "^/startattack%s*") then return end
if string.find(text, "^/stopattack%s*") then return end
if string.find(text, "^/focus%s*") then return end
if string.find(text, "^/clearfocus%s*") then return end
end
ChatFrameEditBox._AddHistoryLine(self, text)
end
local OnEnter = ChatFrameEditBox:GetScript("OnEnterPressed")
ChatFrameEditBox:SetScript("OnEnterPressed", function(a1,a2,a3,a4)
userinput = true
if OnEnter then OnEnter(a1,a2,a3,a4) end
userinput = nil
end)
end
local function GetBagItemName(bag, slot)
local itemID = API.GetContainerItemID(bag, slot)
local name = itemID and API.GetItemNameByID(itemID)
if name then return name end
local itemLink = API.GetContainerItemLink(bag, slot)
local linkedID = itemLink and API.GetItemIDFromLink(itemLink)
local itemName = linkedID and API.GetItemInfo(linkedID)
return itemName
end
local function FindItem(item)
local wanted = string.lower(item or "")
for bag = 4, 0, -1 do
for slot = 1, GetContainerNumSlots(bag) do
local query = GetBagItemName(bag, slot)
if query and string.lower(query) == wanted then
return bag, slot
end
end
end
end
-- ClassicAPI exposes several modern macro primitives as Lua functions but
-- Vanilla does not register their familiar slash-command equivalents.
--
-- Use ShaguTweaks-specific command IDs instead of generic STARTATTACK/FOCUS
-- keys. A SlashCmdList key can exist without the corresponding /command
-- alias, so testing only SlashCmdList.STARTATTACK can silently skip a valid
-- registration and leave Vanilla reporting "Type '/help'...".
local function TrimUnit(msg)
if not msg then return nil end
local _, _, unit = string.find(msg, "^%s*(.-)%s*$")
return unit ~= "" and unit or nil
end
local function IsSlashRegistered(command)
command = string.lower(command)
for key, value in pairs(_G) do
if type(key) == "string"
and string.find(key, "^SLASH_")
and type(value) == "string"
and string.lower(value) == command then
return true
end
end
return false
end
local function RegisterSlashAlias(id, command, handler)
if IsSlashRegistered(command) then return end
_G["SLASH_" .. id .. "1"] = command
_G.SlashCmdList[id] = handler
end
RegisterSlashAlias("SHAGUTWEAKS_STARTATTACK", "/startattack", function(msg)
API.StartAttack(TrimUnit(msg))
end)
RegisterSlashAlias("SHAGUTWEAKS_STOPATTACK", "/stopattack", function()
API.StopAttack()
end)
RegisterSlashAlias("SHAGUTWEAKS_FOCUS", "/focus", function(msg)
API.FocusUnit(TrimUnit(msg))
end)
RegisterSlashAlias("SHAGUTWEAKS_CLEARFOCUS", "/clearfocus", function()
API.ClearFocus()
end)
local function EquipOrUse(msg)
if not msg or msg == "" then return end
local bag, slot
local _, _, parsedBag, parsedSlot = string.find(msg, "^(%d+)%s+(%d+)$")
if parsedBag and parsedSlot then
bag, slot = parsedBag, parsedSlot
else
local _, _, inventorySlot = string.find(msg, "^(%d+)$")
if inventorySlot then
slot = inventorySlot
else
bag, slot = FindItem(msg)
end
end
bag = bag and tonumber(bag) or nil
slot = slot and tonumber(slot) or nil
if bag and slot then
UseContainerItem(bag, slot)
elseif slot then
UseInventoryItem(slot)
end
end
-- Keep the original Macro Tweaks conveniences, but do not steal /equip or
-- /use from another addon that already registered either command.
RegisterSlashAlias("SHAGUTWEAKS_EQUIP", "/equip", EquipOrUse)
RegisterSlashAlias("SHAGUTWEAKS_USE", "/use", EquipOrUse)
end
+78
View File
@@ -0,0 +1,78 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local HookScript = ShaguTweaks.HookScript
local module = ShaguTweaks:register({
title = T["Metric Range"],
description = T["Displays tooltip range units in metres instead of yards."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Tooltip & Items"],
enabled = true,
})
module.enable = function(self)
-- Avoid double processing while the old standalone addon is still enabled.
if IsAddOnLoaded("MetricRange") then return end
local function ConvertText(text)
if type(text) ~= "string" then return text end
-- Intentionally keep the original behavior: only replace the displayed
-- unit label. Do not numerically convert the range value.
text = string.gsub(text, " yards", " metres")
text = string.gsub(text, " yard", " metre")
text = string.gsub(text, " yd", " m")
return text
end
local function ConvertTooltip(tooltip)
if not tooltip or not tooltip.GetName then return end
local name = tooltip:GetName()
if not name then return end
-- GameTooltip-style frames expose NumLines() in Vanilla. Only inspect the
-- lines that actually exist instead of probing 30 left/right font strings
-- on every tooltip show. Keep the historical 30-line ceiling as fallback.
local lines = 30
if tooltip.NumLines then
lines = math.min(tooltip:NumLines() or 0, 30)
end
for i = 1, lines do
local left = _G[name .. "TextLeft" .. i]
if left then
local old = left:GetText()
if old then
local new = ConvertText(old)
if new ~= old then left:SetText(new) end
end
end
local right = _G[name .. "TextRight" .. i]
if right then
local old = right:GetText()
if old then
local new = ConvertText(old)
if new ~= old then right:SetText(new) end
end
end
end
end
local function HookTooltip(tooltip)
if not tooltip then return end
HookScript(tooltip, "OnShow", function()
ConvertTooltip(tooltip)
end)
end
HookTooltip(GameTooltip)
HookTooltip(ItemRefTooltip)
HookTooltip(ShoppingTooltip1)
HookTooltip(ShoppingTooltip2)
HookTooltip(ShoppingTooltip3)
if GameTooltip and GameTooltip:IsVisible() then ConvertTooltip(GameTooltip) end
if ItemRefTooltip and ItemRefTooltip:IsVisible() then ConvertTooltip(ItemRefTooltip) end
end
+162
View File
@@ -0,0 +1,162 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local floor = math.floor
local module = ShaguTweaks:register({
title = T["MiniMap Framerate & Latency"],
description = T["Adds a small framerate & latency display to the mini map."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Minimap & World Map"],
enabled = nil,
})
local TARGET_FPS = 60
local LOW_LATENCY = tonumber(_G.PERFORMANCEBAR_LOW_LATENCY) or 300
local MEDIUM_LATENCY = tonumber(_G.PERFORMANCEBAR_MEDIUM_LATENCY) or 600
local function CreateStatusFrame(width)
local frame = CreateFrame("Frame", nil, Minimap)
frame:SetFrameLevel(64)
frame:SetWidth(width)
frame:SetHeight(23)
frame:SetClampedToScreen(true)
frame:EnableMouse(true)
frame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 8,
edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
frame:SetBackdropBorderColor(.9, .8, .5, 1)
frame:SetBackdropColor(.4, .4, .4, 1)
frame.text = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
frame.text:SetFont(STANDARD_TEXT_FONT, 12, "OUTLINE")
frame.text:SetAllPoints(frame)
frame.text:SetFontObject(GameFontWhite)
return frame
end
local function SetFPSColor(text, fps)
local perc = fps / TARGET_FPS
if perc < 0 then perc = 0 elseif perc > 1 then perc = 1 end
local r, g
if perc <= .5 then
r = 1
g = perc * 2
else
r = (1 - perc) * 2
g = 1
end
text:SetTextColor(r, g, 0, 1)
end
local function SetLatencyColor(text, latency)
if latency > MEDIUM_LATENCY then
text:SetTextColor(1, 0, 0, 1)
elseif latency > LOW_LATENCY then
text:SetTextColor(1, 1, 0, 1)
else
text:SetTextColor(0, 1, 0, 1)
end
end
module.enable = function(self)
if self.ticker then
self.ticker:Cancel()
self.ticker = nil
end
-- Extras is ClassicAPI-first. Do not restore the original permanent
-- OnUpdate implementation when the native scheduler is unavailable.
if type(_G.C_Timer) ~= "table" or type(_G.C_Timer.NewTicker) ~= "function" then
return
end
if not self.fpsFrame then
self.fpsFrame = CreateStatusFrame(49)
self.latencyFrame = CreateStatusFrame(51)
end
local fpsFrame = self.fpsFrame
local latencyFrame = self.latencyFrame
local lowFPS, highFPS
local lowMS, highMS
-- Follow the existing ShaguTweaks MiniMap Clock instead of independently
-- anchoring to MinimapCluster. MiniMap Square and mover integrations already
-- reposition that clock, so these counters follow automatically regardless
-- of module initialization order.
fpsFrame:ClearAllPoints()
latencyFrame:ClearAllPoints()
if _G.MinimapClock then
fpsFrame:SetPoint("RIGHT", _G.MinimapClock, "LEFT", 2, 0)
latencyFrame:SetPoint("LEFT", _G.MinimapClock, "RIGHT", -2, 0)
else
fpsFrame:SetPoint("BOTTOM", Minimap, "BOTTOM", -28, -25)
latencyFrame:SetPoint("BOTTOM", Minimap, "BOTTOM", 28, -25)
end
local function Update()
local fps = floor(GetFramerate() or 0)
local _, _, latency = GetNetStats()
latency = floor(latency or 0)
if fps > 0 and (not lowFPS or fps < lowFPS) then lowFPS = fps end
if not highFPS or fps > highFPS then highFPS = fps end
if latency > 0 and (not lowMS or latency < lowMS) then lowMS = latency end
if not highMS or latency > highMS then highMS = latency end
fpsFrame.text:SetText(tostring(fps))
latencyFrame.text:SetText(tostring(latency))
SetFPSColor(fpsFrame.text, fps)
SetLatencyColor(latencyFrame.text, latency)
end
fpsFrame:SetScript("OnEnter", function()
GameTooltip:ClearLines()
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT")
GameTooltip:AddLine(T["Framerate"])
GameTooltip:AddDoubleLine(T["High"], tostring(highFPS or 0) .. " " .. T["fps"], 1,1,1,1,1,1)
GameTooltip:AddDoubleLine(T["Low"], tostring(lowFPS or 0) .. " " .. T["fps"], 1,1,1,1,1,1)
GameTooltip:Show()
end)
latencyFrame:SetScript("OnEnter", function()
GameTooltip:ClearLines()
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMLEFT")
GameTooltip:AddLine(T["Latency"])
GameTooltip:AddDoubleLine(T["High"], tostring(highMS or 0) .. " " .. T["ms"], 1,1,1,1,1,1)
GameTooltip:AddDoubleLine(T["Low"], tostring(lowMS or 0) .. " " .. T["ms"], 1,1,1,1,1,1)
GameTooltip:Show()
end)
fpsFrame:SetScript("OnLeave", function() GameTooltip:Hide() end)
latencyFrame:SetScript("OnLeave", function() GameTooltip:Hide() end)
fpsFrame:Show()
latencyFrame:Show()
Update()
-- One native ClassicAPI ticker services both displays. Lua only wakes once
-- per second instead of running two permanent OnUpdate handlers every frame.
self.ticker = _G.C_Timer.NewTicker(1, Update)
end
module.disable = function(self)
if self.ticker then
self.ticker:Cancel()
self.ticker = nil
end
if self.fpsFrame then self.fpsFrame:Hide() end
if self.latencyFrame then self.latencyFrame:Hide() end
end
+126
View File
@@ -0,0 +1,126 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Show Bags"],
description = T["Shows bag and keyring buttons when using the reduced actionbar layout. Hold Ctrl+Shift to move the bag bar."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
maintainer = "@shagu (GitHub)",
enabled = nil,
config = {
["panelbag.scale"] = 1,
}
})
module.enable = function(self)
-- The ShaguTweaks loader initializes module config while iterating an
-- unordered table. On a fresh install the Reduced Actionbar key may not have
-- been visited yet, so fall back to that module's declared default.
local reducedTitle = T["Reduced Actionbar Size"]
local reducedEnabled = ShaguTweaks_config and ShaguTweaks_config[reducedTitle]
if reducedEnabled == nil then
local reducedModule = ShaguTweaks.mods and ShaguTweaks.mods[reducedTitle]
reducedEnabled = reducedModule and reducedModule.enabled and 1 or 0
end
if reducedEnabled ~= 1 then return end
-- Moving the bag bar is driven by ClassicAPI modifier events and native
-- Region:IsMouseOver state. Do not keep a permanent polling fallback here.
if not API.modifierstate or not API.regionmouseover then return end
local frames = {
KeyRingButton, CharacterBag3Slot, CharacterBag2Slot, CharacterBag1Slot,
CharacterBag0Slot, MainMenuBarBackpackButton,
}
local bagframe = CreateFrame("Button", "ShaguTweaksReducedActionBarBags", UIParent)
bagframe:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -8, 48)
bagframe:SetWidth(180)
bagframe:SetHeight(42)
bagframe:SetScale(module.config["panelbag.scale"])
bagframe:SetFrameStrata("MEDIUM")
bagframe:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
})
bagframe:SetBackdropBorderColor(.9,.8,.5,1)
bagframe:SetBackdropColor(.4,.4,.4,1)
bagframe:SetClampedToScreen(true)
bagframe:SetMovable(true)
bagframe:EnableMouse(true)
bagframe:RegisterForDrag("LeftButton")
bagframe:SetUserPlaced(true)
bagframe:RegisterEvent("PLAYER_ENTERING_WORLD")
local function UpdateMoveMode()
local active = API.IsMouseOver(bagframe)
and API.IsShiftKeyDown()
and API.IsControlKeyDown()
if active then
if bagframe.mousedisabled then return end
bagframe.mousedisabled = true
for _, frame in ipairs(frames) do
frame:EnableMouse(0)
end
else
if not bagframe.mousedisabled then return end
bagframe.mousedisabled = false
for _, frame in ipairs(frames) do
frame:EnableMouse(1)
end
end
end
bagframe:SetScript("OnEnter", UpdateMoveMode)
bagframe:SetScript("OnLeave", UpdateMoveMode)
local modifier = CreateFrame("Frame", nil, bagframe)
modifier:RegisterEvent("MODIFIER_STATE_CHANGED")
modifier:SetScript("OnEvent", UpdateMoveMode)
bagframe:SetScript("OnDragStart", function()
local shift = API.IsShiftKeyDown()
local control = API.IsControlKeyDown()
if not shift or not control then return end
this:StartMoving()
end)
bagframe:SetScript("OnDragStop", function()
this:StopMovingOrSizing()
end)
bagframe:SetScript("OnEvent", function()
if this.initialized then return end
this.initialized = true
ShaguTweaks.DarkenFrame(bagframe)
for id, frame in ipairs(frames) do
local anchor = frames[id-1] or bagframe
frame:ClearAllPoints()
frame:SetPoint("LEFT", anchor, id == 1 and "LEFT" or "RIGHT", id == 1 and 5 or 2, 0)
frame:SetParent(bagframe)
frame:SetScale(.8)
frame.Show = nil
frame:Show()
-- Child bag buttons normally own the mouse. Re-evaluate move mode when
-- entering or leaving one so Ctrl+Shift already held before mouseover is
-- handled without polling and without replacing their tooltip scripts.
ShaguTweaks.HookScript(frame, "OnEnter", UpdateMoveMode)
ShaguTweaks.HookScript(frame, "OnLeave", UpdateMoveMode)
end
UpdateMoveMode()
end)
end
+126
View File
@@ -0,0 +1,126 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Show Micro Menu"],
description = T["Shows micro menu buttons when using the reduced actionbar layout. Hold Ctrl+Shift to move the micro menu."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Action Bar"],
maintainer = "@shagu (GitHub)",
enabled = nil,
config = {
["panelmicro.scale"] = 1,
}
})
module.enable = function(self)
-- The ShaguTweaks loader initializes module config while iterating an
-- unordered table. On a fresh install the Reduced Actionbar key may not have
-- been visited yet, so fall back to that module's declared default.
local reducedTitle = T["Reduced Actionbar Size"]
local reducedEnabled = ShaguTweaks_config and ShaguTweaks_config[reducedTitle]
if reducedEnabled == nil then
local reducedModule = ShaguTweaks.mods and ShaguTweaks.mods[reducedTitle]
reducedEnabled = reducedModule and reducedModule.enabled and 1 or 0
end
if reducedEnabled ~= 1 then return end
-- Moving the micro menu is driven by ClassicAPI modifier events and native
-- Region:IsMouseOver state. Do not keep a permanent polling fallback here.
if not API.modifierstate or not API.regionmouseover then return end
local frames = {
CharacterMicroButton, SpellbookMicroButton, TalentMicroButton,
QuestLogMicroButton, MainMenuMicroButton, SocialsMicroButton,
WorldMapMicroButton, HelpMicroButton
}
local microframe = CreateFrame("Button", "ShaguTweaksReducedActionBarMicroMenu", UIParent)
microframe:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -8, 8)
microframe:SetWidth(225)
microframe:SetHeight(44)
microframe:SetScale(module.config["panelmicro.scale"])
microframe:SetFrameStrata("MEDIUM")
microframe:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
})
microframe:SetBackdropBorderColor(.9,.8,.5,1)
microframe:SetBackdropColor(.4,.4,.4,1)
microframe:SetClampedToScreen(true)
microframe:SetMovable(true)
microframe:EnableMouse(true)
microframe:RegisterForDrag("LeftButton")
microframe:SetUserPlaced(true)
microframe:RegisterEvent("PLAYER_ENTERING_WORLD")
local function UpdateMoveMode()
local active = API.IsMouseOver(microframe)
and API.IsShiftKeyDown()
and API.IsControlKeyDown()
if active then
if microframe.mousedisabled then return end
microframe.mousedisabled = true
for _, frame in ipairs(frames) do
frame:EnableMouse(0)
end
else
if not microframe.mousedisabled then return end
microframe.mousedisabled = false
for _, frame in ipairs(frames) do
frame:EnableMouse(1)
end
end
end
microframe:SetScript("OnEnter", UpdateMoveMode)
microframe:SetScript("OnLeave", UpdateMoveMode)
local modifier = CreateFrame("Frame", nil, microframe)
modifier:RegisterEvent("MODIFIER_STATE_CHANGED")
modifier:SetScript("OnEvent", UpdateMoveMode)
microframe:SetScript("OnDragStart", function()
local shift = API.IsShiftKeyDown()
local control = API.IsControlKeyDown()
if not shift or not control then return end
this:StartMoving()
end)
microframe:SetScript("OnDragStop", function()
this:StopMovingOrSizing()
end)
microframe:SetScript("OnEvent", function()
if this.initialized then return end
this.initialized = true
ShaguTweaks.DarkenFrame(microframe)
for id, frame in ipairs(frames) do
local anchor = frames[id-1] or microframe
frame:ClearAllPoints()
frame:SetPoint("LEFT", anchor, id == 1 and "LEFT" or "RIGHT", id == 1 and 3.5 or -2, id==1 and 10 or 0)
frame:SetParent(microframe)
frame.Show = nil
frame:Show()
-- Child micro buttons normally own the mouse. Re-evaluate move mode when
-- entering or leaving one so Ctrl+Shift already held before mouseover is
-- handled without polling and without replacing their tooltip scripts.
ShaguTweaks.HookScript(frame, "OnEnter", UpdateMoveMode)
ShaguTweaks.HookScript(frame, "OnLeave", UpdateMoveMode)
end
UpdateMoveMode()
end)
end
+147
View File
@@ -0,0 +1,147 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Unit Frame Abbreviated Names"],
description = T["Abbreviates long target and target-of-target names."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Unit Frames"],
enabled = nil,
})
module.enable = function(self)
local maxLength = 15
local cache = {}
local function AbbrevWord(word)
return string.sub(word, 1, 1) .. ". "
end
local function GetShortName(unit)
local name = UnitName(unit)
if not name then
cache[unit] = nil
return
end
local cached = cache[unit]
if cached and cached.raw == name then
return cached.short
end
local short = name
if strlen(short) > maxLength then
short = string.gsub(short, "^(%S+) ", AbbrevWord)
end
if strlen(short) > maxLength then
short = string.gsub(short, "(%S+) ", AbbrevWord)
end
cache[unit] = {
raw = name,
short = short,
}
return short
end
local function GetNameText(frame)
if not frame then return end
if frame.name then return frame.name end
if frame.GetName then
local frameName = frame:GetName()
if frameName then
return _G[frameName .. "Name"]
end
end
end
local function UpdateFrame(frame, unit)
local text = GetNameText(frame)
local name = GetShortName(unit)
if not text or not name then return end
-- Avoid forcing a FontString update when the displayed value is already
-- correct. This matters most for the 250 ms target-of-target fallback.
if not text.GetText or text:GetText() ~= name then
text:SetText(name)
end
end
local function UpdateTarget()
UpdateFrame(TargetFrame, "target")
end
local function UpdateTargetTarget()
if TargetofTargetFrame then
UpdateFrame(TargetofTargetFrame, "targettarget")
end
end
local function EventValid(name)
return API.eventutils
and _G.C_EventUtils
and _G.C_EventUtils.IsEventValid
and _G.C_EventUtils.IsEventValid(name)
end
local events = CreateFrame("Frame")
events:RegisterEvent("PLAYER_ENTERING_WORLD")
events:RegisterEvent("PLAYER_TARGET_CHANGED")
local hasUnitTarget = EventValid("UNIT_TARGET")
local hasUnitName = EventValid("UNIT_NAME_UPDATE")
if hasUnitTarget then events:RegisterEvent("UNIT_TARGET") end
if hasUnitName then events:RegisterEvent("UNIT_NAME_UPDATE") end
events:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_TARGET_CHANGED" then
UpdateTarget()
UpdateTargetTarget()
elseif event == "UNIT_TARGET" then
if arg1 == "target" then UpdateTargetTarget() end
elseif event == "UNIT_NAME_UPDATE" then
if arg1 == "target" then
UpdateTarget()
elseif arg1 == "targettarget" then
UpdateTargetTarget()
end
end
end)
-- Vanilla has no UNIT_TARGET event for target-of-target changes. Prefer the
-- ClassicAPI native ticker so Lua only runs at the required 250 ms cadence,
-- instead of entering a Lua OnUpdate handler every rendered frame. Keep the
-- old throttled path only for older environments without C_Timer.NewTicker.
if not hasUnitTarget then
local function UpdateTargetTargetFallback()
if UnitName("targettarget") then
UpdateTargetTarget()
end
end
if _G.C_Timer and type(_G.C_Timer.NewTicker) == "function" then
_G.C_Timer.NewTicker(.25, UpdateTargetTargetFallback)
else
local fallback = CreateFrame("Frame")
fallback.elapsed = 0
fallback:SetScript("OnUpdate", function()
this.elapsed = this.elapsed + (arg1 or 0)
if this.elapsed < .25 then return end
this.elapsed = 0
UpdateTargetTargetFallback()
end)
end
end
UpdateTarget()
UpdateTargetTarget()
end
+59
View File
@@ -0,0 +1,59 @@
-- Adapted from TokensWorth/ShaguTweaks-mods (MIT, original copyright GryllsAddons).
local T = ShaguTweaks.T
local Filter = ShaguTweaks.ExtrasChatFilter
local module = ShaguTweaks:register({
title = T["World Chat Hider"],
description = T["Hides World channel messages while inside an instance without leaving the channel."],
expansions = { ["vanilla"] = true, ["tbc"] = nil },
category = T["Chat & Social"],
enabled = nil,
})
local function IsInsideInstance()
if type(IsInInstance) ~= "function" then return false end
local inside = IsInInstance()
return inside == 1 or inside == true
end
local function IsWorldChannel()
-- Vanilla 1.12 CHAT_MSG_CHANNEL exposes the base channel name in arg9.
-- Custom channel names are not localized, so comparing the actual event is
-- both cheaper and safer than querying GetChannelName("world") every time.
if arg9 and string.lower(arg9) == "world" then
return true
end
-- Conservative fallback for clients that omit arg9: arg4 is the numbered
-- channel label (for example "5. World").
if arg4 then
local full = string.lower(arg4)
if full == "world" then return true end
local _, _, base = string.find(full, "^%d+%.%s*(.+)$")
if base == "world" then return true end
end
return false
end
local function FilterWorldChat(event)
if event ~= "CHAT_MSG_CHANNEL" then return false end
if not IsInsideInstance() then return false end
return IsWorldChannel()
end
module.enable = function(self)
if Filter then
Filter:Register("WorldChatHider", FilterWorldChat)
end
end
module.disable = function(self)
if Filter then
Filter:Unregister("WorldChatHider")
end
end
+160
View File
@@ -0,0 +1,160 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local hooksecurefunc = ShaguTweaks.hooksecurefunc
local module = ShaguTweaks:register({
title = T["Reveal World Map"],
description = T["Reveals unexplored world map areas and shows exploration hints."],
expansions = { ["vanilla"] = true, ["tbc"] = false },
category = T["Minimap & World Map"],
maintainer = "@shagu (GitHub)",
enabled = true,
config = {
["map.reveal.marker"] = "on",
}
})
module.enable = function(self)
-- do not load if another map addon is controlling the world map
if Cartographer then return end
if METAMAP_TITLE then return end
local enabled = true
local overlayTextures = {}
local exploreFrames = {}
local mapreveal = {}
mapreveal.onmap = CreateFrame("CheckButton", "shagutweaks_mapreveal_onmap", WorldMapFrame, "UICheckButtonTemplate")
mapreveal.onmap.text = _G["shagutweaks_mapreveal_onmapText"]
mapreveal.onmap:SetWidth(14)
mapreveal.onmap:SetHeight(14)
mapreveal.onmap:SetPoint("TOPLEFT", WorldMapButton, "TOPLEFT", 1, 19)
mapreveal.onmap.text:SetPoint("LEFT", mapreveal.onmap, "RIGHT", 2, 0)
mapreveal.onmap.text:SetText(T["Reveal Unexplored"])
mapreveal.onmap:SetScript("OnShow", function()
this:SetChecked(enabled)
end)
mapreveal.onmap:SetScript("OnClick", function()
enabled = this:GetChecked() and true or false
WorldMapFrame_Update()
end)
local function HideRevealLayer()
for _, texture in ipairs(overlayTextures) do
texture:Hide()
end
for _, frame in ipairs(exploreFrames) do
frame:Hide()
end
end
local function ExploreEnter()
WorldMapTooltip:ClearLines()
WorldMapTooltip:SetOwner(this, "ANCHOR_TOP")
WorldMapTooltip:AddLine(T["Exploration Point"]..":", .3, 1, .8)
WorldMapTooltip:AddLine(this.name or "?", 1, 1, 1)
WorldMapTooltip:Show()
end
local function ExploreLeave()
WorldMapTooltip:Hide()
end
local function GetExploreCenter(overlay)
if overlay.hitRectLeft and overlay.hitRectRight
and overlay.hitRectTop and overlay.hitRectBottom then
return (overlay.hitRectLeft + overlay.hitRectRight) / 2,
(overlay.hitRectTop + overlay.hitRectBottom) / 2
end
if overlay.offsetX and overlay.offsetY
and overlay.textureWidth and overlay.textureHeight then
return overlay.offsetX + overlay.textureWidth / 2,
overlay.offsetY + overlay.textureHeight / 2
end
end
local function UpdateRevealLayer()
HideRevealLayer()
if not enabled then return end
-- ClassicAPI reads WorldMapOverlay.dbc directly from the active client,
-- including custom Turtle-like zones and their real tile layout.
local overlays = API.GetUnexploredMapTextures()
if not overlays then return end
local textureIndex = 0
local exploreIndex = 0
for _, overlay in ipairs(overlays) do
if overlay.tiles then
for _, tile in ipairs(overlay.tiles) do
if tile.file and tile.width and tile.height
and tile.offsetX and tile.offsetY then
textureIndex = textureIndex + 1
local texture = overlayTextures[textureIndex]
if not texture then
texture = WorldMapDetailFrame:CreateTexture(nil, "ARTWORK")
overlayTextures[textureIndex] = texture
end
texture:ClearAllPoints()
texture:SetWidth(tile.width)
texture:SetHeight(tile.height)
texture:SetTexCoord(0, tile.texCoordX or 1, 0, tile.texCoordY or 1)
texture:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", tile.offsetX, -tile.offsetY)
texture:SetTexture(tile.file)
texture:SetVertexColor(.4, .4, .4, 1)
texture:Show()
end
end
end
if self.config["map.reveal.marker"] == "on" then
local x, y = GetExploreCenter(overlay)
if x and y then
exploreIndex = exploreIndex + 1
local explore = exploreFrames[exploreIndex]
if not explore then
explore = CreateFrame("Frame", nil, WorldMapDetailFrame)
explore:SetWidth(16)
explore:SetHeight(16)
explore:EnableMouse(true)
explore:SetFrameLevel(255)
explore:SetScript("OnEnter", ExploreEnter)
explore:SetScript("OnLeave", ExploreLeave)
explore.tex = explore:CreateTexture(nil, "OVERLAY")
explore.tex:SetTexture("Interface\\WorldMap\\WorldMap-MagnifyingGlass")
explore.tex:SetBlendMode("ADD")
explore.tex:SetTexCoord(.08, .92, .08, .92)
explore.tex:SetAllPoints()
exploreFrames[exploreIndex] = explore
end
explore:ClearAllPoints()
explore:SetPoint("CENTER", WorldMapDetailFrame, "TOPLEFT", x, -y)
explore.name = API.GetAreaInfo(overlay.areaID)
or overlay.textureName
or "?"
explore:Show()
end
end
end
end
-- Keep Blizzard's normal explored-map rendering untouched and append only
-- ClassicAPI's unexplored overlays after the native update.
hooksecurefunc("WorldMapFrame_Update", UpdateRevealLayer)
if WorldMapFrame:IsShown() then
UpdateRevealLayer()
end
end