diff --git a/CHANGELOG.md b/CHANGELOG.md index e7dce05..dd056b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/Docs/CLASSICAPI_UPDATES.md b/Docs/CLASSICAPI_UPDATES.md index c5488ea..069c86f 100644 --- a/Docs/CLASSICAPI_UPDATES.md +++ b/Docs/CLASSICAPI_UPDATES.md @@ -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). diff --git a/README.md b/README.md index bab6496..13eb60c 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/ShaguTweaks.toc b/ShaguTweaks.toc index 467697c..5aae787 100644 --- a/ShaguTweaks.toc +++ b/ShaguTweaks.toc @@ -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 diff --git a/compat-dragonflightui.lua b/compat-dragonflightui.lua index 866e9ac..3f63ed1 100644 --- a/compat-dragonflightui.lua +++ b/compat-dragonflightui.lua @@ -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. diff --git a/config.lua b/config.lua index 3a4d936..67e6779 100644 --- a/config.lua +++ b/config.lua @@ -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() diff --git a/main.lua b/main.lua index 4546eae..70e9d0d 100644 --- a/main.lua +++ b/main.lua @@ -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 {} diff --git a/mods/health-numbers.lua b/mods/health-numbers.lua index a5900a1..3647255 100644 --- a/mods/health-numbers.lua +++ b/mods/health-numbers.lua @@ -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) diff --git a/mods/move-unitframes.lua b/mods/move-unitframes.lua index dc685a3..3fd9aa6 100644 --- a/mods/move-unitframes.lua +++ b/mods/move-unitframes.lua @@ -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 diff --git a/mods/unitframes-bighealth.lua b/mods/unitframes-bighealth.lua index fef8e77..3422191 100644 --- a/mods/unitframes-bighealth.lua +++ b/mods/unitframes-bighealth.lua @@ -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 diff --git a/mods/unitframes-energy-tick.lua b/mods/unitframes-energy-tick.lua index 2cf3773..0ecf936 100644 --- a/mods/unitframes-energy-tick.lua +++ b/mods/unitframes-energy-tick.lua @@ -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 diff --git a/mods/unitframes-healing-prediction.lua b/mods/unitframes-healing-prediction.lua index 275a48a..0d1e13c 100644 --- a/mods/unitframes-healing-prediction.lua +++ b/mods/unitframes-healing-prediction.lua @@ -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") diff --git a/mods/unitframes-hide-player-name.lua b/mods/unitframes-hide-player-name.lua index 00918f5..f7c6aa7 100644 --- a/mods/unitframes-hide-player-name.lua +++ b/mods/unitframes-hide-player-name.lua @@ -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 diff --git a/mods/unitframes-hide-player-portrait.lua b/mods/unitframes-hide-player-portrait.lua deleted file mode 100644 index b49cac2..0000000 --- a/mods/unitframes-hide-player-portrait.lua +++ /dev/null @@ -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 diff --git a/mods/unitframes-standalone-player.lua b/mods/unitframes-hide-player-resources.lua similarity index 56% rename from mods/unitframes-standalone-player.lua rename to mods/unitframes-hide-player-resources.lua index e4ad8e7..88eee03 100644 --- a/mods/unitframes-standalone-player.lua +++ b/mods/unitframes-hide-player-resources.lua @@ -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 diff --git a/mods/unitframes-personal-resource-display.lua b/mods/unitframes-personal-resource-display.lua new file mode 100644 index 0000000..5064500 --- /dev/null +++ b/mods/unitframes-personal-resource-display.lua @@ -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 diff --git a/screenshots/golden-world-map.png b/screenshots/golden-world-map.png new file mode 100644 index 0000000..3ffa20a Binary files /dev/null and b/screenshots/golden-world-map.png differ diff --git a/screenshots/movable-bars-preview.png b/screenshots/movable-bars-preview.png new file mode 100644 index 0000000..a484e26 Binary files /dev/null and b/screenshots/movable-bars-preview.png differ diff --git a/screenshots/personal-resource-display.png b/screenshots/personal-resource-display.png new file mode 100644 index 0000000..edadcbf Binary files /dev/null and b/screenshots/personal-resource-display.png differ diff --git a/tests/README.md b/tests/README.md index 9ad48d1..c0c725f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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. diff --git a/tests/test_bighealth_split.lua b/tests/test_bighealth_split.lua index e5c64ec..b321fb3 100644 --- a/tests/test_bighealth_split.lua +++ b/tests/test_bighealth_split.lua @@ -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 diff --git a/tests/test_energy_tick.lua b/tests/test_energy_tick.lua index 575c049..0422fe5 100644 --- a/tests/test_energy_tick.lua +++ b/tests/test_energy_tick.lua @@ -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 diff --git a/tests/test_golden_unit_names.lua b/tests/test_golden_unit_names.lua index c669096..ea12e2a 100644 --- a/tests/test_golden_unit_names.lua +++ b/tests/test_golden_unit_names.lua @@ -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') diff --git a/tests/test_healing_prediction.lua b/tests/test_healing_prediction.lua index 9823e36..0afcaff 100644 --- a/tests/test_healing_prediction.lua +++ b/tests/test_healing_prediction.lua @@ -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)) diff --git a/tests/test_health_text_alignment.lua b/tests/test_health_text_alignment.lua index 8aec674..9acd9d0 100644 --- a/tests/test_health_text_alignment.lua +++ b/tests/test_health_text_alignment.lua @@ -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') diff --git a/tests/test_standalone_player.lua b/tests/test_hide_player_resources.lua similarity index 74% rename from tests/test_standalone_player.lua rename to tests/test_hide_player_resources.lua index 2b19df2..4872e6d 100644 --- a/tests/test_standalone_player.lua +++ b/tests/test_hide_player_resources.lua @@ -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') diff --git a/tests/test_options_search.lua b/tests/test_options_search.lua index a41a809..8ff865f 100644 --- a/tests/test_options_search.lua +++ b/tests/test_options_search.lua @@ -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. diff --git a/tests/test_personal_resource_display.lua b/tests/test_personal_resource_display.lua new file mode 100644 index 0000000..4c368cb --- /dev/null +++ b/tests/test_personal_resource_display.lua @@ -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') diff --git a/tests/test_player_frame_options.lua b/tests/test_player_frame_options.lua index d50a4f7..c2d4e71 100644 --- a/tests/test_player_frame_options.lua +++ b/tests/test_player_frame_options.lua @@ -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')