diff --git a/README.md b/README.md index 349eaba..8cc92a2 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Both skins ship together. Use `/loadskin spinui_glass 1` for Midnight Frost or ` - **One centered eye-line.** Player, target, casting, stance, spell gems, and actions share a bottom-anchored combat band instead of competing with the center of the world. - **Information has a color grammar.** HP is red, mana is blue, endurance and XP are brass gold, and AA is spirit blue. XP and AA keep persistent tick marks while their fills remain correctly scaled to the displayed percentage. - **Native controls stay native.** Hotbuttons, spell gems, pet commands, drag/drop, tooltips, loadouts, and client-driven usable/unusable states retain their original bindings. -- **Attack state is unmistakable.** Both themes place a thick, outline-only pure-red rail directly around the normal player command frame. EverQuest's native auto-attack state controls its flashing, the interior remains completely untouched, and every PlayerWindow display type retains the full working bar. +- **Attack state is unmistakable.** Both themes place a thick, outline-only rail directly around the normal player command frame. EverQuest natively tints it from neutral to vivid red to create the flash, the interior remains completely untouched, and every PlayerWindow display type retains the full working bar. - **The spell deck adapts to the player.** Its default remains the compact icon-only deck; choose **Display Types -> SpinUI Spell Ledger** for a resizable alternate with every icon, live spell name, and memorized slot number. - **Extended targets use the space you give them.** XTAR keeps its familiar single-column default, then flows all 23 Legends targets into additional complete columns as the window is widened—without losing role, aggro, cast, HP, mana, or endurance data. - **Effects read through the artwork.** Spell, song, and pet-effect durations sit directly on their icons in shadowed ember gold instead of growing into opaque timer slabs. diff --git a/spinui_glass/AttackIndicator.tga b/spinui_glass/AttackIndicator.tga index daa6716..3419747 100644 Binary files a/spinui_glass/AttackIndicator.tga and b/spinui_glass/AttackIndicator.tga differ diff --git a/spinui_glass/EQUI_AAWindow.xml b/spinui_glass/EQUI_AAWindow.xml index 53f24a9..b31297e 100644 --- a/spinui_glass/EQUI_AAWindow.xml +++ b/spinui_glass/EQUI_AAWindow.xml @@ -1,13 +1,97 @@ + + + AAW_ProgressPanel + true + + 520 + 2 + + + 150 + 174 + + false + WDT_InsetStone + false + false + false + true + false + + + + AAW_FilterPanel + true + + 520 + 180 + + + 150 + 196 + + false + WDT_InsetStone + false + false + false + true + false + + @@ -1052,11 +1137,12 @@ 220 220 - true + false 150 A_GaugeBackground A_GaugeFill + A_GaugeLines A_GaugeLinesFill A_GaugeEndCapLeft A_GaugeEndCapRight @@ -1289,8 +1375,12 @@ 457 670 900 + AAW_ProgressPanel + AAW_FilterPanel AAW_Subwindows AAW_DescriptionWindows + AAW_ProgressPanelTitle + AAW_FilterPanelTitle AAW_AssignedLabel AAW_AssignedCount AAW_TotalLabel diff --git a/spinui_glass/EQUI_InventoryWindow1.xml b/spinui_glass/EQUI_InventoryWindow1.xml index 4d47c1c..eebf5ee 100644 --- a/spinui_glass/EQUI_InventoryWindow1.xml +++ b/spinui_glass/EQUI_InventoryWindow1.xml @@ -9122,9 +9122,9 @@ true 0 - 220 - 150 - 0 + 208 + 162 + 84 0 @@ -9209,9 +9209,9 @@ true 0 - 51 - 150 - 51 + 126 + 170 + 244 0 diff --git a/spinui_glass/EQUI_InventoryWindow2.xml b/spinui_glass/EQUI_InventoryWindow2.xml index 3e52b3f..3dedbf8 100644 --- a/spinui_glass/EQUI_InventoryWindow2.xml +++ b/spinui_glass/EQUI_InventoryWindow2.xml @@ -1082,9 +1082,9 @@ 0 - 220 - 150 - 0 + 208 + 162 + 84 0 @@ -1163,9 +1163,9 @@ 0 - 51 - 150 - 51 + 126 + 170 + 244 0 diff --git a/spinui_glass/EQUI_InventoryWindow3.xml b/spinui_glass/EQUI_InventoryWindow3.xml index d6943b7..b905a45 100644 --- a/spinui_glass/EQUI_InventoryWindow3.xml +++ b/spinui_glass/EQUI_InventoryWindow3.xml @@ -9187,9 +9187,9 @@ true 0 - 220 - 150 - 0 + 208 + 162 + 84 0 @@ -9274,9 +9274,9 @@ true 0 - 51 - 150 - 51 + 126 + 170 + 244 0 diff --git a/spinui_glass/EQUI_PlayerWindow.xml b/spinui_glass/EQUI_PlayerWindow.xml index 7affc04..60451f4 100644 --- a/spinui_glass/EQUI_PlayerWindow.xml +++ b/spinui_glass/EQUI_PlayerWindow.xml @@ -160,7 +160,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_glass/EQUI_PlayerWindow1.xml b/spinui_glass/EQUI_PlayerWindow1.xml index 86e728c..e9ef14b 100644 --- a/spinui_glass/EQUI_PlayerWindow1.xml +++ b/spinui_glass/EQUI_PlayerWindow1.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_glass/EQUI_PlayerWindow2.xml b/spinui_glass/EQUI_PlayerWindow2.xml index 86e728c..e9ef14b 100644 --- a/spinui_glass/EQUI_PlayerWindow2.xml +++ b/spinui_glass/EQUI_PlayerWindow2.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_glass/EQUI_PlayerWindow3.xml b/spinui_glass/EQUI_PlayerWindow3.xml index 86e728c..e9ef14b 100644 --- a/spinui_glass/EQUI_PlayerWindow3.xml +++ b/spinui_glass/EQUI_PlayerWindow3.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_glass/EQUI_PlayerWindow4.xml b/spinui_glass/EQUI_PlayerWindow4.xml index 86e728c..e9ef14b 100644 --- a/spinui_glass/EQUI_PlayerWindow4.xml +++ b/spinui_glass/EQUI_PlayerWindow4.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_glass/EQUI_PlayerWindow5.xml b/spinui_glass/EQUI_PlayerWindow5.xml index 86e728c..e9ef14b 100644 --- a/spinui_glass/EQUI_PlayerWindow5.xml +++ b/spinui_glass/EQUI_PlayerWindow5.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_glass/EQUI_PlayerWindow6.xml b/spinui_glass/EQUI_PlayerWindow6.xml index 86e728c..e9ef14b 100644 --- a/spinui_glass/EQUI_PlayerWindow6.xml +++ b/spinui_glass/EQUI_PlayerWindow6.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_reloaded/AttackIndicator.tga b/spinui_reloaded/AttackIndicator.tga index daa6716..3419747 100644 Binary files a/spinui_reloaded/AttackIndicator.tga and b/spinui_reloaded/AttackIndicator.tga differ diff --git a/spinui_reloaded/EQUI_AAWindow.xml b/spinui_reloaded/EQUI_AAWindow.xml index 53f24a9..e439449 100644 --- a/spinui_reloaded/EQUI_AAWindow.xml +++ b/spinui_reloaded/EQUI_AAWindow.xml @@ -1,13 +1,97 @@ + + + AAW_ProgressPanel + true + + 520 + 2 + + + 150 + 174 + + false + WDT_InsetStone + false + false + false + true + false + + + + AAW_FilterPanel + true + + 520 + 180 + + + 150 + 196 + + false + WDT_InsetStone + false + false + false + true + false + + @@ -1052,11 +1137,12 @@ 220 220 - true + false 150 A_GaugeBackground A_GaugeFill + A_GaugeLines A_GaugeLinesFill A_GaugeEndCapLeft A_GaugeEndCapRight @@ -1289,8 +1375,12 @@ 457 670 900 + AAW_ProgressPanel + AAW_FilterPanel AAW_Subwindows AAW_DescriptionWindows + AAW_ProgressPanelTitle + AAW_FilterPanelTitle AAW_AssignedLabel AAW_AssignedCount AAW_TotalLabel diff --git a/spinui_reloaded/EQUI_InventoryWindow1.xml b/spinui_reloaded/EQUI_InventoryWindow1.xml index 4d47c1c..eebf5ee 100644 --- a/spinui_reloaded/EQUI_InventoryWindow1.xml +++ b/spinui_reloaded/EQUI_InventoryWindow1.xml @@ -9122,9 +9122,9 @@ true 0 - 220 - 150 - 0 + 208 + 162 + 84 0 @@ -9209,9 +9209,9 @@ true 0 - 51 - 150 - 51 + 126 + 170 + 244 0 diff --git a/spinui_reloaded/EQUI_InventoryWindow2.xml b/spinui_reloaded/EQUI_InventoryWindow2.xml index 3e52b3f..3dedbf8 100644 --- a/spinui_reloaded/EQUI_InventoryWindow2.xml +++ b/spinui_reloaded/EQUI_InventoryWindow2.xml @@ -1082,9 +1082,9 @@ 0 - 220 - 150 - 0 + 208 + 162 + 84 0 @@ -1163,9 +1163,9 @@ 0 - 51 - 150 - 51 + 126 + 170 + 244 0 diff --git a/spinui_reloaded/EQUI_InventoryWindow3.xml b/spinui_reloaded/EQUI_InventoryWindow3.xml index d6943b7..b905a45 100644 --- a/spinui_reloaded/EQUI_InventoryWindow3.xml +++ b/spinui_reloaded/EQUI_InventoryWindow3.xml @@ -9187,9 +9187,9 @@ true 0 - 220 - 150 - 0 + 208 + 162 + 84 0 @@ -9274,9 +9274,9 @@ true 0 - 51 - 150 - 51 + 126 + 170 + 244 0 diff --git a/spinui_reloaded/EQUI_PlayerWindow.xml b/spinui_reloaded/EQUI_PlayerWindow.xml index 117f02c..ec6a8f8 100644 --- a/spinui_reloaded/EQUI_PlayerWindow.xml +++ b/spinui_reloaded/EQUI_PlayerWindow.xml @@ -160,7 +160,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_reloaded/EQUI_PlayerWindow1.xml b/spinui_reloaded/EQUI_PlayerWindow1.xml index 118db15..1c7dc2c 100644 --- a/spinui_reloaded/EQUI_PlayerWindow1.xml +++ b/spinui_reloaded/EQUI_PlayerWindow1.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_reloaded/EQUI_PlayerWindow2.xml b/spinui_reloaded/EQUI_PlayerWindow2.xml index 118db15..1c7dc2c 100644 --- a/spinui_reloaded/EQUI_PlayerWindow2.xml +++ b/spinui_reloaded/EQUI_PlayerWindow2.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_reloaded/EQUI_PlayerWindow3.xml b/spinui_reloaded/EQUI_PlayerWindow3.xml index 118db15..1c7dc2c 100644 --- a/spinui_reloaded/EQUI_PlayerWindow3.xml +++ b/spinui_reloaded/EQUI_PlayerWindow3.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_reloaded/EQUI_PlayerWindow4.xml b/spinui_reloaded/EQUI_PlayerWindow4.xml index 118db15..1c7dc2c 100644 --- a/spinui_reloaded/EQUI_PlayerWindow4.xml +++ b/spinui_reloaded/EQUI_PlayerWindow4.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_reloaded/EQUI_PlayerWindow5.xml b/spinui_reloaded/EQUI_PlayerWindow5.xml index 118db15..1c7dc2c 100644 --- a/spinui_reloaded/EQUI_PlayerWindow5.xml +++ b/spinui_reloaded/EQUI_PlayerWindow5.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/spinui_reloaded/EQUI_PlayerWindow6.xml b/spinui_reloaded/EQUI_PlayerWindow6.xml index 118db15..1c7dc2c 100644 --- a/spinui_reloaded/EQUI_PlayerWindow6.xml +++ b/spinui_reloaded/EQUI_PlayerWindow6.xml @@ -162,7 +162,7 @@ false - + A_AttackIndicatorAnimFill diff --git a/tools/audit_combat_ui.py b/tools/audit_combat_ui.py index 524722f..0c27891 100644 --- a/tools/audit_combat_ui.py +++ b/tools/audit_combat_ui.py @@ -152,7 +152,7 @@ def audit_player_and_target() -> None: ATTACK_INDICATOR_COLOR, )] if colors != expected_colors: - fail("AttackIndicator.tga is no longer a solid opaque pure-red rail") + fail("AttackIndicator.tga is no longer a solid opaque neutral tint source") for name, expected in ( ("A_AttackIndicator", ATTACK_FRAME_SIZE), diff --git a/tools/audit_spinui.py b/tools/audit_spinui.py index a36fb71..e4fa739 100644 --- a/tools/audit_spinui.py +++ b/tools/audit_spinui.py @@ -638,6 +638,73 @@ def audit_inventory_progression() -> None: ) +def audit_aa_window() -> None: + """Protect the AA command deck's bindings, true progress, and hierarchy.""" + root = ET.parse(SKIN / "EQUI_AAWindow.xml").getroot() + for gauge_name, eq_type in ( + ("AAW_ExpGauge", 5), ("AAW_MercAAExpGauge", 150)): + gauge = item(root, "Gauge", gauge_name) + if child_int(gauge, "EQType") != eq_type: + fail(f"AA window {gauge_name} lost EQType {eq_type}") + if (gauge.findtext("DrawLinesFill") or "").strip().casefold() != "false": + fail( + f"AA window {gauge_name} must show total 0-100 progression " + "without the 20-percent sub-tick overlay" + ) + if (gauge.findtext("GaugeDrawTemplate/Lines") or "").strip() != "A_GaugeLines": + fail(f"AA window {gauge_name} lost its fixed progression ticks") + + window = item(root, "Screen", "AAWindow") + pieces = [(node.text or "").strip() for node in window.findall("Pieces")] + for required in ( + "AAW_ProgressPanel", "AAW_ProgressPanelTitle", + "AAW_FilterPanel", "AAW_FilterPanelTitle"): + if pieces.count(required) != 1: + fail(f"AA command deck must mount {required} exactly once") + for panel_name, location, size in ( + ("AAW_ProgressPanel", (520, 2), (150, 174)), + ("AAW_FilterPanel", (520, 180), (150, 196))): + panel = item(root, "Screen", panel_name) + actual_location = ( + child_int(panel, "Location/X"), child_int(panel, "Location/Y")) + actual_size = (child_int(panel, "Size/CX"), child_int(panel, "Size/CY")) + if actual_location != location or actual_size != size: + fail( + f"{panel_name} geometry changed: " + f"{actual_location} {actual_size}" + ) + + list_specs = { + "AAW_GeneralList": ((330, "Ability"), (62, "Rank"), (52, "Cost"), (58, "Reuse")), + "AAW_ArchList": ((330, "Ability"), (62, "Rank"), (52, "Cost"), (58, "Reuse")), + "AAW_ClassList": ((330, "Ability"), (62, "Rank"), (52, "Cost"), (58, "Reuse")), + "AAW_SpecialList": ((330, "Ability"), (62, "Rank"), (52, "Cost"), (58, "Reuse")), + "AAW_FocusList": ((330, "Ability"), (62, "Rank"), (52, "Cost"), (58, "Reuse")), + "AAW_MercAAList": ((380, "Ability"), (66, "Rank"), (56, "Cost")), + } + for list_name, expected in list_specs.items(): + listbox = item(root, "Listbox", list_name) + actual = tuple( + (child_int(column, "Width"), (column.findtext("Heading") or "").strip()) + for column in listbox.findall("Columns") + ) + if actual != expected: + fail(f"AA window {list_name} columns changed: {actual}") + + expected_tabs = { + "AAW_GeneralPage": "General", + "AAW_ArchetypePage": "Archetype", + "AAW_ClassPage": "Class", + "AAW_SpecialPage": "Special", + "AAW_FocusPage": "Focus", + "AAW_MercAAPage": "Merc", + } + for page_name, tab_text in expected_tabs.items(): + page = item(root, "Page", page_name) + if (page.findtext("TabText") or "").strip() != tab_text: + fail(f"AA category {page_name} no longer maps to {tab_text}") + + def audit_map_resizing() -> None: """Keep map resize bounds compatible with every shipped layout.""" default_minimum = (400, 400) @@ -1214,6 +1281,7 @@ def main() -> int: audit_pet_geometry() audit_map_resizing() audit_inventory_progression() + audit_aa_window() audit_inventory_geometry() print("SpinUI asset audit: ALL PASS") print(f" XML {len(xml_files)} | texture refs {len(texture_refs)} | " @@ -1227,7 +1295,7 @@ def main() -> int: f"fixed | commands 14 | effects {BUFF_CAPACITY[pet_default]} " f"icon cells | variants {len(WINDOW_SIZES)}" ) - print(" inventory 660x668 | equipment 23 | ledger 15/15 + 6/6 | footer 6 | persona 23 | bags 12") + print(" inventory 660x668 | equipment 23 | AA deck 6 tabs + true progress | ledger 15/15 + 6/6 | footer 6 | persona 23 | bags 12") return 0 diff --git a/tools/paint_attack_indicator.py b/tools/paint_attack_indicator.py index d4055c8..265747b 100644 --- a/tools/paint_attack_indicator.py +++ b/tools/paint_attack_indicator.py @@ -3,9 +3,11 @@ EverQuest owns the visibility and flashing cadence of the four ``A_AttackIndicatorAnim*`` edge widgets in ``EQUI_PlayerWindow.xml``. The -texture therefore remains a single, fully opaque pure-red frame just like the -working Modern UI contract. Only narrow edge slices are drawn by the XML; the -fill widget is intentionally unbound so player information is never tinted. +texture therefore remains a single, fully opaque neutral frame like the +working Modern UI contract. EverQuest modulates that neutral source from +white/gray to red; pre-coloring the source red destroys the visible flash +because every tint phase remains red. Only narrow edge slices are drawn by +the XML, and the fill widget is intentionally unbound. Run from the repository root: python tools/paint_attack_indicator.py @@ -23,7 +25,9 @@ FRAME_SIZE = (128, 32) SIZE = FRAME_SIZE EDGE_WIDTH = 5 -COLOR = (255, 0, 0, 255) +# Modern uses 189 gray. Full neutral white preserves the same native tint +# animation while raising its red peak to 255 for dark Reloaded/Glass frames. +COLOR = (255, 255, 255, 255) def render() -> Image.Image: @@ -34,8 +38,8 @@ def main() -> int: OUTPUT.parent.mkdir(parents=True, exist_ok=True) save_tga(render(), OUTPUT) print( - "AttackIndicator.tga: native pure-red attack rail painted | " - "EverQuest-controlled flash | no fill wash" + "AttackIndicator.tga: full-bright neutral attack rail painted | " + "EverQuest tint-driven red flash | no fill wash" ) return 0 diff --git a/tools/restyle_combat.py b/tools/restyle_combat.py index ce034e0..00a7c56 100644 --- a/tools/restyle_combat.py +++ b/tools/restyle_combat.py @@ -505,7 +505,7 @@ def attack_fill(_block: str) -> str: # Keep the stock symbol because the client expects it, but leave it # deliberately unbound. Only the four edge widgets may be drawn. return ( - '\t\n' + '\n' '\t\tA_AttackIndicatorAnimFill\n' '\t' ) @@ -513,6 +513,15 @@ def attack_fill(_block: str) -> str: text = change_item( text, "StaticAnimation", "A_AttackIndicatorAnimFill", attack_fill, ) + # change_item intentionally starts at the element's opening ``<`` and + # leaves its existing line prefix in place. Older generator runs returned + # an additional leading tab here, so normalize the one stock placeholder + # explicitly to keep repeated runs byte-stable. + text = re.sub( + r'(?m)^[ \t]*()', + r'\t\1', + text, + ) def root_style(block: str) -> str: block = set_container(block, "Size", CX=360, CY=193) @@ -1333,8 +1342,25 @@ def style_experience_gauges() -> None: path = SKIN / "EQUI_AAWindow.xml" text = path.read_text(encoding="ascii") - text = change_item(text, "Gauge", "AAW_ExpGauge", - lambda b: style_gauge(b, CYAN, CYAN_BRIGHT)) + text = change_item( + text, "Gauge", "AAW_ExpGauge", + lambda b: show_total_progression_ticks( + style_gauge(b, CYAN, CYAN_BRIGHT) + ), + ) + text = change_item( + text, "Gauge", "AAW_MercAAExpGauge", + show_total_progression_ticks, + ) + text = change_matching( + text, "Page", + r"AAW_(?:GeneralPage|ArchetypePage|ClassPage|SpecialPage|FocusPage|MercAAPage)", + lambda b: set_color(b, "TabTextActiveColor", GOLD_BRIGHT), + ) + text = change_matching( + text, "Page", r"AAW_DescriptionPage(?:Current|Next)", + lambda b: set_color(b, "TabTextActiveColor", CYAN_BRIGHT), + ) write_ascii(path, text)