Release 3.8.3: personal resource display and player-frame refinements

This commit is contained in:
2026-11-05 11:27:15 -04:00
parent d51d2556ef
commit 5db1aa9c41
29 changed files with 752 additions and 502 deletions
+59 -214
View File
@@ -1,258 +1,103 @@
# Changelog
Changes in this fork (OEVG/ShaguTweaks-ClassicAPI), newest first. Each entry is one version (3.0 was the first) with its date. The current version is also in `ShaguTweaks.toc` (`## Version`).
Changes in OEVG/ShaguTweaks-ClassicAPI, newest first. Entries summarize released behavior; development iterations and test notes belong in `Docs/` and `tests/`.
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.
Based on [Dusk/ShaguTweaks-ClassicAPI](https://octowow.st/git/Dusk/ShaguTweaks-ClassicAPI) at `4391564` (2026-09-08). Earlier history is available upstream.
## 3.8.3 — 2026-11-05 — Personal Resource Display
- Replace **Standalone Player Frame** with **Personal Resource Display**: independent, movable and resizable copies of player health and power, leaving the original player frame intact. Existing health/power positions migrate.
- Add **Hide Player Frame Resources** to keep only the clean portrait and independently movable class-colored name. **Hide Player Name** now requires this option and also hides the raid group number. Resource hiding and the personal display can be used independently.
- Share healing predictions and Energy & Mana Tick timing with the copied bars, including resizing, and retain the active UI border palette.
- Remove **Hide Player Portrait** and retire its saved setting.
- Condense the changelog into release summaries, removing intermediate testing history.
## 3.8.2 — 2026-11-04 — Healing predictions and native API improvements
- Validation: 25 Lua 5.0 regression suites pass; changed/new Lua files parse with Lua 5.0. The user confirmed range/healing/cast behavior and the final energy/mana, macro and equipment-comparison paths in game. Profiling remains a separate follow-up; no measured performance gain is claimed.
- Consolidate the comparison controller's repeated callbacks and visible-tooltip lookup after user validation of the ownership fix. Preserve the tested deferred hide/re-show recovery and older-API fallback; no debug command or polling driver is added.
- Fix unstable native equipment comparisons during Bagshui/stock tooltip refresh: defer source closure checks, preserve unchanged comparisons, and rebind/refill native tooltips whose ownership was cleared by Hide. Own comparisons through the hovered item button instead of another tooltip, and recover independent comparison hides on events without polling. Equipment/item-cache changes invalidate the display; sources that stay hidden and Shift release still clear it.
- Improve Energy & Mana Tick: accept ClassicAPI login payloads, reset samples between power modes, start the five-second display only from mana-costing casts (including channel-stop ownership), and track regen phase independently so mid-period gains do not reset it. Native duration fills move the spark without Lua OnUpdate; older APIs retain visible-only animation. Resize the spark with its bar.
- Let current ClassicAPI own ordinary macro icons/directives and preserve chosen icons. Macro Icons now adapts only legacy `--showtooltip` and `/pfcast` hints through native display metadata, releases edited/logout overrides, and polls only conditional custom hints. Defer to SuperCleveRoidMacros. Macro Tweaks keeps history filtering and missing interactive aliases, using native command parsing without wrapping all chat messages on current APIs; older supported paths remain.
- Equip Compare uses native full-link comparisons when available, including filled ring/trinket slots, weapons, equipped headers and stat differences. Preserve Shift gating/placement, batch item callbacks, hide comparisons with their owner, and include item-link tooltips. Older APIs retain the previous construction path.
- Remove the temporary `/staction` diagnostic, refresh current integration notes while retaining the historical review, and add a single Lua 5.0 regression runner that discovers all test suites and reports failures.
- Route health and power number events independently for player, target and pet; retain full refreshes for startup, target changes and pet replacement, plus alternate druid power updates.
- Adopt pfUI's native duration-object approach for swing/wand fill and countdown text when supported. Attach the spark to the fill texture; schedule completion/grace and Hunter wind-up boundaries with cancellable timers; reconcile queued-attack colors on action-state events. Preserve classic artwork, positions and behavior. No Lua OnUpdate driver on the native path; older supported APIs retain a throttled fallback that formats text only when the displayed tenth changes.
- Consolidate the Ctrl+Shift+Alt movement/preview chord across eight modules and the one-shot action-bar/standalone layout initialization sequence in the API layer. Preserve native left/right modifier handling and each layout's fallback delay; release initialization event/update scripts after use. This is maintenance cleanup, with no measured FPS gain.
- Remove libcast's unused name-keyed ClassicAPI event mirror and localized combat-text parser. Keep native unit queries authoritative and isolate legacy Aimed Shot/Multi-Shot hooks to Hunters. SuperWoW tracks only missing timing for Enemy Castbars, skips player/complete native records, bounds its cache to 128 remote records and prunes without idle polling. See [cast tracking review](Docs/CAST_TRACKING_REVIEW.md).
- Hide underlying caster-bar buttons while the actual form/bonus bar is visible, including during native Show refreshes. Restore their native filled/empty visibility when the form bar closes. This prevents caster Attack icons showing through empty Cat/Bear slots with floating artwork; action contents and grid counters are unchanged.
- Clarify Auto Stance: it enters required forms; leaving Cat Form to cast Moonfire uses ClassicAPI Auto Cancel Form.
- Range Color uses ClassicAPI action-range subscriptions and change events when available, including slot paging, visibility and macro targets. Each shown button owns one balanced subscription. Older supported DLLs retain the throttled range path; friendly/hostile spell applicability and usability tints are preserved.
- Rename the option to **Healing Predictions**, preserving both previous test-build setting names. Keep learned heal amounts across druid form/spellbook refreshes and unchanged equipment notifications; invalidate only for actual gear, talent or level changes.
- Add **Healing Predictions** (Unit Frames, off): estimated incoming direct heals and upcoming HoT ticks on player and target bars, capped at full health, with native bar texture/size and standalone/Big Health support.
- Reuse OEVG Puppeteer's compatible prediction total and update API when available (verified against private Gitea main `ee273a9`). Do not run a second estimator or add totals twice. Add standalone native-aura HoT tracking for observed tick amounts, including Rejuvenation and Regrowth; each contributes one upcoming tick, matching Puppeteer. See [review findings](Docs/HEALING_PREDICTION_REVIEW.md).
- Replace the unused legacy prediction implementation with a lazy, focused ClassicAPI cast/observed-heal estimator plus explicit-duration vanilla HealComm reception. No tooltip scans, spell-casting hooks, resurrection tracking, localized combat-message parsing or prediction SavedVariables are needed. A single expiry driver runs only while predictions are pending.
- Amounts are estimates, not a native incoming-heal API: own casts need a prior observed non-critical heal this session; the standalone direct-heal path receives compatible vanilla HealComm announcements. HoT estimates require an observed periodic amount and known live aura timing. Compact/group HealComm formats and unresolved percentage-only targets are not projected.
- Reference reviewed: brues-code/pfUI at `560343bd746e00898606cf756bdfe533a4823110`; native range subscription ownership and separated healing-data/display responsibilities guided this implementation.
- New regression checks cover range subscriptions and tints, clamped prediction geometry, native healing events, interruption/expiry and disabled-option lifecycle. In-game tests remain necessary.
- Add **Healing Predictions** for player and target: incoming direct heals and upcoming HoT ticks, with Big Health/standalone support. Reuse compatible Puppeteer data when available; otherwise use an observed-heal estimator and vanilla HealComm. See [prediction details and limitations](Docs/HEALING_PREDICTION_REVIEW.md).
- Use native ClassicAPI range subscriptions for **Range Color**, with balanced button subscriptions and an older-version fallback.
- Improve **Energy & Mana Tick** handling of power changes, mana-costing casts and regeneration timing; use native spark animation when available.
- Use ClassicAPI's macro icons and command parsing, retaining compatibility for legacy custom hints and other macro addons.
- Use native equipment comparisons where available, including full item links, paired equipment slots and stat differences; keep comparisons stable during Bagshui tooltip refreshes.
- Route health/power number updates separately and use native duration timers for swing/wand displays where supported.
- Remove redundant cast tracking and consolidate movement/startup helpers. Add a shared regression-test runner. See the [cast tracking review](Docs/CAST_TRACKING_REVIEW.md).
- Prevent caster-bar icons showing through empty druid form slots. Clarify that Auto Stance enters forms; ClassicAPI Auto Cancel Form leaves them.
## 3.8.1 — 2026-11-04 — ClassicAPI baseline and legacy cleanup
- Remove Auto Dismount, its TurtleWoW patches, unused API wrappers and obsolete saved toggle in favor of ClassicAPI's Auto Dismount, Auto Stand and Auto Cancel Form settings. Retain Auto Stance: it enters a required stance/form rather than cancelling one.
- Extend the native Always Show Action Bars preference to empty main/bonus-bar slots, preserving native drag-grid references and setting toggles.
- Fix disappearing Big Health fills after health updates in standalone mode: copy the hidden name background's RGB color without its zero alpha.
- Require ClassicAPI 1.18.10 or newer and check the loaded DLL plus core timer/nameplate APIs before enabling tweaks. Missing or outdated runtimes show an update/restart message.
- Remove the unused legacy debuff-timing library after Debuff Timer's removal in 3.8, including its TurtleWoW Paladin/Warlock spell-damage listener.
- Remove per-frame legacy nameplate discovery. Remaining theme integration uses deferred native nameplate events and stays dormant without consumers.
- Retain optional feature and missing-data checks, SuperWoW cast support, and compatibility paths not yet proven redundant. This is a selective cleanup, not removal of every fallback.
- Fifteen Lua 5.0 regression suites pass. In-game rendering, startup on installed client versions and CPU/memory differences still require user verification.
- Require **ClassicAPI 1.18.10+**, with a startup check and update message for unsupported runtimes.
- Remove **Auto Dismount** and its unused support code in favor of ClassicAPI's settings; retain Auto Stance for entering required forms.
- Respect **Always Show Action Bars** for empty main/bonus-bar slots.
- Fix disappearing Big Health fills after health updates in standalone mode.
- Remove the unused debuff-timing library and legacy per-frame nameplate discovery; retain optional-mod and missing-data compatibility checks.
## 3.8 — 2026-11-04 — Player frames, movable bars and UI polish
### Added
- **Golden UI:** skip the opaque NPC target name strip when Big Health for Target is enabled, leaving its enlarged health fill unobstructed.
- **Standalone Player Frame:** vertically center health percentage and real-health values in the enlarged player bar, retaining left/right alignment; grouped frame offsets remain unchanged.
- **Loss of Control Alert** now uses a classic tooltip panel and icon rim, native fonts and a textured timer bar. Panel/icon borders follow the active default, Darkened or Golden UI palette while the timer fill retains effect-type colors.
- **Nameplate Castbar, Nameplate Scale and Nameplate Class Colors** removed to avoid conflicts with dedicated nameplate addons. The [migration guide](Docs/EXTRAS_MERGE.md#nameplate-alternatives) links BlizzNameplatesPlus (Blizzard-like), UnitPlates (more detail), and ShaguPlates (extensive customization).
- **Debuff Timer** removed to avoid overlapping target-aura timers. Use [TargetFrameBuff](https://octowow.st/git/OEVG/TargetFrameBuff) with ClassicAPI; see [the Extras migration guide](Docs/EXTRAS_MERGE.md#removed-later).
- **Big Health** is split into **Unit Frame Big Health for Player** and **Unit Frame Big Health for Target**. Existing shared settings migrate to both unless a new choice already exists. Standalone player mana sizing follows the player option.
- **Cast-bar movement** is always enabled, independent of Reduced Actionbar Size and old saved toggle values. Removed its checkbox; existing positions and Ctrl+Shift+Alt drag/right-click reset controls are retained.
- **Reduced Actionbar Size** now includes movement automatically, with no separate checkbox. Hold Ctrl+Shift+Alt to drag labeled markers for the main, extra, pet, stance, experience and reputation bars; right-click a marker to reset it. Positions persist per character. Nearby drops snap to neighbouring bars using their original layout spacing and insets (12 UI-unit tolerance); hidden bars are not snap targets. XP at level cap, reputation without a watched faction or visible status bar, and transparent frames are excluded even if their containers remain shown. Attached movable bars stay in place when their owner is moved, and native relayout cannot erase saved anchors.
- **Reduced Actionbar Size** includes XP and reputation movement, including hidden-bar markers. Removed unnecessary native saved-position calls that caused movement errors on drop/reset. Detached progress bars no longer become anchor points for Reduced Actionbar's automatic action stack. Main-bar snapping to other action bars now aligns visible button edges using native button spacing (6 units when unavailable), instead of preserving unused artwork space. The main and pet bar drag outlines use the actual button edges, preserving the offset when dropped; other bars retain native-frame outline sizing. Bar movement uses an empty outline and moves the actual bar once when dropped; native modifier events are preferred, with a throttled fallback for older clients.
- **Hide Player Name** (Unit Frames, off): hide the detached name piece while Standalone Player Frame is enabled. Does not hide the name on the ordinary grouped player frame.
- **Hide Player Portrait** (Unit Frames, off): hide the portrait image while the player name, health and power remain visible. Works with class-icon portraits and the standalone layout; it leaves the portrait's surrounding artwork intact.
- **Standalone Player Frame** (Unit Frames, off): detach the native Blizzard portrait, name, health and power into four independently movable pieces. Hold Ctrl+Shift+Alt to drag; right-click with those keys held to reset that piece. Positions are saved per character. Big Health also enlarges the power bar to match health. Preserves native bar objects, event updates, child number/tick overlays and normal unit clicks; splits the existing artwork around the pieces. The ordinary grouped player-frame mover is bypassed while this option is enabled.
### Fixed
- **Quest/gossip parchment:** Golden and Darkened UI retain cropped original parchment and torn edges instead of the squared stationery overlay; the surrounding frame keeps its selected theme. Restored parchment draws above native tinted tiles and material textures so texture batching cannot leave orange/dark sections.
- **Golden UI unit names:** use an opaque native window-title strip for NPC target names only, matching the brown map/quest window headers. Player targets and the player frame retain native/class-colored backgrounds; switching targets restores the appropriate header.
- **TrinketMenu styling:** exclude its windows, descendants and minimap border from Golden UI and Darkened UI recoloring. TrinketMenu now owns its styling in every UI mode.
- **Standalone portrait shading:** rebuild both lower ring quadrants from matching clean artwork and restore the level badge through a feature-detected round mask. This removes the dark shadow patch beside the badge. Older clients without texture masking retain the native left artwork.
- **Standalone portrait/name:** retain a single level badge on the left, with the resting/combat icon covering the level number while active. Give the detached name its own class-colored background using the health texture, so Big Health's hidden stock name background no longer leaves it blank.
- **Standalone artwork:** replace shared-frame crops on the name, health and power pieces with separate rounded Blizzard border outlines. Complete the portrait ring by mirroring its clean half, removing the attached bar artwork. Feature-detect ClassicAPI's round portrait mask for the status glow.
- **Modern Class Colors:** use the modern health texture on player and target class-colored name backgrounds as well as health bars, including when Big Health is disabled.
### Validation
- Thirteen Lua 5.0 suites cover standalone frame layout and text, independent Big Health settings and migration, bar movement/snapping, always-on cast-bar movement, Loss of Control styling, parchment/name headers, options search, rarity updates and palette compatibility. Mock checks cannot validate client rendering, native event order or FPS; visual changes were iterated with in-game feedback.
- Add **Standalone Player Frame**, detaching the native portrait, name, health and power into movable pieces, with clean rounded artwork, a class-colored name and saved positions. Add **Hide Player Portrait** and standalone-only **Hide Player Name**.
- Split **Big Health** into independent player and target options, migrating existing settings. Standalone power sizing follows the player setting.
- Include action, pet, stance, XP and reputation bar movement in **Reduced Actionbar Size**. Ctrl+Shift+Alt dragging supports saved positions, reset and snapping to visible bars.
- Make cast-bar movement always available and remove its checkbox.
- Give **Loss of Control Alert** classic artwork and borders that follow the active UI mode.
- Preserve native quest/gossip parchment colors and torn edges in Golden/Darkened UI. Golden UI NPC name headers use opaque title artwork; player names retain class styling.
- Extend Modern Class Colors to name backgrounds. Leave TrinketMenu styling to TrinketMenu.
- Remove Debuff Timer and the three nameplate options to avoid overlapping dedicated addons. See [alternatives and migration notes](Docs/EXTRAS_MERGE.md#removed-later).
## 3.7.4 — 2026-11-03 — Advanced Options search
### Added
- **Advanced Options search:** filter options by name, description, category or slider label, ignoring case. Matching options are shown even in collapsed categories. Includes a clear button and an empty-results message; unsaved option changes persist while filtering. Search text is indexed once per module, widgets are reused, and no polling is added.
- Add case-insensitive search by option name, description, category and slider label, retaining unsaved changes while filtering.
## 3.7.3 — 2026-11-03 — Golden UI
### Added
- Add **Golden UI**, with configurable gold frame/border colors and brown title bars while retaining default text colors. Darkened UI takes precedence when both are selected.
- Share theme handling across native windows and supported addons, including aux/AuxCraft, Bagshui, Outfitter, VCB and RollFor. Preserve parchment, item icons, slot interiors and existing quality colors.
- Extend **Item Rarity Borders** to outgoing mail/TurtleMail attachments and both sides of trade windows, refreshing on item changes and clearing empty slots. **Loot Quality** remains specific to the loot window.
- Center Tooltip Details health text within its bar.
- **Golden UI** (Interface, off): the Darkened UI recolor with a gold palette, like the WoW Forever client. Frame art is tinted gold (`goldmode.color`, default 1, .78, .4), and backdrop borders, tooltip borders and the borders around buttons get a softer gold (`goldmode.border`, default .8, .62, .32). Tooltip and dialog backgrounds keep their own colors. Both colors can be changed with `/st`. If Darkened UI is also enabled, Darkened UI wins and Golden UI does nothing.
## 3.7.2 — 2026-11-02 — Loss of Control Alert, Totem Timers and movable cast bar
### Changed
- **Golden UI** retains the default UI text colors for titles, names and labels. The pale-gold text override and its `goldmode.text` setting have been removed.
- **Golden UI** darkens the window title bars to WoW Forever's red-brown (`goldmode.bar`, default .8, .63, .62): world map, character, spellbook, quest log, merchant, friends, mail and RollFor's classic loot window. Where the title sits in the same layer as the art, the title is drawn again on top of the bar; the lighter patch in the world map's stone art is evened out.
- **Darkened UI and Golden UI** tint the round border of addon minimap buttons (the stock tracking border, as on RollFor's button), also when the addon makes its button after login.
- **Darkened UI and Golden UI** now keep these addons' borders in the palette too, even when they set their colors again later or open a window after login: ShaguDPS, Bagshui, Outfitter, SoulstoneTracker, TrinketMenu, OctoBus, RollFor and VCB. Outfitter's own window art and the lower half of Artisan's profession window (its own textures, which stayed silver) are tinted too, VCB's buff and weapon-buff buttons get a border like the Blizzard buff buttons, the loot window, and RollFor's classic loot window (its own art plus the item rows), are recolored each time they open, and Loot Quality draws common (white) items with the palette's border color. Only light grey, white and silver borders change; colored ones (item quality, hover highlights, class or theme colors) and dark ones stay as the addon draws them. No change is needed in those addons.
- **Darkened UI:** works the same, but now shares its recolor code with Golden UI. Big Health's replacement unit frame art follows whichever of the two is on.
### Fixed
- **Golden UI:** Advanced Options category panels use the configured gold border color whenever the list is rebuilt. Default and Darkened UI panel styling is preserved.
- **UI hotfix review:** preserve marked rarity colors across backdrop replacements. Batch outgoing-mail refresh requests through ClassicAPI's native one-shot timer when available, keep the older queue fallback, and skip hidden-mail refreshes. Remove the redundant full trade rarity pass. Lua 5.0 regression checks are in `tests/`; scope and performance limits are recorded in `Docs/UI_OPTIONS_REVIEW.md`.
- **Loot Quality:** keep the loot-window title region separate from the module's name, avoiding a string/frame collision when enabling the option.
- **Trade rarity borders:** derive rarity from each column's own legacy item link (not its display name or native item-ID shortcuts) and refresh after Blizzard updates that slot. Empty slots and closing the window clear their borders, preventing colors from appearing on the opposite side or carrying over to a later trade.
- **Trade quality lookup:** resolve the link's item ID through ClassicAPI's item-quality API, as for mail attachments. In-game diagnostics showed that the global `GetItemInfo(link)` call returned no quality despite valid trade item IDs, leaving every border transparent.
- **TurtleMail rarity borders:** update each attachment directly when its item texture changes, so adding items colors the border immediately and removing items clears it without switching tabs. This also works when TurtleMail replaces the global mail update function at login.
- **Item Rarity Borders:** controls outgoing-mail borders (including every TurtleMail attachment) and both sides of trade windows. All qualities use their item color, including white common items, in the original, Darkened and Golden UI modes. Borders refresh when attachments or trade items change and clear when slots are emptied. Loot Quality only colors the loot window itself; it does not enable mail or trade borders.
- **Golden UI and Darkened UI:** keep neutral attachment borders in the active palette when items are added to Send Mail, including TurtleMail's extra slots. Colored rarity borders remain unchanged.
- **Golden UI and Darkened UI:** aux and AuxCraft windows, their child panels and drag outlines now use the active border palette. Their background colors and colored item borders are preserved, including when the windows are created after login.
- **Golden UI and Darkened UI:** preserve the original inbox parchment and item-slot colors while keeping the surrounding frame, header and child buttons in the active palette. Cropped copies of the original mail artwork prevent tinted or darkened paper without the straight edge of replacement stationery, including with TurtleMail. The copies belong to the Inbox tab so they cannot cover the Send Mail or Log tabs.
- **Golden UI and Darkened UI:** recolor only the borders of TurtleMail's attachment slots and Bagshui's empty-slot artwork, including after Bagshui refreshes its slots. Empty insets, item icons, quality colors and custom solid empty-slot backgrounds retain their own colors.
- **Slot border hotfix:** keep the original background untinted and draw only narrow border strips anchored to each button. Remove the center-copy approach that left some mail slots tinted and produced misplaced artwork when Bagshui reused slots.
- **Tooltip Details:** the health numbers sit centered on the tooltip's health bar instead of sticking out over its top border.
## 3.7.2 — 2026-11-02 — Loss of Control Alert, Totem Timers and Movable Cast Bar
### Added
- **Loss of Control Alert** (Interface, off): a large icon, the effect's name and a countdown with a time bar in the middle of the screen while you are stunned, feared, confused, charmed, possessed, silenced, pacified, disarmed, rooted or locked out of a spell school (a kick or Counterspell shows the school). The most disabling effect is shown when several are active. Hold Ctrl+Shift+Alt to show a preview and drag it, right-click to reset (`/lossofcontrol reset` also works). Uses ClassicAPI's `C_LossOfControl`, redraws on its events and ticks only while the alert is visible.
- **Totem Timers** (Interface, off, Shamans only): an icon per active totem in the order Fire, Earth, Water, Air, with the time left (yellow under 10 seconds, red under 5) and a bar. Left-click targets the totem (`TargetTotem`), hovering shows its tooltip. Hold Ctrl+Shift+Alt to show four preview icons and drag them, right-click to reset (`/totemtimers reset` also works). Uses ClassicAPI's `GetTotemInfo`, `GetTotemTimeLeft` and `PLAYER_TOTEM_UPDATE`, and ticks only while a totem is out.
- **Swing Timer / Wand Timer:** each bar (main hand, off hand, ranged) now moves on its own with Ctrl+Shift+Alt, and right-click puts back only the bar you clicked. They used to move as one block, and the bars that were not moved followed the cast bar. Dragging the cast bar now pins the swing bars where they are first, so only the cast bar moves.
- **Enemy Castbars** (Unit Frames) is movable too: hold Ctrl+Shift+Alt to show a marker where the target's cast bar goes, drag it, right-click to reset. The spot is saved as an offset from the target frame, so the bar follows the frame when you move it, and the automatic position below the buffs no longer applies once you have moved it. The marker also shows without a target.
- **Movable Cast Bar** (Action Bar, on): hold Ctrl+Shift+Alt to show a marker over the player's cast bar and drag it anywhere, right-click it to put the bar back. The position is saved per character, and it keeps the spot even when Reduced Actionbar or another addon re-anchors the bar. The Swing Timer, Wand Timer and the Improved Castbar icon are anchored to the cast bar and follow it. Nothing changes until you move it.
### Changed
- **README:** the "Built-in Extras modules" link now opens [Docs/EXTRAS_MERGE.md](Docs/EXTRAS_MERGE.md), which lists where each Extras module went, which ones were left out and why.
- Add **Loss of Control Alert** and **Totem Timers**, using ClassicAPI events with animation only while active. Both support modifier dragging and reset.
- Add **Movable Cast Bar** and independent movement for swing, wand and enemy cast bars, with saved positions.
- Link the [Extras migration guide](Docs/EXTRAS_MERGE.md) from the README.
## 3.7.1 — 2026-11-02 — Key-Down Casting removed
### Removed
- **Key-Down Casting** and **Alt Self-Cast** (Action Bar). ClassicAPI now has this built into the game, which is the better way to do it:
- `ActionButtonUseKeyDown` is a client setting for the main bar, the side bars and the pet bar. The game itself uses the action when the key is pressed, so there is no addon code in the way.
- The module replaced the action-bar key functions with its own Lua versions and ran a timer four times a second to keep them in place. The native setting needs neither, so it costs no CPU and cannot be overwritten by another addon.
- The setting is kept with your game settings instead of per character.
- To turn it on, type `/console ActionButtonUseKeyDown 1` once (`0` turns it off again).
- Alt Self-Cast only worked together with Key-Down Casting, so it is gone too. ClassicAPI has the game's own `SELFCAST` modified click (Alt by default), which self-casts `SPELL` and `ITEM` key bindings.
- Remove **Key-Down Casting** and its dependent **Alt Self-Cast** option in favor of ClassicAPI's native settings. Use `/console ActionButtonUseKeyDown 1` for key-down activation and the native SELFCAST modified click for self-casting.
## 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.
- Merge Dusk's Extras action-bar, chat, loot, macro, merchant, map, tooltip and abbreviated-name modules, preserving their saved choices. Warn when the separate Extras addon remains enabled.
- Leave raid frames, bag helpers and auction Alt-Buy to dedicated addons. See [the module list](Docs/EXTRAS_MERGE.md).
- Improve XP text legibility with a shadow instead of an outline.
## 3.6 — 2026-11-02 — Map window, Transports button and Loot History
### Added
- **Loot History** (Loot, on): `/loothistory` opens a window with the recent group-loot rolls. Each item shows who won it with which roll; click it to see everyone's need, greed or pass and their rolls. A Clear button empties the list. Ported from brues-code/pfUI's loot history and drawn with the classic dialog look. Needs ClassicAPI's `C_LootHistory`, which keeps the last 128 rolled items through `/reload`.
- **WorldMap Window:** when [OctoBus](https://github.com/Phridgey/OctoBus) is installed, a **Transports** button left of the Continent dropdown shows or hides the OctoBus window, like a left-click on its minimap icon. While the map is open, the OctoBus window is drawn above it.
### Fixed
- **WorldMap Window:** the map keeps the size picked with Ctrl+Mousewheel after closing and reopening it, and after a reload. It used to go back to the default size every time it opened.
- **WorldMap Window:** the title bar drags the window too. On Turtle WoW the title is drawn above the window's edge, so only the dropdown row could be dragged before.
- Add **Loot History**, using ClassicAPI's stored group-roll results, and an OctoBus **Transports** button on the world map.
- Preserve map scale across reopening/reload and allow dragging from its title bar.
## 3.5 — 2026-11-01 — Swing Timer and Wand Timer
### Added
- **Swing Timer** (Action Bar, off by default): main-hand and off-hand swing timers styled like the classic cast bar, with the weapon's icon on the left. They sit just above the cast bar; hold Ctrl+Shift+Alt to drag them, right-click to reset. A bar turns gray while the target is out of reach.
- **Swing Timer queue colors:** the main-hand bar shows a queued Heroic Strike in light blue and Cleave in green (warriors), and Maul in orange (druids). It goes back to gold when the swing goes off, is refused, or is cancelled.
- **Wand Timer** (Action Bar, off by default): the time until your next wand shot in the same style. It also times Auto Shot for bows, guns and crossbows, and turns red during the last half second, when moving would cancel the shot.
- Both are ported from brues-code/pfUI's swing timer and need ClassicAPI v1.15.10 or newer (`PLAYER_SWING`).
- Add classic-style main/off-hand swing timers, queued-attack colors and ranged/wand timing with Hunter wind-up indication, based on brues-code/pfUI.
## 3.4 — 2026-11-01 — Hide Pet Unit Frame and XP numbers
### Added
- **Hide Pet Unit Frame** (Unit Frames, off by default): hides the pet frame below the player frame for pet classes.
### Changed
- **XP Bar Text:** hovering the experience bar shows the current and needed XP, like the reputation bar (`1555/3000`), followed by the percentage and the rested bonus.
- Add **Hide Pet Unit Frame** and show current/required XP, percentage and rested bonus on hover.
## 3.3 — 2026-11-01 — World map colors and per-frame checks
### Fixed
- Correct class colors for packed world-map raid icons and battleground teammates.
- Limit map, target-of-target portrait and enlarged-minimap checks to relevant visibility/update intervals.
- **WorldMap Class Colors:** raid members on the world map and battlefield minimap show their own class color. In a raid the map packs its icons (the first icon is the first raid member on that map, not `raid1`), so icons used to show other players' colors, such as a priest in paladin pink. Battleground team members outside your raid are colored from the player cache.
## 3.2 — 2026-11-01 — Performance
### Changed
- Bound and expire the shared player cache, refresh sighting ages and avoid redundant GUID/guild lookups. See [cache details](Docs/PERFORMANCE.md).
- Throttle Range Color checks and retain Blizzard's native out-of-range hotkey coloring.
- **WorldMap Class Colors:** runs only while the world map or battlefield minimap is open.
- **Unit Frame Class Portraits:** the target-of-target check runs five times a second instead of every frame. A new target of target still gets its class icon at once.
- **Enlarged Minimap:** the check that keeps the buff rows clear of the bigger minimap runs only while the default buff frame is shown. With a buff addon that hides it, such as VCB, the check stops.
## 3.1 — 2026-10-31 — ClassicAPI review and tidy-up
## 3.2 — 2026-11-01 — Performance (`1ae8398`)
- Prevent duplicate Debuff Timer countdown text with ClassicAPI's native countdown enabled.
- Refresh feature documentation, compatibility notes and repository ignore rules.
### Changed
## 3.0 — 2026-10-30 — OEVG customization
- **Player cache:** the shared cache (`ShaguTweaks_player_cache`) now expires once per session. Players with a known class are kept 180 days after they were last seen, players whose class was never learned 90 days, and the cache holds at most 10,000 players (the oldest sightings go first). Before this, most entries never expired and one account's file reached 4.1 MB. See [Docs/PERFORMANCE.md](Docs/PERFORMANCE.md).
- **Player cache:** every sighting (mouseover, target, nameplate, party, raid, guild, friends, `/who`, chat) refreshes a player's age. Guild members and friends are refreshed each session, so they do not expire.
- **Player cache:** a player's GUID is saved only while the class is unknown. Such players are resolved later with `GetPlayerInfoByGUID`.
- **Chat:** a speaker whose class is known and who spoke within the hour no longer triggers a GUID lookup.
- **Guild roster:** a full rescan runs at most once a minute unless the member count changed.
- **Range Color:** the range check runs every 0.2 seconds per button. It used to run on every frame for every ranged button.
- **Range Color:** Blizzard's own range check works again, so hotkey numbers turn red out of range, as in the default UI.
### Removed
- Social Colors' separate 180-day cache cleanup. Expiry now lives in `libs/libunitscan.lua` and runs whether or not Social Colors is enabled.
### Upgrade note
- The first login after updating can remove many old cache entries. Class colors and chat levels come back as soon as those players are seen again.
## 3.1 — 2026-10-31 — ClassicAPI v1.18.13 review and tidy-up (`e4c7741`)
### Fixed
- **Debuff Timer:** native cooldown frames hide ClassicAPI's own countdown numbers, so the timer text is not doubled when the `countdownForCooldowns` CVar is on.
### Changed
- README rewritten: one feature list, module names and default states matching the in-game options, the recommended ClassicAPI version, and links to the docs.
- [Docs/CLASSICAPI_UPDATES.md](Docs/CLASSICAPI_UPDATES.md) records the review of ClassicAPI v1.17.7 to v1.18.13. ClassicAPI v1.18.10 or newer is recommended for its nameplate crash fix.
- `.gitignore` covers editor and merge leftovers.
### Removed
- The Russian `## Notes-ruRU` line in `ShaguTweaks.toc`.
## 3.0 — 2026-10-30 — OEVG customization (`48d45f8`, `d705a9b`)
### Added
- **Modern Class Colors:** an optional palette for all nine Vanilla classes, including blue shamans, with class-tinted health artwork and precolored rage, energy and focus bars. Mana uses the light-blue retail fill on every supported frame, including the druid's mana bar in bear and cat form. See [Docs/MODERN_MANA.md](Docs/MODERN_MANA.md).
- **White Unit Frame Numbers:** health, percentage and power numbers in white instead of gold.
- **Unit Frame Big Health:** the resting glow is limited to the portrait.
### Changed
- **Real Health Numbers:** percentage on the left and current health on the right. The numbers copy the character name's font, size and shadow. Unknown target health stays percentage-only.
- **Movable Unit Frames:** hold Ctrl+Shift+Alt to drag.
- **Tooltip Details:** guild ranks and the leader marker are restored, with optional modern class colors and health texture. Cached colors no longer leak between tooltips.
- **Range Color:** spells without a range no longer use the legacy fallback, and wrong friendly or hostile targets and stale red or no-mana tints are handled.
- **ClassicAPI integration:** native cooldown frames, filtered unit events, corrected chat identity and payload handling, and fewer duplicate player-cache records when ClassicAPI's own player cache is on. See [Docs/CLASSICAPI_UPDATES.md](Docs/CLASSICAPI_UPDATES.md).
### Removed
- All non-English locale and translation files. The addon loads English data only.
- The **Unit Frame Health Colors** module (`mods/health-color.lua`).
- Add **Modern Class Colors** and modern health/power textures, including druid mana, plus independent **White Unit Frame Numbers**.
- Place health percentage left and current health right with matching name-font styling. Limit Big Health's resting glow to the portrait.
+5 -1
View File
@@ -1,6 +1,6 @@
# ClassicAPI integration updates
## Current integration (3.8.2, 2026-11-04)
## Current integration (3.8.3, 2026-11-05)
The [ClassicAPI/pfUI overlap review](CLASSICAPI_PFUI_REVIEW_382.md) pins current
source revisions and records optional recoloring improvements. Its energy/mana
@@ -18,6 +18,10 @@ assumed available from the minimum version.
- Healing Predictions shares compatible OEVG Puppeteer data when available;
otherwise it uses a lazy ClassicAPI event/observed-heal estimator with live HoT
aura timing. See [the estimator review](HEALING_PREDICTION_REVIEW.md).
- Personal Resource Display mirrors the native player bars without a second
health/power event driver; its health copy shares the existing prediction
estimator. Resource hiding is independent, and Hide Player Name requires it.
The former standalone setting/positions migrate; Hide Player Portrait is retired.
- Castbars query ClassicAPI directly. The unused name-keyed cast mirror and
combat-text parser are removed; SuperWoW supplements missing enemy timing with
a bounded GUID cache. See [the cast review](CAST_TRACKING_REVIEW.md).
+21 -8
View File
@@ -9,10 +9,24 @@ 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.8.2.** What changed in each update: see the [changelog](CHANGELOG.md).
**Version 3.8.3.** 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 [Docs/EXTRAS_MERGE.md](Docs/EXTRAS_MERGE.md) for where each module went and why some were left out.
## Screenshots
Personal Resource Display, independently positioned player name and movable action bars with Golden UI.
![Personal Resource Display and movable action bars](screenshots/personal-resource-display.png)
Golden UI world map with the OctoBus Transports panel.
![Golden UI world map with OctoBus integration](screenshots/golden-world-map.png)
Modifier-key previews for movable action bars, cast bars and personal resources.
![Movable action bars and cast-bar previews](screenshots/movable-bars-preview.png)
## 🔌 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 required (it includes a crash fix for addons that restyle nameplates). The addon checks the loaded DLL version and core APIs before enabling tweaks, and feature-detects newer range and duration-timer APIs. See [current integration notes and the historical review](Docs/CLASSICAPI_UPDATES.md).
@@ -41,18 +55,17 @@ Use the **Search** field to filter by option name, description, category or slid
**Unit frames**
- **Hide Player Portrait** (off): hides only the player's portrait image, including a class-icon portrait, without hiding the name, health or power bar. The surrounding portrait artwork remains.
- **Hide Player Name** (off): hides the detached name piece only while **Standalone Player Frame** is enabled. It has no effect on the ordinary grouped player frame.
- **Unit Frame Energy & Mana Tick** (off): uses native spark movement when available, retains the five-second mana sweep for mana-costing spells, and observes the two-second regen phase separately. Form changes reset power samples; drains and mid-period gains do not falsely start/reset the cast-cost display. The spark resizes with Big Health/standalone bars.
- **Healing Predictions** (off): a green segment shows incoming direct heals plus one upcoming tick per active HoT, capped at maximum health. Reuses compatible Puppeteer prediction data when available, including its HoTs, without starting a second estimator. Otherwise ClassicAPI supplies cast/aura/combat events: own direct heals need an observed non-critical heal per spell rank each session, and HoTs such as Rejuvenation/Regrowth need an observed tick amount and identified live aura. Form changes preserve estimates; actual gear, talent or level changes invalidate own standalone samples. Other direct heals can arrive through explicit-duration vanilla HealComm messages. Unknown amounts/timing and percentage-only target health are not projected. Supports Big Health and Standalone Player Frame. See the [estimator review](Docs/HEALING_PREDICTION_REVIEW.md).
- **Standalone Player Frame** (off): detaches the existing Blizzard portrait, name, health and power into four independently movable pieces. Hold **Ctrl+Shift+Alt** and drag a piece; right-click with those keys held to reset only that piece. Positions are stored per character. Existing bars, updates, numeric overlays, textures and click actions are retained. With **Unit Frame Big Health for Player**, the power bar matches the enlarged health bar's width and height. This option handles its own movement even when Movable Unit Frames is off; the ordinary player-frame mover yields to it. Disabling the option and reloading restores the grouped frame while keeping detached positions for later use.
- **Hide Player Name** (off): hides the name and raid group number while **Hide Player Frame Resources** is enabled. Its checkbox is unavailable without that prerequisite.
- **Unit Frame Energy & Mana Tick** (off): uses native spark movement when available, retains the five-second mana sweep for mana-costing spells, and observes the two-second regen phase separately. Form changes reset power samples; drains and mid-period gains do not falsely start/reset the cast-cost display. The spark also appears on the Personal Resource Display power bar, sharing timing and resizing with either bar.
- **Healing Predictions** (off): a green segment shows incoming direct heals plus one upcoming tick per active HoT, capped at maximum health. Reuses compatible Puppeteer prediction data when available, including its HoTs, without starting a second estimator. Otherwise ClassicAPI supplies cast/aura/combat events: own direct heals need an observed non-critical heal per spell rank each session, and HoTs such as Rejuvenation/Regrowth need an observed tick amount and identified live aura. Form changes preserve estimates; actual gear, talent or level changes invalidate own standalone samples. Other direct heals can arrive through explicit-duration vanilla HealComm messages. Unknown amounts/timing and percentage-only target health are not projected. Supports Big Health and Personal Resource Display. See the [estimator review](Docs/HEALING_PREDICTION_REVIEW.md).
- **Personal Resource Display** (off): independent copies of your health and power bars, leaving the original player frame intact. Hold **Ctrl+Shift+Alt** to drag either bar or resize it with its bottom-right grip; right-click with those keys held to reset that bar. Positions and sizes are saved per character. Big Health for Player gives both bars the enlarged height. Copied bars share native values, textures and colors, plus Healing Predictions when enabled. Replaces Standalone Player Frame and migrates its health/power positions.
The standalone bars use clean rounded Blizzard border outlines, following the active UI palette. The name has a separate class-colored fill using the health texture, including with Big Health enabled. The portrait keeps one level badge on the left; the resting/combat icon covers its level number while active. ClassicAPI's portrait mask is used for the status glow when available; it is feature-detected.
- **Hide Player Frame Resources** (off): hides the original health and power bars, leaving a clean rounded portrait and a class-colored name. Independent of Personal Resource Display; use either or both. Ctrl+Shift+Alt-drag moves the name independently; right-click with those keys held resets it beside the portrait. Its position is saved per character. **Movable Unit Frames** moves the portrait; an undetached name follows it. The portrait keeps its native left level badge and resting/combat icons.
- **Modern Class Colors:** optional palette for all nine Vanilla classes, including blue shamans. Listed directly below **Unit Frame Class Colors**.
- **Modern textures:** class-tinted health artwork and precolored rage, energy and focus artwork. Mana uses the light-blue rectangular retail fill on every supported frame, including the druid's separate mana bar in bear/cat forms.
- **Real Health Numbers:** an independent module. Percentage on the left and current health on the right, inset 4 pixels; power numbers remain centered. It uses direct values when available and a lazy Vanilla health estimator for percentage-only targets, so unknown absolute target health stays percentage-only.
- **Number styling:** health, percentage and power numbers copy the corresponding character name's font, size and shadow. Gold is the default; **White Unit Frame Numbers** under Unit Frames switches the numbers to white independently of Modern Class Colors.
- **Unit Frame Big Health for Player** and **Unit Frame Big Health for Target** independently enlarge their respective health bars. Both are presentation-only and no longer forces numeric health/power text. With it, the resting pulse is limited to the portrait, and the full status artwork is restored outside resting or in combat.
- **Unit Frame Big Health for Player** and **Unit Frame Big Health for Target** independently enlarge their respective health bars. The player option also class-colors the player health bar and the Personal Resource Display health bar when enabled. Both are presentation-only and do not force numeric health/power text. The resting pulse is limited to the portrait, and the full status artwork is restored outside resting or in combat.
- **Movable Unit Frames:** Player, Target, Party, Minimap, Buffs, Debuffs and Weapon Buffs share one mover and alignment grid. Hold **Ctrl+Shift+Alt** to drag; right-click while unlocked to reset.
- **Hide Pet Unit Frame:** hides the pet frame below the player frame for pet classes. The stock pet code keeps running, so nothing breaks when the pet is summoned, dismissed or dies.
+3 -3
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.8.2
## Version: 3.8.3
## Dependencies: !!!ClassicAPI
## SavedVariables: ShaguTweaks_player_cache, ShaguTweaks_social_cache
## SavedVariablesPerCharacter: ShaguTweaks_config, ShaguTweaks_cache
@@ -65,9 +65,9 @@ mods\unitframes-bighealth.lua
mods\health-numbers.lua
mods\unitframes-healing-prediction.lua
mods\unitframes-classportrait.lua
mods\unitframes-hide-player-portrait.lua
mods\unitframes-hide-player-name.lua
mods\unitframes-standalone-player.lua
mods\unitframes-hide-player-resources.lua
mods\unitframes-personal-resource-display.lua
mods\unitframes-energy-tick.lua
mods\unitframes-hide-pet.lua
mods\cooldown-numbers.lua
+3
View File
@@ -98,6 +98,9 @@ Block("MiniMap Clock")
Block("MiniMap Tweaks")
Block("MiniMap Square")
Block("Movable Unit Frames")
Block("Personal Resource Display")
Block("Hide Player Frame Resources")
Block("Hide Player Name")
-- DragonflightUI replaces the stock player/target bars and provides its own
-- health/power text. Keep the real ShaguTweaks modules disabled there even
-- though Real Health Numbers is once again a genuine core module.
+26
View File
@@ -157,6 +157,26 @@ settings.searchClear:SetScript("OnClick", function()
settings.search:ClearFocus()
end)
settings.UpdateOptionAvailability = function(self)
if not settings.entries then return end
for title, button in pairs(settings.entries) do
local module = ShaguTweaks.mods[title]
if module and module.requires then
local available = true
for _, required in ipairs(module.requires) do
if current_config[required] ~= 1 then available = false end
end
button.dependencyMet = available
if available then button:Enable() else button:Disable() end
local label = _G["AdvancedSettingsGUI" .. title .. "Text"]
if label then
if available then label:SetTextColor(1, .82, 0)
else label:SetTextColor(.5, .5, .5) end
end
end
end
end
settings.load = function(self)
-- never use more than 3/4 of the screen size
max_height = math.min(UIParent:GetHeight()/UIParent:GetScale()*0.75, 680)
@@ -349,11 +369,16 @@ settings.load = function(self)
end)
button:SetScript("OnClick", function()
if this.dependencyMet == false then
this:SetChecked(current_config[this.title] == 1 and true or nil)
return
end
if this:GetChecked() then
current_config[this.title] = 1
else
current_config[this.title] = 0
end
settings:UpdateOptionAvailability()
end)
text:SetText(title)
entry = entry + 1
@@ -416,6 +441,7 @@ settings.load = function(self)
required_height = required_height + height + spacing
end
settings:UpdateOptionAvailability()
-- set container size to required height
if not next(gui) then
settings.empty:Show()
+18
View File
@@ -77,6 +77,8 @@ ShaguTweaks:SetScript("OnEvent", function()
-- Gold numbers belong to Real Health Numbers; retire the duplicate toggle.
ShaguTweaks_config["Unit Frame Health Colors"] = nil
-- The portrait-only hiding option has been removed.
ShaguTweaks_config[ShaguTweaks.T["Hide Player Portrait"]] = nil
-- Split the former shared setting without overriding either new choice.
local oldBigHealth = ShaguTweaks_config[ShaguTweaks.T["Unit Frame Big Health"]]
@@ -88,6 +90,22 @@ ShaguTweaks:SetScript("OnEvent", function()
ShaguTweaks_config[ShaguTweaks.T["Unit Frame Big Health"]] = nil
end
-- Replace detached native bars with copies; hiding the originals is opt-in.
local oldStandalone = ShaguTweaks.T["Standalone Player Frame"]
local resourceDisplay = ShaguTweaks.T["Personal Resource Display"]
if ShaguTweaks_config[resourceDisplay] == nil then
ShaguTweaks_config[resourceDisplay] = ShaguTweaks_config[oldStandalone]
end
if ShaguTweaks_config.StandalonePlayerFrame then
local positions = ShaguTweaks_config.PersonalResourceDisplay or {}
for _, key in ipairs({"Health", "Power"}) do
if not positions[key] then positions[key] = ShaguTweaks_config.StandalonePlayerFrame[key] end
end
ShaguTweaks_config.PersonalResourceDisplay = positions
end
ShaguTweaks_config[oldStandalone] = nil
ShaguTweaks_config.StandalonePlayerFrame = nil
-- initialize overwrites
ShaguTweaks_config.overwrites = ShaguTweaks_config.overwrites or {}
+1 -3
View File
@@ -143,9 +143,7 @@ module.enable = function(self)
if bar then bar:SetStatusBarTexture(texture) end
end
local playerHealthY = PlayerFrameHealthBar:GetHeight() >= 20 and -7 or 0.5
-- Grouped Big Health reserves the top for the player's name. The
-- standalone frame has a separate name holder, so center its numbers.
if ShaguTweaks_config[T["Standalone Player Frame"]] == 1 then playerHealthY = 0.5 end
-- The native Big Health frame reserves the top for the player's name.
local targetHealthY = TargetFrameHealthBar:GetHeight() >= 20 and -7 or 0.5
AnchorHealth(self.valueTexts.playerHealth, PlayerFrameHealthBar, playerHealthY)
-1
View File
@@ -124,7 +124,6 @@ module.enable = function(self)
}
local function Resolve(target)
if target.name == "PlayerFrame" and ShaguTweaks_config[T["Standalone Player Frame"]] == 1 then return end
local handle = _G[target.name]
if not handle then return end
+2 -2
View File
@@ -3,7 +3,7 @@ local hooksecurefunc = hooksecurefunc or ShaguTweaks.hooksecurefunc
local playerModule = ShaguTweaks:register({
title = T["Unit Frame Big Health for Player"],
description = T["Increases the healthbar of the player unitframe."],
description = T["Enlarges the player health bar and colors it by class. Also applies the class color to the Personal Resource Display health bar when enabled."],
expansions = { ["vanilla"] = true },
category = T["Unit Frames"],
enabled = nil,
@@ -144,7 +144,7 @@ local function Enable(player, target)
local function ApplyPlayerHealthColor()
if not PlayerFrameNameBackground or not playerSetStatusBarColor then return end
local r, g, b = PlayerFrameNameBackground:GetVertexColor()
-- Standalone hides this color-source texture with alpha zero. Its
-- Resource hiding sets this color-source texture's alpha to zero. Its
-- visibility must not propagate to the actual health fill on updates.
playerSetStatusBarColor(PlayerFrameHealthBar, r, g, b, 1)
end
+41 -1
View File
@@ -3,7 +3,7 @@ local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Unit Frame Energy & Mana Tick"],
description = T["Adds an energy & mana tick to the player frame."],
description = T["Adds an energy & mana tick to the player frame and Personal Resource Display."],
expansions = { ["vanilla"] = true }, category = T["Unit Frames"], enabled = nil,
})
module.enable = function(self)
@@ -140,4 +140,44 @@ module.enable = function(self)
"UNIT_SPELLCAST_SUCCEEDED", "UNIT_SPELLCAST_CHANNEL_STOP" }
for _, name in ipairs(events) do API.RegisterUnitEvent(tick, name, "player") end
SizeSpark()
-- Mirror the existing sweep; do not create another clock or event listener.
ShaguTweaks.RegisterResourceTickBar = function(parent)
if self.resourceTick then return end
local copy = CreateFrame("StatusBar", nil, parent)
self.resourceTick = copy
copy:SetAllPoints(parent)
copy:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
copy:SetStatusBarColor(1, 1, 1, 0)
copy:SetMinMaxValues(0, 1)
copy.spark = copy:CreateTexture(nil, "OVERLAY")
copy.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
copy.spark:SetBlendMode("ADD")
copy.spark:SetPoint("CENTER", copy:GetStatusBarTexture(), "RIGHT", 0, 0)
local function Resize()
copy.spark:SetHeight(parent:GetHeight() + 10)
copy.spark:SetWidth(parent:GetHeight() + 4)
end
local function Visibility()
if tick:IsShown() then copy:Show() else copy:Hide() end
if tick.spark:IsShown() then copy.spark:Show() else copy.spark:Hide() end
end
ShaguTweaks.HookScript(parent, "OnSizeChanged", Resize)
ShaguTweaks.HookScript(tick, "OnShow", Visibility)
ShaguTweaks.HookScript(tick, "OnHide", Visibility)
ShaguTweaks.hooksecurefunc(tick.spark, "Show", Visibility)
ShaguTweaks.hooksecurefunc(tick.spark, "Hide", Visibility)
if native then
ShaguTweaks.hooksecurefunc(tick, "SetTimerDuration", function(bar, duration, interpolation, direction)
copy:SetTimerDuration(duration, interpolation, direction)
end)
if tick.start then copy:SetTimerDuration(tick.duration, _G.Enum.StatusBarInterpolation.Immediate,
_G.Enum.StatusBarTimerDirection.ElapsedTime) end
else
ShaguTweaks.hooksecurefunc(tick, "SetValue", function(bar, value) copy:SetValue(value) end)
copy:SetValue(tick:GetValue() or 0)
end
Resize(); Visibility()
end
local display = ShaguTweaks.PersonalResourceDisplay
if display then ShaguTweaks.RegisterResourceTickBar(display.power.bar) end
end
+25 -15
View File
@@ -30,32 +30,42 @@ end
local function RefreshAll()
for _, state in ipairs(states) do Refresh(state) end
end
local function AttachBar(unit, bar)
if not bar then return end
for _, state in ipairs(states) do if state.bar == bar then return end end
local state = { unit = unit, bar = bar, fill = bar:CreateTexture(nil,"ARTWORK") }
table.insert(states,state)
state.fill:Hide()
local function Changed() Refresh(state) end
ShaguTweaks.hooksecurefunc(bar,"SetValue",Changed)
ShaguTweaks.hooksecurefunc(bar,"SetMinMaxValues",Changed)
ShaguTweaks.hooksecurefunc(bar,"SetStatusBarTexture",Changed)
ShaguTweaks.hooksecurefunc(bar,"SetWidth",Changed)
ShaguTweaks.hooksecurefunc(bar,"SetHeight",Changed)
ShaguTweaks.HookScript(bar,"OnShow",Changed)
ShaguTweaks.HookScript(bar,"OnSizeChanged",Changed)
ShaguTweaks.HookScript(bar,"OnHide",function() state.fill:Hide() end)
Refresh(state)
end
-- Additional displays share the existing estimator and event driver.
ShaguTweaks.RegisterHealingPredictionBar = function(unit, bar)
if enabled then AttachBar(unit, bar) end
end
module.enable = function()
if enabled then return end
enabled = true
if not states then
states = {}
for _, definition in ipairs({{"player",PlayerFrameHealthBar},{"target",TargetFrameHealthBar}}) do
local bar = definition[2]
if bar then
local state = { unit = definition[1], bar = bar, fill = bar:CreateTexture(nil,"ARTWORK") }
table.insert(states,state)
state.fill:Hide()
local function Changed() Refresh(state) end
ShaguTweaks.hooksecurefunc(bar,"SetValue",Changed)
ShaguTweaks.hooksecurefunc(bar,"SetMinMaxValues",Changed)
ShaguTweaks.hooksecurefunc(bar,"SetStatusBarTexture",Changed)
ShaguTweaks.HookScript(bar,"OnShow",Changed)
ShaguTweaks.HookScript(bar,"OnSizeChanged",Changed)
ShaguTweaks.HookScript(bar,"OnHide",function() state.fill:Hide() end)
end
end
AttachBar("player", PlayerFrameHealthBar)
AttachBar("target", TargetFrameHealthBar)
P.changed = function(name)
for _, state in ipairs(states) do if UnitName(state.unit)==name then Refresh(state) end end
end
events = CreateFrame("Frame")
events:SetScript("OnEvent",RefreshAll)
end
local display = ShaguTweaks.PersonalResourceDisplay
if display then AttachBar("player", display.health.bar) end
P:Enable()
ShaguTweaks.API.RegisterUnitEvent(events,"UNIT_HEALTH","player","target")
ShaguTweaks.API.RegisterUnitEvent(events,"UNIT_MAXHEALTH","player","target")
+12 -4
View File
@@ -2,16 +2,24 @@ local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Hide Player Name"],
description = T["Hides your detached player name. Only applies while Standalone Player Frame is enabled."],
description = T["Hides your player name and raid group number. Requires Hide Player Frame Resources."],
requires = { T["Hide Player Frame Resources"] },
expansions = { ["vanilla"] = true },
category = T["Unit Frames"],
enabled = nil,
})
module.enable = function(self)
if ShaguTweaks_config[T["Standalone Player Frame"]] ~= 1 then return end
local layout = ShaguTweaks.StandalonePlayerFrame
if ShaguTweaks_config[T["Hide Player Frame Resources"]] ~= 1 then return end
local layout = ShaguTweaks.PlayerFrameResourcesLayout
if layout and layout.name then layout.name:Hide() end
-- Standalone setup also reads the saved setting after all modules load,
local group = PlayerFrameGroupIndicator
if group and not self.groupHidden then
self.groupHidden = true
local setAlpha = group.SetAlpha
setAlpha(group, 0)
ShaguTweaks.hooksecurefunc(group, "SetAlpha", function(frame) setAlpha(frame, 0) end)
end
-- Resource-hiding setup also reads the saved setting after all modules load,
-- so this works regardless of their initialization order.
end
-21
View File
@@ -1,21 +0,0 @@
local T = ShaguTweaks.T
local module = ShaguTweaks:register({
title = T["Hide Player Portrait"],
description = T["Hides only your player portrait image. The player name, health bar and power bar remain visible."],
expansions = { ["vanilla"] = true },
category = T["Unit Frames"],
enabled = nil,
})
module.enable = function(self)
if self.installed or not PlayerPortrait then return end
self.installed = true
local setAlpha = PlayerPortrait.SetAlpha
setAlpha(PlayerPortrait, 0)
-- Keep the texture object available to the stock portrait code and class
-- portrait module. Neither its visibility nor its parent's bars are altered.
ShaguTweaks.hooksecurefunc(PlayerPortrait, "SetAlpha", function(texture)
setAlpha(texture, 0)
end)
end
@@ -1,152 +1,87 @@
local _G = ShaguTweaks.GetGlobalEnv()
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Standalone Player Frame"],
description = T["Separates the existing player portrait, name, health and power bars. Hold Ctrl+Shift+Alt to drag each piece; right-click with those keys held to reset it. Big Health also enlarges the power bar."],
expansions = { ["vanilla"] = true },
category = T["Unit Frames"],
enabled = nil,
title = T["Hide Player Frame Resources"],
description = T["Hides the original player health and power bars, leaving the clean round portrait and name. Hold Ctrl+Shift+Alt to drag the name independently; right-click with those keys held to reset it. Personal Resource Display is independent."],
expansions = { ["vanilla"] = true }, category = T["Unit Frames"], enabled = nil,
})
local Unlocked = ShaguTweaks.API.AreMoveModifiersDown
local function Position(holder, point)
holder:ClearAllPoints()
holder:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", point.x, point.y)
end
local function StopDrag(holder)
if not holder.dragging then return end
holder:StopMovingOrSizing()
holder:SetUserPlaced(false)
holder.dragging = nil
holder.ignoreClick = true
local x, y = holder:GetLeft(), holder:GetTop()
if x and y then
holder.positions[holder.key] = { x = x, y = y }
Position(holder, holder.positions[holder.key])
end
end
local function MouseDown()
local holder = this.standaloneHolder
holder.ignoreClick = nil
if Unlocked() and arg1 == "RightButton" then
StopDrag(holder)
holder.positions[holder.key] = nil
Position(holder, holder.defaultPosition)
holder.ignoreClick = true
elseif not Unlocked() and this.standaloneMouseDown then
this.standaloneMouseDown()
end
end
local function DragStart()
if not Unlocked() then return end
local holder = this.standaloneHolder
holder.dragging = true
holder:StartMoving()
end
local function DragStop()
StopDrag(this.standaloneHolder)
end
local function UnitClick()
local holder = this.standaloneHolder
if holder.ignoreClick or holder.dragging or Unlocked() then return end
if arg1 == "RightButton" and not SpellIsTargeting() then
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, holder:GetName(), 0, 0)
elseif this.standaloneMouseUp then
this.standaloneMouseUp()
else
PlayerFrame_OnClick(arg1)
end
end
local function BindHandle(handle, holder)
handle.standaloneHolder = holder
handle.standaloneMouseDown = handle:GetScript("OnMouseDown")
handle:RegisterForDrag("LeftButton")
handle:EnableMouse(true)
handle:SetScript("OnMouseDown", MouseDown)
handle:SetScript("OnDragStart", DragStart)
handle:SetScript("OnDragStop", DragStop)
if handle:GetObjectType() == "Button" then
handle:RegisterForClicks("LeftButtonUp", "RightButtonUp")
handle:SetScript("OnClick", UnitClick)
else
handle.standaloneMouseUp = handle:GetScript("OnMouseUp")
handle:SetScript("OnMouseUp", UnitClick)
end
end
local function MoveRegion(region, holder, x, y)
if not region then return end
region:SetParent(holder)
region:ClearAllPoints()
region:SetPoint("TOPLEFT", holder, "TOPLEFT", x, y)
end
module.enable = function(self)
if self.scheduled then return end
self.scheduled = true
local function Setup()
if self.installed then return end
if not PlayerFrame or not PlayerPortrait or not PlayerFrameTexture
or not PlayerFrameHealthBar or not PlayerFrameManaBar then return end
if not PlayerFrameHealthBar:GetLeft() or not PlayerFrameHealthBar:GetTop()
or not PlayerPortrait:GetLeft() or not PlayerPortrait:GetTop() then return end
if self.installed or not PlayerFrame or not PlayerPortrait or not PlayerFrameTexture then return end
if not PlayerPortrait:GetLeft() or not PlayerFrame:GetLeft() then return end
self.installed = true
ShaguTweaks_config.StandalonePlayerFrame = ShaguTweaks_config.StandalonePlayerFrame or {}
local positions = ShaguTweaks_config.StandalonePlayerFrame
local scale = PlayerFrame:GetEffectiveScale() / UIParent:GetEffectiveScale()
local big = ShaguTweaks_config[T["Unit Frame Big Health for Player"]] == 1
local width = PlayerFrameHealthBar:GetWidth()
local healthHeight = PlayerFrameHealthBar:GetHeight()
local manaHeight = big and healthHeight or PlayerFrameManaBar:GetHeight()
local left = PlayerFrameHealthBar:GetLeft() - 4
local top = PlayerFrameHealthBar:GetTop() + 3
local portraitLeft, portraitTop = PlayerPortrait:GetLeft() - 8, PlayerPortrait:GetTop() + 4
local function Holder(key, w, h, x, y)
local holder = CreateFrame("Button", "ShaguTweaksPlayer" .. key, UIParent)
holder:SetScale(scale)
holder:SetWidth(w)
holder:SetHeight(h)
holder:SetMovable(true)
holder:SetClampedToScreen(true)
holder:SetFrameStrata(PlayerFrame:GetFrameStrata())
local holder = CreateFrame("Frame", "ShaguTweaksPlayer" .. key, PlayerFrame)
holder:SetWidth(w); holder:SetHeight(h)
holder:SetFrameLevel(PlayerFrame:GetFrameLevel() + 3)
holder.unit = "player"
holder.key, holder.positions = key, positions
holder.defaultPosition = { x = x, y = y }
Position(holder, positions[key] or holder.defaultPosition)
BindHandle(holder, holder)
holder:SetScript("OnHide", function() StopDrag(this) end)
holder:SetScript("OnReceiveDrag", PlayerFrame_OnReceiveDrag)
if UnitFrame_OnEnter and UnitFrame_OnLeave and UnitFrame_OnUpdate then
holder:SetScript("OnEnter", function()
if Unlocked() then return end
UnitFrame_OnEnter()
if this.updateTooltip then this:SetScript("OnUpdate", UnitFrame_OnUpdate) end
end)
holder:SetScript("OnLeave", function()
UnitFrame_OnLeave()
this:SetScript("OnUpdate", nil)
end)
end
holder:SetPoint("TOPLEFT", PlayerFrame, "TOPLEFT", x - PlayerFrame:GetLeft(), y - PlayerFrame:GetTop())
return holder
end
local portrait = Holder("Portrait", 80, 80, portraitLeft, portraitTop)
local name = Holder("Name", width + 8, 23, left, top + 25)
local health = Holder("Health", width + 8, healthHeight + 6, left, top)
local mana = Holder("Power", width + 8, manaHeight + 6, left, top - healthHeight - 8)
ShaguTweaks.StandalonePlayerFrame = { portrait = portrait, name = name, health = health, power = mana }
local name = Holder("Name", width + 8, 23, portraitLeft + 85, portraitTop - 14)
ShaguTweaks.PlayerFrameResourcesLayout = { portrait = portrait, name = name }
ShaguTweaks_config.PlayerFrameResources = ShaguTweaks_config.PlayerFrameResources or {}
local positions = ShaguTweaks_config.PlayerFrameResources
local nameX, nameY = portraitLeft + 85 - PlayerFrame:GetLeft(), portraitTop - 14 - PlayerFrame:GetTop()
local function PositionName()
name:ClearAllPoints()
if positions.Name then
name:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", positions.Name.x, positions.Name.y)
else
name:SetPoint("TOPLEFT", PlayerFrame, "TOPLEFT", nameX, nameY)
end
end
local dragging, dropped
local function StopName()
if not dragging then return end
name:StopMovingOrSizing()
name:SetUserPlaced(false)
dragging, dropped = nil, true
local x, y = name:GetLeft(), name:GetTop()
if x and y then positions.Name = { x = x, y = y } end
PositionName()
end
name:SetMovable(true)
name:SetClampedToScreen(true)
name:EnableMouse(true)
name:RegisterForDrag("LeftButton")
name:SetScript("OnDragStart", function()
if not API.AreMoveModifiersDown() then return end
dragging, dropped = true, nil
name:StartMoving()
end)
name:SetScript("OnDragStop", StopName)
name:SetScript("OnHide", StopName)
name:SetScript("OnMouseUp", function()
if API.AreMoveModifiersDown() and arg1 == "RightButton" then
StopName()
positions.Name, dropped = nil, nil
PositionName()
elseif dragging then
StopName()
dropped = nil
elseif dropped then
dropped = nil
elseif PlayerFrame_OnClick then
-- Retain native unit clicks even after the name is moved away.
local previous = this
this = PlayerFrame
PlayerFrame_OnClick(arg1)
this = previous
end
end)
PositionName()
-- Split the native artwork before changing its source texture's anchors.
-- Preserve its UV orientation (the player's stock artwork is mirrored).
local art = PlayerFrameTexture
@@ -206,19 +141,16 @@ module.enable = function(self)
end
-- The combined frame texture has a portrait arc at the left ends of its
-- bars. A separate Blizzard nine-slice edge gives clean rounded corners
-- at both normal and Big Health heights, without clipping the bar fill.
-- bars. Give the separate name strip a rounded Blizzard border.
local border = {
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
edgeSize = 12, tile = true, tileSize = 8,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
}
local color = ShaguTweaks.UIBorder
for _, holder in ipairs({ name, health, mana }) do
holder:SetBackdrop(border)
holder:SetBackdropBorderColor(color and color.r or 1, color and color.g or 1,
color and color.b or 1, 1)
end
name:SetBackdrop(border)
name:SetBackdropBorderColor(color and color.r or 1, color and color.g or 1,
color and color.b or 1, 1)
-- Keep status/role indicators with the portrait. Their native event
-- handlers and parent visibility continue running unchanged.
@@ -270,7 +202,6 @@ module.enable = function(self)
playerName:ClearAllPoints()
playerName:SetPoint("CENTER", name, "CENTER", 0, 0)
end
MoveRegion(_G.PlayerFrameNameBackground, name, 4, -2)
-- Big Health deliberately hides the stock name background. Keep its
-- color source for that module, but draw a separate class strip here.
name.classBackground = name:CreateTexture(nil, "BACKGROUND")
@@ -287,38 +218,7 @@ module.enable = function(self)
classColor and classColor.g or cg, classColor and classColor.b or cb, 1)
if ShaguTweaks_config[T["Hide Player Name"]] == 1 then name:Hide() end
local function DetachBar(bar, holder, h)
-- Vanilla's UnitFrameManaBar_Update calls UpdateManaType(GetParent()).
-- Keep that parent contract as well as the bar's own unit/events.
holder.healthbar = PlayerFrameHealthBar
holder.manabar = PlayerFrameManaBar
bar:SetParent(holder)
bar:ClearAllPoints()
bar:SetPoint("TOPLEFT", holder, "TOPLEFT", 4, -3)
bar:SetWidth(width)
bar:SetHeight(h)
BindHandle(bar, holder)
local background = bar:CreateTexture(nil, "BACKGROUND")
background:SetTexture(0, 0, 0, .5)
background:SetAllPoints(bar)
if bar.TextString then
bar.TextString:SetParent(bar)
bar.TextString:ClearAllPoints()
bar.TextString:SetPoint("CENTER", bar, "CENTER", 0, 0)
end
end
DetachBar(PlayerFrameHealthBar, health, healthHeight)
DetachBar(PlayerFrameManaBar, mana, manaHeight)
if _G.PlayerFrameAlternatePowerBar then
local alternate = _G.PlayerFrameAlternatePowerBar
alternate:SetParent(mana)
alternate:ClearAllPoints()
alternate:SetPoint("TOPLEFT", mana, "BOTTOMLEFT", 4, -2)
alternate:SetWidth(width)
end
-- Hide the now-redundant shared backing, without hiding PlayerFrame: its
-- native events, pet frame, menu and status scripts still need to run.
-- Keep native bar objects and events alive. Copies may still mirror them.
local function Suppress(region)
if not region then return end
local setAlpha = region.SetAlpha
@@ -328,8 +228,9 @@ module.enable = function(self)
Suppress(PlayerFrameTexture)
Suppress(_G.PlayerFrameBackground)
Suppress(_G.PlayerFrameNameBackground)
PlayerFrame:EnableMouse(false)
Suppress(PlayerFrameHealthBar)
Suppress(PlayerFrameManaBar)
Suppress(_G.PlayerFrameAlternatePowerBar)
if PlayerStatusTexture then
MoveRegion(PlayerStatusTexture, portrait, 1, 0)
local function CropStatus()
@@ -344,8 +245,5 @@ module.enable = function(self)
CropStatus()
end
end
-- Wait until all modules and their one-shot OnUpdates have initialized.
-- This also captures the player's restored layout-cache position.
API.AfterWorldReady(Setup, true)
end
@@ -0,0 +1,163 @@
local T = ShaguTweaks.T
local API = ShaguTweaks.API
local module = ShaguTweaks:register({
title = T["Personal Resource Display"],
description = T["Adds independent copies of your health and power bars without changing the player frame. Hold Ctrl+Shift+Alt to drag a bar or its bottom-right resize grip; right-click a bar with those keys held to reset it."],
expansions = { ["vanilla"] = true }, category = T["Unit Frames"], enabled = nil,
})
local border = { edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
edgeSize = 12, tile = true, tileSize = 8,
insets = { left = 3, right = 3, top = 3, bottom = 3 } }
local function Position(state, saved)
local point = saved or state.default
state.frame:ClearAllPoints()
state.frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", point.x, point.y)
state.frame:SetWidth(point.width or state.default.width)
state.frame:SetHeight(point.height or state.default.height)
end
local function Finish(state)
if not state.dragging then return end
state.frame:StopMovingOrSizing()
state.frame:SetUserPlaced(false)
state.dragging = nil
local x, y = state.frame:GetLeft(), state.frame:GetTop()
if x and y then
state.positions[state.key] = { x = x, y = y,
width = state.frame:GetWidth(), height = state.frame:GetHeight() }
Position(state, state.positions[state.key])
end
end
local function Begin()
if not API.AreMoveModifiersDown() then return end
local state = this.resourceState
state.dragging = true
if this == state.grip then state.frame:StartSizing("BOTTOMRIGHT")
else state.frame:StartMoving() end
end
local function Stop() Finish(this.resourceState) end
local function MouseDown()
if arg1 ~= "RightButton" or not API.AreMoveModifiersDown() then return end
local state = this.resourceState
Finish(state)
state.positions[state.key] = nil
Position(state)
end
module.enable = function(self)
if self.scheduled then return end
self.scheduled = true
local function Setup()
if self.states then return end
self.states = {}
ShaguTweaks_config.PersonalResourceDisplay = ShaguTweaks_config.PersonalResourceDisplay or {}
local positions = ShaguTweaks_config.PersonalResourceDisplay
local scale = PlayerFrame:GetEffectiveScale() / UIParent:GetEffectiveScale()
local big = ShaguTweaks_config[T["Unit Frame Big Health for Player"]] == 1
local width = PlayerFrameHealthBar:GetWidth() + 8
local healthHeight = PlayerFrameHealthBar:GetHeight() + 6
local powerHeight = big and healthHeight or PlayerFrameManaBar:GetHeight() + 6
local x, y = UIParent:GetWidth() / scale / 2 - width / 2,
UIParent:GetHeight() / scale / 2 - 120
local function Add(key, source, height, top)
local frame = CreateFrame("Frame", "ShaguTweaksResource" .. key, UIParent)
frame:SetScale(scale)
frame:SetFrameStrata(PlayerFrame:GetFrameStrata())
frame:SetFrameLevel(PlayerFrame:GetFrameLevel() + 3)
frame:SetMovable(true); frame:SetResizable(true); frame:SetClampedToScreen(true)
frame:SetMinResize(64, 18); frame:SetMaxResize(600, 160)
frame:SetBackdrop(border)
local color = ShaguTweaks.UIBorder
frame:SetBackdropBorderColor(color and color.r or 1, color and color.g or 1, color and color.b or 1, 1)
local state = { frame = frame, key = key, positions = positions,
default = { x = x, y = top, width = width, height = height } }
table.insert(self.states, state)
frame.resourceState = state
Position(state, positions[key])
local bar = CreateFrame("StatusBar", nil, frame)
frame.bar = bar
bar.unit = "player"
bar:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -3)
-- Update dimensions explicitly: anchored-child size notifications can
-- precede resolved geometry while the holder is being resized.
local function ResizeBar()
bar:SetWidth(math.max(1, frame:GetWidth() - 8))
bar:SetHeight(math.max(1, frame:GetHeight() - 6))
end
frame:SetScript("OnSizeChanged", ResizeBar)
ResizeBar()
local background = bar:CreateTexture(nil, "BACKGROUND")
background:SetTexture(0, 0, 0, .5); background:SetAllPoints(bar)
local text = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
text:SetPoint(key == "Health" and "RIGHT" or "CENTER", bar,
key == "Health" and "RIGHT" or "CENTER", key == "Health" and -4 or 0, 0)
local percent
if key == "Health" then
percent = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
percent:SetPoint("LEFT", bar, "LEFT", 4, 0)
end
local function Appearance()
local texture = source:GetStatusBarTexture()
bar:SetStatusBarTexture(texture and texture:GetTexture() or "Interface\\TargetingFrame\\UI-StatusBar")
local r, g, b = source:GetStatusBarColor()
bar:SetStatusBarColor(r, g, b, 1)
end
local function Values()
local minimum, maximum = source:GetMinMaxValues()
local value = source:GetValue() or 0
maximum = maximum or 0
bar:SetMinMaxValues(minimum or 0, maximum > 0 and maximum or 1)
bar:SetValue(value)
if value ~= state.value or maximum ~= state.maximum then
state.value, state.maximum = value, maximum
text:SetText(ShaguTweaks.Abbreviate(value))
if percent then percent:SetText((maximum > 0 and math.floor(value / maximum * 100 + .5) or 0) .. "%") end
end
end
ShaguTweaks.hooksecurefunc(source, "SetValue", Values)
ShaguTweaks.hooksecurefunc(source, "SetMinMaxValues", Values)
ShaguTweaks.hooksecurefunc(source, "SetStatusBarColor", Appearance)
ShaguTweaks.hooksecurefunc(source, "SetStatusBarTexture", Appearance)
Appearance(); Values()
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", Begin); frame:SetScript("OnDragStop", Stop)
frame:SetScript("OnMouseDown", MouseDown)
frame:SetScript("OnHide", Stop)
local grip = CreateFrame("Button", nil, frame)
state.grip, grip.resourceState = grip, state
grip:SetWidth(12); grip:SetHeight(12); grip:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
grip:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
grip:RegisterForDrag("LeftButton")
grip:SetScript("OnDragStart", Begin); grip:SetScript("OnDragStop", Stop)
grip:SetScript("OnHide", Stop)
return frame
end
local health = Add("Health", PlayerFrameHealthBar, healthHeight, y)
local power = Add("Power", PlayerFrameManaBar, powerHeight, y - healthHeight - 2)
ShaguTweaks.PersonalResourceDisplay = { health = health, power = power }
if ShaguTweaks.RegisterResourceTickBar then ShaguTweaks.RegisterResourceTickBar(power.bar) end
if ShaguTweaks.RegisterHealingPredictionBar then
ShaguTweaks.RegisterHealingPredictionBar("player", health.bar)
end
local function UpdateHandles()
local unlocked = API.AreMoveModifiersDown()
for _, state in ipairs(self.states) do
if not unlocked then Finish(state) end
state.frame:EnableMouse(unlocked)
if unlocked then state.grip:Show() else state.grip:Hide() end
end
end
local events = CreateFrame("Frame")
self.events = events
if API.modifierstate then
events:RegisterEvent("MODIFIER_STATE_CHANGED")
events:SetScript("OnEvent", UpdateHandles)
else
events.elapsed = 0
events:SetScript("OnUpdate", function()
events.elapsed = events.elapsed + (arg1 or 0)
if events.elapsed >= .1 then events.elapsed = 0; UpdateHandles() end
end)
end
UpdateHandles()
end
API.AfterWorldReady(Setup, true)
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

+10 -8
View File
@@ -9,7 +9,7 @@ sh tests/run_all.sh ../tools/lua5.0/lua5.0
The interpreter defaults to `lua5.0` if omitted. The runner discovers all
`tests/test_*.lua` files, reports every failure, prints a final count, and exits
unsuccessfully if any suite fails. It is developer tooling, is not loaded by the
addon, and has no in-game overhead. The current suite contains 25 test files.
addon, and has no in-game overhead. The current suite contains 26 test files.
Individual checks can also be run from the addon root:
@@ -19,7 +19,8 @@ lua5.0 tests/test_ui_slot_edges.lua .
lua5.0 tests/test_ui_palette.lua .
lua5.0 tests/test_options_search.lua .
lua5.0 tests/test_player_frame_options.lua .
lua5.0 tests/test_standalone_player.lua .
lua5.0 tests/test_hide_player_resources.lua .
lua5.0 tests/test_personal_resource_display.lua .
lua5.0 tests/test_actionbar_movers.lua .
lua5.0 tests/test_golden_unit_names.lua .
lua5.0 tests/test_quest_parchment.lua .
@@ -54,11 +55,12 @@ reuse, and preservation/application of unsaved settings.
They cannot validate client rendering, actual event order, DLL behavior, or FPS.
In-game testing remains necessary after changes to these paths.
Player-frame checks cover portrait-only hiding, modern name textures, retention
of native bar objects/events/text, enlarged power sizing, independent positions,
drag/reset and clicks, and initialization without idle polling. The standalone
layout's artwork crops, tooltip layering, native resting/combat animations,
scale changes and interactions with other addons must still be verified in game.
Player-frame checks cover independent resource copies and resource hiding, native
bar/event retention, Big Health sizing, saved positions/sizes, drag/reset,
modifier release and option dependencies. Portrait crops, native animations,
resize rendering and interactions with other addons still need in-game checks.
Compact-frame checks also cover independent name positions/reset, native unit
click forwarding, stopping a hidden drag and persistent raid group-number hiding.
Action-bar checks cover the Reduced Actionbar prerequisite, automatic movement integration, outline-only dragging,
dependent bars, saved anchors, native relayout, reset, hidden progress bars and
@@ -76,7 +78,7 @@ failures, expiry/cache limits and Hunter custom timing/hooks. See
Runtime-helper checks load the production API layer and exercise all modifier
combinations, native left/right state precedence, independent deferred startup
requests, preserved fallback delays and removal of one-shot scripts. Action-bar
and standalone fixtures also use the real shared helpers. In game, verify that
and resource-display fixtures also use the real shared helpers. In game, verify that
Ctrl+Shift+Alt previews, drag/reset and restored positions still work after login
and reload, including the cast bar, timers and unit frames.
+1 -1
View File
@@ -7,7 +7,7 @@ local function Check(player,target)
function methods:SetHeight(v) self.height=v end
function methods:SetPoint() end
function methods:SetVertexColor() end
function methods:GetVertexColor() return .2,.5,.8,0 end -- suppressed standalone name background
function methods:GetVertexColor() return .2,.5,.8,0 end -- suppressed native name background
function methods:GetTexCoord() return 0,0,0,1,1,0,1,1 end
function methods:GetWidth() return 256 end
function methods:SetWidth() end
+15
View File
@@ -42,6 +42,10 @@ local function Check(native)
C_Spell.GetSpellPowerCost=function() error('Use positional spell metadata without allocating a cost table') end
end
function ShaguTweaks:register(m) modules[m.title]=m;return m end
function ShaguTweaks.hooksecurefunc(o,k,callback)
local original=o[k]
o[k]=function(self,a,b,c) original(self,a,b,c);callback(self,a,b,c) end
end
function ShaguTweaks.HookScript(f,k,callback)
local old=f:GetScript(k);f:SetScript(k,function() if old then old() end;callback() end)
end
@@ -53,6 +57,14 @@ local function Check(native)
power=70;emit('UNIT_MANA','target');assert(tick.lastPower==80)
emit('UNIT_MANA','player');assert(not tick.ruleEnd) -- drain is not an FSR cast
now=1;power=80;emit('UNIT_MANA','player');assert(tick.phase==1 and tick.period==2)
local resource=frame('StatusBar');resource.height=24
local count=table.getn(timers)
ShaguTweaks.RegisterResourceTickBar(resource)
local copy=module.resourceTick
ShaguTweaks.RegisterResourceTickBar(resource);assert(module.resourceTick==copy)
assert(table.getn(timers)==count and not next(copy.events) and not copy.scripts.OnUpdate)
assert(copy.spark:IsShown() and copy.spark.height==34)
resource.height=40;resource.scripts.OnSizeChanged();assert(copy.spark.height==50)
now=1.8;power=85;emit('UNIT_MANA','player');assert(tick.phase==1) -- mid-period proc
emit('UNIT_SPELLCAST_SUCCEEDED','player',2);assert(not tick.ruleEnd) -- free cast
emit('UNIT_SPELLCAST_SUCCEEDED','player',1);assert(tick.ruleEnd==6.8 and tick.period==5)
@@ -65,6 +77,7 @@ local function Check(native)
PlayerFrameManaBar.height=30;PlayerFrameManaBar.scripts.OnSizeChanged();assert(tick.spark.height==40 and tick.spark.width==34)
local old=tick.wake
power=100;combat=false;emit('UNIT_MANA','player');assert(not tick:IsShown())
assert(not copy:IsShown())
if old then assert(old.cancelled) end
if native then assert(not tick.scripts.OnUpdate and tick.nativeDuration)
else assert(tick.scripts.OnUpdate) end
@@ -73,6 +86,8 @@ local function Check(native)
now=10.5
if native then assert(tick.nativeDuration.start==10 and tick.nativeDuration.duration==2)
else tick.scripts.OnUpdate();assert(tick.value==.25) end
assert(copy:IsShown() and copy.spark:IsShown())
if native then assert(copy.nativeDuration==tick.nativeDuration) else assert(copy.value==tick.value) end
-- A late wake catches up to the observed clock, not to now (no phase drift).
if native then now=14.2;tick.wake.callback();assert(tick.start==14) end
end
+2 -2
View File
@@ -67,7 +67,7 @@ function ShadeTitleBars() end
function RestoreMailParchment() end
function GoldenUnitNameBar(frame) seen[frame]=true end
assert(loadstring(string.sub(source,d)))()
ShaguTweaks_config={['Standalone Player Frame']=1}
ShaguTweaks_config={['Personal Resource Display']=1}
golden:enable();assert(seen[TargetFrame] and not seen[PlayerFrame])
ShaguTweaks_config={};seen={}
golden:enable();assert(seen[TargetFrame] and not seen[PlayerFrame])
@@ -79,4 +79,4 @@ ShaguTweaks_config={['Unit Frame Big Health for Player']=1,['Unit Frame Big Heal
golden:enable();assert(not next(seen))
ShaguTweaks_config={['Darkened UI']=1};seen={}
golden:enable();assert(not next(seen) and applied==5)
print('Golden name headers: opaque native title texture, palette match, live text, font retention, idempotence, standalone exemption and Darkened UI precedence passed')
print('Golden name headers: opaque native title texture, palette match, live text, font retention, idempotence, player exemption and Darkened UI precedence passed')
+17
View File
@@ -119,6 +119,23 @@ gear='New robe';event='UNIT_INVENTORY_CHANGED';arg1='player';driver.scripts.OnEv
assert(P:UnitGetIncomingHeals('target')==80, tostring(P:UnitGetIncomingHeals('target'))) -- only own gear-dependent tick samples invalidated
auras={};AuraChanged();arg1=.2;if driver.scripts.OnUpdate then driver.scripts.OnUpdate() end
assert(P:UnitGetIncomingHeals('target')==0 and not fill.visible and not driver.scripts.OnUpdate)
-- Resource copies attach once and share the same estimator/events, including
-- when the native bar has been made transparent by resource hiding.
local copy=object();copy.width=200
local before=table.getn(frames)
ShaguTweaks.RegisterHealingPredictionBar('player',copy)
local copyFill=copy.fill
ShaguTweaks.RegisterHealingPredictionBar('player',copy)
assert(copy.fill==copyFill and table.getn(frames)==before)
P:Heal('Healer','Me',200,3000)
assert(copyFill.visible and copyFill.width==40)
copy:SetWidth(300);copy:SetHeight(40)
assert(copyFill.width==60 and copyFill.height==40 and copyFill.point[4]==150)
mod:disable();assert(not copyFill.visible)
local late=object()
ShaguTweaks.RegisterHealingPredictionBar('player',late);assert(not rawget(late,'fill'))
ShaguTweaks.PersonalResourceDisplay={health={bar=late}}
mod:enable();assert(late.fill and table.getn(frames)==before)
mod:disable()
local file=assert(io.open(root..'main.lua'));local source=file:read('*a');file:close();assert(loadstring(source))
local first=assert(string.find(source,' local prediction =',1,true))
+4 -4
View File
@@ -14,15 +14,15 @@ local function text()
return {ClearAllPoints=function() end,SetJustifyH=function(self,v) self.justify=v end,
SetPoint=function(self,...) self.point=arg end}
end
for _,standalone in ipairs({0,1}) do
ShaguTweaks_config={['Standalone Player Frame']=standalone}
for _,displayEnabled in ipairs({0,1}) do
ShaguTweaks_config={['Personal Resource Display']=displayEnabled}
for _,h in ipairs({12,30}) do
height=h;local py,ty=y()
assert(py==((standalone==1 or h==12) and .5 or -7) and ty==-7)
assert(py==((h==12) and .5 or -7) and ty==-7)
local value=text();value.percentText=text()
anchor(value,PlayerFrameHealthBar,py)
assert(value.point[1]=='RIGHT' and value.point[4]==-4 and value.point[5]==py)
assert(value.percentText.point[1]=='LEFT' and value.percentText.point[4]==4 and value.percentText.point[5]==py)
end
end
print('Standalone health numbers: vertically centered, left/right insets retained, grouped/target offsets unchanged')
print('Native health numbers: grouped alignment retained with independent resource display')
@@ -49,15 +49,13 @@ local function Check(big,timer,hidden,hideName,maskAvailable)
function methods:Hide() self.visible=false end
function methods:Show() self.visible=true end
CreateFrame=new
local keys=false
ShaguTweaks={T=setmetatable({},{__index=function(t,k) return k end}),API={timer=timer}}
function ShaguTweaks.GetGlobalEnv() return _G end
dofile(root..'api.lua')
ShaguTweaks.API.timer=timer
local keys=false
ShaguTweaks.API.AreMoveModifiersDown=function() return keys end
function ShaguTweaks:register(module) modules[module.title]=module;return module end
ShaguTweaks.API.IsShiftKeyDown=function() return keys end
ShaguTweaks.API.IsControlKeyDown=ShaguTweaks.API.IsShiftKeyDown
ShaguTweaks.API.IsAltKeyDown=ShaguTweaks.API.IsShiftKeyDown
function ShaguTweaks.API.Defer(callback) table.insert(pending,callback) end
function ShaguTweaks.hooksecurefunc(object,name,callback)
if type(object)=='string' then callback=name;name=object;object=_G end
@@ -76,6 +74,7 @@ local function Check(big,timer,hidden,hideName,maskAvailable)
PlayerLevelText=new('FontString','PlayerLevelText',PlayerFrame)
PlayerLevelText.centerX=117;PlayerLevelText.centerY=496
PlayerFrame.name=new('FontString','PlayerName',PlayerFrame)
PlayerFrameGroupIndicator=new('Frame','PlayerFrameGroupIndicator',PlayerFrame)
PlayerPortrait=new('Texture','PlayerPortrait',PlayerFrame);PlayerPortrait.alpha=hidden and 0 or 1
PlayerRestIcon=new('Texture','PlayerRestIcon',PlayerFrame);PlayerRestIcon.width=31
PlayerRestIcon.left=101;PlayerRestIcon.top=513
@@ -93,27 +92,45 @@ local function Check(big,timer,hidden,hideName,maskAvailable)
PlayerFrameHealthBar:SetScript('OnEvent',healthEvent)
local numbers=new('FontString',nil,PlayerFrameHealthBar)
PlayerStatusTexture=new('Texture','PlayerStatusTexture',PlayerFrame)
PlayerFrameDropDown={}
PlayerFrame_OnReceiveDrag=noop
PlayerFrame_UpdateStatus=noop
local clicked,menu
function PlayerFrame_OnClick(button) clicked=button end
function SpellIsTargeting() return false end
function ToggleDropDownMenu(a,b,c,anchor) menu=anchor end
ShaguTweaks_config={['Standalone Player Frame']=1,['Hide Player Name']=hideName and 1 or 0,['Unit Frame Big Health for Player']=big and 1 or 0,StandalonePlayerFrame={Health={x=420,y=520}}}
local clicked,clickFrame
PlayerFrame_OnClick=function(button) clicked=button;clickFrame=this end
ShaguTweaks_config={['Hide Player Frame Resources']=1,['Hide Player Name']=hideName and 1 or 0,['Unit Frame Big Health for Player']=big and 1 or 0,
PlayerFrameResources=big and {Name={x=420,y=520}} or nil}
local originalHealth,originalMana,originalPortrait=PlayerFrameHealthBar,PlayerFrameManaBar,PlayerPortrait
dofile(root..'mods/unitframes-hide-player-name.lua')
modules['Hide Player Name']:enable()
dofile(root..'mods/unitframes-standalone-player.lua')
assert(not ShaguTweaks.StandalonePlayerFrame)
modules['Standalone Player Frame']:enable()
if hideName then modules['Hide Player Name']:enable() end
dofile(root..'mods/unitframes-hide-player-resources.lua')
assert(not ShaguTweaks.PlayerFrameResourcesLayout)
modules['Hide Player Frame Resources']:enable()
local ready=frames[table.getn(frames)]
fire(ready,'OnEvent');fire(ready,'OnUpdate')
if timer then assert(table.getn(pending)==1);pending[1]() else fire(ready,'OnUpdate') end
local layout=ShaguTweaks.StandalonePlayerFrame
local layout=ShaguTweaks.PlayerFrameResourcesLayout
assert(layout.name.visible==(not hideName))
assert(layout.health.backdrop.edgeFile=='Interface\\Tooltips\\UI-Tooltip-Border')
assert(not layout.health.art and not layout.power.art)
assert(PlayerFrameGroupIndicator.alpha==(hideName and 0 or 1))
PlayerFrameGroupIndicator:Show();PlayerFrameGroupIndicator:SetAlpha(1)
assert(PlayerFrameGroupIndicator.alpha==(hideName and 0 or 1))
if not hideName then
assert(layout.name.points[2]==(big and UIParent or PlayerFrame))
if big then assert(layout.name.points[4]==420 and layout.name.points[5]==520) end
fire(layout.name,'OnDragStart');assert(not layout.name.moving)
fire(layout.name,'OnMouseUp','LeftButton');assert(clicked=='LeftButton' and clickFrame==PlayerFrame)
keys=true
local portraitPoint=layout.portrait.points
fire(layout.name,'OnDragStart');assert(layout.name.moving)
layout.name.left=300;layout.name.top=400
fire(layout.name,'OnDragStop');assert(not layout.name.moving)
assert(ShaguTweaks_config.PlayerFrameResources.Name.x==300 and layout.name.points[2]==UIParent)
assert(layout.portrait.points==portraitPoint)
fire(layout.name,'OnMouseUp','RightButton')
assert(not ShaguTweaks_config.PlayerFrameResources.Name and layout.name.points[2]==PlayerFrame)
fire(layout.name,'OnDragStart');layout.name.left=350;layout.name.top=450
fire(layout.name,'OnHide');assert(not layout.name.moving and ShaguTweaks_config.PlayerFrameResources.Name.x==350)
fire(layout.name,'OnMouseUp','RightButton');keys=false
fire(layout.name,'OnMouseUp','RightButton');assert(clicked=='RightButton' and clickFrame==PlayerFrame)
assert(not layout.name:GetScript('OnUpdate'))
end
assert(layout.portrait.art.width==40 and layout.portrait.art.height==(maskAvailable==false and 72 or 36) and layout.portrait.artRight.height==36)
assert(layout.portrait.artBottomRight.height==36)
if maskAvailable==false then
@@ -135,24 +152,17 @@ local function Check(big,timer,hidden,hideName,maskAvailable)
assert(layout.name.classBackground.color[1]==.53 and layout.name.classBackground.color[3]==.93)
assert(PlayerFrameNameBackground.alpha==0 and layout.name.classBackground.visible)
assert(layout and originalHealth==PlayerFrameHealthBar and originalMana==PlayerFrameManaBar and originalPortrait==PlayerPortrait)
assert(PlayerFrameHealthBar:GetParent()==layout.health and PlayerFrameManaBar:GetParent()==layout.power)
assert(layout.power.unit=="player" and layout.power.manabar==PlayerFrameManaBar)
assert(PlayerFrameHealthBar:GetParent()==PlayerFrame and PlayerFrameManaBar:GetParent()==PlayerFrame)
assert(PlayerPortrait:GetParent()==layout.portrait and PlayerPortrait.alpha==(hidden and 0 or 1))
assert(numbers:GetParent()==PlayerFrameHealthBar and PlayerFrameHealthBar:GetScript('OnEvent')==healthEvent)
assert(PlayerFrame.visible and PlayerFrame.mouse==false and PlayerFrameTexture.alpha==0)
assert(layout.health.left==420 and layout.health.top==520)
assert(PlayerFrameManaBar.height==(big and 30 or 12))
assert(PlayerFrame.visible and PlayerFrame.mouse~=false and PlayerFrameTexture.alpha==0)
assert(PlayerFrameHealthBar.alpha==0 and PlayerFrameManaBar.alpha==0)
assert(PlayerFrameManaBar.height==12 and PlayerFrameHealthBar.height==(big and 30 or 12))
assert(layout.portrait:GetParent()==PlayerFrame and layout.name:GetParent()==PlayerFrame)
assert(not ready:GetScript('OnUpdate'))
fire(layout.name,'OnClick','LeftButton');assert(clicked=='LeftButton')
fire(PlayerFrameHealthBar,'OnMouseUp','RightButton');assert(menu==layout.health:GetName())
keys=true
fire(PlayerFrameManaBar,'OnDragStart');assert(layout.power.moving)
layout.power.left=300;layout.power.top=400
fire(PlayerFrameManaBar,'OnDragStop');assert(not layout.power.moving and ShaguTweaks_config.StandalonePlayerFrame.Power.x==300)
fire(layout.power,'OnMouseDown','RightButton');assert(not ShaguTweaks_config.StandalonePlayerFrame.Power)
assert(layout.power.left==layout.power.defaultPosition.x)
PlayerFrameHealthBar:SetAlpha(1);assert(PlayerFrameHealthBar.alpha==0)
PlayerFrameTexture:SetAlpha(1);assert(PlayerFrameTexture.alpha==0)
PlayerFrame_UpdateStatus();assert(PlayerStatusTexture.width==72)
end
Check(false,true,false,false);Check(true,true,true,true);Check(false,false,false,true);Check(true,false,false,false);Check(false,true,false,false,false)
print('Standalone player: native object/event/text retention, big power sizing, portrait hiding, saved positions, drag/reset, click handling and idle initialization passed')
print('Hide resources: native bars/events retained, portrait/mask fallback, independent saved name movement/reset/clicks, hidden drag cleanup and raid group hiding passed')
+15
View File
@@ -14,6 +14,8 @@ function methods:GetHeight() return self.height or 900 end
function methods:GetScale() return 1 end
function methods:SetChecked(v) self.checked=v end
function methods:GetChecked() return self.checked end
function methods:Enable() self.enabled=true end
function methods:Disable() self.enabled=false end
function methods:SetValue(v) self.value=v; if self.scripts.OnValueChanged then local old=this; this=self; self.scripts.OnValueChanged(); this=old end end
function methods:GetValue() return self.value end
function methods:CreateFontString() return CreateFrame() end
@@ -43,6 +45,9 @@ local function add(title,category,description,sliders)
ShaguTweaks.mods[title]={title=title,category=category,description=description,sliders=sliders,expansions={vanilla=true}}
end
add('Golden UI','Interface','Colors borders gold')
add('Hide Player Frame Resources','Unit Frames','Hides native resources')
add('Hide Player Name','Unit Frames','Hides native name')
ShaguTweaks.mods['Hide Player Name'].requires={'Hide Player Frame Resources'}
add('Item Rarity Borders','Tooltip & Items','Colors mail and trade items')
add('Range Color','Action Bar','Shows distance',{{key='range.delay',label='Update interval',min=1,max=10,default=3}})
for i=1,40 do add('Other '..i,'Other','Another feature') end
@@ -51,6 +56,16 @@ dofile(root..'config.lua')
local gui=AdvancedSettingsGUI
this=gui; gui.scripts.OnShow(); this=nil
assert(gui.scrollframe.visible)
local hideName=gui.entries['Hide Player Name']
local hideResources=gui.entries['Hide Player Frame Resources']
assert(not hideName.enabled)
hideName:SetChecked(true);this=hideName;hideName.scripts.OnClick();assert(not hideName.checked)
hideResources:SetChecked(true);this=hideResources;hideResources.scripts.OnClick();assert(hideName.enabled)
hideName:SetChecked(true);this=hideName;hideName.scripts.OnClick()
gui.search:SetText('Hide Player Name');assert(hideName.enabled and hideName.checked)
gui.search:SetText('')
hideResources:SetChecked(false);this=hideResources;hideResources.scripts.OnClick()
assert(not hideName.enabled and hideName.checked) -- retain dependent choice while dormant
local firstCount=made
local checkbox=gui.entries['Golden UI']; checkbox:SetChecked(true); this=checkbox; checkbox.scripts.OnClick(); this=nil
-- Collapse a category before searching: matches must still be visible.
+179
View File
@@ -0,0 +1,179 @@
local root=(arg[1] or '.') .. '/'
local function Check(big,timer,hideResources)
local modules,frames,pending={},{},{}
local methods={}
local function noop() end
local function new(kind,name,parent)
local object=setmetatable({kind=kind or 'Frame',name=name,parent=parent,scripts={},width=119,height=12,left=106,top=550,scale=1,alpha=1,visible=true},{__index=function(t,k) if methods[k] then return methods[k] elseif string.find(k,'^[A-Z]') then return noop end end})
table.insert(frames,object); if name then _G[name]=object end; return object
end
function methods:GetName() return self.name end
function methods:GetObjectType() return self.kind end
function methods:SetParent(parent) self.parent=parent end
function methods:GetParent() return self.parent end
function methods:SetWidth(w) self.width=w end
function methods:SetHeight(h) self.height=h end
function methods:GetWidth() return self.width end
function methods:GetHeight() return self.height end
function methods:GetLeft() return self.left end
function methods:GetTop() return self.top end
function methods:GetCenter() return self.centerX,self.centerY end
function methods:ClearAllPoints() self.points={} end
function methods:SetPoint(p,relative,rp,x,y) self.points={p,relative,rp,x,y}; if rp=='BOTTOMLEFT' then self.left=x;self.top=y end end
function methods:SetAllPoints(relative) self.allPoints=relative end
function methods:SetScale(scale) self.scale=scale end
function methods:GetEffectiveScale() return self.scale end
function methods:SetFrameLevel(level) self.level=level end
function methods:GetFrameLevel() return self.level or 1 end
function methods:GetFrameStrata() return 'BACKGROUND' end
function methods:SetScript(script,callback) self.scripts[script]=callback end
function methods:GetScript(script) return self.scripts[script] end
function methods:CreateTexture() return new('Texture',nil,self) end
function methods:SetMask(path) self.mask=path end
function methods:SetTexture(path) self.path=path end
function methods:SetBackdrop(backdrop) self.backdrop=backdrop end
function methods:GetTexture() return self.path end
function methods:GetStatusBarTexture() return {GetTexture=function() return 'NativeHealthTexture' end} end
function methods:GetStatusBarColor() return unpack(self.barColor or {.53,.53,.93,1}) end
function methods:SetText(text) self.text=text end
function methods:SetTexCoord(a,b,c,d) self.uv={a,b,c,d} end
function methods:GetTexCoord() return 1,0,1,.78125,.09375,0,.09375,.78125 end
function methods:SetVertexColor(r,g,b,a) self.color={r,g,b,a} end
function methods:GetVertexColor() return 1,.78,.4,1 end
function methods:SetDrawLayer(layer) self.layer=layer end
function methods:SetAlpha(alpha) self.alpha=alpha end
function methods:EnableMouse(enabled) self.mouse=enabled end
function methods:StartMoving() self.moving=true end
function methods:StopMovingOrSizing() self.moving=false;self.sizing=false end
function methods:Hide() self.visible=false end
function methods:Show() self.visible=true end
CreateFrame=new
local keys=false
ShaguTweaks={T=setmetatable({},{__index=function(t,k) return k end}),API={timer=timer}}
function ShaguTweaks.GetGlobalEnv() return _G end
dofile(root..'api.lua')
ShaguTweaks.API.modifierstate=timer
ShaguTweaks.API.timer=timer
function ShaguTweaks:register(module) modules[module.title]=module;return module end
ShaguTweaks.API.IsShiftKeyDown=function() return keys end
ShaguTweaks.API.IsControlKeyDown=ShaguTweaks.API.IsShiftKeyDown
ShaguTweaks.API.IsAltKeyDown=ShaguTweaks.API.IsShiftKeyDown
function ShaguTweaks.API.Defer(callback) table.insert(pending,callback) end
function ShaguTweaks.hooksecurefunc(object,name,callback)
if type(object)=='string' then callback=name;name=object;object=_G end
local original=object[name] or noop
object[name]=function(a,b,c,d) original(a,b,c,d);callback(a,b,c,d) end
end
local oldThis,oldArg
local function fire(frame,script,button)
oldThis,oldArg=this,arg1;this,arg1=frame,button; local callback=frame:GetScript(script); if callback then callback() end; this,arg1=oldThis,oldArg
end
UIParent=new('Frame','UIParent');PlayerFrame=new('Button','PlayerFrame',UIParent)
PlayerFrame.width=232;PlayerFrame.height=100
function UnitClass() return 'Warlock','WARLOCK' end
function UnitLevel() return 60 end
ShaguTweaks.ModernClassColors={WARLOCK={r=.53,g=.53,b=.93}}
PlayerLevelText=new('FontString','PlayerLevelText',PlayerFrame)
PlayerLevelText.centerX=117;PlayerLevelText.centerY=496
PlayerFrame.name=new('FontString','PlayerName',PlayerFrame)
PlayerPortrait=new('Texture','PlayerPortrait',PlayerFrame)
PlayerRestIcon=new('Texture','PlayerRestIcon',PlayerFrame);PlayerRestIcon.width=31
PlayerRestIcon.left=101;PlayerRestIcon.top=513
PlayerStatusGlow=new('Frame','PlayerStatusGlow',PlayerFrame);PlayerStatusGlow.width=32
PlayerStatusGlow.left=101;PlayerStatusGlow.top=513
PlayerFrameTexture=new('Texture','PlayerFrameTexture',PlayerFrame);PlayerFrameTexture.path=big and 'BigHealthArt' or 'StockArt'
PlayerFrameBackground=new('Texture','PlayerFrameBackground',PlayerFrame)
PlayerFrameNameBackground=new('Texture','PlayerFrameNameBackground',PlayerFrame)
PlayerFrameHealthBar=new('StatusBar','PlayerFrameHealthBar',PlayerFrame)
PlayerFrameManaBar=new('StatusBar','PlayerFrameManaBar',PlayerFrame)
PlayerFrameHealthBar.height=big and 30 or 12
PlayerFrameHealthBar.TextString=new('FontString',nil,PlayerFrame)
PlayerFrameManaBar.TextString=new('FontString',nil,PlayerFrame)
local healthEvent=function() end
PlayerFrameHealthBar:SetScript('OnEvent',healthEvent)
local numbers=new('FontString',nil,PlayerFrameHealthBar)
PlayerStatusTexture=new('Texture','PlayerStatusTexture',PlayerFrame)
PlayerFrame_UpdateStatus=noop
function methods:CreateFontString() return new('FontString',nil,self) end
function methods:SetMinMaxValues(a,b) self.minimum=a;self.maximum=b end
function methods:GetMinMaxValues() return self.minimum or 0,self.maximum or 100 end
function methods:SetValue(v) self.value=v end
function methods:GetValue() return self.value or 75 end
function methods:SetStatusBarColor(r,g,b,a) self.barColor={r,g,b,a} end
function methods:StartSizing(direction) self.sizing=direction end
ShaguTweaks.Abbreviate=function(v) return tostring(v) end
ShaguTweaks_config={['Hide Player Frame Resources']=hideResources and 1 or 0,
['Unit Frame Big Health for Player']=big and 1 or 0,
PersonalResourceDisplay={Health={x=420,y=520,width=180,height=40}}}
local nativeHeight=PlayerFrameHealthBar:GetHeight()
if hideResources then
dofile(root..'mods/unitframes-hide-player-resources.lua')
modules['Hide Player Frame Resources']:enable()
local ready=frames[table.getn(frames)]
fire(ready,'OnEvent');fire(ready,'OnUpdate')
if timer then pending[1]();pending={} else fire(ready,'OnUpdate') end
end
local parent=PlayerFrameHealthBar:GetParent()
local alpha=PlayerFrameHealthBar.alpha
local healthPoints=PlayerFrameHealthBar.points
local nameParent=PlayerFrame.name:GetParent()
dofile(root..'mods/unitframes-personal-resource-display.lua')
modules['Personal Resource Display']:enable()
local ready=frames[table.getn(frames)]
fire(ready,'OnEvent');fire(ready,'OnUpdate')
if timer then pending[1]() else fire(ready,'OnUpdate') end
local display=ShaguTweaks.PersonalResourceDisplay
assert(display and display.health.bar~=PlayerFrameHealthBar)
assert(PlayerFrameHealthBar:GetParent()==parent and PlayerFrameHealthBar.alpha==alpha)
assert(PlayerFrameHealthBar.points==healthPoints and PlayerFrameHealthBar:GetHeight()==nativeHeight)
assert(PlayerFrameHealthBar:GetScript('OnEvent')==healthEvent and PlayerFrame.name:GetParent()==nameParent)
assert(PlayerFrame.visible and PlayerFrame.mouse~=false)
assert(display.health:GetParent()==UIParent and display.power:GetParent()==UIParent)
assert(display.health.width==180 and display.health.height==40 and display.health.left==420)
assert(display.power.height==(big and 36 or 18))
assert(display.health.bar.value==75 and display.health.bar.maximum==100)
PlayerFrameHealthBar:SetMinMaxValues(0,1000);PlayerFrameHealthBar:SetValue(400)
assert(display.health.bar.value==400 and display.health.bar.maximum==1000)
PlayerFrameHealthBar:SetStatusBarColor(.2,.3,.4,0)
assert(display.health.bar.barColor[1]==.2 and display.health.bar.barColor[4]==1)
local events=modules['Personal Resource Display'].events
assert((events:GetScript('OnUpdate')==nil)==timer)
assert(not display.health.mouse and not display.health.resourceState.grip.visible)
keys=true
if timer then fire(events,'OnEvent') else fire(events,'OnUpdate',.1) end
assert(display.health.mouse and display.health.resourceState.grip.visible)
fire(display.health,'OnDragStart');assert(display.health.moving)
display.health.left=300;display.health.top=400
fire(display.health,'OnDragStop')
assert(ShaguTweaks_config.PersonalResourceDisplay.Health.x==300)
local grip=display.health.resourceState.grip
fire(grip,'OnDragStart');assert(display.health.sizing=='BOTTOMRIGHT')
display.health:SetWidth(220);display.health:SetHeight(48)
fire(display.health,'OnSizeChanged')
assert(display.health.bar.width==212 and display.health.bar.height==42)
keys=false
if timer then fire(events,'OnEvent') else fire(events,'OnUpdate',.1) end
assert(not display.health.sizing and ShaguTweaks_config.PersonalResourceDisplay.Health.width==220)
keys=true
fire(display.health,'OnMouseDown','RightButton')
assert(not ShaguTweaks_config.PersonalResourceDisplay.Health)
assert(display.health.width==127 and display.health.height==nativeHeight+6)
assert(not ready:GetScript('OnUpdate'))
end
Check(false,true,false);Check(true,true,false);Check(false,true,true);Check(true,true,true)
Check(false,false,false);Check(true,false,true)
-- Migration preserves explicit new choices and old health/power positions.
local f=assert(io.open(root..'main.lua'));local s=f:read('*a');f:close()
local a=assert(string.find(s,' -- Replace detached native bars',1,true))
local b=assert(string.find(s,' -- initialize overwrites',a,true))
local migrate=assert(loadstring(string.sub(s,a,b-1)))
ShaguTweaks_config={['Standalone Player Frame']=1,StandalonePlayerFrame={Health={x=10,y=20}}}
migrate()
assert(ShaguTweaks_config['Personal Resource Display']==1 and not ShaguTweaks_config['Hide Player Frame Resources'])
assert(ShaguTweaks_config.PersonalResourceDisplay.Health.x==10 and not ShaguTweaks_config.StandalonePlayerFrame)
ShaguTweaks_config={['Standalone Player Frame']=1,['Personal Resource Display']=0,
PersonalResourceDisplay={Health={x=30}},StandalonePlayerFrame={Health={x=10},Power={x=40}}}
migrate()
assert(ShaguTweaks_config['Personal Resource Display']==0 and ShaguTweaks_config.PersonalResourceDisplay.Health.x==30)
assert(ShaguTweaks_config.PersonalResourceDisplay.Power.x==40)
print('Personal resources: independent native/copy layout combinations, source updates, resizing, drag/reset, modifier release, fallback and migration passed')
+19 -11
View File
@@ -8,12 +8,6 @@ function ShaguTweaks.hooksecurefunc(object,name,callback)
if type(object)~='table' then return end
local original=object[name]; object[name]=function(self,a,b,c,d) original(self,a,b,c,d); callback(self,a,b,c,d) end
end
PlayerPortrait={alpha=1}; function PlayerPortrait:SetAlpha(v) self.alpha=v end
local health={visible=true}; local mana={visible=true}
dofile(root..'mods/unitframes-hide-player-portrait.lua')
modules['Hide Player Portrait']:enable()
assert(PlayerPortrait.alpha==0 and health.visible and mana.visible)
PlayerPortrait:SetAlpha(1); assert(PlayerPortrait.alpha==0)
MAX_PARTY_MEMBERS=0
local deferred
function CreateFrame() return {SetScript=function(self,event,callback) if callback then deferred={frame=self,callback=callback} end end,Hide=function() end} end
@@ -29,13 +23,27 @@ modules['Modern Class Colors']:enable()
this=deferred.frame; deferred.callback(); this=nil
local expected='Interface\\AddOns\\ShaguTweaks\\img\\BlizzardRetailBar'
assert(PlayerFrameNameBackground.path==expected and TargetFrameNameBackground.path==expected and PlayerFrameHealthBar.path==expected)
print('Lua 5.0: portrait-only alpha lock and matching modern name/health artwork passed')
print('Lua 5.0: matching modern name/health artwork passed')
-- Hiding the player name never affects the grouped player frame.
dofile(root..'mods/unitframes-hide-player-name.lua')
local nameHidden=false
ShaguTweaks.StandalonePlayerFrame={name={Hide=function() nameHidden=true end}}
ShaguTweaks_config={['Standalone Player Frame']=0}
ShaguTweaks.PlayerFrameResourcesLayout={name={Hide=function() nameHidden=true end}}
ShaguTweaks_config={['Hide Player Frame Resources']=0}
modules['Hide Player Name']:enable();assert(not nameHidden)
ShaguTweaks_config['Standalone Player Frame']=1
ShaguTweaks_config['Hide Player Frame Resources']=1
modules['Hide Player Name']:enable();assert(nameHidden)
print('Hide Player Name: standalone-only guard passed')
print('Hide Player Name: hide-resources guard passed')
-- Removed options must not be loaded or retain saved switches.
local file=assert(io.open(root..'ShaguTweaks.toc'));local toc=file:read('*a');file:close()
assert(not string.find(toc,'unitframes-hide-player-portrait.lua',1,true))
assert(not string.find(toc,'unitframes-standalone-player.lua',1,true))
file=assert(io.open(root..'main.lua'));local source=file:read('*a');file:close()
local first=assert(string.find(source,' -- Gold numbers belong',1,true))
local last=assert(string.find(source,' -- Split the former shared setting',first,true))
local retire=assert(loadstring(string.sub(source,first,last-1)))
ShaguTweaks_config={['Hide Player Portrait']=1,['Unit Frame Health Colors']=1,
['Hide Player Frame Resources']=1,['Personal Resource Display']=1}
retire()
assert(not ShaguTweaks_config['Hide Player Portrait'] and not ShaguTweaks_config['Unit Frame Health Colors'])
assert(ShaguTweaks_config['Hide Player Frame Resources']==1 and ShaguTweaks_config['Personal Resource Display']==1)
print('Removed player options: absent from manifest and saved toggles retired without changing current options')