Skip to content

feat (ui): add a classic (0.97) theme - #711

Open
vguille wants to merge 45 commits into
sven-n:mainfrom
vguille:classic-theme
Open

vguille wants to merge 45 commits into
sven-n:mainfrom
vguille:classic-theme

Conversation

@vguille

@vguille vguille commented Oct 11, 2026 •

Copy link
Copy Markdown

Summary

As discussed in #610, this adds a classic (0.97) theme, on top of the current main (7a0a02a1f). The PR is the theme plus the data it binds: no behaviour changes in C++, nothing that only one theme turns on, and no window loses a feature in it.

Compared with the version first reviewed:

  • the classic login and character select scenes are out of this PR, and will come as a separate one;
  • the classic chat behaviour (0.97's F2 / F4 keys and the chat buttons, UI/Chat/ClassicChat), the "no move marker" hook and the classic [Capabilities] keys (ThemeCapability) are out of this PR, removed from every commit. They change how the game behaves, not how it looks, so they can come later as their own PR if you want them. The chat now runs on the stock keys with the 0.97 look;
  • the theme no longer restricts character classes: the creation arrows step through the classes C++ enables;
  • the theme no longer hides features the client has: where the Season 1 and 2 clients (which kept the classic interface) made a place for something, the theme draws it as they did (SD column, command window and friend buttons, quest / pet / personal shop buttons, the Dark Lord's pet commands);
  • the guild window shows everything the client has (Guild, Members and Alliance tabs, figures, roles, every button), dressed in classic parts. The theme no longer forks guild_info.rml to reduce it to 0.97's notice-and-members window;
  • the settings window is in, restyled over the new option_window.rml (including the Enhanced look's tuning rows);
  • focused tests cover the new C++ (class stepping and [Stacking]).

What stays in C++

Only data for the theme, in upstream's naming:

  • UI/HUD/MainFrameExtras: added to the main_frame model. party_open / guild_open / command_open with mainframe_party_click / mainframe_guild_click / mainframe_command_click and their tooltips, and coord_x / coord_y. The command button toggles the command window under the D key's rules (nothing in Chaos Castle, a notice in a battle zone).
  • CharMakeWin: job_previous / job_next, the nearest enabled class each way (-1 for none), for a theme that steps through classes with arrows (Character/ClassStep.h, tested).
  • ItemEnduranceInfo: in_party, so a theme can keep the pet column clear of the party list.
  • RmlTheme: a [Stacking] section in theme.ini, inherited like the other sections and applied by ApplyStackingDepth when a document loads (ThemedStackingDepth, tested). Documents a theme doesn't list keep the table's depth.

13 files, about 300 lines.

Commits

  1. Expose the classic HUD's extra parts to the theme (MainFrameExtras).
  2. Add a classic (0.97) theme (rml/rcss + assets): themes/classic with Base=legacy, shipping only the windows it restyles. The original art is unconverted and lives outside the RmlUi tree, as you suggested: Data/Interface/Classic (plus 099b/, s1/, s2/ for the few later classic-era files) and Data/Logo/Classic; .gitignore lets both folders in. Verdana comes from the player's system via [Fonts], with the bundled face as fallback. Each stylesheet names the original client function it follows (its main.exe address) and the positions in the original 640x480 frame.
  3. Keep the classic HUD extras off the per-frame heap; document the theme (docs/CODING_RULES.md follow-ups and a section in theming-and-modding.md).
  4. Fix the classic pet bar and server list states on the new upstream.
  5. Move the classic pet bar, party list and event HUDs aside of open windows (--world-right, in_party).
  6. Address the classic theme review: stacking and class arrows ([Stacking], job_previous / job_next, tests).
  7. Size the classic scene windows' text like the native text, honouring the UI scale.
  8. Keep the classic character window's derived lines clear of the next stat box (found by the §25 matrix).
  9. Stack the classic HUD under the windows, so they cover the dragons as in 0.97 ([Stacking] main_frame.rml=1.95).
  10. Hide the classic right dragon whole while a window docks on the right (theme only).
  11. Give the command window (D) the classic plates.
  12. Rewrite the classic message boxes on the current message box contract.
  13. Give the login and character scenes the 0.97 Esc menu.
  14. Give the in-game Esc menu the 0.97 plates.
  15. Give the in-game confirmations the 0.97 tall box.
  16. Document the classic theme's dialogs and command window.
  17. Drop the line-height values RmlUi does not parse from the classic theme (upstream's syntax check).
  18. Dress the classic menus, hints and lists in 0.97's parts: quick command, window menu (U), warp list, event timers, button hints, Season 1's scrollbar.
  19. Dress the classic friend, chat room and letter windows as Season 1's.
  20. Give the classic character-side windows classic parts: pet, quest, NPC quest, MU Helper, personal / purchase shop, extended inventory and storage, Lucky Item; the Dark Lord's Command row where Season 1 draws it, only when C++ shows it.
  21. Give the classic NPC and event windows classic parts: Kanturu and Crywolf as the Season 2 client drew them; Illusion Temple, Imperial Guardian, Doppelganger, Gens, lucky coin, Julia's market, Master Level, guild war / battle soccer, reconnect and the server message with the closest classic window's parts.
  22. Give the classic duel, Castle Siege and cash shop windows the later classic clients' look (Season 1 for duel and siege, Season 2 for the cash shop).
  23. Document the classic theme's later-client art and additive copies.
  24. Show the Season 1 and 2 clients' HUD parts in the classic theme: the SD column, the command window and friend buttons on the bar, the quest and pet buttons in the character window, the personal shop button in the inventory.
  25. Give the classic theme legacy's skill icons and show the pet commands: the classic clients' skill atlas has no icon for the skills added later, so every skill uses legacy's icon; the Dark Lord's pet commands get their own row above the skill list instead of being hidden.
  26. Restyle the classic settings window over legacy's new one: the classic stylesheet is legacy's (wider panel, scrolling settings area, the close cross, the Enhanced tuning rows) followed by 0.97's parts: the dialog stone, the title, tabs and Reset buttons as 0.97 text plates, and the classic check boxes, scrollbar and line colours.
  27. Fix the classic pet window's tabs and no-pet message: legacy's two-class tab size rules beat the theme's, so the inactive tab came out larger and over the active one; the no-pet message is Season 1's (red with a shadow, no band).
  28. Show the whole guild window in the classic theme: the fork of guild_info.rml goes; every page, figure, list and button stays, with Season 1's tabs and labelled buttons (text_button), single-line tables and the Season 1 scrollbar.
  29. Draw the classic Chaos Goblin menu as Season 2's box: the Season 2 client already has this menu with the same three buttons (message box 0x8e), so the theme copies it: its stone box, orange title, grey plates lit under the mouse and pale blue labels, from a derived texture (Message_box1_60x5, added to tools/classic_theme/make_classic_derived_textures.py). The recipe lines keep Season 2's heights but are centred, since MuMain's longer lines ran past the box from Season 2's x.
  30. Draw the classic social windows' outline in Season 1's order: the friends, chat and letter windows' 5-unit outline as CUIBaseWindow::DrawOutLine() drew it (the inner lines crossing into the corners, a dot in each corner but a resizeable window's bottom-right one); before, the top and bottom corners differed.
  31. Show the guild window's tab hit areas only with a guild: guild_info.rml's three hit areas sat outside the no-guild check that hides the tab strip and labels, so a theme that draws them showed three empty tabs over the join hint.
  32. Let the classic guild mark editor take clicks again: the classic guild creation sheet predates legacy's rule giving the mark grid's and palette's cells the pointer, so picking a colour and painting did nothing.
  33. Let the classic item windows' grids take clicks again: the classic inventory, shop, storage, trade and Chaos Machine sheets predate legacy's #panel.slot-placed { pointer-events: none; }, so their panels caught the native grid's clicks and an item couldn't be picked up; legacy's panel text size comes back too (shop, storage, trade, party).
  34. Let the classic chat bar take the pointer: the bar asks IsPointerOver(), and the classic one took no pointer, so a click on it walked the character.
  35. Show the Rena previews in the classic Golden Archer window: the classic rows predate legacy placing the live previews by their item boxes.
  36. Keep the classic settings window's box edges sharp: legacy's settings window is 440 wide, so 0.97's stone and edge strip (213 wide) were stretched about twice over and their 5-texel sides blurred into wide bands. Both now come widened ahead of time (Message_box1_440x300, Message_box2_440, in make_classic_derived_textures.py): the sides keep their width and only the stone between them stretches.
  37. Fit the classic NPC windows' headers in the 0.97 band: the classic docked frame's header is shorter than legacy's, so the NPC quest window's quest title (Sebina, Marlon) and the quest progress windows' subject fell on its ornament, and the NPC dialogue's Gens banner sat on it. The two lines close up inside the band and the banner moves under the ornament (new classic copies of npc_dialogue.rcss and quest_progress.rcss).
  38. Put the classic potion counts in the bar's ovals: the Q/W/E stack counts sat above the icons, over the slots' frames; 0.97 draws them (004be48b) at x 226 + 31i, y 447, in the oval the bar has right of each slot.
  39. Show the login options in the classic login box: the classic login hid "Remember Username" and "Remember Password", which neither 0.97 nor Season 1 (the same box) has. They now sit on the box's two blank plates, with the classic social windows' check box; the labels are sized with the box and break into two lines, so they fit at every resolution.
  40. Draw the classic pet window's stats as Season 1 does: the level in gold on a Level_box, the stat lines on dark bands, and the life bar untextured (a dark red back with the life in red), as Season 1 draws them (00412bb0).
  41. Draw the classic trainer and Lahap menus as Season 2's boxes: the trainer's menu and pet recovery and Lahap's jewel menu, from the Season 2 client (00462c5b, 00463377, message box 0xa1), with a derived texture (Message_box1_40x6). The other npc-menu menus, which no classic client has, take the trainer's box.
  42. Draw the classic Elpis, reset, Delgado and cherry menus in classic boxes: Elpis from the Season 2 client's window (006d3959); the stat reset helper, Delgado and the cherry blossom menu take the trainer's box, its plates widening for longer labels.
  43. Give the classic progress box, password warning and chat commands classic parts: Season 1's progress bar in the native progress box, the classic confirmation's stone box for the save-password warning, classic plates on the chat commands window (J).
  44. Lower the classic mix button under the recipe texts: every mixing NPC opens the same window; its button at 0.97's (75, 300) covered MuMain's longer recipe lists, so it sits at legacy's y 380.
  45. Draw the classic mix lists smaller on small canvases: the lists are at the native text size, which doesn't shrink with the window, so at a dp ratio of 1.3 or less (800x600, 1280x720 at 75%) the list lines are drawn at 85% / 75% (@media (max-resolution)).

Where a window doesn't exist in 0.97, the theme follows the Season 1 or Season 2 client when it has the window (checked against their executables); otherwise it takes the parts of the closest classic window.

Changes to existing files

Small, and all extending existing models:

  • MainFrameWindow.cpp: MainFrameExtras::BindModel / SyncModel (one line each).
  • CharMakeWin.h/.cpp: job_previous / job_next in the model.
  • RmlTheme.h/.cpp: ThemedStackingDepth() and the [Stacking] lookup in ApplyStackingDepth.
  • ItemEnduranceRmlModel.h / ItemEnduranceInfo.cpp / item_endurance.rml: the in_party field and class.
  • guild_info.rml: data-if="!no_guild" on the three tab hit areas, like the tabs they belong to (nothing visible changes in legacy).
  • npc_quest.rml: a with-quest-title class on the NPC's name while the quest title shows under it, so a theme can place the name alone (a Dark Lord or Magic Gladiator at Marlon) differently (legacy has no rule for it).
  • docs/rmlui-ui-system/theming-and-modding.md: [Stacking] and a section on the classic theme.

What the classic theme still hides

Parts no classic client has a place for, each still reachable from the keyboard: the cash shop button (X), the window menu button (Esc / U), the fourth item hotkey slot (R still uses it), the S6 five-slot skill row (1-0 still work), the Master Level button and the Chaos Goblin menu's Close button (Season 2's box has none; Esc does the same).
Lahap's jewel picker and amount and the personal shop's currency menu keep legacy's look: they have no kind a theme can target (details in the comment of 25/10).

Things worth discussing

  • The bare 0.97 bar: the theme now shows the Season 1 / 2 parts. Someone who wants exactly 0.97's bar (no SD column, no command / friend / quest / pet / shop buttons) can't get it from this theme. Would you rather have that as a child theme (classic-097, Base=classic, hiding those parts in RCSS) or as theme.ini switches? I'll follow whichever you prefer, in a later PR.
  • MainFrameExtras: these fields could simply live in CMainFrameWindow's model. I kept them in a separate file to make the review easier.
  • Scenes and chat behaviour: each as its own PR. For the scenes, following your earlier suggestion, I'll look at choosing the scene in config.ini.

Known issue: thin dark line around UI sprites (engine)

Every RmlUi sprite with transparency gets a thin dark line along its box: the classic HUD dragons, but also legacy's MU Helper bar. It isn't theme-specific (measured on bd5f66a99; the cause is unchanged on 7a0a02a1f, where images still sample through linear_sampler and clamp_sampler only serves the post-process passes):

  • 596f23f8a stopped padding RmlUi textures to a power of two (LoadImageExclusive(..., false)).
  • The SDL_gpu backend's default linear_sampler uses SDL_GPU_SAMPLERADDRESSMODE_REPEAT with linear filtering.
  • At a sprite's edge, the filter blends in the texel from the opposite edge of the texture. When one side is transparent and the other opaque, that shows as a dark line. Before, the transparent padding of the power-of-two texture hid it.

A/B on the same frame: switching linear_sampler to CLAMP_TO_EDGE removes the line completely (the row on the dragons' top edge goes from luminance 55.5 to 68.0, while the neighbouring rows stay identical). I didn't keep that change, since it would break image repetition.

Possible fixes:

  • use the clamping sampler for images that don't repeat, and REPEAT only for decorators that tile;
  • keep a 1-texel transparent gutter around non-repeating textures;
  • pad only the textures that never tile.

Happy to test whichever you prefer.

Testing

Built on Windows (preset windows-x64, RelWithDebInfo) against RmlUi afc25fec, and tested against a local server. On the current head, character_class_step_tests and rml_stacking_order_tests pass, as do the drift, syntax and geometry checks. In theme_file_interface_tests (run per case, as CTest does), 16 of 18 cases pass, including the [Stacking] one; the two that abort (0xc0000409: "focused confirmation input claims its document..." and "options keep displayed theme identities...") abort the same way on plain main (7a0a02a1f) on my machine, so they aren't from this PR.

  • Login, server list, character select and creation (the arrows step through every enabled class), entering the world.
  • HUD (SD column, command window and friend buttons opening their windows, skill list), inventory, character, party, shop, storage, trade, Chaos Machine, guild, Devil Square / Blood Castle entries, and the windows listed in commits 18-22.
  • §25 matrix with the classic theme: 800x600, 1280x720, 1920x1080, 2560x1440, 3440x1440 and 3840x2160 at 75/100/125/150%. On 15/10: login, character select and creation, message box, world, inventory and character, chat (the two issues it found are fixed in commits 8 and 9). On 18/10, after the Season 1 / 2 HUD parts and the skill icons, the same 24 combinations: the bar (SD column, command window and friend buttons), the open skill list, inventory with character window, and the command window and friend buttons clicked where the theme puts them. Both buttons open their windows in all 24 and everything places correctly.
  • The Dark Lord's pet-command row was checked in tools/rml_preview.py only: the 0.97 test server has no Dark Lord.
  • On the current head (24/10): the settings window with the Enhanced look's rows (opened in the game); the guild window's three tabs as a guild master (tab switching, emblem, figures, announcement, member list and its buttons, the alliance explanation); the pet window without a pet. The pet window's tab switch wasn't confirmed: the test harness's synthetic click doesn't switch this window's tabs in legacy either. The Chaos Goblin menu, with its three buttons and Esc, clicked in the game. The friends and write-letter windows' corners (the chat and read-letter windows share the same rules; seen in tools/rml_preview.py); the guild window without a guild (no empty tabs, the whole join hint) and with one (tabs as before, in the preview); the guild creation's emblem (a colour picked and cells painted); an item picked up from the classic inventory, which failed before commit 33, and the inventory's buttons still working; the shop, storage, trade and party windows unchanged by commit 33 (before / after captures); the Golden Archer's Rena previews. The chat bar (commit 34) follows legacy's rule and wasn't clicked in the game.
    The settings window's sides (in the game); Sebina's quest window header (in the game), and the name-only case, the NPC dialogue with the Gens banner and the quest progress windows in tools/rml_preview.py (the 0.97 test server has none of them).
    The potion count in the bar's oval (in the game).
    The login box's two options at the 24 resolution / UI scale combinations of the matrix above, and a click checking and unchecking one (in the game).
    Against OpenMU with a Dark Lord: the pet window with a Dark Horse, Lahap's menu, and the trainer's menu and pet recovery (the trainer's boxes opened from my fork's debug console, with the same stylesheets).
    The Elpis, stat reset, Delgado and cherry blossom menus and the progress box (the same way), and the seed extraction's mix window at the 24 resolution / UI scale combinations. The save-password warning in tools/rml_preview.py; the chat commands window (J) wasn't opened in the game.

Screenshots

Select Server
image
Login
image
Select character
image
HUD with potion count position fixed
image
Inventory and Vault
image
MU Helper
image
Character stats
image
Friends Chat
image
Chaos Machine
image
Party
image
Priest Sebina
image
Guild NPC
image
Option Menu
image
Warp Window
image
Guild Members List
image
Assistant
image
Julia
image

Related issues

Follow-up to #610 (#610 (comment)), where the classic theme was proposed.

Checklist

  • I have read and followed docs/CODING_RULES.md.
  • I have built the project locally (see docs/build/README.md).
    • Built and tested on Windows (preset windows-x64, RelWithDebInfo), against RmlUi afc25fec.
  • Changes are focused on one concern; the diff is as small as it reasonably can be.
    • The theme and the data it binds. Behaviour changes (scenes, classic chat) are out, for their own PRs.

@vguille vguille mentioned this pull request Oct 11, 2026
2 of 3 tasks
@nitoygo

nitoygo commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Comments (AI-assisted)

It isn't ready to merge under our architecture rules. Theme inheritance, separate asset folders, and capability declarations are sound, but these issues remain. (reference: docs/rmlui-ui-system)

Must fix

  1. The replacement scenes exceed the agreed scope.
    What: The PR includes new 3D scenes and camera behavior. Problem: The earlier guidance explicitly put those in another feature PR.
    Fix: Split the scene commit, assets, and hooks into that PR. If skipped: Accepting the theme also commits us to a scene-selection design that hasn't been agreed.

  2. Chat presentation is hardcoded in C++. — ClassicChat.cpp
    What: C++ chooses the button hover region using original coordinates and forces the input document's stacking depth. Problem: A custom theme can move the buttons in RCSS while their activation area remains elsewhere. This violates principles §1, §16, and §31.
    Fix: Define the hover region in markup/RCSS and read its actual geometry or events. Keep depth decisions in the shared stacking integration. If skipped: Themes cannot fully rearrange this UI without changing C++.

  3. The theme restricts available character classes. — char_make.rml
    What: The next arrow is disabled at it_index >= 3. Problem: Dark Lord, Summoner, and Rage Fighter remain unreachable even when C++ enables them. The theme is imposing a gameplay restriction, contrary to §22.
    Fix: Navigate the eligible classes supplied by C++; retain the classic arrow presentation. If skipped: Changing the visual theme removes character-creation choices.

  4. Chat state is not restored after document reloads. — ClassicChat.cpp
    What: Static flags remember whether framing and silence classes were applied. Problem: Reloading the documents removes those classes without clearing the flags. Reloading classic, or switching to a theme inheriting its capability, can leave framed chat misplaced and normal messages visible despite F2's state.
    Fix: Reapply state to rebuilt documents through the existing reload lifecycle, or compare against the actual element classes. If skipped: Theme switching breaks the retained chat settings.

  5. Legacy's whisper filter survives switching to classic. — ClassicChat.cpp
    What: Legacy F2 changes the chat log's message type; classic F2 changes a separate visibility flag. Problem: Press F2 in legacy, then select classic: the log remains whisper-only, and classic F2 never restores the other message types.
    Fix: Reconcile the existing chat filter when changing modes, with one authoritative state owner. If skipped: Classic chat silently omits party, guild, system, and normal messages.

Should fix

  1. The new behavior has no automated regression tests.
    What: The PR changes chat state, navigation, capabilities, and scene lifecycle without adding tests. Problem: Existing syntax and contract guards cannot detect the failures above.
    Fix: Add focused tests for reloads, legacy/classic transitions, and eligible-class navigation; validate representative aspect ratios and UI scales as required by §25. If skipped: Passing guards gives misleading confidence.

@nitoygo

nitoygo commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

@sven-n can you weigh in on this? re: on how the current S6 chat, and this to-be-introduced classic chat, should be designed together with your discord chat in the long run?

or we can just implement as-is, and leave the chat design unification to you?

@sven-n

sven-n commented Oct 12, 2026

Copy link
Copy Markdown
Owner

The chat which is synced with discord is mainly guild/alliance chat. I think the UI is not so much affected by that. I'll need some time to test the new discord PR first before I can say anything more detailed 😅

@nitoygo

nitoygo commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Ah, I understand. So it's more on the handler side of the chat inputs. (I thought there was going to be a discord widget xD)

@nitoygo
nitoygo changed the base branch from dev/rmlui-ui-system to main October 13, 2026 16:25
@nitoygo

nitoygo commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

Hello @vguille, I'll changed the target to main since dev/rmlui-ui-system was already merged.
But the comments hare are still valid: #711 (comment)

@vguille

vguille commented Oct 14, 2026

Copy link
Copy Markdown
Author

Hi @nitoygo , thanks a lot for the review, and congrats on getting #610 merged! I was really happy to see the theme system land in main. I'm very excited about it, and it makes this kind of work so much nicer.

I've updated the PR: it's now rebased on main (6 linear commits) and addresses all the points from your review:

  1. Scenes: removed from this PR (the scene commit, its hooks, the models and the two capabilities). They'll come as a separate PR, and I'll follow your suggestion of choosing the scene in config.ini.
  2. Chat presentation in C++: the buttons' hover area is now an element placed by the theme (#chat_btns_zone). The forced z-index is gone: a theme can restack its own documents in a new [Stacking] section of theme.ini, applied by ApplyStackingDepth when the document loads.
  3. Character classes: the model exposes job_previous / job_next, and the classic arrows step through every class C++ enables.
  4. Reloads: no more "applied" flags; the state classes are compared with the element every frame, so rebuilt documents get them back.
  5. F2: one owner (CChatInputBox) in every theme, reapplied after a reload, so switching between legacy and classic stays consistent.
  6. Tests: class stepping, what F2 lists under each theme, and [Stacking] (inheritance, fallback to the table, the classic input between the HUD and the tooltips).

The description is updated with the details. So far I've tested at 1600x900 and 100% UI scale only; I'll go through the §25 resolution / scale matrix next. Thanks for publishing bd5f66a99 too, it builds fine with it.

Could you take another look when you have time? Two earlier questions are still open: should the theme stay classic or become classic-97, and would you rather have MainFrameExtras folded into CMainFrameWindow?

@vguille

vguille commented Oct 15, 2026

Copy link
Copy Markdown
Author

@nitoygo I've pushed three more commits after running the §25 resolution / UI-scale matrix (details in the description): the character window's derived lines no longer run into the next stat box at 75%; the classic scene windows' text follows the native text size (0.97 drew its font at screen pixels, so this matches both the original and legacy's rule); and the classic HUD is stacked under the windows through [Stacking], so a window covers the dragons as in 0.97 and the left_covered / right_covered bindings are gone.

One observation on the placement service, nothing this PR needs: UncoveredWorldLeft/Right follow the slot rather than a dragged window, so the pet column, party list and event HUDs stay beside an empty slot until the window is placed again, in every theme.

@Mosch0512

Copy link
Copy Markdown
Collaborator

dang i love it :D

@nitoygo

nitoygo commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

One observation on the placement service, nothing this PR needs: UncoveredWorldLeft/Right follow the slot rather than a dragged window, so the pet column, party list and event HUDs stay beside an empty slot until the window is placed again, in every theme.

Yeah, the placement service (for the workspace layout) is still not that mature. As more people try it and notice some limitations, we will have to somehow redesign so it works well for different/more complex cases.

@vguille
vguille force-pushed the classic-theme branch 4 times, most recently from 61c53c2 to 0193c7a Compare October 17, 2026 22:08
@Mosch0512

Copy link
Copy Markdown
Collaborator

character selection scene and login scene need the right world and than everything is insane 👯

vguille and others added 12 commits October 23, 2026 23:24
UI/HUD/MainFrameExtras: game state the classic (0.97) bar shows and the
S6 one does not, added to the main_frame model in upstream's naming
(party_open / guild_open with clicks and tooltips, coord_x / coord_y,
left_covered / right_covered). Only data: the theme draws them. A theme
that does not bind them pays nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
themes/classic builds on legacy (theme.ini Base=legacy) and ships only
the windows it restyles, drawn with the 0.97 client's own art:

- login box and server list, character select frame and creation
  window, HUD (bar, orbs, AG, experience, Q/W/E, skills, Party / Guild
  buttons, coordinates), inventory, character, party, pet and item
  durability HUD, NPC shop, storage, trade, Chaos Machine, guild window
  and creation, event entries (Devil Square, Blood Castle, Golden
  Archer), message boxes and the chat (0.97's frameless lines at the top-left
  corner, or its framed window);
- no login Menu / Credits bar, map title or buff strip, as in 0.97;
- Verdana from the player's system ([Fonts]), the bundled face as
  fallback.

The art is the original files, unconverted, kept out of the RmlUi tree
so their names cannot collide with S6 files:
- Data/Interface/Classic: the 0.97 Data/Interface (099b/ for the few
  classic-era files 0.99b adds);
- Data/Logo/Classic: the 0.97 login box art.
.gitignore lets both folders in. A few textures 0.97 tiles or stretches
over a fixed size ship pre-built (tools/classic_theme), as do the skill
icon rules.

Each stylesheet names the 0.97 function it follows (its main.exe
address) and the positions in the original 640x480 frame. The HUD data it
binds is in the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Following docs/CODING_RULES.md, the HUD extras convert a tooltip only
when its source text changes and format the coordinates only when the
hero changes tiles.

docs/rmlui-ui-system/theming-and-modding.md gets a short section on the
classic theme: what changes for the player and the data another theme
can reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- item_endurance.rcss follows the new layout, where the theme places the
  pet column (#pets in the screen's stretch, #pet_column, the arrow row)
  instead of C++: the pet's bar is back at 0.97's (575, 16), its name
  ending at x 625 and the arrows at (550, 4). The durability icons keep
  legacy's placement.
- server_select.rcss covers every state base.rcss now styles on the
  server buttons (.server-group-btn / .server-option-btn): the selected
  group, hover and press kept the legacy sprites (the selected group
  turned red) and colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dows

0.97 places several overlays from the right edge of the area its open
windows leave free (004cb520: 640, 450 or 260) instead of the screen's,
so they move aside when a window opens:
- the pet's life and the arrow count follow --world-right, 50 further
  left in a party so they stay clear of the party list (004bec00,
  004bf540); the pet HUD's model gets an in_party field and #pets an
  .in-party class for it;
- the party list is placed by the theme again, left of the docked
  windows (it lost its place when upstream moved that to the themes);
- the event HUD frame is centred on the free area, as 0.97 centred its
  scores and timers (004bc220, 004bf090).

Before, the pet bar stayed at x 575 and was drawn over the party window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Stacking: a theme may restack its documents in theme.ini [Stacking]
  (inherited like the other sections), applied when a document loads
  (ThemedStackingDepth). The classic theme raises chat_input.rml over
  the HUD there instead of C++ forcing its z-index every frame.
- Character creation: the model exposes job_previous / job_next, the
  nearest enabled class each way; the classic arrows use them instead
  of stopping at the fourth class.
- Tests: class stepping, [Stacking] and the classic theme's chat input
  depth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the UI scale

The classic create window and the scene message boxes stretch their art
with the window height (--px), but 0.97 drew its font at screen pixels
inside the stretched frame (CreateFontA with the default height; a line
height of 12-15 px stepped by the screen width), so their text follows
the native text size, as legacy's login box and server list do: 13rem
under the scene-window-scale root for the create window, 1rem for the
message boxes. The fixed 13px they had ignored the player's UI scale.
The stylesheets say so, and the message box's comment loses a stray
reference.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tat box

The classic window leaves 36 reference px between an attribute's first
derived line and the next stat box, but the rows were 13 (three of them
39), so the strips always ran into the box and, where the panel is
smaller than the native text (UI scale 75% at 800x600), the third line's
glyphs crossed onto it. The rows are 12 now: three end on the box at
every panel scale.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… in 0.97

0.97 draws its windows over the bar's ends, where the dragons stand. The
classic theme hid a dragon when the placement service said a docked
window covered that side, which left it hidden after the player dragged
the inventory away (the uncovered world follows the slot, not the
window). The theme now stacks main_frame.rml under the windows
([Stacking], 1.95: under the lowest window at 2.0, over the event HUDs
at 1.92), so whatever stands on a dragon covers it, wherever the player
leaves it. The left_covered / right_covered bindings are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On a wide screen the bar is centred and the windows dock at the screen's
edge, so a docked window covered only part of the right dragon (half of
it at 1920x1080). While the windows docked on the right reach past the
bar's right end (--world-right against 50vw + 320dp), the dragon's
width clamps to zero; otherwise it is its 108dp. At 4:3 the window
still covers it, as in 0.97. A window dragged away keeps the dragon
hidden until the workspace places it again, since --world-right
follows the slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic theme had no command_window.rcss, so the D window kept
legacy's S6 buttons (newui_btn_empty) and target box inside the 0.97
frame. 0.97 has no command window (its player commands are chat
commands), so the classic sheet is legacy's with classic art: the twelve
buttons are the 0.97 text plate, Message_box squashed to the button
box, brightened and warmed under the mouse and while armed, as 0.97
lights its plates; the target box is 0.99b's Cursorid_wnd. Positions,
ids and events are legacy's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic message_box_view.rcss was written when C++ placed the in-game
boxes and laid out their lines and buttons; since the themes place and
lay them out, it left every box (progress, Blood Castle / Chaos Castle
results, Devil Square ranking, jewel dissolve, guild appointment)
without a place or a line layout. It now starts from legacy's current
sheet with 0.97's art: 213 wide like 0.97's boxes, the Message_box1
stone between Message_box2 edges, Ok/Ok2 and the Bar plate for the
labelled buttons, and 0.97's own geometry where it has the box (the
Blood Castle result 213x170 and the Devil Square ranking 213x290 at
y 60, from RenderMessageBox 0051af50). Kinds 0.97 does not have keep
legacy's layout, narrowed to 213.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vguille and others added 4 commits October 23, 2026 23:24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic theme hid what the 0.97 bar has no place for. The Season 1
and 2 clients, which kept the classic interface, made a place for some
of it, so the theme now draws those parts as they did instead of hiding
features the client has:

- HUD: Season 2's left third (with the command window button's box and
  the SD column's recess) and Season 1's right third (with the friend
  button's box); the SD column (Season 2's FUN_005eb880), the command
  window button (FUN_0040af9e) and the friend button (Season 1's
  00493930), which blinks Season 1's letter for a new letter or chat;
- character window: Season 1's quest and pet buttons;
- inventory: Season 1's personal shop button, with Close and Repair
  where Season 1 draws them.

MainFrameExtras exposes command_open, command_tooltip and
mainframe_command_click, which toggles the command window under the D
key's rules (nothing in Chaos Castle, a notice in a battle zone).

The cash shop and menu buttons, the fourth item hotkey, the S6 skill
row and the Master Level button stay hidden: no classic client has a
place for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic theme drew skills 1-56 with 0.97's Skill.jpg (32x36 icons
with their own stone frame). Skills the later clients added have no
icon there, so the classic theme now uses legacy's skill_icons.rcss for
every skill: the 20x28 icons centred in the 32x36 cells of the ring and
the list. gen_classic_skill_icons.py and the classic skill_icons.rcss
are gone.

The Dark Lord's pet commands were hidden, which took them away from the
player in this theme (architecture principles §22). They now have their
own row of the same cells, centred just above the skill list, while the
list is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Legacy's option_window.rcss was rewritten (wider panel, a scrolling
settings area, the close cross, and the tuning rows of the Enhanced
features: chevrons, sliders, Reset buttons, More settings), and the
classic copy still carried the old layout. The classic file is now
legacy's followed by the classic parts: 0.97's dialog stone and edges,
the title, tabs and Reset buttons as 0.97 text plates, and the classic
social windows' check boxes, scrollbar and line colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vguille and others added 9 commits October 24, 2026 00:26
The tabs are sharp text, and legacy's two-class size rules for them
(56x22 times the panel scale) beat the classic theme's plain .tab-btn,
so the inactive tab came out larger and lower, over the active one.
The classic sizes now use the same selectors and the panel scale, and
the tabs are 78 wide side by side across the frame so the English
labels fit (Season 1's 49 held its short labels). Without a pet, the
message is Season 1's: red (220, 60, 60) with a black shadow, in the
middle of the frame, with no band; legacy's subtitle goes, as Season 1
has one title and the tab names the pet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic theme forked guild_info.rml to show the Guild and Members
pages together and hid the tabs, the figures, the roles, the alliance
and most buttons, as 0.97 has one guild window. A theme should dress a
window, not take its features away (architecture principles §22), so
the fork of the markup goes and every page, figure, list and button
stays, in the classic pieces: Season 1's win_button tabs (dimmed, the
current one lit and taller, as in its pet window), the tables as single
lines in the classic social windows' colour, 0.97's text colours, the
buttons on Season 1's text_button (its labelled buttons' art, as the
classic MU Helper's) within the frame, and the Season 1 scrollbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chaos-mix menu still had the Season 6 look. Season 2 already had this
menu with the same three buttons (message box 0x8e), so the classic theme
now copies it: Message_box2 edges around five Message_box1 strips stretched
to 60, the orange title, the Message_box plates at 150x35 tinted grey and
lit under the mouse, and the pale blue bold labels. The recipe lines keep
Season 2's heights and spacing but are centred, since MuMain's longer lines
ran past the box's edge from Season 2's x. Season 2 has no Close button, so
it is hidden; Esc still closes the menu.

The stone comes as a derived texture, Message_box1_60x5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The friends, chat and letter windows drew the top band, then the sides over
it, then the bottom band over the sides, so the top and bottom corners
differed and the corner dots were missing. Draw the 5-unit outline as
CUIBaseWindow::DrawOutLine() did: the bands across the full width, then
each side's 146 and 106 lines down the full height (the inner lines cross
into the corners), the sides' 37 only between the bands, and a dot in each
corner except the bottom-right one of a window that resizes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tab strip and labels are inside the no-guild check but the three hit
areas were not, so a theme that draws them (the classic plates) showed
three empty tabs over the join hint for a character without a guild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic guild creation sheet is a copy of legacy's made before legacy
gave the mark grid's and palette's cells the pointer, so in the classic
theme the cells kept .gm-cell's pointer-events: none and neither picking a
colour nor painting did anything. Restore legacy's rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic inventory, shop, storage, trade and Chaos Machine sheets are
copies of legacy's made before legacy's panels stopped taking the pointer
once the workspace places them, so in the classic theme the panel caught
the clicks meant for the native item grid and an item could not be picked
up. Restore legacy's #panel.slot-placed rule in the five, and legacy's
panel text size in the shop, storage, trade and party windows (not in the
Chaos Machine, where it moves the lists off 0.97's place).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the chat bar asks IsPointerOver(), a bar that takes no pointer lets a
click on it fall through to the world and walk the character. The classic
bar's panel took none; it now takes the pointer as legacy's does, and the
two halves of its art still take none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic rows predate legacy placing the live Rena previews by their
item boxes, so the two icons were not drawn. Place them left of each
count's "X".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vguille

vguille commented Oct 24, 2026

Copy link
Copy Markdown
Author

@nitoygo

I'm taking this out of draft. I believe it's ready for review, and if you agree, for merging. The branch is rebased on the current main (7a0a02a1f) and the description is up to date.

What stays out of this PR

The PR is only the theme plus the data it binds. Everything that changes how the game behaves, rather than how it looks, I took out and kept in my own fork:

  • the 3D login scene and the character select scene of the classic client (its Logo models, camera and sky; the creation window's face preview);
  • the classic chat keys (F2 / F4 as 0.97 used them);
  • hiding the marker where the hero is sent to walk (0.97 has none);
  • a theme's own loading screen (LoadingScene loads the shared document directly);
  • the [Capabilities] keys a theme used to ask C++ for these.

What the theme system would need for a fully classic interface without C++

To bring the whole classic interface here, I'd like to know what you'd expect for each of these:

  • Scenes: choosing the login and character select scenes. You suggested config.ini, which works for me. Do you picture a scene as data (models, camera, sky listed in a file) or as code per scene?
  • Move marker: a way for a theme to hide or replace it, e.g. a theme.ini key or a themeable effect.
  • Loading screen: letting a theme ship its own loading.rml, like any other document.
  • Chat behaviour: keys or chat modes a theme could choose, so the classic F4 cycle doesn't need its own code.
  • Parts from different client versions: the bare 0.97 bar, as a child theme or as theme.ini switches (the question from the description).

Parts still in the Season 6 look

A few buttons and icons stay as legacy draws them, because no classic client had the feature: the inventory expansion button, the skill icons (the classic atlas has no icon for the later skills, so every skill uses legacy's, to keep them consistent) and the NPC menus from later seasons (Elpis, Delgardo, cherry blossom, jewel integration, trainer, stat reset). Where Season 1 or 2 had a window, the theme copies it from their executables.

I didn't want to generate art for those, so the theme only ships original textures. I'd like your opinion: would you accept AI-made textures for them, drawn in the classic style and kept as close to the original parts as possible, so nothing looks artificial? Or would you rather keep original art only, with the legacy look where there's none?

Reminder: thin dark line around UI sprites (engine)

This is still open and isn't specific to the theme: every RmlUi image with transparency gets a thin dark line along its box (the classic HUD's dragons, legacy's MU Helper bar). RmlUi textures are no longer padded to a power of two (596f23f8a), and the SDL_gpu backend still samples images through linear_sampler, which uses REPEAT (on 7a0a02a1f, clamp_sampler is only used by the post-process passes). So at an edge, linear filtering blends in the opposite edge's texels. Switching that sampler to CLAMP_TO_EDGE removes the line, but would break images that tile. Options: clamp for images that don't repeat and keep REPEAT for tiled decorators; a 1-texel transparent gutter; or padding only the textures that never tile. Happy to test whichever you prefer.

Thanks!

@vguille
vguille marked this pull request as ready for review October 24, 2026 15:56
vguille and others added 2 commits October 24, 2026 22:07
Legacy's settings window is 440 wide, so 0.97's dialog stone (213x40)
and edge strip (213x5) were stretched about twice over and their 5-texel
sides blurred into wide bands on the left and right. Ship both widened
ahead of time instead (make_classic_derived_textures.py): the sides keep
their width and only the stone between them stretches, in proportion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic docked frame's header (Item_Back01's dark band, y 5-30) is
shorter than legacy's, and the NPC windows kept legacy's header lines:
the quest title of the NPC quest window (Sebina, Marlon) and the subject
of the quest progress windows fell on the band's ornament, and the NPC
dialogue's Gens contribution banner sat on it. Close the two lines up
inside the band, and move the banner under the ornament.

The NPC quest window shows only the name for a Dark Lord or Magic
Gladiator at Marlon, so npc_quest.rml marks the name with
.with-quest-title when the quest title shows; alone it stays where the
other classic windows put their title. Legacy has no rule for the class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vguille

vguille commented Oct 25, 2026

Copy link
Copy Markdown
Author

character selection scene and login scene need the right world and than everything is insane 👯

Yesss 🔥🔥, I can't wait until we can swap the login and character selection scenes! I already have it all working in a fork, but to make it 100% faithful to the original 97d I had to change the C++. So it's doable, it's just that the theme engine doesn't support these things yet.

image image image image

@vguille vguille changed the title Add a classic (0.97) theme feat (ui): add a classic (0.97) theme Oct 25, 2026
vguille and others added 2 commits October 24, 2026 22:37
The Q/W/E stack counts were centred above the item icons, over the
slots' frames. 0.97 itself draws them (004be48b) with RenderNumber at
x 226 + 31i, y 447, which puts them in the oval the bar's art has right
of each slot. Place the label there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The classic login hid "Remember Username" and "Remember Password", which
neither 0.97 nor Season 1 (the same box) has, and nothing else in the
theme reached them. Put them on the two blank plates of the box's second
row, each with the classic social windows' check box. The labels are
sized with the box, not at the native text size, and break into two
lines at their space, so the longest word (German's "Benutzernamen")
fits at every resolution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ezequielzacca

Copy link
Copy Markdown
Contributor

character selection scene and login scene need the right world and than everything is insane 👯

Yesss 🔥🔥, I can't wait until we can swap the login and character selection scenes! I already have it all working in a fork, but to make it 100% faithful to the original 97d I had to change the C++. So it's doable, it's just that the theme engine doesn't support these things yet.

image image image image

Interesting! Do you have plans to add a follow up to add support to the engine?

vguille and others added 2 commits October 25, 2026 12:26
The level sat in legacy's group-box title between the tabs and the
frame, and the life bar was legacy's textured gauge. Season 1 (00412bb0)
writes the level in gold on a Level_box at (13, 96), the stat lines from
y 120 every 15 at x 24 over a band of black at 50%, and draws the life
bar untextured: a 145x8 dark red back with the life, up to 143x6, in red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Season 2 client already has the trainer's menu and pet recovery
(00462c5b, 00463377) and Lahap's jewel menu (message box 0xa1), so the
classic theme copies them instead of legacy's S6 boxes: the 213-wide
stone box from Message_box1 rows between Message_box2 edges (the rows
come from a derived texture, Message_box1_40x6), gold labels on 120x22
plates and exit_01 as Close for the trainer, 150x35 plates with pale blue
labels and a Close plate for Lahap. The trainer's title is orange without
a band, as the Chaos Goblin's and Lahap's (Season 2 writes it black on a
grey band). The other npc-menu menus (Lucky Item, seed master, seed
researcher), which no classic client has, take the trainer's box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vguille

vguille commented Oct 25, 2026

Copy link
Copy Markdown
Author

@nitoygo

While testing the classic theme against OpenMU (Dark Lord, trainer, Lahap), I found a few dialogs a theme can't reach. I'd like to know how you'd want them handled.

Generic menus without a kind

Only three g_pGenericMenuDialog callers don't set kind:

  • ShowGemIntegrationJewelDialog and ShowGemIntegrationMixDialog (Lahap's jewel picker and amount);
  • ShowPriceTypeMenu (MyShopInventory.cpp, the personal shop's currency menu).

A theme has nothing to tell them apart, from each other or from any later menu without a kind. They get no data-kind and no system-menu / highlight-title class. RmlUi afc25fec has no :has(), so the jewel picker's cols-2 cells can't select their panel either. A theme can restyle every kindless menu or none of them. In this PR, Lahap's main menu (kind gem-integration) has the classic look, but the next two dialogs fall back to legacy's.

Adding a kind to them isn't enough on its own. kind != '' also switches the document to the native-frame layout: the content box becomes absolute, and the panel's height comes from per-kind rules. Legacy and modern would each need sizes for the new kinds. I see two options:

  • give them kinds, with legacy and modern sizes, like the other native menus;
  • or add a separate field that only names the menu, with no effect on the layout, set by every caller (e.g. GenericMenuConfig::id as data-id on the body).

One kind for different menus

npc-menu covers the trainer, the Lucky Item trade NPC, the seed master and the seed researcher. A theme can't give the trainer its own box without giving it to the other three. The classic theme doesn't mind: no classic client has the other three, so they take the trainer's Season 2 box. A per-menu name like the one above would solve this too.

State hidden with data-if

data-if hides an element with a local display: none (DataViewIf). A stylesheet can't override it and no selector can see it. So when a shared document hides something with data-if, every theme gets that decision. One example was the guild window's tab hit areas, which this PR now puts under the same data-if as their tabs. If shared documents exposed state with data-class-* and left display to the stylesheets, each theme could decide. Not urgent, just something I ran into.

Everything else I found still in the legacy look can be reached from a theme: Elpis, the stat reset helper, Delgado, the cherry blossom menu, and the native boxes shown through MessageBoxView (Lahap's dismantle list among them).

Thanks!

vguille and others added 4 commits October 25, 2026 13:01
Elpis takes the Season 2 client's window (006d3959, 006d3b05): the
214x250 stone box at y 65 with its two strips, the white title, the text
from (20, 50) and three win_button buttons, no Close (Esc closes it). The
stat reset helper, Delgado and the cherry blossom menu, which no classic
client has, take the trainer's Season 2 box. The trainer box's plates
now widen for labels longer than them (other languages, these menus).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ssic parts

The native progress box (crown switch, seal registration, Illusion
Temple) draws Season 1's progress bar (Progress_back with the 0.97 fill)
instead of S6's switch bar. The login's save-password warning takes the
classic confirmation's stone box and plates. The chat commands window (J)
takes the classic plates for its two buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every mixing NPC opens the same window (CMixInventory). The classic one
put its button at 0.97's (75, 300), where 0.97's short texts ended, but
MuMain's lists and descriptions run to about y 367, so the button
covered them (seed extraction, for one). Put it at legacy's y 380,
keeping 0.97's x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mix window's lists are at the native text size, which does not shrink
with the window: per unit of the window it is 9.6 at a dp ratio of 1.25
(800x600) and 11.7 at 0.94 (800x600 at 75%), against 7.5 at 2. There the
long recipes (seed extraction) wrapped into more lines and ran under the
Mix button even at y 380. On those canvases (max-resolution 1.3x and 1x)
the list lines are drawn at 85% and 75%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants